< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Description.WebHttpServiceModelCompat
Assembly: CoreWCF.WebHttp
File(s): /home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.WebHttp/src/CoreWCF/Description/WebHttpServiceModelCompat.cs
Line coverage
25%
Covered lines: 19
Uncovered lines: 57
Coverable lines: 76
Total lines: 168
Line coverage: 25%
Branch coverage
28%
Covered branches: 11
Total branches: 38
Branch coverage: 28.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
ServiceModelAttributeFixup(...)87.5%8883.33%
CheckForAndConvertSmAttributes(...)0%660%
GetNativeAttribute(...)100%11100%
.cctor()100%11100%
Convert()75%4475%
Convert(...)50%2266.66%
.cctor()100%110%
Convert(...)0%220%
Build(...)100%110%
BuildDynamic()0%16160%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.WebHttp/src/CoreWCF/Description/WebHttpServiceModelCompat.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4#nullable enable
 5using System;
 6using System.Collections.Concurrent;
 7using System.Collections.Generic;
 8using System.Linq;
 9using System.Linq.Expressions;
 10using System.Reflection;
 11using System.Text.RegularExpressions;
 12using CoreWCF.Collections.Generic;
 13using CoreWCF.Web;
 14
 15namespace CoreWCF.Description;
 16
 17/// <summary>
 18/// This class contains the logic to convert System.ServiceModel.Web attributes into
 19/// CoreWCF.WebHttp attributes.
 20/// This allows endpoints to be defined using WCF classic attributes as well as CoreWCF
 21/// attributes, for a simpler migration path.
 22/// </summary>
 23internal static class WebHttpServiceModelCompat
 24{
 25    public static void ServiceModelAttributeFixup(ServiceEndpoint endpoint)
 26    {
 40027        foreach (OperationDescription operationDescription in endpoint.Contract.Operations)
 28        {
 16329            if (operationDescription.OperationBehaviors is KeyedByTypeCollection<IOperationBehavior> behaviors
 16330                && behaviors.Find<WebGetAttribute>() == null
 16331                && behaviors.Find<WebInvokeAttribute>() == null)
 32            {
 033                CheckForAndConvertSmAttributes(operationDescription);
 34            }
 35        }
 3736    }
 37
 38    private static void CheckForAndConvertSmAttributes(OperationDescription od)
 39    {
 040        var opMethod = od.SyncMethod ?? od.TaskMethod ?? od.BeginMethod;
 041        var attributes = opMethod.GetCustomAttributes().ToArray();
 42
 043        var convertedAttributes = AttributeConverters.Convert(attributes);
 044        foreach(var converted in convertedAttributes)
 045            od.OperationBehaviors.Add(converted);
 046    }
 47
 48    public static TAttribute? GetNativeAttribute<TAttribute>(MethodInfo opMethod) where TAttribute : Attribute, IOperati
 49    {
 4650        var attributes = opMethod.GetCustomAttributes();
 4651        return AttributeConverters.Convert(attributes).OfType<TAttribute>()
 4652            .FirstOrDefault();
 53    }
 54
 55    private static class AttributeConverters
 56    {
 57
 158        private static readonly IReadOnlyDictionary<string, Func<Attribute, IOperationBehavior?>> s_attributeConverters 
 159            new Dictionary<string, Func<Attribute, IOperationBehavior?>>
 160            {
 161                { "System.ServiceModel.Web.WebGetAttribute", AttributeConverter<WebGetAttribute>.Convert },
 162                { "System.ServiceModel.Web.WebInvokeAttribute", AttributeConverter<WebInvokeAttribute>.Convert }
 163            };
 64
 65        public static IEnumerable<IOperationBehavior> Convert(IEnumerable<Attribute> attributes)
 66        {
 20867            foreach (Attribute attribute in attributes)
 68            {
 5869                if (Convert(attribute) is { } convertedAttribute)
 070                    yield return convertedAttribute;
 71            }
 4672        }
 73
 74        private static IOperationBehavior? Convert(Attribute attribute)
 75        {
 5876            if (s_attributeConverters.TryGetValue(attribute.GetType().FullName, out var converter))
 77            {
 078                return converter.Invoke(attribute);
 79            }
 80
 5881            return null;
 82        }
 83    }
 84
 85    private static class AttributeConverter<TOut> where TOut : class, IOperationBehavior
 86    {
 087        private static readonly ConcurrentDictionary<Type, Func<Attribute, TOut?>> s_converterCache = new();
 88
 089        private static readonly MethodInfo s_buildDynamicMethodInfo = typeof(AttributeConverter<TOut>)
 090            .GetMethod(nameof(BuildDynamic), BindingFlags.Static | BindingFlags.NonPublic)!;
 91
 92        public static IOperationBehavior? Convert(Attribute attribute)
 93        {
 094            var type = attribute.GetType();
 095            if (!s_converterCache.TryGetValue(type, out var converter))
 96            {
 097                converter = s_converterCache[type] = Build(type);
 98            }
 99
 0100            return converter(attribute);
 101        }
 102
 103        /// <summary>
 104        /// Creates a delegate that pattern matches for one type of attribute
 105        /// then converts it to the equivalent CoreWcf attribute.
 106        ///
 107        /// Since TInput comes from a reflected assembly, we only get Type,
 108        /// rather than a strong generic parameter. This means we need to
 109        /// use reflection on Type.
 110        /// </summary>
 111        /// <param name="inputType">The source attribute type</param>
 112        /// <returns>A CoreWCF attribute</returns>
 113        /// <typeparam name="TOut">The converted attribute type</typeparam>
 114        private static Func<Attribute, TOut?> Build(Type inputType)
 115        {
 0116            return (Func<Attribute, TOut?>) s_buildDynamicMethodInfo
 0117                .MakeGenericMethod(inputType)
 0118                .Invoke(null, Array.Empty<object>());
 119        }
 120
 121        private static Func<Attribute, TOut?> BuildDynamic<TInput>()
 122        {
 0123            var inputExpression = Expression.Parameter(typeof(TInput));
 124
 125            // For each property build a get-set pair to copy properties
 126            // to the target value.
 0127            var memberBindings =
 0128                from inputProp in typeof(TInput).GetProperties()
 0129                join outputProp in typeof(TOut).GetProperties() on inputProp.Name equals outputProp.Name
 0130                where inputProp.CanRead
 0131                where outputProp.CanWrite
 0132                // Since we are dealing with Attributes, only primitive types are available
 0133                // strings, numbers, and enums.
 0134                // Therefore a simple cast expression should convert the enums simply.
 0135                let value = Expression.Convert(Expression.Property(inputExpression, inputProp), outputProp.PropertyType)
 0136                // We need to copy the IsXXXSetExplicitly properties last
 0137                // as they can be overwritten by the other setters.
 0138                orderby inputProp.Name.EndsWith("SetExplicitly")
 0139                select (MemberBinding)Expression.Bind(outputProp, value);
 140
 0141            var convert = Expression.Lambda<Func<TInput, TOut>>(
 0142                Expression.MemberInit(
 0143                    Expression.New(typeof(TOut)),
 0144                    memberBindings.ToArray()
 0145                ),
 0146                inputExpression
 0147            ).Compile();
 148
 0149            return attribute =>
 0150            {
 0151                if (attribute is TInput input)
 0152                {
 0153                    // This is the same as
 0154                    // return new CoreWCF.WebHttp.TOutAttribute()
 0155                    // {
 0156                    //     Foo = (CoreWCF.WebHttp.SomeEnum)input.Foo,
 0157                    //     Bar = (CoreWCF.WebHttp.SomeEnum)input.Bar,
 0158                    //     Baz = (CoreWCF.WebHttp.SomeEnum)input.Baz,
 0159                    // };
 0160                    var ret = convert(input);
 0161                    return ret;
 0162                }
 0163
 0164                return null;
 0165            };
 166        }
 167    }
 168}