switch - ¿Cómo intercepto una llamada a método en C#?
como se usa el switch en c# (15)
- Escribe tu propia biblioteca AOP.
- Use la reflexión para generar un proxy de registro sobre sus instancias (no estoy seguro si puede hacerlo sin cambiar alguna parte de su código existente).
- Vuelva a escribir el ensamblaje e inyecte su código de registro (básicamente lo mismo que 1).
- Aloje el CLR y agregue el registro en este nivel (creo que esta es la solución más difícil de implementar, aunque no estoy seguro de si tiene los ganchos necesarios en el CLR).
Para una clase dada, me gustaría tener la funcionalidad de rastreo, es decir, me gustaría registrar cada llamada al método (firma de método y valores de parámetros reales) y cada salida de método (solo la firma del método).
¿Cómo logro esto suponiendo que:
- No quiero usar ninguna biblioteca de AOP de terceros para C #,
- No deseo agregar código duplicado a todos los métodos que quiero rastrear,
- No quiero cambiar la API pública de la clase: los usuarios de la clase deberían poder llamar a todos los métodos exactamente de la misma manera.
Para hacer la pregunta más concreta, supongamos que hay 3 clases:
public class Caller
{
public static void Call()
{
Traced traced = new Traced();
traced.Method1();
traced.Method2();
}
}
public class Traced
{
public void Method1(String name, Int32 value) { }
public void Method2(Object object) { }
}
public class Logger
{
public static void LogStart(MethodInfo method, Object[] parameterValues);
public static void LogEnd(MethodInfo method);
}
¿Cómo invoco Logger.LogStart y Logger.LogEnd para cada llamada a Method1 y Method2 sin modificar el método Caller.Call y sin agregar las llamadas explícitamente a Traced.Method1 y Traced.Method2 ?
Editar: ¿Cuál sería la solución si se me permite cambiar ligeramente el método de Llamada?
C # no es un lenguaje orientado a AOP. Tiene algunas características de AOP y puedes emular a otras, pero hacer un AOP con C # es doloroso.
Busqué maneras de hacer exactamente lo que quería hacer y no encontré una manera fácil de hacerlo.
Según lo entiendo, esto es lo que quieres hacer:
[Log()]
public void Method1(String name, Int32 value);
y para hacer eso tienes dos opciones principales
Herede su clase de MarshalByRefObject o ContextBoundObject y defina un atributo que herede de IMessageSink. Este artículo tiene un buen ejemplo. No obstante, debes tener en cuenta que si usas un MarshalByRefObject, el rendimiento bajará muchísimo, y lo digo en serio, estoy hablando de un rendimiento perdido de 10 veces, así que piensa detenidamente antes de intentarlo.
La otra opción es inyectar código directamente. En tiempo de ejecución, lo que significa que tendrá que usar el reflejo para "leer" cada clase, obtener sus atributos e inyectar la llamada apropiada (y para el caso, creo que no podría usar el método Reflection.Emit ya que creo que Reflection.Emit wouldn no le permite insertar un nuevo código dentro de un método ya existente). En el momento del diseño, esto significará crear una extensión para el compilador de CLR, que sinceramente no tengo idea de cómo se hace.
La última opción es usar un marco de IoC . Tal vez no sea la solución perfecta, ya que la mayoría de los marcos de IoC funcionan definiendo puntos de entrada que permiten que los métodos se enganchen, pero, dependiendo de lo que quieras lograr, eso podría ser una buena aproximación.
Eche un vistazo a esto: cosas bastante pesadas .. http://msdn.microsoft.com/en-us/magazine/cc164165.aspx
Essential .net - don box tenía un capítulo sobre lo que necesita llamado Interceptación. Me puse algo de esto aquí (lo siento por los colores de la fuente - tenía un tema oscuro en ese entonces ...) http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html
El AOP es imprescindible para la implementación limpia del código; sin embargo, si desea rodear un bloque en C #, los métodos genéricos tienen un uso relativamente más fácil. (con intelli sense y fuertemente tipado código) Ciertamente, NO puede ser una alternativa para AOP.
A pesar de que PostSHarp tiene pequeños problemas de errores (no me siento seguro para usar en la producción), es una buena cosa.
Clase de contenedor genérico,
public class Wrapper
{
public static Exception TryCatch(Action actionToWrap, Action<Exception> exceptionHandler = null)
{
Exception retval = null;
try
{
actionToWrap();
}
catch (Exception exception)
{
retval = exception;
if (exceptionHandler != null)
{
exceptionHandler(retval);
}
}
return retval;
}
public static Exception LogOnError(Action actionToWrap, string errorMessage = "", Action<Exception> afterExceptionHandled = null)
{
return Wrapper.TryCatch(actionToWrap, (e) =>
{
if (afterExceptionHandled != null)
{
afterExceptionHandled(e);
}
});
}
}
el uso podría ser así (con un sentido inteligente, por supuesto)
var exception = Wrapper.LogOnError(() =>
{
MessageBox.Show("test");
throw new Exception("test");
}, "Hata");
He encontrado una manera diferente que puede ser más fácil ...
Declare un método InvokeMethod
[WebMethod]
public object InvokeMethod(string methodName, Dictionary<string, object> methodArguments)
{
try
{
string lowerMethodName = ''_'' + methodName.ToLowerInvariant();
List<object> tempParams = new List<object>();
foreach (MethodInfo methodInfo in serviceMethods.Where(methodInfo => methodInfo.Name.ToLowerInvariant() == lowerMethodName))
{
ParameterInfo[] parameters = methodInfo.GetParameters();
if (parameters.Length != methodArguments.Count()) continue;
else foreach (ParameterInfo parameter in parameters)
{
object argument = null;
if (methodArguments.TryGetValue(parameter.Name, out argument))
{
if (parameter.ParameterType.IsValueType)
{
System.ComponentModel.TypeConverter tc = System.ComponentModel.TypeDescriptor.GetConverter(parameter.ParameterType);
argument = tc.ConvertFrom(argument);
}
tempParams.Insert(parameter.Position, argument);
}
else goto ContinueLoop;
}
foreach (object attribute in methodInfo.GetCustomAttributes(true))
{
if (attribute is YourAttributeClass)
{
RequiresPermissionAttribute attrib = attribute as YourAttributeClass;
YourAttributeClass.YourMethod();//Mine throws an ex
}
}
return methodInfo.Invoke(this, tempParams.ToArray());
ContinueLoop:
continue;
}
return null;
}
catch
{
throw;
}
}
Luego defino mis métodos como tal
[WebMethod]
public void BroadcastMessage(string Message)
{
//MessageBus.GetInstance().SendAll("<span class=''system''>Web Service Broadcast: <b>" + Message + "</b></span>");
//return;
InvokeMethod("BroadcastMessage", new Dictionary<string, object>() { {"Message", Message} });
}
[RequiresPermission("editUser")]
void _BroadcastMessage(string Message)
{
MessageBus.GetInstance().SendAll("<span class=''system''>Web Service Broadcast: <b>" + Message + "</b></span>");
return;
}
Ahora puedo tener el cheque en tiempo de ejecución sin la inyección de dependencia ...
No hay trampas en el sitio :)
Esperemos que acepte que esto es menos importante que un Marco AOP o que se deriva de MarshalByRefObject o que utiliza clases remotas o proxy.
La forma más sencilla de lograr eso es probablemente usar PostSharp . Inyecta código dentro de sus métodos en función de los atributos que le aplica. Le permite hacer exactamente lo que quiere.
Otra opción es usar la API de creación de perfiles para inyectar código dentro del método, pero eso es realmente duro.
Lo mejor que puede hacer antes de C # 6 con ''nameof'' lanzado es usar StackTrace lento y linq Expressions.
Por ejemplo, para tal método
public void MyMethod(int age, string name)
{
log.DebugTrace(() => age, () => name);
//do your stuff
}
Tal línea puede ser producida en su archivo de registro
Method ''MyMethod'' parameters age: 20 name: Mike
Aquí está la implementación:
//TODO: replace with ''nameof'' in C# 6
public static void DebugTrace(this ILog log, params Expression<Func<object>>[] args)
{
#if DEBUG
var method = (new StackTrace()).GetFrame(1).GetMethod();
var parameters = new List<string>();
foreach(var arg in args)
{
MemberExpression memberExpression = null;
if (arg.Body is MemberExpression)
memberExpression = (MemberExpression)arg.Body;
if (arg.Body is UnaryExpression && ((UnaryExpression)arg.Body).Operand is MemberExpression)
memberExpression = (MemberExpression)((UnaryExpression)arg.Body).Operand;
parameters.Add(memberExpression == null ? "NA" : memberExpression.Member.Name + ": " + arg.Compile().DynamicInvoke().ToString());
}
log.Debug(string.Format("Method ''{0}'' parameters {1}", method.Name, string.Join(" ", parameters)));
#endif
}
No sé una solución, pero mi enfoque sería el siguiente.
Decora la clase (o sus métodos) con un atributo personalizado. En otra parte del programa, permita que una función de inicialización refleje todos los tipos, lea los métodos decorados con los atributos e inserte algún código IL en el método. En realidad, podría ser más práctico reemplazar el método por un stub que llame a LogStart
, el método real y luego LogEnd
. Además, no sé si puede cambiar los métodos usando la reflexión, por lo que podría ser más práctico reemplazar todo el tipo.
Podrías lograrlo con la característica Interception de un contenedor DI como Castle Windsor . De hecho, es posible configurar el contenedor de tal manera que se interceptarían todas las clases que tienen un método decorado por un atributo específico.
Con respecto al punto n. ° 3, OP solicitó una solución sin marco de AOP. Supuse en la siguiente respuesta que lo que se debería evitar era Aspect, JointPoint, PointCut, etc. De acuerdo con la Interception , ninguno de ellos debe cumplir lo que se pide.
Configure el registro genérico de un Interceptor, basado en la presencia de un atributo:
public class RequireInterception : IContributeComponentModelConstruction
{
public void ProcessModel(IKernel kernel, ComponentModel model)
{
if (HasAMethodDecoratedByLoggingAttribute(model.Implementation))
{
model.Interceptors.Add(new InterceptorReference(typeof(ConsoleLoggingInterceptor)));
model.Interceptors.Add(new InterceptorReference(typeof(NLogInterceptor)));
}
}
private bool HasAMethodDecoratedByLoggingAttribute(Type implementation)
{
foreach (var memberInfo in implementation.GetMembers())
{
var attribute = memberInfo.GetCustomAttributes(typeof(LogAttribute)).FirstOrDefault() as LogAttribute;
if (attribute != null)
{
return true;
}
}
return false;
}
}
Agregue el IContributeComponentModelConstruction creado al contenedor
container.Kernel.ComponentModelBuilder.AddContributor(new RequireInterception());
Y puedes hacer lo que quieras en el interceptor mismo
public class ConsoleLoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.Writeline("Log before executing");
invocation.Proceed();
Console.Writeline("Log after executing");
}
}
Agregue el atributo de registro a su método para iniciar sesión
public class Traced
{
[Log]
public void Method1(String name, Int32 value) { }
[Log]
public void Method2(Object object) { }
}
Tenga en cuenta que será necesario cierto manejo del atributo si solo se debe interceptar algún método de una clase. Por defecto, todos los métodos públicos serán interceptados.
Podrías utilizar potencialmente el Patrón Decorador GOF y ''decorar'' todas las clases que necesitan rastreo.
Probablemente solo sea realmente práctico con un contenedor IOC (pero como señalador anterior, es posible que desee considerar la interceptación del método si va a seguir la ruta del IOC).
Primero, debe modificar su clase para implementar una interfaz (en lugar de implementar MarshalByRefObject).
interface ITraced {
void Method1();
void Method2()
}
class Traced: ITraced { .... }
A continuación, necesita un objeto envoltorio genérico basado en RealProxy para decorar cualquier interfaz que permita interceptar cualquier llamada al objeto decorado.
class MethodLogInterceptor: RealProxy
{
public MethodLogInterceptor(Type interfaceType, object decorated)
: base(interfaceType)
{
_decorated = decorated;
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase;
Console.WriteLine("Precall " + methodInfo.Name);
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
Console.WriteLine("Postcall " + methodInfo.Name);
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext, methodCall);
}
}
Ahora estamos listos para interceptar llamadas a Method1 y Method2 of ITraced
public class Caller
{
public static void Call()
{
ITraced traced = (ITraced)new MethodLogInterceptor(typeof(ITraced), new Traced()).GetTransparentProxy();
traced.Method1();
traced.Method2();
}
}
Puede utilizar el marco de código abierto CInject en CodePlex. Puede escribir un código mínimo para crear un Inyector y lograr que intercepte cualquier código rápidamente con CInject. Además, como esto es de código abierto, puedes extender esto también.
O puede seguir los pasos que se mencionan en este artículo sobre Llamadas a métodos de interceptación utilizando IL y crear su propio interceptor utilizando clases Reflection.Emit en C #.
Si desea rastrear sus métodos sin limitación (sin adaptación de código, sin Marco de AOP, sin código duplicado), déjeme decirle que necesita algo de magia ...
En serio, resolví implementar un Framework AOP trabajando en tiempo de ejecución.
Puede encontrarlo aquí: NConcern .NET AOP Framework
Decidí crear este Marco de AOP para responder a este tipo de necesidades. es una biblioteca simple, muy liviana. Puede ver un ejemplo de registrador en la página de inicio.
Si no desea utilizar un ensamblado de terceros, puede explorar el código fuente (fuente abierta) y copiar los archivos Aspect.Directory.cs y Aspect.Directory.Entry.cs para adaptarlos a sus deseos. Estas clases permiten reemplazar tus métodos en tiempo de ejecución. Solo te pediría que respetases la licencia.
Espero que encuentres lo que necesitas o que te convenza de que finalmente uses un Marco AOP.
Si escribe una clase, llámese Tracing, que implementa la interfaz IDisposable, podría envolver todos los cuerpos de método en un
Using( Tracing tracing = new Tracing() ){ ... method body ...}
En la clase Tracing, puede manejar la lógica de las trazas en el método constructor / Dispose, respectivamente, en la clase Tracing para realizar un seguimiento de la entrada y la salida de los métodos. Tal que:
public class Traced
{
public void Method1(String name, Int32 value) {
using(Tracing tracer = new Tracing())
{
[... method body ...]
}
}
public void Method2(Object object) {
using(Tracing tracer = new Tracing())
{
[... method body ...]
}
}
}
necesita interceptar a Ayende para obtener una respuesta sobre cómo lo hizo: http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx