Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ Core support for aspect-interceptor, dependency injection integration, web appli
* [shriek-fx](https://github.com/ElderJames/shriek-fx)  
* [Util](https://github.com/dotnetcore/Util)
* [Zxw.Framework.NetCore](https://github.com/VictorTzeng/Zxw.Framework.NetCore)
* [Tars.Net](https://github.com/TarsNET)
* [FastCache](https://github.com/sj-distributor/FastCache)

## Contributors
Expand Down
99 changes: 99 additions & 0 deletions docs/0.AOP-Introduction.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# 0. AOP Introduction

## AOP Concept

Aspect-Oriented Programming (AOP) - We know that the characteristics of Object-Oriented Programming are inheritance, polymorphism, and encapsulation. Encapsulation requires distributing functionality across different objects, which is often called responsibility allocation in software design. In other words, we design different methods for different classes. This spreads the code across individual classes. The benefit is reducing code complexity and making classes reusable.

However, people have also discovered that while dispersing code, it also increases code duplication. What does this mean? For example, we might need to log in every method in two classes. According to OOP design principles, we must add logging content to the methods in both classes. Perhaps they are identical, but because OOP design prevents classes from connecting with each other, we cannot consolidate this duplicated code.

Maybe someone would say: that's easy, we can write this code in an independent class with an independent method, and then call it in both classes. However, this would couple these two classes with the independent class mentioned above, and changes to it would affect both classes. So, is there any way that allows us to add code arbitrarily when needed? The programming idea of dynamically inserting code into specified methods of specified classes at runtime is called Aspect-Oriented Programming.

Generally speaking, we call the code fragment that cuts into specified classes and specified methods an "aspect", while which classes and methods to cut into is called the "pointcut". With AOP, we can extract common code from several classes into a slice, and insert it into objects when needed, thus changing their original behavior. From this perspective, AOP is actually just a supplement to OOP. OOP distinguishes classes horizontally, while AOP adds specific code vertically to objects. With AOP, OOP becomes three-dimensional. Adding the time dimension, AOP transforms OOP from 2D to 3D, from a plane to a solid. Technically speaking, AOP is basically implemented through proxy mechanisms.

AOP has been a milestone in programming history and is a very beneficial supplement to OOP programming.

This section is adapted from Zhihu - Discussion on AOP concept: https://www.zhihu.com/question/24863332

For those who want to understand AOP in detail, you can read the above [Zhihu link](https://www.zhihu.com/question/24863332)

Or https://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/

Or https://www.cnblogs.com/DjlNet/p/7603654.html

Apologies for being lazy here, I won't provide a detailed overview.

## AOP Implementation Methods

AOP implementations will use some common methods:

* Using a preprocessor (like preprocessor in C++) to add source code.
* Using a post-processor to add instructions on compiled binary code.
* Using a special compiler to add code at compile time.
* Using code interceptors at runtime to intercept execution and add the required code.

However, the most common are post-processing and code interception:

* Post-processing, or **Static Weaving**

Refers to using commands provided by the AOP framework to compile, thus generating AOP proxy classes at the compilation stage, also known as compile-time enhancement or static weaving.

In .NET, this is typically done by intercepting the compilation process through custom Build Tasks in MSBuild, inserting your own IL into the generated assembly.

.NET framework representative: [PostSharp](https://www.postsharp.net/aop.net)

* Code interception, or **Dynamic Proxy**, **Dynamic Weaving**, **Code Hijacking**

AOP dynamic proxy classes are generated "temporarily" in memory at runtime, thus also known as runtime enhancement or dynamic proxy.

In .NET, this is typically done at runtime through Emit technology to generate dynamic assemblies and dynamic proxy types to intercept target methods.

.NET framework representative: [Castle DynamicProxy](https://github.com/castleproject/Core/blob/master/docs/dynamicproxy-introduction.md) and [AspectCore](https://github.com/dotnetcore/AspectCore-Framework)

## Brief Overview of AspectCore's Dynamic Weaving

Simply put, dynamic weaving is creating some `proxy classes` of our real business implementation classes at runtime.

Through the `proxy class`, some interceptor classes invisible to business logic code are called at runtime.

Borrowing an example from Castle DynamicProxy to illustrate:

``` csharp
public class Interceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine("Before target call");
try
{
invocation.Proceed();
}
catch(Exception)
{
Console.WriteLine("Target threw an exception!");
throw;
}
finally
{
Console.WriteLine("After target call");
}
}
}
```

This is an interceptor class.

The purpose of dynamic weaving is to call the `Interceptor` to "intercept" `IInvocation` through the `proxy class` without `IInvocation` knowing about this interceptor class.

The example below shows the invocation:

![](https://github.com/castleproject/Core/raw/master/docs/images/proxy-pipeline.png)

The blue area is the proxy class area. To the outside world, it looks like the proxied object.

But when actually called, like the yellow arrow, it goes through layer after layer of interceptor classes before finally calling the proxied object.

The return also follows the green arrow, going through layer after layer of interceptor classes.

Finally achieving the purpose of dynamic proxy.

Currently, AspectCore uses dynamic proxy as the AOP implementation rather than theoretically more performant static weaving, because I believe dynamic proxy allows for better IoC integration and can access more runtime metadata information in aspects. After continuous optimization, the performance of AspectCore dynamic proxy is no longer inferior to static weaving implementations.
241 changes: 241 additions & 0 deletions docs/1.Getting-Started.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
# 1. Getting Started Guide

Version 2.0.0 :arrow_up: (Simple code example: https://github.com/cdcd72/NetCore.AspectCore.AOP.Demo)
Version 2.0.0 :arrow_down: (Simple code example: https://github.com/fs7744/AspectCoreDemo)

## Getting Started with AspectCore

* Launch Visual Studio. From the File menu, select New > Project. Select the ASP.NET Core Web Application project template and create a new ASP.NET Core Web Application project.

* Install `AspectCore.Extensions.DependencyInjection` package from Nuget:
```
PM> Install-Package AspectCore.Extensions.DependencyInjection
```
* Generally, you can customize an attribute class using the abstract `AbstractInterceptorAttribute` which implements the `IInterceptor` interface. AspectCore implements attribute-based interceptor configuration by default. Your custom interceptor looks like this:
``` csharp
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
{
public async override Task Invoke(AspectContext context, AspectDelegate next)
{
try
{
Console.WriteLine("Before service call");
await next(context);
}
catch (Exception)
{
Console.WriteLine("Service threw an exception!");
throw;
}
finally
{
Console.WriteLine("After service call");
}
}
}
```
* Define the `ICustomService` interface and its implementation `CustomService`:
``` csharp
public interface ICustomService
{
[CustomInterceptor]
void Call();
}

public class CustomService : ICustomService
{
public void Call()
{
Console.WriteLine("service calling...");
}
}
```
* Inject `ICustomService` in `HomeController`:
``` csharp
public class HomeController : Controller
{
private readonly ICustomService _service;
public HomeController(ICustomService service)
{
_service = service;
}

public IActionResult Index()
{
_service.Call();
return View();
}
}
```
* Register `ICustomService`, then configure the container to create proxy types in `ConfigureServices`:
``` csharp
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ICustomService, CustomService>();
services.AddMvc();
services.ConfigureDynamicProxy();
}
```
* Finally, add `UseServiceProviderFactory(new DynamicProxyServiceProviderFactory())` in `Program`'s `CreateHostBuilder`:
``` csharp
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
// ...
.UseServiceProviderFactory(new DynamicProxyServiceProviderFactory());
```

---
## Interceptor Configuration
* Global Interceptors. Use the `ConfigureDynamicProxy(Action<IAspectConfiguration>)` overload method, where `IAspectConfiguration` provides `Interceptors` to register global interceptors:
``` csharp
services.ConfigureDynamicProxy(config =>
{
config.Interceptors.AddTyped<CustomInterceptorAttribute>();
});
```
Global interceptor with constructor parameters. Add a parameterized constructor in `CustomInterceptorAttribute`:
``` csharp
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
{
private readonly string _name;
public CustomInterceptorAttribute(string name)
{
_name = name;
}
public async override Task Invoke(AspectContext context, AspectDelegate next)
{
try
{
Console.WriteLine("Before service call");
await next(context);
}
catch (Exception)
{
Console.WriteLine("Service threw an exception!");
throw;
}
finally
{
Console.WriteLine("After service call");
}
}
}
```
Modify global interceptor registration:
``` csharp
services.ConfigureDynamicProxy(config =>
{
config.Interceptors.AddTyped<CustomInterceptorAttribute>(args: new object[] { "custom" });
});
```
Global interceptor as a service. Add in `ConfigureServices`:
``` csharp
services.AddTransient<CustomInterceptorAttribute>(provider => new CustomInterceptorAttribute("custom"));
```
Modify global interceptor registration:
``` csharp
services.ConfigureDynamicProxy(config =>
{
// Add registered service
config.Interceptors.AddServiced<CustomInterceptorAttribute>();
});
```
Global interceptor acting on specific `Service` or `Method`. The following code demonstrates a global interceptor acting on classes with the `Service` suffix:
``` csharp
services.ConfigureDynamicProxy(config =>
{
config.Interceptors.AddTyped<CustomInterceptorAttribute>(method => method.Name.EndsWith("MethodName"));
});
```
Specific global interceptor using wildcards:
``` csharp
services.ConfigureDynamicProxy(config =>
{
config.Interceptors.AddTyped<CustomInterceptorAttribute>(Predicates.ForService("*Service"));
});
```
* In AspectCore, `NonAspectAttribute` is provided to prevent `Service` or `Method` from being proxied:
``` csharp
[NonAspect]
public interface ICustomService
{
void Call();
}
```
Also supports global ignore configuration, and wildcards:
``` csharp
services.ConfigureDynamicProxy(config =>
{
// Services under the App1 namespace will not be proxied
config.NonAspectPredicates.AddNamespace("App1");

// Services under namespaces ending in App1 will not be proxied
config.NonAspectPredicates.AddNamespace("*.App1");

// ICustomService interface will not be proxied
config.NonAspectPredicates.AddService("ICustomService");

// Interfaces and classes with Service suffix will not be proxied
config.NonAspectPredicates.AddService("*Service");

// Methods named Query will not be proxied
config.NonAspectPredicates.AddMethod("Query");

// Methods with Query suffix will not be proxied
config.NonAspectPredicates.AddMethod("*Query");
});
```
* Dependency Injection in Interceptors. Supports property injection, constructor injection, and service locator pattern in interceptors.

Property injection. Mark properties with `public get and set` access in the interceptor with the `[AspectCore.DependencyInjection.FromServiceContextAttribute]` attribute to automatically inject the property, for example:
``` csharp
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
{
// Note: Property injection only works when using config.Interceptors.AddTyped<CustomInterceptorAttribute>();
// It cannot be used with services.AddSingleton<CustomInterceptorAttribute>() + services.ConfigureDynamicProxy(config => { config.Interceptors.AddServiced<CustomInterceptorAttribute>(); });
[FromServiceContext]
public ILogger<CustomInterceptorAttribute> Logger { get; set; }


public override Task Invoke(AspectContext context, AspectDelegate next)
{
Logger.LogInformation("call interceptor");
return next(context);
}
}
```
Constructor injection requires making the interceptor a `Service`. Besides global interceptors, you can still use `ServiceInterceptor` to activate the interceptor from DI:

``` csharp
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
{
private readonly ILogger<CustomInterceptor> ctorlogger;

// Note: When globally configured with config.Interceptors.AddTyped<CustomInterceptorAttribute>(); constructor injection cannot be automatically injected, needs manual handling
// Constructor injection only works automatically when using services.AddSingleton<CustomInterceptorAttribute>(); + services.ConfigureDynamicProxy(config => { config.Interceptors.AddServiced<CustomInterceptorAttribute>(); });
public CustomInterceptor(ILogger<CustomInterceptor> ctorlogger)
{
this.ctorlogger = ctorlogger;
}
}
```

Service locator pattern. The interceptor context `AspectContext` can get the current scoped `ServiceProvider`:
``` csharp
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
{
public override Task Invoke(AspectContext context, AspectDelegate next)
{
var logger = context.ServiceProvider.GetService<ILogger<CustomInterceptorAttribute>>();
logger.LogInformation("call interceptor");
return next(context);
}
}
```

Version 2.0.0 :arrow_up: ps: Original article (https://www.thinkinmd.com/post/2020/03/20/use-aspectcore-to-implement-aop-mechanism/)
Version 2.0.0 :arrow_down: ps: Original article (http://www.cnblogs.com/liuhaoyang/p/aspectcore-introduction-1.html)
Loading
Loading