Skip to content

Commit 0700ac7

Browse files
committed
docs: add English: add English documentation for AspectCore Framework
- Translate all Chinese documentation to English - Add AOP introduction (0.AOP-Introduction.md) - Add Getting Started guide (1.Getting-Started.md) - Add IoC and dependency injection documentation (injector.md) - Add reflection extensions documentation (reflection-extensions.md) - Add reflection overview (reflection.md) This addresses the community request for English documentation.
1 parent f1b6d9b commit 0700ac7

5 files changed

Lines changed: 653 additions & 0 deletions

File tree

docs/en/0.AOP-Introduction.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 Concepts
4+
5+
Aspect-Oriented Programming (AOP is the abbreviation of Aspect Oriented Program). We know that the characteristics of object-oriented programming are inheritance, polymorphism, and encapsulation. Encapsulation requires distributing functionality to different objects, which is often referred to as responsibility allocation in software design. In practice, this means designing different methods for different classes. This way, code is distributed into individual classes. The benefit of this approach is that it reduces code complexity and makes classes reusable.
6+
7+
However, people have also discovered that while dispersing code, it also increases code duplication. What does this mean? For example, in two classes, we might need to add logging to every method. According to object-oriented design methods, we would have to add logging content to the methods in both classes. Perhaps they are exactly the same, but because object-oriented design prevents direct connections between classes, we cannot unify these repetitive codes.
8+
9+
Some might say, that's easy, we can write this code in an independent class and method, and then call it from both classes. However, this creates coupling between the two classes and our independent class. Changes to it would affect both classes. So, is there a way to allow us to add code freely when needed? The programming idea of dynamically injecting code into specified methods and positions of specified classes at runtime is called aspect-oriented programming.
10+
11+
Generally speaking, the code fragment injected into specified classes and methods is called an aspect, while the specification of which classes and methods to inject into is called a pointcut. With AOP, we can extract code common to several classes into an aspect, and inject it into objects when needed, thereby changing their original behavior. From this perspective, AOP is actually just a supplement to OOP. OOP distinguishes individual classes horizontally, while AOP adds specific code to objects vertically. With AOP, OOP becomes three-dimensional. If we add the time dimension, AOP transforms OOP from two-dimensional to three-dimensional, from flat to立体. Technically speaking, AOP is basically implemented through proxy mechanisms.
12+
13+
AOP can be said to be a milestone in programming history and is a very beneficial supplement to OOP programming.
14+
15+
This section is excerpted from Zhihu - Discussion on AOP concepts: 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+
I'll skip a detailed overview here for brevity.
24+
25+
## AOP Implementation Methods
26+
27+
AOP implementations often use common methods:
28+
29+
* Using preprocessors (like preprocessors in C++) to add source code.
30+
* Using post-processors to add instructions on compiled binary code.
31+
* Using special compilers to add code at compile time.
32+
* Using code interceptors at runtime to intercept execution and add required code.
33+
34+
However, the most common approaches are post-processing and code interception:
35+
36+
* Post-processing, also called static weaving
37+
38+
This refers to using commands provided by the AOP framework to compile, thereby generating AOP proxy classes during the compilation phase, hence also called compile-time enhancement or static weaving.
39+
40+
In .NET, this is typically done at compile time by intercepting the compilation process through custom Build Tasks in MSBuild and inserting your own IL into the generated assembly.
41+
42+
.NET framework representative: [PostSharp](https://www.postsharp.net/aop.net)
43+
44+
* Code interception, also called dynamic proxy, dynamic weaving, or code hijacking
45+
46+
This generates AOP dynamic proxy classes "temporarily" in memory at runtime, hence also called runtime enhancement or dynamic proxy.
47+
48+
In .NET, this is typically done at runtime using Emit technology to generate dynamic assemblies and dynamic proxy types to intercept target methods.
49+
50+
.NET framework representatives: [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 the creation of `proxy classes` for our actual business implementation classes at runtime.
55+
56+
Through `proxy classes`, interceptor classes invisible to business logic code are called at runtime.
57+
58+
Using 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 `Interceptor` to "intercept" `IInvocation` through the `proxy class` without `IInvocation` knowing about this interceptor class.
86+
87+
The following diagram shows the call example:
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 a proxed object.
92+
93+
But in actual calls, as shown by the yellow arrow, it goes through layer after layer of interceptor classes before finally calling the proxed object.
94+
95+
The return path is similar to the green arrow, going through layer after layer of interceptor classes.
96+
97+
Ultimately achieving the purpose of dynamic proxy.
98+
99+
Currently, AspectCore uses dynamic proxy as its AOP implementation rather than the theoretically more performant static weaving implementation. This is because I personally believe the dynamic proxy approach can achieve better IoC integration and can access more runtime metadata information in aspects. Moreover, after continuous optimization, AspectCore's dynamic proxy performance is not inferior to static weaving implementations.

docs/en/1.Getting-Started.md

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# 1. Getting Started
2+
3+
Version 2.0.0 :arrow_up: (Simple code examples: https://github.com/cdcd72/NetCore.AspectCore.AOP.Demo)
4+
Version 2.0.0 :arrow_down: (Simple code examples: https://github.com/fs7744/AspectCoreDemo)
5+
6+
## Start Using AspectCore
7+
8+
* Launch Visual Studio. From the File menu, select New > Project. Select the ASP.NET Core Web Application project template to 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+
* In general cases, you can use the abstract `AbstractInterceptorAttribute` to customize your attribute class, which implements the `IInterceptor` interface. AspectCore provides default interceptor configuration based on `Attribute`. Our 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 class `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())` to `Program`'s `CreateHostBuilder`:
80+
```csharp
81+
public static IHostBuilder CreateHostBuilder(string[] args) =>
82+
Host.CreateDefaultBuilder(args)
83+
.ConfigureWebHostDefaultsDefaults(webBuilder =>
84+
{
85+
webBuilder.UseStartup<Startup>();
86+
})
87+
// ...
88+
.UseServiceProviderFactory(new DynamicProxyServiceProviderFactory());
89+
```
90+
91+
---
92+
## Interceptor Configuration
93+
94+
* Global interceptors. Use the `ConfigureDynamicProxy(Action<IAspectConfiguration>)` overload method, where `IAspectConfiguration` provides `Interceptors` to register global interceptors:
95+
```csharp
96+
services.ConfigureDynamicProxy(config =>
97+
{
98+
config.Interceptors.AddTyped<CustomInterceptorAttribute>();
99+
});
100+
```
101+
Global interceptor with constructor parameters. Add a parameterized constructor in `CustomInterceptorAttribute`:
102+
```csharp
103+
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
104+
{
105+
private readonly string _name;
106+
public CustomInterceptorAttribute(string name)
107+
{
108+
_name = name;
109+
}
110+
public async override Task Invoke(AspectContext context, AspectDelegate next)
111+
{
112+
try
113+
{
114+
Console.WriteLine("Before service call");
115+
await next(context);
116+
}
117+
catch (Exception)
118+
{
119+
Console.WriteLine("Service threw an exception!");
120+
throw;
121+
}
122+
finally
123+
{
124+
Console.WriteLine("After service call");
125+
}
126+
}
127+
}
128+
```
129+
Modify global interceptor registration:
130+
```csharp
131+
services.ConfigureDynamicProxy(config =>
132+
{
133+
config.Interceptors.AddTyped<CustomInterceptorAttribute>(args: new object[] { "custom" });
134+
});
135+
```
136+
Global interceptor as a service. Add in `ConfigureServices`:
137+
```csharp
138+
services.AddTransient<CustomInterceptorAttribute>(provider => new CustomInterceptorAttribute("custom"));
139+
```
140+
Modify global interceptor registration:
141+
```csharp
142+
services.ConfigureDynamicProxy(config =>
143+
{
144+
// Add registered service
145+
config.Interceptors.AddServiced<CustomInterceptorAttribute>();
146+
});
147+
```
148+
Global interceptor targeting specific `Service` or `Method`. The following code demonstrates a global interceptor acting on classes ending with `Service`:
149+
```csharp
150+
services.ConfigureDynamicProxy(config =>
151+
{
152+
config.Interceptors.AddTyped<CustomInterceptorAttribute>(method => method.Name.EndsWith("MethodName"));
153+
});
154+
```
155+
Specific global interceptor using wildcards:
156+
```csharp
157+
services.ConfigureDynamicProxy(config =>
158+
{
159+
config.Interceptors.AddTyped<CustomInterceptorAttribute>(Predicates.ForService("*Service"));
160+
});
161+
```
162+
163+
* AspectCore provides `NonAspectAttribute` to make `Service` or `Method` not proxied:
164+
```csharp
165+
[NonAspect]
166+
public interface ICustomService
167+
{
168+
void Call();
169+
}
170+
```
171+
Also supports global ignore configuration and wildcards:
172+
```csharp
173+
services.ConfigureDynamicProxy(config =>
174+
{
175+
// Services under App1 namespace won't be proxied
176+
config.NonAspectPredicates.AddNamespace("App1");
177+
178+
// Services under namespaces where the last level is App1 won't be proxied
179+
config.NonAspectPredicates.AddNamespace("*.App1");
180+
181+
// ICustomService interface won't be proxied
182+
config.NonAspectPredicates.AddService("ICustomService");
183+
184+
// Interfaces and classes ending with Service won't be proxied
185+
config.NonAspectPredicates.AddService("*Service");
186+
187+
// Methods named Query won't be proxied
188+
config.NonAspectPredicates.AddMethod("Query");
189+
190+
// Methods ending with Query won't be proxied
191+
config.NonAspectPredicates.AddMethod("*Query");
192+
});
193+
```
194+
195+
* Dependency injection in interceptors. Supports property injection, constructor injection, and service locator pattern in interceptors.
196+
197+
Property injection. In interceptors, mark properties with `public get and set` permissions with the `[AspectCore.DependencyInjection.FromServiceContextAttribute]` attribute to automatically inject the property:
198+
```csharp
199+
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
200+
{
201+
// ps: Property injection only works when using config.Interceptors.AddTyped<CustomInterceptorAttribute>();
202+
// It does not work with services.AddSingleton<CustomInterceptorAttribute>(); + services.ConfigureDynamicProxy(config => { config.Interceptors.AddServiced<CustomInterceptorAttribute>(); });
203+
[FromServiceContext]
204+
public ILogger<CustomInterceptorAttribute> Logger { get; set; }
205+
206+
public override Task Invoke(AspectContext context, AspectDelegate next)
207+
{
208+
Logger.LogInformation("call interceptor");
209+
return next(context);
210+
}
211+
}
212+
```
213+
Constructor injection requires the interceptor to be a `Service`. Besides global interceptors, you can still use `ServiceInterceptor` to make interceptors activated from DI:
214+
215+
```csharp
216+
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
217+
{
218+
private readonly ILogger<CustomInterceptor> ctorlogger;
219+
220+
// ps: When globally configured with config.Interceptors.AddTyped<CustomInterceptorAttribute>(), constructor injection cannot be automatically injected, needs manual handling
221+
// Only works with services.AddSingleton<CustomInterceptorAttribute>(); + services.ConfigureDynamicProxy(config => { config.Interceptors.AddServiced<CustomInterceptorAttribute>(); });
222+
public CustomInterceptor(ILogger<CustomInterceptor> ctorlogger)
223+
{
224+
this.ctorlogger = ctorlogger;
225+
}
226+
}
227+
```
228+
229+
Service locator pattern. The interceptor context `AspectContext` can get the current scoped `ServiceProvider`:
230+
```csharp
231+
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
232+
{
233+
public override Task Invoke(AspectContext context, AspectDelegate next)
234+
{
235+
var logger = context.ServiceProvider.GetService<ILogger<CustomInterceptorAttribute>>();
236+
logger.LogInformation("call interceptor");
237+
return next(context);
238+
}
239+
}
240+
```
241+
242+
Version 2.0.0 :arrow_up: ps: Original text (https://www.thinkinmd.com/post/2020/03/20/use-aspectcore-to-implement-aop-mechanism/)
243+
Version 2.0.0 :arrow_down: ps: Original text (http://www.cnblogs.com/liuhaoyang/p/aspectcore-introduction-1.html)

0 commit comments

Comments
 (0)