Dora. Interception: The AOP framework for. NET Core [4]: demonstrates several typical applications,

Source: Internet
Author: User

Dora. Interception: The AOP framework for. NET Core [4]: demonstrates several typical applications,

To help you better understand Dora. interception, and better apply it to your project. We use the following simple examples to demonstrate several common AOP applications in Dora. interception. For the examples shown below, they are just guiding applications, so I will try to simplify them as much as possible. If you need to transplant the corresponding application scenarios to specific project development, more optimizations are required. Download the source code from here.

Directory
1. format input parameters
2. Automated parameter verification
Iii. Automatically cache the return values of methods

1. format input parameters

Some methods have some requirements on the format of input parameters, but we do not want to limit too much on the original input parameters, we can format the input parameters through AOP. Taking the following code as an example, the Demo Invoke method has a string type parameter input. We hope this value will always be stored in an uppercase format, but we hope that the original input is case insensitive, therefore, we add an UpperCaseAttribute to the parameter as follows. This type of format conversion is implemented through a custom Interceptor named ArgumentConversionInterceptor. convertargumentsattriattribute on the standard method is its InterceptorAttribute. In the Main method, we create a Demo object (actually a Demo proxy object) in the form of DI and call its Invoke method, the input parameters in lower-case format are automatically converted to the upper-case format.

  1 class Program  2 {  3     static void Main(string[] args)  4     {  5         var demo = new ServiceCollection()  6                 .AddSingleton<Demo, Demo>()  7                 .BuildInterceptableServiceProvider()  8                 .GetRequiredService<Demo>();  9         Debug.Assert(demo.Invoke("foobar") == "FOOBAR"); 10     } 11 } 12 public class Demo 13 { 14     [ConvertArguments] 15     public virtual string Invoke([UpperCase]string input) 16     { 17         return input; 18     } 19 }

Next we will use Dora. Intercecption to implement this application scenario. The UpperCaseAttribute that corresponds to the parameter input is used to register a corresponding ArgumentConvertor. Because its essence is to convert parameters, the abstract ArgumentConvertor is represented by the following interface. IArgumentConvertor has a unique Convert method to Convert parameters. The input of this method is an ArgumentConveresionContext object. Through this context object, we can obtain the ParameterInfo object and parameter value representing the current parameter.

  1 public interface IArgumentConvertor  2 {  3     object Convert(ArgumentConveresionContext context);  4 }  5   6 public class ArgumentConveresionContext  7 {  8     public ParameterInfo ParameterInfo { get; }  9     public object Value { get; } 10  11     public ArgumentConveresionContext(ParameterInfo parameterInfo, object valule) 12     { 13         this.ParameterInfo = parameterInfo; 14         this.Value = valule; 15     } 16 }

Just as Dora. Interception deliberately separates Interceptor from Interceptor, we will also provide ArgumentConvertorProvider of ArgumentConvertor through the following interface.

  1 public interface IArgumentConvertorProvider  2 {  3     IArgumentConvertor GetArgumentConvertor();  4 }

For simplicity, We have UpperCaseAttribute implement both the IArgumentConvertor and IArgumentConvertorProvider interfaces. In the implemented Convert method, we Convert the input parameters into a large write format. As for the Implementation Method GetArgumentConvertor, you only need to return it.

  1 [AttributeUsage(AttributeTargets.Parameter)]  2 public class UpperCaseAttribute : Attribute, IArgumentConvertor, IArgumentConvertorProvider  3 {  4     public object Convert(ArgumentConveresionContext context)  5     {  6         if (context.ParameterInfo.ParameterType == typeof(string))  7         {  8             return context.Value?.ToString()?.ToUpper();  9         } 10         return context.Value; 11     } 12  13     public IArgumentConvertor GetArgumentConvertor() 14     { 15         return this; 16     } 17 }

Finally, let's take a look at how the Interceptor that truly completes parameter conversion is implemented. The following code shows that in the InvokeAsync method of ArgumentConversionInterceptor, The MethodInfo object that represents the target method is obtained by identifying the TargetMethod attribute of the InvocationContext object that calls the context, then, all argumentconverterproviders on the standard parameters are parsed. Then, get the corresponding parameter value through the Arguments attribute of InvocationContext, and create the ArgumentConveresionContext object between the parameter value and the corresponding MethodInfo object. The latter is finally passed into the ArgumentConverterProvider for the corresponding parameter. The converted parameters are re-written to the parameter list represented by the Arguments attribute of InvocationContext.

  1 public class ArgumentConversionInterceptor  2 {  3     private InterceptDelegate _next;  4   5     public ArgumentConversionInterceptor(InterceptDelegate next)  6     {  7         _next = next;  8     }  9  10     public Task InvokeAsync(InvocationContext invocationContext) 11     { 12         var parameters = invocationContext.TargetMethod.GetParameters(); 13         for (int index = 0; index < invocationContext.Arguments.Length; index++) 14         { 15             var parameter = parameters[index]; 16             var converterProviders = parameter.GetCustomAttributes(false).OfType<IArgumentConvertorProvider>().ToArray(); 17             if (converterProviders.Length > 0) 18             { 19                 var convertors = converterProviders.Select(it => it.GetArgumentConvertor()).ToArray(); 20                 var value = invocationContext.Arguments[0]; 21                 foreach (var convertor in convertors) 22                 { 23                     var context = new ArgumentConveresionContext(parameter, value); 24                     value = convertor.Convert(context); 25                 } 26                 invocationContext.Arguments[index] = value; 27             } 28         } 29         return _next(invocationContext); 30     } 31 } 32  33 public class ConvertArgumentsAttribute : InterceptorAttribute 34 { 35     public override void Use(IInterceptorChainBuilder builder) 36     { 37         builder.Use<ArgumentConversionInterceptor>(this.Order); 38     } 39 }
2. Automated parameter verification

It is a more common application scenario of AOP to apply the corresponding verification rules to method parameters to achieve automatic parameter verification. The following code snippet is used as an example. It is also the Demo Invoke method. We apply a MaxLengthAttribute feature on the input parameter. This is a ValidationAttribute provided by Microsoft to limit the length of a string. In this example, the length of the string is limited to 5 characters or less, and a verification error message is provided. The Interceptor provided by validateargumentsattritor on the standard method is used for parameter verification. In the Main method, we obtain the proxy object corresponding to the Demo according to DI and call its Invoke method. Because the length of the input string ("Foobar") is 6, verification fails. The result is that a ValidationException type exception is thrown, which is further encapsulated as an aggresponexception exception.

1 class Program 2 {3 static void Main (string [] args) 4 {5 var demo = new ServiceCollection () 6. addSingleton <Demo, Demo> () 7. buildInterceptableServiceProvider () 8. getRequiredService <Demo> (); 9 try 10 {11 demo. invoke ("Foobar"); 12 Debug. fail ("Expected verification exception not thrown"); 13} 14 catch (aggresponexception ex) 15 {16 ValidationException validationException = (ValidationException) ex. innerException; 17 Debug. assert ("the string length cannot exceed 5" = validationException. message); 18} 19} 20} 21 public class Demo 22 {23 [ValidateArguments] 24 public virtual string Invoke (25 [MaxLength (5, errorMessage = "string Length cannot exceed 5")] 26 string input) 27 {28 return input; 29} 30}

Let's take a look at validateargumentsattritor and how the Interceptor provided by it is implemented. The following code shows that the implementation of ValidationInterceptor is similar to that of ArgumentConversionInterceptor. The logic is very simple and I will not explain it. Here I will talk about another problem by the way: Some frameworks will apply Interceptor directly to parameters (for example, WCF can define ParameterInspector to test parameters ), I think it is inappropriate in terms of design. Because the essence of AOP is to block methods, Interceptor should only map to methods in the end, parameter verification, conversion, and other parameter-based processing must be performed by an Interceptor. In other words, the rules applied to parameters serve a specific type of Interceptor. These rules should be parsed by the corresponding Interceptor, but Interceptor itself should not be mapped to parameters.

  1 public class ValidationInterceptor  2 {  3     private InterceptDelegate _next;  4   5     public ValidationInterceptor(InterceptDelegate next)  6     {  7         _next = next;  8     }  9  10     public Task InvokeAsync(InvocationContext invocationContext) 11     { 12         var parameters = invocationContext.TargetMethod.GetParameters(); 13         for (int index = 0; index < invocationContext.Arguments.Length; index++) 14         { 15             var parameter = parameters[index]; 16             var attributes = parameter.GetCustomAttributes(false).OfType<ValidationAttribute>(); 17             foreach (var attribute in attributes) 18             { 19                 var value = invocationContext.Arguments[index]; 20                 var context = new ValidationContext(value); 21                 attribute.Validate(value, context); 22             } 23         } 24         return _next(invocationContext); 25     } 26 } 27  28 public class ValidateArgumentsAttribute : InterceptorAttribute 29 { 30     public override void Use(IInterceptorChainBuilder builder) 31     { 32         builder.Use<ValidationInterceptor>(this.Order); 33     } 34 }
Iii. Automatically cache the return values of methods

Sometimes we define a method like this: the method itself performs some time-consuming operations and returns the final processing result, and the input of the method determines the output of the method. To avoid frequent execution of time-consuming methods, we can use the AOP method to automatically cache the return values of the method. Let's take a look at the final result as an example. As shown in the following code snippet, the Demo type has a GetCurrentTime to return the current time. It has a parameter used to specify the Kind (Local, UTC, or Unspecified) of the return time ). This method marks a CaheReturnValueAttribute that provides an Interceptor to cache the return value of the method. The cache is for input parameters. That is to say, if the input parameters are the same, the execution results are the same. The debugging assertions of the Main method confirm this.

class Program{    static void Main(string[] args)    {        var demo = new ServiceCollection()                .AddMemoryCache()                .AddSingleton<Demo, Demo>()                .BuildInterceptableServiceProvider()                .GetRequiredService<Demo>();        var time1 = demo.GetCurrentTime(DateTimeKind.Local);        Thread.Sleep(1000);        Debug.Assert(time1 == demo.GetCurrentTime(DateTimeKind.Local));        var time2 = demo.GetCurrentTime(DateTimeKind.Utc);        Debug.Assert(time1 != time2);        Thread.Sleep(1000);        Debug.Assert(time2 == demo.GetCurrentTime(DateTimeKind.Utc));        var time3 = demo.GetCurrentTime(DateTimeKind.Unspecified);        Debug.Assert(time3 != time1);        Debug.Assert(time3 != time2);        Thread.Sleep(1000);        Debug.Assert(time3 == demo.GetCurrentTime(DateTimeKind.Unspecified));        Console.Read();    }} public class Demo{    [CacheReturnValue]    public virtual DateTime GetCurrentTime(DateTimeKind dateTimeKind)    {        switch (dateTimeKind)        {            case DateTimeKind.Local: return DateTime.Now.ToLocalTime();            case DateTimeKind.Utc: return DateTime.UtcNow;            default: return DateTime.Now;        }    }}

How to define CacheInterceptor for caching, but before that, let's take a look at the definition of the cache Key. The cache Key is a CacheKey defined as follows. It consists of two parts, indicating the MethodBase of the method and the parameters passed in by calling the method.

public struct Cachekey{public MethodBase Method { get; }public object[] InputArguments { get; }public Cachekey(MethodBase method, object[] arguments){    this.Method = method;    this.InputArguments = arguments;}public override bool Equals(object obj){    if (!(obj is Cachekey))    {        return false;    }    Cachekey another = (Cachekey)obj;       if (!this.Method.Equals(another.Method))    {        return false;    }    for (int index = 0; index < this.InputArguments.Length; index++)    {        var argument1 = this.InputArguments[index];        var argument2 = another.InputArguments[index];        if (argument1 == null && argument2 == null)        {            continue;        }        if (argument1 == null || argument2 == null)        {            return false;        }        if (!argument2.Equals(argument2))        {            return false;        }    }    return true;}public override int GetHashCode(){    int hashCode = this.Method.GetHashCode();    foreach (var argument in this.InputArguments)    {        hashCode = hashCode ^ argument.GetHashCode();    }    return hashCode;}}

The following is the definition of CacheInterceptor. It can be seen that the implementation logic is very simple. CacheInterceptor uses the IMemoryCache provided in the form of method injection to cache the return values of method calls. In the InvokeAsync method, we create a CacheKey object as the cache Key based on the MethodBase provided by the current execution context representing the current method and input parameters. If the corresponding return value can be extracted from the cache based on this Key, it will directly save this value to the execution context and terminate the call of the current method. Otherwise, if the returned value is not cached, it will continue to be called and save the returned value to the cache after the call is completed for later use.

public class CacheInterceptor{    private readonly InterceptDelegate _next;                public CacheInterceptor(InterceptDelegate next)    {        _next = next;                        }    public async Task InvokeAsync(InvocationContext context, IMemoryCache cache)    {        var key = new Cachekey(context.Method, context.Arguments);        if (cache.TryGetValue(key, out object value))        {            context.ReturnValue = value;        }        else        {            await _next(context);            cache.Set(key, context.ReturnValue);        }    } }

The CacheReturnValueAttribute on the GetCurrent method is defined as follows. It only needs to register the above CacheInterceptor In the rewrite Use method according to the standard method. By the way, separating Interceptor from the Attribute registered with it also has the advantage: I can specify a different name for Attribute, such as CacheReturnValueAttribute.

[AttributeUsage(AttributeTargets.Method)]public class CacheReturnValueAttribute : InterceptorAttribute{    public override void Use(IInterceptorChainBuilder builder)    {        builder.Use<CacheInterceptor>(this.Order);    }}


Dora. Interception: The AOP framework for. NET Core [1]: Brand New Version
Dora. Interception: The AOP framework created for. NET Core [2]: Different Interceptor definition methods
Dora. Interception: The AOP framework created for. NET Core [3]: registration of Interceptor
Dora. Interception: The AOP framework for. NET Core [4]: demonstrate several typical applications

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.