| | | 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 |
| | | 5 | | using System; |
| | | 6 | | using System.Collections.Concurrent; |
| | | 7 | | using System.Collections.Generic; |
| | | 8 | | using System.Linq; |
| | | 9 | | using System.Linq.Expressions; |
| | | 10 | | using System.Reflection; |
| | | 11 | | using System.Text.RegularExpressions; |
| | | 12 | | using CoreWCF.Collections.Generic; |
| | | 13 | | using CoreWCF.Web; |
| | | 14 | | |
| | | 15 | | namespace 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> |
| | | 23 | | internal static class WebHttpServiceModelCompat |
| | | 24 | | { |
| | | 25 | | public static void ServiceModelAttributeFixup(ServiceEndpoint endpoint) |
| | | 26 | | { |
| | 400 | 27 | | foreach (OperationDescription operationDescription in endpoint.Contract.Operations) |
| | | 28 | | { |
| | 163 | 29 | | if (operationDescription.OperationBehaviors is KeyedByTypeCollection<IOperationBehavior> behaviors |
| | 163 | 30 | | && behaviors.Find<WebGetAttribute>() == null |
| | 163 | 31 | | && behaviors.Find<WebInvokeAttribute>() == null) |
| | | 32 | | { |
| | 0 | 33 | | CheckForAndConvertSmAttributes(operationDescription); |
| | | 34 | | } |
| | | 35 | | } |
| | 37 | 36 | | } |
| | | 37 | | |
| | | 38 | | private static void CheckForAndConvertSmAttributes(OperationDescription od) |
| | | 39 | | { |
| | 0 | 40 | | var opMethod = od.SyncMethod ?? od.TaskMethod ?? od.BeginMethod; |
| | 0 | 41 | | var attributes = opMethod.GetCustomAttributes().ToArray(); |
| | | 42 | | |
| | 0 | 43 | | var convertedAttributes = AttributeConverters.Convert(attributes); |
| | 0 | 44 | | foreach(var converted in convertedAttributes) |
| | 0 | 45 | | od.OperationBehaviors.Add(converted); |
| | 0 | 46 | | } |
| | | 47 | | |
| | | 48 | | public static TAttribute? GetNativeAttribute<TAttribute>(MethodInfo opMethod) where TAttribute : Attribute, IOperati |
| | | 49 | | { |
| | 46 | 50 | | var attributes = opMethod.GetCustomAttributes(); |
| | 46 | 51 | | return AttributeConverters.Convert(attributes).OfType<TAttribute>() |
| | 46 | 52 | | .FirstOrDefault(); |
| | | 53 | | } |
| | | 54 | | |
| | | 55 | | private static class AttributeConverters |
| | | 56 | | { |
| | | 57 | | |
| | 1 | 58 | | private static readonly IReadOnlyDictionary<string, Func<Attribute, IOperationBehavior?>> s_attributeConverters |
| | 1 | 59 | | new Dictionary<string, Func<Attribute, IOperationBehavior?>> |
| | 1 | 60 | | { |
| | 1 | 61 | | { "System.ServiceModel.Web.WebGetAttribute", AttributeConverter<WebGetAttribute>.Convert }, |
| | 1 | 62 | | { "System.ServiceModel.Web.WebInvokeAttribute", AttributeConverter<WebInvokeAttribute>.Convert } |
| | 1 | 63 | | }; |
| | | 64 | | |
| | | 65 | | public static IEnumerable<IOperationBehavior> Convert(IEnumerable<Attribute> attributes) |
| | | 66 | | { |
| | 208 | 67 | | foreach (Attribute attribute in attributes) |
| | | 68 | | { |
| | 58 | 69 | | if (Convert(attribute) is { } convertedAttribute) |
| | 0 | 70 | | yield return convertedAttribute; |
| | | 71 | | } |
| | 46 | 72 | | } |
| | | 73 | | |
| | | 74 | | private static IOperationBehavior? Convert(Attribute attribute) |
| | | 75 | | { |
| | 58 | 76 | | if (s_attributeConverters.TryGetValue(attribute.GetType().FullName, out var converter)) |
| | | 77 | | { |
| | 0 | 78 | | return converter.Invoke(attribute); |
| | | 79 | | } |
| | | 80 | | |
| | 58 | 81 | | return null; |
| | | 82 | | } |
| | | 83 | | } |
| | | 84 | | |
| | | 85 | | private static class AttributeConverter<TOut> where TOut : class, IOperationBehavior |
| | | 86 | | { |
| | 0 | 87 | | private static readonly ConcurrentDictionary<Type, Func<Attribute, TOut?>> s_converterCache = new(); |
| | | 88 | | |
| | 0 | 89 | | private static readonly MethodInfo s_buildDynamicMethodInfo = typeof(AttributeConverter<TOut>) |
| | 0 | 90 | | .GetMethod(nameof(BuildDynamic), BindingFlags.Static | BindingFlags.NonPublic)!; |
| | | 91 | | |
| | | 92 | | public static IOperationBehavior? Convert(Attribute attribute) |
| | | 93 | | { |
| | 0 | 94 | | var type = attribute.GetType(); |
| | 0 | 95 | | if (!s_converterCache.TryGetValue(type, out var converter)) |
| | | 96 | | { |
| | 0 | 97 | | converter = s_converterCache[type] = Build(type); |
| | | 98 | | } |
| | | 99 | | |
| | 0 | 100 | | 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 | | { |
| | 0 | 116 | | return (Func<Attribute, TOut?>) s_buildDynamicMethodInfo |
| | 0 | 117 | | .MakeGenericMethod(inputType) |
| | 0 | 118 | | .Invoke(null, Array.Empty<object>()); |
| | | 119 | | } |
| | | 120 | | |
| | | 121 | | private static Func<Attribute, TOut?> BuildDynamic<TInput>() |
| | | 122 | | { |
| | 0 | 123 | | var inputExpression = Expression.Parameter(typeof(TInput)); |
| | | 124 | | |
| | | 125 | | // For each property build a get-set pair to copy properties |
| | | 126 | | // to the target value. |
| | 0 | 127 | | var memberBindings = |
| | 0 | 128 | | from inputProp in typeof(TInput).GetProperties() |
| | 0 | 129 | | join outputProp in typeof(TOut).GetProperties() on inputProp.Name equals outputProp.Name |
| | 0 | 130 | | where inputProp.CanRead |
| | 0 | 131 | | where outputProp.CanWrite |
| | 0 | 132 | | // Since we are dealing with Attributes, only primitive types are available |
| | 0 | 133 | | // strings, numbers, and enums. |
| | 0 | 134 | | // Therefore a simple cast expression should convert the enums simply. |
| | 0 | 135 | | let value = Expression.Convert(Expression.Property(inputExpression, inputProp), outputProp.PropertyType) |
| | 0 | 136 | | // We need to copy the IsXXXSetExplicitly properties last |
| | 0 | 137 | | // as they can be overwritten by the other setters. |
| | 0 | 138 | | orderby inputProp.Name.EndsWith("SetExplicitly") |
| | 0 | 139 | | select (MemberBinding)Expression.Bind(outputProp, value); |
| | | 140 | | |
| | 0 | 141 | | var convert = Expression.Lambda<Func<TInput, TOut>>( |
| | 0 | 142 | | Expression.MemberInit( |
| | 0 | 143 | | Expression.New(typeof(TOut)), |
| | 0 | 144 | | memberBindings.ToArray() |
| | 0 | 145 | | ), |
| | 0 | 146 | | inputExpression |
| | 0 | 147 | | ).Compile(); |
| | | 148 | | |
| | 0 | 149 | | return attribute => |
| | 0 | 150 | | { |
| | 0 | 151 | | if (attribute is TInput input) |
| | 0 | 152 | | { |
| | 0 | 153 | | // This is the same as |
| | 0 | 154 | | // return new CoreWCF.WebHttp.TOutAttribute() |
| | 0 | 155 | | // { |
| | 0 | 156 | | // Foo = (CoreWCF.WebHttp.SomeEnum)input.Foo, |
| | 0 | 157 | | // Bar = (CoreWCF.WebHttp.SomeEnum)input.Bar, |
| | 0 | 158 | | // Baz = (CoreWCF.WebHttp.SomeEnum)input.Baz, |
| | 0 | 159 | | // }; |
| | 0 | 160 | | var ret = convert(input); |
| | 0 | 161 | | return ret; |
| | 0 | 162 | | } |
| | 0 | 163 | | |
| | 0 | 164 | | return null; |
| | 0 | 165 | | }; |
| | | 166 | | } |
| | | 167 | | } |
| | | 168 | | } |