From 01b971540cac07cacbd32fb07a46046b8dd08c14 Mon Sep 17 00:00:00 2001 From: jweber Date: Mon, 9 May 2016 18:26:10 -0700 Subject: [PATCH] Issue #21 - Copying custom service and method attributes Custom attributes on the base service interface were not being copied onto the generated Async interface --- .../DynamicProxyTypeGeneratorTests.cs | 65 +++++- .../DynamicProxyTypeGenerator.cs | 188 ++++++++++-------- 2 files changed, 164 insertions(+), 89 deletions(-) diff --git a/source/WcfClientProxyGenerator.Tests/DynamicProxyTypeGeneratorTests.cs b/source/WcfClientProxyGenerator.Tests/DynamicProxyTypeGeneratorTests.cs index 1adebdb..c2cf607 100644 --- a/source/WcfClientProxyGenerator.Tests/DynamicProxyTypeGeneratorTests.cs +++ b/source/WcfClientProxyGenerator.Tests/DynamicProxyTypeGeneratorTests.cs @@ -21,6 +21,45 @@ public interface IOverloadedService int Method(string input, string input2); } + [AttributeUsage(AttributeTargets.Interface)] + public class CustomServiceAttributeAttribute : Attribute + { + public const string CtorArg = "hello world"; + public const int NumberProperty = 100; + + public CustomServiceAttributeAttribute(string name) + { + Name = name; + } + + public string Name { get; } + public int Number { get; set; } + } + + [AttributeUsage(AttributeTargets.Method)] + public class CustomMethodAttributeAttribute : Attribute + { + public const string CtorArg = "method"; + public const int NumberProperty = 200; + + public CustomMethodAttributeAttribute(string name) + { + Name = name; + } + + public string Name { get; } + public int Number { get; set; } + } + + [ServiceContract] + [CustomServiceAttribute(CustomServiceAttributeAttribute.CtorArg, Number = CustomServiceAttributeAttribute.NumberProperty)] + public interface ICustomAttributeService + { + [OperationContract] + [CustomMethodAttribute(CustomMethodAttributeAttribute.CtorArg, Number = CustomMethodAttributeAttribute.NumberProperty)] + string Method(string input); + } + [ServiceContract] public interface IAsyncTestInterface { @@ -104,8 +143,7 @@ public interface IAsyncTestInterface3 } #endregion - - + [TestFixture] public class DynamicProxyTypeGeneratorTests { @@ -128,6 +166,29 @@ public void ContractsWithOverloadedMethods_DoNotDuplicateSupportMethods() Assert.That(() => this.GenerateTypes(), Throws.Nothing); } + [Test] + public void CustomAttributes_AreCopiedTo_GeneratedInterface() + { + var types = this.GenerateTypes(); + var attr = types.AsyncInterface.GetCustomAttribute(); + + Assert.That(attr.Name, Is.EqualTo(CustomServiceAttributeAttribute.CtorArg)); + Assert.That(attr.Number, Is.EqualTo(CustomServiceAttributeAttribute.NumberProperty)); + } + + [Test] + public void CustomAttributes_AreCopiedTo_GeneratedMethods() + { + var types = this.GenerateTypes(); + + var method = types.AsyncInterface.GetMethods().First(); + + var attr = method.GetCustomAttribute(); + + Assert.That(attr.Name, Is.EqualTo(CustomMethodAttributeAttribute.CtorArg)); + Assert.That(attr.Number, Is.EqualTo(CustomMethodAttributeAttribute.NumberProperty)); + } + [Test] public void AsyncInterface_AsyncMethodSignature_IsCreatedForSyncMethodWithReturnValue() { diff --git a/source/WcfClientProxyGenerator/DynamicProxyTypeGenerator.cs b/source/WcfClientProxyGenerator/DynamicProxyTypeGenerator.cs index 1d74c44..e3adbf3 100644 --- a/source/WcfClientProxyGenerator/DynamicProxyTypeGenerator.cs +++ b/source/WcfClientProxyGenerator/DynamicProxyTypeGenerator.cs @@ -75,17 +75,11 @@ public static GeneratedTypes GenerateTypes() if (!serviceMethods.Any()) { - throw new InvalidOperationException(String.Format("Service interface {0} has no OperationContact methods. Is this a proper WCF service interface?", interfaceType.Name)); + throw new InvalidOperationException( + $"Service interface {interfaceType.Name} has no OperationContact methods. Is this a proper WCF service interface?"); } - ServiceContractAttribute serviceContractAttribute = null; - object[] customAttributes = interfaceType.GetCustomAttributes(typeof(ServiceContractAttribute), true); - if (customAttributes.Any()) - { - serviceContractAttribute = customAttributes[0] as ServiceContractAttribute; - } - - var asyncInterfaceType = GenerateAsyncInterface(serviceMethods, serviceContractAttribute); + var asyncInterfaceType = GenerateAsyncInterface(serviceMethods); // build proxy @@ -160,10 +154,8 @@ private static string GetOperationContractAction(MethodInfo methodInfo) string serviceNamespace = serviceContract.Namespace ?? "http://tempuri.org"; serviceNamespace = serviceNamespace.TrimEnd('/'); - string defaultAction = string.Format("{0}/{1}/{2}", - serviceNamespace, - methodInfo.DeclaringType.Name, - operationContractAttr.Name ?? methodInfo.Name); + string defaultAction = + $"{serviceNamespace}/{methodInfo.DeclaringType.Name}/{operationContractAttr.Name ?? methodInfo.Name}"; return defaultAction; } @@ -184,10 +176,8 @@ private static string GetOperationContractReplyAction(MethodInfo methodInfo) string serviceNamespace = serviceContract.Namespace ?? "http://tempuri.org"; serviceNamespace = serviceNamespace.TrimEnd('/'); - string defaultAction = string.Format("{0}/{1}/{2}Response", - serviceNamespace, - methodInfo.DeclaringType.Name, - operationContractAttr.Name ?? methodInfo.Name); + string defaultAction = + $"{serviceNamespace}/{methodInfo.DeclaringType.Name}/{operationContractAttr.Name ?? methodInfo.Name}Response"; return defaultAction; } @@ -195,7 +185,36 @@ private static string GetOperationContractReplyAction(MethodInfo methodInfo) return operationContractAttr.ReplyAction; } - private static Type GenerateAsyncInterface(IList serviceMethods, ServiceContractAttribute serviceContractAttribute) + private static IEnumerable CloneCustomAttributes(IEnumerable attrData) + { + foreach (var data in attrData) + { + var ctorArgs = data.ConstructorArguments + .Select(m => m.Value) + .ToArray(); + + var properties = data.NamedArguments? + .Where(m => m.MemberInfo is PropertyInfo) + .Select(m => new { pi = m.MemberInfo as PropertyInfo, val = m.TypedValue.Value }) + .ToArray(); + + var fields = data.NamedArguments? + .Where(m => m.MemberInfo is FieldInfo) + .Select(m => new { fi = m.MemberInfo as FieldInfo, val = m.TypedValue.Value }) + .ToArray(); + + yield return new CustomAttributeBuilder( + data.Constructor, + ctorArgs, + properties?.Select(m => m.pi).ToArray() ?? new PropertyInfo[0], + properties?.Select(m => m.val).ToArray() ?? new object[0], + fields?.Select(m => m.fi).ToArray() ?? new FieldInfo[0], + fields?.Select(m => m.val).ToArray() ?? new object[0]); + } + + } + + private static Type GenerateAsyncInterface(IList serviceMethods) { var moduleBuilder = DynamicProxyAssembly.ModuleBuilder; @@ -211,23 +230,8 @@ private static Type GenerateAsyncInterface(IList serviceMethods, Ser var generatedAsyncInterfaceAttrBuilder = new CustomAttributeBuilder(generatedAsyncInterfaceAttrCtor, new object[0]); asyncInterfaceBuilder.SetCustomAttribute(generatedAsyncInterfaceAttrBuilder); - Type serviceContractAttrType = typeof(ServiceContractAttribute); - var serviceContractAttrCtor = serviceContractAttrType.GetConstructor(Type.EmptyTypes); - - var props = new List(); - var vals = new List(); - - props.Add(serviceContractAttrType.GetProperty(nameof(ServiceContractAttribute.CallbackContract))); - props.Add(serviceContractAttrType.GetProperty(nameof(ServiceContractAttribute.SessionMode))); - - foreach (PropertyInfo prop in props) - { - vals.Add(prop.GetValue(serviceContractAttribute)); - } - - CustomAttributeBuilder serviceContractAttrBuilder = new CustomAttributeBuilder(serviceContractAttrCtor, new object[0], props.ToArray(), vals.ToArray()); - - asyncInterfaceBuilder.SetCustomAttribute(serviceContractAttrBuilder); + foreach (var builder in CloneCustomAttributes(typeof(TServiceInterface).GetCustomAttributesData())) + asyncInterfaceBuilder.SetCustomAttribute(builder); var nonAsyncServiceMethods = serviceMethods .Where(m => !typeof(Task).IsAssignableFrom(m.ReturnType) @@ -245,12 +249,14 @@ private static void CheckServiceInterfaceValidity(Type type) { if (!type.IsPublic && !type.IsNestedPublic) { - throw new InvalidOperationException(String.Format("Service interface {0} is not declared public. WcfClientProxyGenerator cannot work with non-public service interfaces.", type.Name)); + throw new InvalidOperationException( + $"Service interface {type.Name} is not declared public. WcfClientProxyGenerator cannot work with non-public service interfaces."); } if (!type.HasAttribute()) { - throw new InvalidOperationException(String.Format("Service interface {0} is not marked with ServiceContract attribute. Is this a proper WCF service interface?", type.Name)); + throw new InvalidOperationException( + $"Service interface {type.Name} is not marked with ServiceContract attribute. Is this a proper WCF service interface?"); } } @@ -300,58 +306,70 @@ private static void GenerateAsyncTaskMethod( for (int i = 1; i <= parameterTypes.Length; i++) methodBuilder.DefineParameter(i, ParameterAttributes.None, parameters[i-1].Name); - - var originalOperationContract = methodInfo.GetCustomAttribute(); - Type attrType = typeof(OperationContractAttribute); - var attributeCtor = attrType - .GetConstructor(Type.EmptyTypes); + Func cloneOperationContractAttribute = () => + { + var originalOperationContract = methodInfo.GetCustomAttribute(); - var actionProp = attrType.GetProperty(nameof(OperationContractAttribute.Action)); - var replyActionProp = attrType.GetProperty(nameof(OperationContractAttribute.ReplyAction)); - var nameProp = attrType.GetProperty(nameof(OperationContractAttribute.Name)); - var isOneWayProp = attrType.GetProperty(nameof(OperationContractAttribute.IsOneWay)); - var isInitiatingProp = attrType.GetProperty(nameof(OperationContractAttribute.IsInitiating)); - var isTerminatingProp = attrType.GetProperty(nameof(OperationContractAttribute.IsTerminating)); + Type attrType = typeof(OperationContractAttribute); + var attributeCtor = attrType + .GetConstructor(Type.EmptyTypes); - string actionValue = GetOperationContractAction(methodInfo); - string replyActionValue = GetOperationContractReplyAction(methodInfo); + var actionProp = attrType.GetProperty(nameof(OperationContractAttribute.Action)); + var replyActionProp = attrType.GetProperty(nameof(OperationContractAttribute.ReplyAction)); + var nameProp = attrType.GetProperty(nameof(OperationContractAttribute.Name)); + var isOneWayProp = attrType.GetProperty(nameof(OperationContractAttribute.IsOneWay)); + var isInitiatingProp = attrType.GetProperty(nameof(OperationContractAttribute.IsInitiating)); + var isTerminatingProp = attrType.GetProperty(nameof(OperationContractAttribute.IsTerminating)); - var propertyInfos = new List - { - actionProp, - isOneWayProp, - isInitiatingProp, - isTerminatingProp - }; + string actionValue = GetOperationContractAction(methodInfo); + string replyActionValue = GetOperationContractReplyAction(methodInfo); - var propertyValues = new List - { - actionValue, - originalOperationContract.IsOneWay, - originalOperationContract.IsInitiating, - originalOperationContract.IsTerminating - }; + var propertyInfos = new List + { + actionProp, + isOneWayProp, + isInitiatingProp, + isTerminatingProp + }; - if (!originalOperationContract.IsOneWay) - { - propertyInfos.Add(replyActionProp); - propertyValues.Add(replyActionValue); - } + var propertyValues = new List + { + actionValue, + originalOperationContract.IsOneWay, + originalOperationContract.IsInitiating, + originalOperationContract.IsTerminating + }; - if (!string.IsNullOrEmpty(originalOperationContract.Name)) - { - propertyInfos.Add(nameProp); - propertyValues.Add(originalOperationContract.Name); - } + if (!originalOperationContract.IsOneWay) + { + propertyInfos.Add(replyActionProp); + propertyValues.Add(replyActionValue); + } - var attributeBuilder = new CustomAttributeBuilder( - attributeCtor, - new object[0], - propertyInfos.ToArray(), - propertyValues.ToArray()); + if (!string.IsNullOrEmpty(originalOperationContract.Name)) + { + propertyInfos.Add(nameProp); + propertyValues.Add(originalOperationContract.Name); + } - methodBuilder.SetCustomAttribute(attributeBuilder); + var attributeBuilder = new CustomAttributeBuilder( + attributeCtor, + new object[0], + propertyInfos.ToArray(), + propertyValues.ToArray()); + + return attributeBuilder; + }; + + methodBuilder.SetCustomAttribute(cloneOperationContractAttribute()); + + var remainingCustomAttributes = methodInfo + .GetCustomAttributesData() + .Where(m => m.AttributeType != typeof(OperationContractAttribute)); + + foreach (var builder in CloneCustomAttributes(remainingCustomAttributes)) + methodBuilder.SetCustomAttribute(builder); } /// @@ -613,14 +631,10 @@ private static IList GenerateServiceCallWrapperType( .Where(t => t.IsByRef) .ToArray(); - string className = string.Format("{0}_{1}", - methodInfo.Name, - string.Join("_", parameterTypes.Select(m => m.Name))); + string className = $"{methodInfo.Name}_{string.Join("_", parameterTypes.Select(m => m.Name))}"; - string typeName = string.Format( - "WcfClientProxyGenerator.DynamicProxy.{0}Support.{1}", - typeof(TServiceInterface).Name, - className); + string typeName = + $"WcfClientProxyGenerator.DynamicProxy.{typeof(TServiceInterface).Name}Support.{className}"; var serviceCallTypeBuilder = DynamicProxyAssembly.ModuleBuilder.DefineType(typeName);