¿Qué hacen realmente los interceptores con mi clase c#?
class castle-dynamicproxy (2)
Me pidieron que implementara el proxy dinámico de Castle en mi aplicación web asp.net y estaba revisando un par de artículos que obtuve de Castle Project y Code Project sobre el proxy dinámico de Castle en la aplicación web asp.net ....
Ambos artículos funcionan con la creación de interceptores pero no entiendo por qué los interceptores se usan con las clases ... ¿Por qué debería interceptar mi clase que se comporta correctamente?
La razón por la que usaría Castle-DynamicProxy es por lo que se llama Programación Orientada a Aspectos. Le permite insertar código en el flujo de operación estándar de su código sin la necesidad de volverse dependiente del código en sí.
Un ejemplo simple es, como siempre, el registro. Que crearía un DynamicProxy alrededor de una clase que tiene errores desde que registra los datos que entran en el método y atrapa las excepciones y luego registra la excepción.
Usando el interceptor, su código actual no tiene idea de que existe (suponiendo que tenga su software integrado de manera desacoplada con las interfaces) y puede cambiar el registro de sus clases con un contenedor de inversión de control para usar la clase proxied sin tener que hacerlo cambiar una sola línea más donde en el código. Luego, cuando resuelves el error, puedes desactivar el proxying.
Se puede ver un uso más avanzado de proxying con NHibernate, donde toda la carga diferida se maneja a través de proxies.
Digamos que su clase necesita hacer 3 cosas para una determinada operación:
- Realice un control de seguridad;
- Registra la llamada al método;
- Caché el resultado.
Supongamos además que su clase no sabe nada sobre la forma específica en que ha configurado su seguridad, registro o almacenamiento en caché. Necesitas depender de las abstracciones de estas cosas.
Hay algunas maneras de hacerlo. Una forma sería configurar un grupo de interfaces y usar inyección de constructor:
public class OrderService : IOrderService
{
private readonly IAuthorizationService auth;
private readonly ILogger logger;
private readonly ICache cache;
public OrderService(IAuthorizationService auth, ILogger logger,
ICache cache)
{
if (auth == null)
throw new ArgumentNullException("auth");
if (logger == null)
throw new ArgumentNullException("logger");
if (cache == null)
throw new ArgumentNullException("cache");
this.auth = auth;
this.logger = logger;
this.cache = cache;
}
public Order GetOrder(int orderID)
{
auth.AssertPermission("GetOrder");
logger.LogInfo("GetOrder:{0}", orderID);
string cacheKey = string.Format("GetOrder-{0}", orderID);
if (cache.Contains(cacheKey))
return (Order)cache[cacheKey];
Order order = LookupOrderInDatabase(orderID);
cache[cacheKey] = order;
return order;
}
}
Este no es un código horrible, pero piense en los problemas que estamos presentando:
La clase
OrderService
no puede funcionar sin las tres dependencias. Si queremos que sea así, tenemos que empezar a pegar el código con controles nulos en todas partes.Estamos escribiendo una tonelada de código adicional para realizar una operación relativamente simple (buscando un pedido).
Todo este código repetitivo tiene que repetirse en todos los métodos, lo que lo convierte en una implementación muy grande, fea y propensa a errores.
Aquí hay una clase que es mucho más fácil de mantener:
public class OrderService : IOrderService
{
[Authorize]
[Log]
[Cache("GetOrder-{0}")]
public virtual Order GetOrder(int orderID)
{
return LookupOrderInDatabase(orderID);
}
}
En la Programación Orientada a Aspectos , estos atributos se denominan Puntos de Unión , cuyo conjunto completo se denomina Corte de Puntos .
En lugar de escribir el código de dependencia, una y otra vez, dejamos "pistas" de que se deben realizar algunas operaciones adicionales para este método.
Por supuesto, estos atributos tienen que convertirse en código en algún momento , pero puede diferir todo el camino hasta su código de aplicación principal, al crear un proxy para OrderService
(tenga en cuenta que el método GetOrder
se ha convertido en virtual
porque debe ser anulado para el servicio) e interceptando el método GetOrder
.
Escribir el interceptor podría ser tan simple como esto:
public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
if (Attribute.IsDefined(invocation.Method, typeof(LogAttribute))
{
Console.Writeline("Method called: "+ invocation.Method.Name);
}
invocation.Proceed();
}
}
Y crear el proxy sería:
var generator = new ProxyGenerator();
var orderService = (IOrderService)generator.CreateClassProxy(typeof(OrderService),
new LoggingInterceptor());
Este no es solo un código mucho menos repetitivo, sino que elimina por completo la dependencia real , porque mira lo que hemos hecho, ni siquiera tenemos una autorización o un sistema de almacenamiento en caché, pero el sistema aún se ejecuta. Simplemente podemos insertar la lógica de autorización y almacenamiento en caché más adelante registrando otro interceptor y comprobando AuthorizeAttribute
o CacheAttribute
.
Esperemos que esto explique el "por qué".
Barra lateral: como comenta Krzysztof Koźmic, no es una "mejor práctica" de DP utilizar un interceptor dinámico como este. En el código de producción, no desea que el interceptor se ejecute para métodos innecesarios, por lo tanto, use un IInterceptorSelector en su lugar.