diff --git "a/docs/1.\344\275\277\347\224\250\346\214\207\345\215\227.md" "b/docs/1.\344\275\277\347\224\250\346\214\207\345\215\227.md" index d81deebe..77645e2a 100644 --- "a/docs/1.\344\275\277\347\224\250\346\214\207\345\215\227.md" +++ "b/docs/1.\344\275\277\347\224\250\346\214\207\345\215\227.md" @@ -87,6 +87,170 @@ // 略 .UseServiceProviderFactory(new DynamicProxyServiceProviderFactory()); ``` +--- +## 高级拦截器功能 + +### 获取方法信息 + +在拦截器中,你可以通过 `AspectContext` 获取丰富的方法信息: + +```csharp +public class MethodInfoInterceptorAttribute : AbstractInterceptorAttribute +{ + public override Task Invoke(AspectContext context, AspectDelegate next) + { + // 获取服务类型 + var serviceType = context.ServiceDescriptor.ServiceType; + Console.WriteLine($"Service: {serviceType.Name}"); + + // 获取实现类型 + var implementationType = context.ImplementationMethod?.DeclaringType?.Name; + Console.WriteLine($"Implementation: {implementationType}"); + + // 获取方法名称 + var methodName = context.ProxyMethod.Name; + Console.WriteLine($"Method: {methodName}"); + + // 获取参数 + var parameters = context.Parameters; + Console.WriteLine($"Parameters: {string.Join(", ", parameters.Select(p => p?.ToString() ?? "null"))}"); + + // 获取返回值(在 next 调用之后) + return next(context).ContinueWith(t => + { + var returnValue = context.ReturnValue; + Console.WriteLine($"Return value: {returnValue}"); + return Task.CompletedTask; + }); + } +} +``` + +### 修改参数和返回值 + +你可以在拦截器中修改方法的参数和返回值: + +```csharp +public class ParameterModifierInterceptorAttribute : AbstractInterceptorAttribute +{ + public override Task Invoke(AspectContext context, AspectDelegate next) + { + // 修改参数(索引从0开始) + if (context.Parameters.Length > 0) + { + context.Parameters[0] = "Modified value"; + } + + return next(context).ContinueWith(t => + { + // 修改返回值 + if (context.ReturnValue != null && context.ReturnValue is string str) + { + context.ReturnValue = str.ToUpper(); + } + return Task.CompletedTask; + }); + } +} +``` + +### 异步拦截 + +AspectCore 完美支持异步方法: + +```csharp +public interface IAsyncService +{ + [CustomInterceptor] + Task GetDataAsync(); +} + +public class AsyncService : IAsyncService +{ + public async Task GetDataAsync() + { + await Task.Delay(100); + return "Hello Async"; + } +} + +public class AsyncInterceptorAttribute : AbstractInterceptorAttribute +{ + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + Console.WriteLine("Before async call"); + await next(context); + Console.WriteLine($"After async call, result: {context.ReturnValue}"); + } +} +``` + +### 拦截器链 + +多个拦截器可以组合使用,形成拦截器链: + +```csharp +public class LogInterceptorAttribute : AbstractInterceptorAttribute +{ + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + Console.WriteLine($"[Log] Entering {context.ProxyMethod.Name}"); + await next(context); + Console.WriteLine($"[Log] Exiting {context.ProxyMethod.Name}"); + } +} + +public class CacheInterceptorAttribute : AbstractInterceptorAttribute +{ + private static readonly Dictionary Cache = new(); + + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + var cacheKey = $"{context.ServiceMethod.DeclaringType.Name}.{context.ServiceMethod.Name}"; + + if (Cache.TryGetValue(cacheKey, out var cachedValue)) + { + Console.WriteLine($"[Cache] Hit: {cacheKey}"); + context.ReturnValue = cachedValue; + return; + } + + Console.WriteLine($"[Cache] Miss: {cacheKey}"); + await next(context); + Cache[cacheKey] = context.ReturnValue; + } +} + +// 使用 +public interface IMultiService +{ + [CacheInterceptor] + [LogInterceptor] + string GetData(); +} +``` + +### 条件拦截 + +使用 `Predicate` 可以实现条件拦截: + +```csharp +public class AdminOnlyInterceptorAttribute : AbstractInterceptorAttribute +{ + public override Task Invoke(AspectContext context, AspectDelegate next) + { + var user = context.ServiceProvider.GetService(); + + if (!user.IsAdmin) + { + throw new UnauthorizedAccessException("Only admin can access this method"); + } + + return next(context); + } +} +``` + --- ## 拦截器配置 * 全局拦截器。使用`ConfigureDynamicProxy(Action)`的重载方法,其中`IAspectConfiguration`提供`Interceptors`注册全局拦截器: @@ -223,7 +387,7 @@ } ``` - 服务定位器模式。拦截器上下文`AspectContext`可以获取当前Scoped的`ServiceProvider`: + 服务定位器模式。拦截器上下文`AspectContextudi`可以获取当前Scoped的`ServiceProvider`: ``` csharp public class CustomInterceptorAttribute : AbstractInterceptorAttribute { @@ -236,5 +400,229 @@ } ``` +### 拦截器中的参数值访问 + +拦截器可以通过 `AspectContext.Parameters` 访问方法的参数值: + +```csharp +public class ParameterAccessInterceptorAttribute : AbstractInterceptorAttribute +{ + public override Task Invoke(AspectContext context, AspectDelegate next) + { + // 访问参数 + for (int i = 0; i < context.Parameters.Length; i++) + { + var param = context.Parameters[i]; + Console.WriteLine($"Parameter {i}: {param?.ToString() ?? "null"}"); + } + + return next(context); + } +} +``` + +### 拦截器中的返回值修改 + +拦截器可以修改方法的返回值: + +```csharp +public class ReturnValueInterceptorAttribute : AbstractInterceptorAttribute +{ + public override Task Invoke(AspectContext context, AspectDelegate next) + { + return next(context).ContinueWith(t => + { + // 修改返回值 + if (context.ReturnValue != null) + { + var originalValue = context.ReturnValue.ToString(); + context.ReturnValue = $"Modified: {originalValue}"; + } + return Task.CompletedTask; + }); + } +} +``` + +### 拦截器中的异常处理 + +拦截器可以捕获和处理异常: + +```csharp +public class ExceptionHandlingInterceptorAttribute : AbstractInterceptorAttribute +{ + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + try + { + await next(context); + } + catch (Exception ex) + { + Console.WriteLine($"Exception caught: {ex.Message}"); + + // 可以选择重新抛出异常 + // throw; + + // 或者返回默认值 + context.ReturnValue = "Error occurred"; + } + } +} +``` + +--- +## 常见使用场景 + +### 1. 日志记录 + +```csharp +public class LoggingInterceptorAttribute : AbstractInterceptorAttribute +{ + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + var logger = context.ServiceProvider.GetService>(); + var methodName = $"{context.ServiceMethod.DeclaringType.Name}.{context.ServiceMethod.Name}"; + + var parameters = string.Join(", ", context.Parameters.Select(p => p?.ToString() ?? "null")); + logger.LogInformation($"[START] {methodName}({parameters})"); + + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + try + { + await next(context); + stopwatch.Stop(); + logger.LogInformation($"[END] {methodName} completed in {stopwatch.ElapsedMilliseconds}ms. Result: {context.ReturnValue}"); + } + catch (Exception ex) + { + stopwatch.Stop(); + logger.LogError(ex, $"[ERROR] {methodName} failed after {stopwatch.ElapsedMilliseconds}ms"); + throw; + } + } +} +``` + +### 2. 缓存 + +```csharp +public class CacheInterceptorAttribute : AbstractInterceptorAttribute +{ + private readonly IMemoryCache _cache; + private readonly int _durationSeconds; + + public CacheInterceptorAttribute(IMemoryCache cache, int durationSeconds = 60) + { + _cache = cache; + _durationSeconds = durationSeconds; + } + + public override Task Invoke(AspectContext context, AspectDelegate next) + { + var cacheKey = GetCacheKey(context); + + if (_cache.TryGetValue(cacheKey, out var cachedValue)) + { + context.ReturnValue = cachedValue; + return Task.CompletedTask; + } + + return next(context).ContinueWith(t => + { + _cache.Set(cacheKey, context.ReturnValue, TimeSpan.FromSeconds(_durationSeconds)); + return Task.CompletedTask; + }); + } + + private string GetCacheKey(AspectContext context) + { + return $"{context.ServiceMethod.DeclaringType.Name}.{context.ServiceMethod.Name}_{string.Join("_", context.Parameters)}"; + } +} +``` + +### 3. 验证 + +```csharp +public class ValidationInterceptorAttribute : AbstractInterceptorAttribute +{ + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + foreach (var param in context.Parameters) + { + if (param == null) + { + throw new ArgumentException("Parameters cannot be null"); + } + } + + await next(context); + } +} +``` + +### 4. 重试 + +```csharp +public class RetryInterceptorAttribute : AbstractInterceptorAttribute +{ + private readonly int _maxRetries; + private readonly int _delayMs; + + public RetryInterceptorAttribute(int maxRetries = 3, int delayMs = 1000) + { + _maxRetries = maxRetries; + _delayMs = delayMs; + } + + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + var retryCount = 0; + + while (true) + { + try + { + await next(context); + return; + } + catch (Exception ex) when (retryCount < _maxRetries) + { + retryCount++; + Console.WriteLine($"Retry {retryCount}/{_maxRetries} after exception: {ex.Message}"); + await Task.Delay(_delayMs); + } + } + } +} +``` + +### 5. 性能监控 + +```csharp +public class PerformanceInterceptorAttribute : AbstractInterceptorAttribute +{ + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + + try + { + await next(context); + } + finally + { + stopwatch.Stop(); + var methodName = $"{context.ServiceMethod.DeclaringType.Name}.{context.ServiceMethod.Name}"; + Console.WriteLine($"[PERF] {methodName} took {stopwatch.ElapsedMilliseconds}ms"); + + // 可以在这里记录到监控系统 + // Metrics.Record(methodName, stopwatch.ElapsedMilliseconds); + } + } +} +``` + 2.0.0 版本 :arrow_up: ps : 原文(https://www.thinkinmd.com/post/2020/03/20/use-aspectcore-to-implement-aop-mechanism/) 2.0.0 版本 :arrow_down: ps : 原文(http://www.cnblogs.com/liuhaoyang/p/aspectcore-introduction-1.html) diff --git a/docs/en/0.AOP-Introduction.md b/docs/en/0.AOP-Introduction.md new file mode 100644 index 00000000..a352eddd --- /dev/null +++ b/docs/en/0.AOP-Introduction.md @@ -0,0 +1,99 @@ +# 0. AOP Introduction + +## AOP Concepts + +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. + +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. + +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. + +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. + +AOP can be said to be a milestone in programming history and is a very beneficial supplement to OOP programming. + +This section is excerpted from Zhihu - Discussion on AOP concepts: 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 + +I'll skip a detailed overview here for brevity. + +## AOP Implementation Methods + +AOP implementations often use common methods: + +* Using preprocessors (like preprocessors in C++) to add source code. +* Using post-processors to add instructions on compiled binary code. +* Using special compilers to add code at compile time. +* Using code interceptors at runtime to intercept execution and add required code. + +However, the most common approaches are post-processing and code interception: + +* Post-processing, also called static weaving + + 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. + + 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. + + .NET framework representative: [PostSharp](https://www.postsharp.net/aop.net) + +* Code interception, also called dynamic proxy, dynamic weaving, or code hijacking + + This generates AOP dynamic proxy classes "temporarily" in memory at runtime, hence also called runtime enhancement or dynamic proxy. + + In .NET, this is typically done at runtime using Emit technology to generate dynamic assemblies and dynamic proxy types to intercept target methods. + + .NET framework representatives: [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 the creation of `proxy classes` for our actual business implementation classes at runtime. + +Through `proxy classes`, interceptor classes invisible to business logic code are called at runtime. + +Using 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 `Interceptor` to "intercept" `IInvocation` through the `proxy class` without `IInvocation` knowing about this interceptor class. + +The following diagram shows the call example: + +![](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 a proxed object. + +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. + +The return path is similar to the green arrow, going through layer after layer of interceptor classes. + +Ultimately achieving the purpose of dynamic proxy. + +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. diff --git a/docs/en/1.Getting-Started.md b/docs/en/1.Getting-Started.md new file mode 100644 index 00000000..f72dea7f --- /dev/null +++ b/docs/en/1.Getting-Started.md @@ -0,0 +1,243 @@ +# 1. Getting Started + +Version 2.0.0 :arrow_up: (Simple code examples: https://github.com/cdcd72/NetCore.AspectCore.AOP.Demo) +Version 2.0.0 :arrow_down: (Simple code examples: https://github.com/fs7744/AspectCoreDemo) + +## Start Using AspectCore + +* 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. + +* Install `AspectCore.Extensions.DependencyInjection` package from Nuget: + ``` + PM> Install-Package AspectCore.Extensions.DependencyInjection + ``` +* 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: + ```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 class `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(); + services.AddMvc(); + services.ConfigureDynamicProxy(); + } + ``` +* Finally, add `UseServiceProviderFactory(new DynamicProxyServiceProviderFactory())` to `Program`'s `CreateHostBuilder`: + ```csharp + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaultsDefaults(webBuilder => + { + webBuilder.UseStartup(); + }) + // ... + .UseServiceProviderFactory(new DynamicProxyServiceProviderFactory()); + ``` + +--- +## Interceptor Configuration + +* Global interceptors. Use the `ConfigureDynamicProxy(Action)` overload method, where `IAspectConfiguration` provides `Interceptors` to register global interceptors: + ```csharp + services.ConfigureDynamicProxy(config => + { + config.Interceptors.AddTyped(); + }); + ``` + 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(args: new object[] { "custom" }); + }); + ``` + Global interceptor as a service. Add in `ConfigureServices`: + ```csharp + services.AddTransient(provider => new CustomInterceptorAttribute("custom")); + ``` + Modify global interceptor registration: + ```csharp + services.ConfigureDynamicProxy(config => + { + // Add registered service + config.Interceptors.AddServiced(); + }); + ``` + Global interceptor targeting specific `Service` or `Method`. The following code demonstrates a global interceptor acting on classes ending with `Service`: + ```csharp + services.ConfigureDynamicProxy(config => + { + config.Interceptors.AddTyped(method => method.Name.EndsWith("MethodName")); + }); + ``` + Specific global interceptor using wildcards: + ```csharp + services.ConfigureDynamicProxy(config => + { + config.Interceptors.AddTyped(Predicates.ForService("*Service")); + }); + ``` + +* AspectCore provides `NonAspectAttribute` to make `Service` or `Method` not proxied: + ```csharp + [NonAspect] + public interface ICustomService + { + void Call(); + } + ``` + Also supports global ignore configuration and wildcards: + ```csharp + services.ConfigureDynamicProxy(config => + { + // Services under App1 namespace won't be proxied + config.NonAspectPredicates.AddNamespace("App1"); + + // Services under namespaces where the last level is App1 won't be proxied + config.NonAspectPredicates.AddNamespace("*.App1"); + + // ICustomService interface won't be proxied + config.NonAspectPredicates.AddService("ICustomService"); + + // Interfaces and classes ending with Service won't be proxied + config.NonAspectPredicates.AddService("*Service"); + + // Methods named Query won't be proxied + config.NonAspectPredicates.AddMethod("Query"); + + // Methods ending with Query won't be proxied + config.NonAspectPredicates.AddMethod("*Query"); + }); + ``` + +* Dependency injection in interceptors. Supports property injection, constructor injection, and service locator pattern in interceptors. + + Property injection. In interceptors, mark properties with `public get and set` permissions with the `[AspectCore.DependencyInjection.FromServiceContextAttribute]` attribute to automatically inject the property: + ```csharp + public class CustomInterceptorAttribute : AbstractInterceptorAttribute + { + // ps: Property injection only works when using config.Interceptors.AddTyped(); + // It does not work with services.AddSingleton(); + services.ConfigureDynamicProxy(config => { config.Interceptors.AddServiced(); }); + [FromServiceContext] + public ILogger Logger { get; set; } + + public override Task Invoke(AspectContext context, AspectDelegate next) + { + Logger.LogInformation("call interceptor"); + return next(context); + } + } + ``` + Constructor injection requires the interceptor to be a `Service`. Besides global interceptors, you can still use `ServiceInterceptor` to make interceptors activated from DI: + + ```csharp + public class CustomInterceptorAttribute : AbstractInterceptorAttribute + { + private readonly ILogger ctorlogger; + + // ps: When globally configured with config.Interceptors.AddTyped(), constructor injection cannot be automatically injected, needs manual handling + // Only works with services.AddSingleton(); + services.ConfigureDynamicProxy(config => { config.Interceptors.AddServiced(); }); + public CustomInterceptor(ILogger 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>(); + logger.LogInformation("call interceptor"); + return next(context); + } + } + ``` + +Version 2.0.0 :arrow_up: ps: Original text (https://www.thinkinmd.com/post/2020/03/20/use-aspectcore-to-implement-aop-mechanism/) +Version 2.0.0 :arrow_down: ps: Original text (http://www.cnblogs.com/liuhaoyang/p/aspectcore-introduction-1.html) diff --git a/docs/en/injector.md b/docs/en/injector.md new file mode 100644 index 00000000..1f155042 --- /dev/null +++ b/docs/en/injector.md @@ -0,0 +1,217 @@ +# IoC Container and Dependency Injection in AspectCore + +The IOC pattern and dependency injection have been a very popular pattern in recent years. I believe everyone is familiar with it. ASP.NET Core provides dependency injection as built-in infrastructure. For readers still unfamiliar with dependency injection, you can check the dependency injection related chapters in the ASP.NET Core Chinese documentation we translated: [ASP.NET Core Chinese Documentation Chapter 3 Principles (10) Dependency Injection](http://www.cnblogs.com/dotNETCoreSG/p/aspnetcore-3_10-dependency-injection.html). + +Given the importance of IoC, while AspectCore provides AOP features, it also provides a lightweight, high-performance IoC container `AspectCore.Injector` that can be seamlessly integrated with AOP. + +### Getting Started + +`AspectCore.DependencyInjection` is built into AspectCore.Core package. We can get it through nuget: + +``` + Install-Package AspectCore.Core -pre +``` + +### Container and Service Registration + +In AspectCore.Injector, the container is named `IServiceContext`. Create a container using the default implementation of the container, and provides three methods: type, instance, and factory for service registration: + +```csharp +IServiceContext services = new ServiceContext(); + +// Register service using type +services.AddType(); + +// Register service using instance, service lifecycle limited to singleton +services.AddInstance(new TaskService()); + +// Register service using delegate factory +services.AddDelegate(resolver => new TaskService()); +``` + +### Service Resolution + +AspectCore.DependencyInjection resolves services through `IServiceResolver`: + +```csharp +// Create service resolver +IServiceResolver serviceResolver = services.Build(); + +// Resolve single service +ISampleService sampleService = serviceResolver.Resolve(); + +// Resolve single service and verify if null, throw exception if null +ISampleService sampleServiceRequired = serviceResolver.ResolveRequired(); + +// Resolve service collection, returns empty collection if not registered +IEnumerable sampleServices = serviceResolver.ResolveMany(); +``` + +### Dependency Injection + +AspectCore.DependencyInjection provides two ways: constructor injection and property injection: + +```csharp +public interface ISampleService +{ +} + +public class SampleService : ISampleService +{ + private readonly ISampleRepository _sampleRepository; + private readonly ILogger _logger; + + // Constructor injection + public SampleService(ISampleRepository sampleRepository, ILogger logger) + { + _sampleRepository = sampleRepository; + _logger = logger; + } +} + +public interface ISampleRepository +{ +} + +public class SampleRepository : ISampleRepository +{ + // Property injection. Property injection condition: mark with FromServiceContext attribute and allow set. Properties meeting these conditions are automatically injected + [FromServiceContext] + public ILogger Logger { get; set; } +} +``` + +### Lifecycle + +AspectCore.DependencyInjection provides the following lifecycles: + +* **Transient** - Transient lifetime services are created each time they are requested. This lifecycle is suitable for lightweight, stateless services. + +* **Scoped** - Scoped lifetime services are created once per scope. + +* **Singleton** - Singleton lifetime services are created the first time they are resolved, and each subsequent resolution will use the same instance. If your application requires singleton behavior, it is recommended to let the service container manage the service lifecycle rather than implementing the singleton pattern and managing object lifecycle in your own class. + +### AOP Integration + +In AspectCore.Injector, AOP integration with AspectCore.DynamicProxy is enabled by default, and can be configured through the IServiceContainer's Configure method. + +``` +services.Configure(config => +{ + config.Interceptors.AddTyped(); +}); +``` + +### Using AspectCore.Injector in ASP.NET Core + +Install AspectCore.Extensions.DependencyInjection nuget package: + +``` + Install-Package AspectCore.Extensions.DependencyInjection -pre +``` + +Modify ConfigureServices: + +``` +public IServiceProvider ConfigureServices(IServiceCollection services) +{ + // Add your services... + + // Add services from IServiceCollection to ServiceContainer container + var container = services.ToServiceContainer(); + return container.Build(); +} +``` + +With just two lines of code, you can replace the default DependencyInjection with AspectCore.Injector in ASP.NET Core. + +### Performance + +Autofac is one of the more popular IoC containers in .NET/.NET Core. We used Autofac (version 4.6.2) as a performance comparison target. We compared performance from three aspects: resolving simple objects, property injection, and constructor injection. + +Benchmark class written as follows: + +```csharp +[AllStatisticsColumn] +[MemoryDiagnoser] +public class Benckmarks +{ + private readonly IServiceResolver serviceResolver; + private readonly IContainer container; + + public Benckmarks() + { + var containerBuilder = new ContainerBuilder(); + containerBuilder.RegisterType().As().InstancePerDependency(); + containerBuilder.RegisterType().As().InstancePerDependency(); + containerBuilder.RegisterType().As().InstancePerDependency().PropertiesAutowired(); + containerBuilder.RegisterType().As().InstancePerDependency(); + container = containerBuilder.Build(); + + var serviceContext = new ServiceContext(); + serviceContext.AddType(Lifetime.Transient); + serviceContext.AddType(Lifetime.Transient); + serviceContext.AddType(Lifetime.Transient); + serviceContext.AddType(Lifetime.Transient); + serviceResolver = serviceContext.Build(); + } + + [Benchmark] + public object Autofac_Sample_Resolve() + { + return container.Resolve(); + } + + [Benchmark] + public object AspectCore_Sample_Resolve() + { + return serviceResolver.Resolve(); + } + + [Benchmark] + public object Autofac_PropertyInjection() + { + return container.Resolve(); + } + + [Benchmark] + public object AspectCore_PropertyInjection() + { + return serviceResolver.Resolve(); + } + + [Benchmark] + public object Autofac_ConstructorInjection() + { + return container.Resolve(); + } + + [Benchmark] + public object AspectCore_ConstructorInjection() + { + return serviceResolver.Resolve(); + } +} +``` + +Run Benchmark in Release mode: + +``` +BenchmarkDotNet=v0.10.8, OS=Windows 10 Threshold 2 (10.0.10586) +Processor=Intel Core i5-4590 CPU 3.30GHz (Haswell), ProcessorCount=4 +Frequency=3215206 Hz, Resolution=311.0221 ns, Timer=TSC +dotnet cli version=2.0.0 + [Host] : .NET Core 4.6.00001.0, 64bit RyuJIT + DefaultJob : .NET Core 4.6.00001.0, 64bit RyuJIT + + | Method | Mean | Min | Max | Op/s | Gen 0 | Allocated | + |-------------------------------- |------------:|------------:|------------:|-------------:|-------:|----------:| + | Autofac_Sample_Resolve | 494.83 ns | 482.52 ns | 506.58 ns | 2,020,908.9 | 0.2384 | 752 B | + | AspectCore_Sample_Resolve | 88.52 ns | 87.92 ns | 89.31 ns | 11,296,837.3 | 0.0279 | 88 B | + | Autofac_PropertyInjection | 2,014.46 ns | 2,004.18 ns | 2,028.83 ns | 496,411.0 | 0.5875 | 1856 B | + | AspectCore_PropertyInjection | 307.55 ns | 303.61 ns | 310.74 ns | 3,251,544.6 | 0.1063 | 336 B | + | Autofac_ConstructorInjection | 1,465.71 ns | 1,454.43 ns | 1,480.38 ns | 682,263.5 | 0.6084 | 1920 B | + | AspectCore_ConstructorInjection | 284.94 ns | 283.55 ns | 286.05 ns | 3,509,500.8 | 0.0987 | 312 B | +``` + +Sample: [IoC-Sample](https://github.com/AspectCore/IoC-Sample) diff --git a/docs/en/reflection-extensions.md b/docs/en/reflection-extensions.md new file mode 100644 index 00000000..1b673aa4 --- /dev/null +++ b/docs/en/reflection-extensions.md @@ -0,0 +1,93 @@ +## Get AspectCore.Extension.Reflection +Get AspectCore.Extension.Reflection through nuget: +``` + Install-Package AspectCore.Extensions.Reflection -pre +``` + +## Constructor Reflection Extension +Provides `ConstructorReflector` as the entry point for constructor reflection extension, usage similar to `System.Reflection.ConstructorInfo`: +``` +var constructorInfo = typeof(ConstructorFakes).GetTypeInfo().GetConstructor(new Type[0]); +var reflector = constructorInfo.GetReflector(); +var instance = reflector.Invoke(args); +``` +Performance test (Reflection is the reflection call provided by .NET Core, Reflector is the AspectCore.Extension.Reflection call, Native is the hardcoded call, same below): +``` + | Method | Mean | Error | StdDev | StdErr | Op/s | Gen 0 | Allocated | + |----------- |-----------:|----------:|----------:|----------:|--------------:|-------:|----------:| + | Reflection | 119.505 ns | 0.5146 ns | 0.4814 ns | 0.1243 ns | 8,367,831.8 | 0.0074 | 24 B | + | Reflector | 8.990 ns | 0.0403 ns | 0.0377 ns | 0.0097 ns | 111,236,649.9 | 0.0076 | 24 B | + | Native | 3.825 ns | 0.0620 ns | 0.0580 ns | 0.0150 ns | 261,404,148.5 | 0.0076 | 24 B | +``` + +## Method Invocation Reflection Extension +Provides `MethodReflector` as the entry point for method reflection extension, usage similar to `System.Reflection.MethodInfo`: +``` +var typeInfo = typeof(MethodFakes).GetTypeInfo(); +var method = typeInfo.GetMethod("Call"); +var refector = method.GetReflector(); +refector.Invoke(instance,args); +``` +Performance test: +``` + | Method | Mean | Error | StdDev | StdErr | Op/s | + |------------------- |------------:|----------:|----------:|----------:|----------------:| + | Native_Call | 1.0473 ns | 0.0064 ns | 0.0050 ns | 0.0015 ns | 954,874,046.8 | + | Reflection_Call | 91.9543 ns | 0.3540 ns | 0.3311 ns | 0.0855 ns | 10,874,961.4 | + | Reflector_Call | 7.1544 ns | 0.0628 ns | 0.0587 ns | 0.0152 ns | 139,774,408.3 | + ``` + +## Property Invocation Reflection Extension +Provides `PropertyReflector` as the entry point for property reflection extension, usage similar to `System.Reflection.PropertyInfo`: +``` +var property = typeof(PropertyFakes).GetTypeInfo().GetProperty("Property"); +var reflector = property.GetReflector(); +var value = reflector.GetValue(instance); +``` +Performance test: +``` + | Method | Mean | Error | StdDev | StdErr | Op/s | Gen 0 | Allocated | + |-------------------------- |-----------:|----------:|----------:|----------:|--------------:|-------:|----------:| + | Native_Get_Property | 1.178 ns | 0.0244 ns | 0.0229 ns | 0.0059 ns | 848,858,716.1 | - | 0 B | + | Reflection_Get_Property | 103.028 ns | 0.2217 ns | 0.2074 ns | 0.0535 ns | 9,706,088.1 | - | 0 B | + | Reflector_Get_Property | 4.172 ns | 0.0194 ns | 0.0172 ns | 0.0046 ns | 239,694,827.7 | - | 0 B | + | Native_Set_Property | 2.002 ns | 0.0122 ns | 0.0114 ns | 0.0030 ns | 499,447,543.5 | - | 0 B | + | Reflection_Set_Property | 188.313 ns | 0.5347 ns | 0.5002 ns | 0.1292 ns | 5,310,298.0 | 0.0203 | 64 B | + | Reflector_Set_Property | 5.878 ns | 0.0234 ns | 0.0219 ns | 0.0056 ns | 170,138,324.7 | - | 0 B | + ``` + +## Attribute Retrieval Extension +Example of retrieving attributes marked on a method. + +Method definition: +``` +[Attribute1] +[Attribute2("benchmark", Id = 10000)] +[Attribute3] +[Attribute3] +[Attribute3] +public void Method() +{ +} +``` +Use MethodReflector to get attributes: +``` +var method = type.GetMethod("Method"); +var reflector = method.GetReflector(); +var attribute1 = reflector.GetCustomAttribute(typeof(Attribute1)); +var attributes = reflector.GetCustomAttributes(); +``` +Performance test: +``` + | Method | Mean | Op/s | Gen 0 | Allocated | + |-------------------------------------------- |------------:|-------------:|-------:|----------:| + | Reflection_GetCustomAttribute | 4,642.13 ns | 215,418.5 | 0.2289 | 744 B | + | Reflector_GetCustomAttribute | 35.52 ns | 28,154,302.3 | 0.0101 | 32 B | + | Reflection_GetCustomAttributes_WithAttrType | 5,354.49 ns | 186,759.2 | 0.3281 | 1048 B | + | Reflector_GetCustomAttributes_WithAttrType | 168.61 ns | 5,930,816.1 | 0.0710 | 224 B | + | Reflection_GetCustomAttributes_All | 7,915.45 ns | 126,335.2 | 0.5035 | 1632 B | + | Reflector_GetCustomAttributes_All | 98.36 ns | 10,166,253.6 | 0.0737 | 232 B | + | Reflection_IsDefined | 1,723.30 ns | 580,283.6 | 0.0801 | 256 B | + | Reflector_IsDefined | 35.55 ns | 28,126,759.1 | - | 0 B | +``` +As you can see, AspectCore.Extension.Reflection provides 2 orders of magnitude performance improvements over reflection across the board, reaching the same magnitude as hardcoded calls. The optimization for attribute retrieval is particularly significant. diff --git a/docs/en/reflection.md b/docs/en/reflection.md new file mode 100644 index 00000000..8baaf189 --- /dev/null +++ b/docs/en/reflection.md @@ -0,0 +1 @@ +In the process of implementing AOP from scratch, it's inevitable to need a lot of reflection-related operations. Although reflection performance has been significantly optimized in .NET 4.5+/.NET Core, to pursue extreme performance, I implemented some reflection alternatives, including constructor invocation, method invocation, field reading/writing, property reading/writing, and attribute retrieval. During refactoring, I encapsulated these reflection extension operations into a separate project to make it convenient for myself and others to use. [Get AspectCore.Extension.Reflection](https://github.com/dotnetcore/AspectCore-Framework/blob/master/docs/reflection-extensions.md)