Cómo determinar si una clase está decorada con un aspecto PostSharp en tiempo de comstackción o ejecución

Tengo una serie de servicios WCF de la forma.

[WCFEndpoint] public class MyWCFEndpoint : WCFSvcBase, IMyWCFEndpoint { } 

donde WCFEndpoint es un PostSharp OnMethodBoundaryAspect:

 [Serializable] public class WCFEndpointAttribute : OnMethodBoundaryAspect { } 

El objective principal de [WCFEndpoint] es proporcionar información sobre la duración de las llamadas de WCF a través de las anulaciones de OnEntry y OnExit, así como también otra información de diagnóstico.

El problema es que los desarrolladores ocasionalmente se olvidan de agregar [WCFEndpoint] a los nuevos servicios WCF (EDITAR: Y otros desarrolladores que hacen revisiones de código se olvidan de mencionarlo).

Mi objective es garantizar que cada clase derivada de WCFSvcBase esté decorada con el atributo [WCFEndpoint]. Mi plan era escribir una prueba automatizada (NUnit) para encontrar todas las clases derivadas de WCFSvcBase, luego examinar los atributos personalizados y confirmar que WCFEndpointAttribute está en ese conjunto (simplificado para facilitar la lectura):

 Assembly assm = Assembly.GetAssembly(typeof(ReferenceSvc)); Type[] types = assm.GetTypes(); IEnumerable 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!" ) } } 

Mi problema es que WCFEndpointAttribute no aparece en GetCustomAttributes (true), a pesar de que se deriva de System.Attribute. Confirmé esto mirando la asamblea en .Net Reflector también.

Idealmente, debido a que OnMethodBoundaryAspect se deriva de Attribute, me gustaría “imprimir” de alguna manera el [WCFEndpoint] (o atributo personalizado análogo) en los metadatos de ensamblaje comstackdos finales. Esta es, con mucho, la respuesta más simple conceptualmente: es visible en el código y es visible en los metadatos del conjunto. ¿Hay alguna forma de hacer esto?

Encontré este artículo que describe el uso de un TypeLevelAspect que inyecta automáticamente un atributo personalizado, que funcionaría muy bien si pudiera derivar WCFEndpointAttribute de TypeLevelAspect y OnMethodBoundaryAspect (y sí, sé por qué no puedo hacer eso). 🙂

Otras formas en las que he considerado resolver esto son:

1) Realice el análisis de código para confirmar que [WCFEndpoint] está “cerca” (una línea arriba) : WCFSvcBase . Esto tiene problemas obvios con la mantenibilidad / robustez.

2) Adjunte automáticamente el [WCFEndpoint] a todas las clases derivadas de WCFSvcBase a través de multidifusión . No me gusta esto porque oculta el detalle de que PostSharp / atributos están en juego cuando se examina el código de servicio, aunque es posible si no hay soluciones más elegantes.

3) Cree un AssemblyLevelAspect para escupir una lista de todas las clases con el atributo [WCFEndpoint] en el momento de la comstackción. Luego podría comparar esta lista estática con la lista de clases generadas por reflexión derivadas de WCFBaseSvc. Detalles de AssemblyLevelAspect aquí .

También debo señalar que estoy limitado a la versión Free / Express de PostSharp.

Pude conservar el atributo WCFEndpoint al incluir PersistMetadata = true en la definición de aspecto:

 [Serializable] [MulticastAttributeUsage(PersistMetaData = true)] public class WCFEndpointAttribute : OnMethodBoundaryAspect { } 

Mirando los metadatos en .Net Reflector, ahora veo

 public class MyWCFEndpoint : WCFSvcBase, IMyWCFEndpoint { [WCFEndpoint] static MyWCFEndpoint(); [WCFEndpoint] public MyWCFEndpoint(); [WCFEndpoint] public void EndpointFn1(); [WCFEndpoint] public void EndpointFn2(); } 

La definición de WCFEndpointAttribute almacenada en los metadatos es

 public WCFEndpointAttribute() { } 

Desde aquí, puedo ajustar mi regla de validación a “Si hay al menos un método en una clase derivada de WCFSvcBase con el atributo WCFEndpoint, la validación pasa; de lo contrario, falla”.

El código de validación se convierte en

 IEnumerable 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!" ) } } 

Ciertamente no me di cuenta de que OnMethodBoundaryAspect estaba siendo multidifusión para todos los miembros (privado, público, estático, etc.), aunque eso ciertamente tiene sentido en retrospectiva. Puede que termine creando un aspecto compuesto que incluya un TypeLevelAspect para la clase y un aspecto OnMethodBoundaryEntry para los miembros (para poder buscar todas las clases directamente), pero estos metadatos son suficientes para resolver mi problema inmediato.

¡Gracias a todos por la ayuda para reducir esto!

Si todos sus WcfEndpoints realmente necesitan ser entretejidos con un Aspecto, usted podría:

  • Confíe en sus desarrolladores para agregar [WCFEndpoint] a cada servicio
  • Escriba un progtwig para verificar su fuente para el atributo [WCFEndpoint] (sugiero usar Rosyln)
  • Supongamos que sus progtwigdores son tontos, propensos a errores y, en su lugar, agregue el aspecto programáticamente. (Tenga en cuenta que en realidad no quiero decir tonto, mi CIO de hoy pronunció un buen discurso sobre cómo debería dejar que las computadoras hagan cosas que no agregan valor, y que sus desarrolladores pasen TODO su tiempo agregando valor).

Postsharp tiene una característica llamada Programmatic Tipping , que es donde usas C # para escribir un progtwig para describir dónde agregar aspectos. Agregue uno de esos a su conjunto de herramientas de construcción y olvídese completamente de su atributo [WCFEndpoint], lo que le deja más tiempo para escribir código.