问题
I have a series of WCF services of the form
[WCFEndpoint]
public class MyWCFEndpoint : WCFSvcBase, IMyWCFEndpoint
{
}
where WCFEndpoint is a PostSharp OnMethodBoundaryAspect:
[Serializable]
public class WCFEndpointAttribute : OnMethodBoundaryAspect
{
}
The main purpose of [WCFEndpoint] is to provide duration information on WCF calls via overrides of OnEntry and OnExit, as well as other diagnostic information.
The problem is that developers occasionally forget to add the [WCFEndpoint] to new WCF services (EDIT: AND other developers doing code reviews forget to mention it!).
My objective is to guarantee that every class derived from WCFSvcBase is decorated with the [WCFEndpoint] attribute. My plan was to write an automated (NUnit) test to find all classes derived from WCFSvcBase, then look through the custom attributes and confirm the WCFEndpointAttribute is in that set (simplified for readability):
Assembly assm = Assembly.GetAssembly(typeof(ReferenceSvc));
Type[] types = assm.GetTypes();
IEnumerable<Type> serviceTypes =
types.Where(type => type.IsSubclassOf(typeof(WCFSvcBase)) && !type.IsAbstract );
foreach (Type serviceType in serviceTypes)
{
if (!serviceType.GetCustomAttributes(true).Any(x => x.GetType() == typeof(WCFEndpointAttribute)))
{
Assert.Fail( "Found an incorrectly decorated svc!" )
}
}
My problem is that the WCFEndpointAttribute is not appearing in GetCustomAttributes(true) - even though it derives from System.Attribute. I confirmed this by looking at the assembly in .Net Reflector as well.
Ideally, because OnMethodBoundaryAspect derives from Attribute, I'd like to somehow "print" the [WCFEndpoint] (or analogous custom attribute) into the final compiled assembly metadata. This is by far the simplest answer conceptually: it's visible in the code and it's visible in the assembly metadata. Is there a way to do this?
I found this article describing the use of a TypeLevelAspect that automatically injects a custom attribute, which would work great if I could derive WCFEndpointAttribute from both TypeLevelAspect and OnMethodBoundaryAspect (and yes, I know why I can't do that). :)
Other ways I've considered solving this are:
1) Perform code parsing to confirm [WCFEndpoint] is "near" (one line above) : WCFSvcBase
. This has obvious problems with maintainability/robustness.
2) Automatically attach the [WCFEndpoint] to all classes deriving from WCFSvcBase via multicasting. I dislike this because it obscures the detail that PostSharp/attributes are in play when examining the service code, though it is possible if there are no more elegant solutions.
3) Create an AssemblyLevelAspect to spit out a list of all classes with the [WCFEndpoint] attribute at build time. I could then compare this static list to the reflection-generated list of classes that derive from WCFBaseSvc. AssemblyLevelAspect details here.
I should also point out I'm limited to the Free/Express version of PostSharp.
回答1:
I was able to persist the WCFEndpoint attribute by including PersistMetadata=true in the aspect definition:
[Serializable]
[MulticastAttributeUsage(PersistMetaData = true)]
public class WCFEndpointAttribute : OnMethodBoundaryAspect
{
}
Looking at the metadata in .Net Reflector, I now see
public class MyWCFEndpoint : WCFSvcBase, IMyWCFEndpoint
{
[WCFEndpoint]
static MyWCFEndpoint();
[WCFEndpoint]
public MyWCFEndpoint();
[WCFEndpoint]
public void EndpointFn1();
[WCFEndpoint]
public void EndpointFn2();
}
The definition of WCFEndpointAttribute stored in the metadata is
public WCFEndpointAttribute()
{
}
From here, I can adjust my validation rule to "If there is at least one method on a class derived from WCFSvcBase with the attribute WCFEndpoint, validation passes; else it fails."
The validation code becomes
IEnumerable<Type> serviceTypes =
types.Where(type => type.IsSubclassOf(typeof(WCFSvcBase))
&& !type.IsAbstract );
foreach (Type serviceType in serviceTypes)
{
var members = serviceType.GetMembers();
if (!members.Exists( member => member.GetCustomAttributes(true).Any(attrib => attrib.GetType() == typeof(WCFEndpointAttribute))))
{
Assert.Fail( "Found an incorrectly decorated svc!" )
}
}
I certainly did not realize the OnMethodBoundaryAspect was being multicast to all members (private, public, static, etc.), though that certainly makes sense in hindsight. I may end up creating a composite aspect that includes a TypeLevelAspect for the class and an OnMethodBoundaryEntry aspect for the members (so I can look for all classes directly), but this metadata is sufficient to solve my immediate problem.
Thanks to all for the help in narrowing this down!
回答2:
If your WcfEndpoints really all need to be weaved with an Aspect, you could:
- Trust your developers to add [WCFEndpoint] to every Service
- Write a program to check your source for the [WCFEndpoint] attribute (I suggest using Rosyln)
- Assume your programmers are dumb, error prone, and add the aspect in programmatically instead. (Note I don't actually mean dumb, my CIO today gave a good speech about how you should leave computers to do things that doesn't add value, and have your devs spend ALL their time adding value).
Postsharp has a feature call Programmatic Tipping, which is where you use C# to write a program to describe where to add aspects. Add one of those to your build toolchain and forget about your [WCFEndpoint] attribute completely, leaving you more time to write code.
来源:https://stackoverflow.com/questions/24070246/how-to-determine-if-a-class-is-decorated-with-a-postsharp-aspect-at-build-or-run