Skip to content

Commit ad07f08

Browse files
committed
docs: add English documentation
- Add 0.AOP-Introduction.en.md - AOP concept introduction - Add 1.Getting-Started.en.md - Getting started guide with examples - Add injector.en.md - IoC container and dependency injection documentation - Add reflection-extensions.en.md - Reflection extensions performance guide - Add reflection.en.md - Reflection overview Closes #250
1 parent e6092ed commit ad07f08

5 files changed

Lines changed: 651 additions & 0 deletions

File tree

docs/0.AOP-Introduction.en.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# 0. AOP Introduction
2+
3+
## AOP Concept
4+
5+
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.
6+
7+
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.
8+
9+
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.
10+
11+
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.
12+
13+
AOP has been a milestone in programming history and is a very beneficial supplement to OOP programming.
14+
15+
This section is adapted from Zhihu - Discussion on AOP concept: https://www.zhihu.com/question/24863332
16+
17+
For those who want to understand AOP in detail, you can read the above [Zhihu link](https://www.zhihu.com/question/24863332)
18+
19+
Or https://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/
20+
21+
Or https://www.cnblogs.com/DjlNet/p/7603654.html
22+
23+
Apologies for being lazy here, I won't provide a detailed overview.
24+
25+
## AOP Implementation Methods
26+
27+
AOP implementations will use some common methods:
28+
29+
* Using a preprocessor (like preprocessor in C++) to add source code.
30+
* Using a post-processor to add instructions on compiled binary code.
31+
* Using a special compiler to add code at compile time.
32+
* Using code interceptors at runtime to intercept execution and add the required code.
33+
34+
However, the most common are post-processing and code interception:
35+
36+
* Post-processing, or **Static Weaving**
37+
38+
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.
39+
40+
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.
41+
42+
.NET framework representative: [PostSharp](https://www.postsharp.net/aop.net)
43+
44+
* Code interception, or **Dynamic Proxy**, **Dynamic Weaving**, **Code Hijacking**
45+
46+
AOP dynamic proxy classes are generated "temporarily" in memory at runtime, thus also known as runtime enhancement or dynamic proxy.
47+
48+
In .NET, this is typically done at runtime through Emit technology to generate dynamic assemblies and dynamic proxy types to intercept target methods.
49+
50+
.NET framework representative: [Castle DynamicProxy](https://github.com/castleproject/Core/blob/master/docs/dynamicproxy-introduction.md) and [AspectCore](https://github.com/dotnetcore/AspectCore-Framework)
51+
52+
## Brief Overview of AspectCore's Dynamic Weaving
53+
54+
Simply put, dynamic weaving is creating some `proxy classes` of our real business implementation classes at runtime.
55+
56+
Through the `proxy class`, some interceptor classes invisible to business logic code are called at runtime.
57+
58+
Borrowing an example from Castle DynamicProxy to illustrate:
59+
60+
``` csharp
61+
public class Interceptor : IInterceptor
62+
{
63+
public void Intercept(IInvocation invocation)
64+
{
65+
Console.WriteLine("Before target call");
66+
try
67+
{
68+
invocation.Proceed();
69+
}
70+
catch(Exception)
71+
{
72+
Console.WriteLine("Target threw an exception!");
73+
throw;
74+
}
75+
finally
76+
{
77+
Console.WriteLine("After target call");
78+
}
79+
}
80+
}
81+
```
82+
83+
This is an interceptor class.
84+
85+
The purpose of dynamic weaving is to call the `Interceptor` to "intercept" `IInvocation` through the `proxy class` without `IInvocation` knowing about this interceptor class.
86+
87+
The example below shows the invocation:
88+
89+
![](https://github.com/castleproject/Core/raw/master/docs/images/proxy-pipeline.png)
90+
91+
The blue area is the proxy class area. To the outside world, it looks like the proxied object.
92+
93+
But when actually called, like the yellow arrow, it goes through layer after layer of interceptor classes before finally calling the proxied object.
94+
95+
The return also follows the green arrow, going through layer after layer of interceptor classes.
96+
97+
Finally achieving the purpose of dynamic proxy.
98+
99+
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.

docs/1.Getting-Started.en.md

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
# 1. Getting Started Guide
2+
3+
Version 2.0.0 :arrow_up: (Simple code example: https://github.com/cdcd72/NetCore.AspectCore.AOP.Demo)
4+
Version 2.0.0 :arrow_down: (Simple code example: https://github.com/fs7744/AspectCoreDemo)
5+
6+
## Getting Started with AspectCore
7+
8+
* 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.
9+
10+
* Install `AspectCore.Extensions.DependencyInjection` package from Nuget:
11+
```
12+
PM> Install-Package AspectCore.Extensions.DependencyInjection
13+
```
14+
* 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:
15+
``` csharp
16+
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
17+
{
18+
public async override Task Invoke(AspectContext context, AspectDelegate next)
19+
{
20+
try
21+
{
22+
Console.WriteLine("Before service call");
23+
await next(context);
24+
}
25+
catch (Exception)
26+
{
27+
Console.WriteLine("Service threw an exception!");
28+
throw;
29+
}
30+
finally
31+
{
32+
Console.WriteLine("After service call");
33+
}
34+
}
35+
}
36+
```
37+
* Define the `ICustomService` interface and its implementation `CustomService`:
38+
``` csharp
39+
public interface ICustomService
40+
{
41+
[CustomInterceptor]
42+
void Call();
43+
}
44+
45+
public class CustomService : ICustomService
46+
{
47+
public void Call()
48+
{
49+
Console.WriteLine("service calling...");
50+
}
51+
}
52+
```
53+
* Inject `ICustomService` in `HomeController`:
54+
``` csharp
55+
public class HomeController : Controller
56+
{
57+
private readonly ICustomService _service;
58+
public HomeController(ICustomService service)
59+
{
60+
_service = service;
61+
}
62+
63+
public IActionResult Index()
64+
{
65+
_service.Call();
66+
return View();
67+
}
68+
}
69+
```
70+
* Register `ICustomService`, then configure the container to create proxy types in `ConfigureServices`:
71+
``` csharp
72+
public void ConfigureServices(IServiceCollection services)
73+
{
74+
services.AddTransient<ICustomService, CustomService>();
75+
services.AddMvc();
76+
services.ConfigureDynamicProxy();
77+
}
78+
```
79+
* Finally, add `UseServiceProviderFactory(new DynamicProxyServiceProviderFactory())` in `Program`'s `CreateHostBuilder`:
80+
``` csharp
81+
public static IHostBuilder CreateHostBuilder(string[] args) =>
82+
Host.CreateDefaultBuilder(args)
83+
.ConfigureWebHostDefaults(webBuilder =>
84+
{
85+
webBuilder.UseStartup<Startup>();
86+
})
87+
// ...
88+
.UseServiceProviderFactory(new DynamicProxyServiceProviderFactory());
89+
```
90+
91+
---
92+
## Interceptor Configuration
93+
* Global Interceptors. Use the `ConfigureDynamicProxy(Action<IAspectConfiguration>)` overload method, where `IAspectConfiguration` provides `Interceptors` to register global interceptors:
94+
``` csharp
95+
services.ConfigureDynamicProxy(config =>
96+
{
97+
config.Interceptors.AddTyped<CustomInterceptorAttribute>();
98+
});
99+
```
100+
Global interceptor with constructor parameters. Add a parameterized constructor in `CustomInterceptorAttribute`:
101+
``` csharp
102+
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
103+
{
104+
private readonly string _name;
105+
public CustomInterceptorAttribute(string name)
106+
{
107+
_name = name;
108+
}
109+
public async override Task Invoke(AspectContext context, AspectDelegate next)
110+
{
111+
try
112+
{
113+
Console.WriteLine("Before service call");
114+
await next(context);
115+
}
116+
catch (Exception)
117+
{
118+
Console.WriteLine("Service threw an exception!");
119+
throw;
120+
}
121+
finally
122+
{
123+
Console.WriteLine("After service call");
124+
}
125+
}
126+
}
127+
```
128+
Modify global interceptor registration:
129+
``` csharp
130+
services.ConfigureDynamicProxy(config =>
131+
{
132+
config.Interceptors.AddTyped<CustomInterceptorAttribute>(args: new object[] { "custom" });
133+
});
134+
```
135+
Global interceptor as a service. Add in `ConfigureServices`:
136+
``` csharp
137+
services.AddTransient<CustomInterceptorAttribute>(provider => new CustomInterceptorAttribute("custom"));
138+
```
139+
Modify global interceptor registration:
140+
``` csharp
141+
services.ConfigureDynamicProxy(config =>
142+
{
143+
// Add registered service
144+
config.Interceptors.AddServiced<CustomInterceptorAttribute>();
145+
});
146+
```
147+
Global interceptor acting on specific `Service` or `Method`. The following code demonstrates a global interceptor acting on classes with the `Service` suffix:
148+
``` csharp
149+
services.ConfigureDynamicProxy(config =>
150+
{
151+
config.Interceptors.AddTyped<CustomInterceptorAttribute>(method => method.Name.EndsWith("MethodName"));
152+
});
153+
```
154+
Specific global interceptor using wildcards:
155+
``` csharp
156+
services.ConfigureDynamicProxy(config =>
157+
{
158+
config.Interceptors.AddTyped<CustomInterceptorAttribute>(Predicates.ForService("*Service"));
159+
});
160+
```
161+
* In AspectCore, `NonAspectAttribute` is provided to prevent `Service` or `Method` from being proxied:
162+
``` csharp
163+
[NonAspect]
164+
public interface ICustomService
165+
{
166+
void Call();
167+
}
168+
```
169+
Also supports global ignore configuration, and wildcards:
170+
``` csharp
171+
services.ConfigureDynamicProxy(config =>
172+
{
173+
// Services under the App1 namespace will not be proxied
174+
config.NonAspectPredicates.AddNamespace("App1");
175+
176+
// Services under namespaces ending in App1 will not be proxied
177+
config.NonAspectPredicates.AddNamespace("*.App1");
178+
179+
// ICustomService interface will not be proxied
180+
config.NonAspectPredicates.AddService("ICustomService");
181+
182+
// Interfaces and classes with Service suffix will not be proxied
183+
config.NonAspectPredicates.AddService("*Service");
184+
185+
// Methods named Query will not be proxied
186+
config.NonAspectPredicates.AddMethod("Query");
187+
188+
// Methods with Query suffix will not be proxied
189+
config.NonAspectPredicates.AddMethod("*Query");
190+
});
191+
```
192+
* Dependency Injection in Interceptors. Supports property injection, constructor injection, and service locator pattern in interceptors.
193+
194+
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:
195+
``` csharp
196+
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
197+
{
198+
// Note: Property injection only works when using config.Interceptors.AddTyped<CustomInterceptorAttribute>();
199+
// It cannot be used with services.AddSingleton<CustomInterceptorAttribute>() + services.ConfigureDynamicProxy(config => { config.Interceptors.AddServiced<CustomInterceptorAttribute>(); });
200+
[FromServiceContext]
201+
public ILogger<CustomInterceptorAttribute> Logger { get; set; }
202+
203+
204+
public override Task Invoke(AspectContext context, AspectDelegate next)
205+
{
206+
Logger.LogInformation("call interceptor");
207+
return next(context);
208+
}
209+
}
210+
```
211+
Constructor injection requires making the interceptor a `Service`. Besides global interceptors, you can still use `ServiceInterceptor` to activate the interceptor from DI:
212+
213+
``` csharp
214+
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
215+
{
216+
private readonly ILogger<CustomInterceptor> ctorlogger;
217+
218+
// Note: When globally configured with config.Interceptors.AddTyped<CustomInterceptorAttribute>(); constructor injection cannot be automatically injected, needs manual handling
219+
// Constructor injection only works automatically when using services.AddSingleton<CustomInterceptorAttribute>(); + services.ConfigureDynamicProxy(config => { config.Interceptors.AddServiced<CustomInterceptorAttribute>(); });
220+
public CustomInterceptor(ILogger<CustomInterceptor> ctorlogger)
221+
{
222+
this.ctorlogger = ctorlogger;
223+
}
224+
}
225+
```
226+
227+
Service locator pattern. The interceptor context `AspectContext` can get the current scoped `ServiceProvider`:
228+
``` csharp
229+
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
230+
{
231+
public override Task Invoke(AspectContext context, AspectDelegate next)
232+
{
233+
var logger = context.ServiceProvider.GetService<ILogger<CustomInterceptorAttribute>>();
234+
logger.LogInformation("call interceptor");
235+
return next(context);
236+
}
237+
}
238+
```
239+
240+
Version 2.0.0 :arrow_up: ps: Original article (https://www.thinkinmd.com/post/2020/03/20/use-aspectcore-to-implement-aop-mechanism/)
241+
Version 2.0.0 :arrow_down: ps: Original article (http://www.cnblogs.com/liuhaoyang/p/aspectcore-introduction-1.html)

0 commit comments

Comments
 (0)