Skip to content

Commit

Permalink
Issue #21 - Copying custom service and method attributes
Browse files Browse the repository at this point in the history
Custom attributes on the base service interface were not being copied onto the generated Async interface
  • Loading branch information
jweber committed May 10, 2016
1 parent 3cefa14 commit 01b9715
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -104,8 +143,7 @@ public interface IAsyncTestInterface3
}

#endregion



[TestFixture]
public class DynamicProxyTypeGeneratorTests
{
Expand All @@ -128,6 +166,29 @@ public void ContractsWithOverloadedMethods_DoNotDuplicateSupportMethods()
Assert.That(() => this.GenerateTypes<IOverloadedService>(), Throws.Nothing);
}

[Test]
public void CustomAttributes_AreCopiedTo_GeneratedInterface()
{
var types = this.GenerateTypes<ICustomAttributeService>();
var attr = types.AsyncInterface.GetCustomAttribute<CustomServiceAttributeAttribute>();

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<ICustomAttributeService>();

var method = types.AsyncInterface.GetMethods().First();

var attr = method.GetCustomAttribute<CustomMethodAttributeAttribute>();

Assert.That(attr.Name, Is.EqualTo(CustomMethodAttributeAttribute.CtorArg));
Assert.That(attr.Number, Is.EqualTo(CustomMethodAttributeAttribute.NumberProperty));
}

[Test]
public void AsyncInterface_AsyncMethodSignature_IsCreatedForSyncMethodWithReturnValue()
{
Expand Down
188 changes: 101 additions & 87 deletions source/WcfClientProxyGenerator/DynamicProxyTypeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,11 @@ public static GeneratedTypes GenerateTypes<TActionInvokerProvider>()

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

Expand Down Expand Up @@ -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;
}
Expand All @@ -184,18 +176,45 @@ 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;
}

return operationContractAttr.ReplyAction;
}

private static Type GenerateAsyncInterface(IList<MethodInfo> serviceMethods, ServiceContractAttribute serviceContractAttribute)
private static IEnumerable<CustomAttributeBuilder> CloneCustomAttributes(IEnumerable<CustomAttributeData> 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<MethodInfo> serviceMethods)
{
var moduleBuilder = DynamicProxyAssembly.ModuleBuilder;

Expand All @@ -211,23 +230,8 @@ private static Type GenerateAsyncInterface(IList<MethodInfo> 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<PropertyInfo>();
var vals = new List<object>();

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)
Expand All @@ -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<ServiceContractAttribute>())
{
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?");
}
}

Expand Down Expand Up @@ -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<OperationContractAttribute>();

Type attrType = typeof(OperationContractAttribute);
var attributeCtor = attrType
.GetConstructor(Type.EmptyTypes);
Func<CustomAttributeBuilder> cloneOperationContractAttribute = () =>
{
var originalOperationContract = methodInfo.GetCustomAttribute<OperationContractAttribute>();

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<PropertyInfo>
{
actionProp,
isOneWayProp,
isInitiatingProp,
isTerminatingProp
};
string actionValue = GetOperationContractAction(methodInfo);
string replyActionValue = GetOperationContractReplyAction(methodInfo);

var propertyValues = new List<object>
{
actionValue,
originalOperationContract.IsOneWay,
originalOperationContract.IsInitiating,
originalOperationContract.IsTerminating
};
var propertyInfos = new List<PropertyInfo>
{
actionProp,
isOneWayProp,
isInitiatingProp,
isTerminatingProp
};

if (!originalOperationContract.IsOneWay)
{
propertyInfos.Add(replyActionProp);
propertyValues.Add(replyActionValue);
}
var propertyValues = new List<object>
{
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);
}

/// <summary>
Expand Down Expand Up @@ -613,14 +631,10 @@ private static IList<FieldBuilder> 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);

Expand Down

0 comments on commit 01b9715

Please sign in to comment.