内置依赖注入
在依赖注入模式中,依赖一般都需要注册到一个服务容器中,ASP.NET Core 提供了一个内置的服务容器 IServiceProvider。服务依赖需要在应用的 Startup.ConfigureServices 方法中注册。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddScoped<IMyDependency, MyDependency>(); // <=
}
使用Autofac容器替换默认容器
这里以 ASP.NET Core 3.1为例,ASP.NET Core 1.1 - 2.2用法略有不同。使用VS2019新建一个ASP.NET Core 3.1项目。
- 首先从Nuget安装
Autofac.Extensions.DependencyInjection。 - 修改Program.CreateHostBuilder方法:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory()) //<=
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
- 在Startup类里添加一个ConfigureContainer方法,这是约定名称,在这个方法中可以访问Autofac容器并注册依赖。
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
...
}
public void ConfigureContainer(ContainerBuilder builder)
{
// Register your own things directly with Autofac, like:
builder.RegisterModule(new MyApplicationModule());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
}
}
编写服务
接下来我们来编写一个服务测试一下。
首先在项目中新建一个Services文件夹,在文件夹中新建一个接口IMyService:
public interface IMyService
{
int DoSomething(int id);
}
再新建一个实现类MyService:
public class MyService : IMyService
{
public int DoSomething(int id)
{
Console.WriteLine("DoSomething:"+id);
return id;
}
}
这个类没有什么功能,只是接收一个int值,打印一句话,并返回收到的值。
因为不希望Startup类太长,我们不在ConfigureContainer方法中注册所有服务,新建一个DependencyRegister类:
public class DependencyRegister
{
public static ContainerBuilder Register(ContainerBuilder builder)
{
builder.RegisterType<MyService>().As<IMyService>();
return builder;
}
}
这个类有一个静态方法Register,接收ContainerBuilder作为参数,并返回ContainerBuilder,我们在这个类中注册所有的服务,之后在Startup.ConfigureContainer中调用这个方法,返回ContainerBuilder是可能还需要一些调用。
在Startup.ConfigureContainer中:
public void ConfigureContainer(ContainerBuilder builder)
{
DependencyRegister.register(builder);
}
接下来新建一个控制器HomeController,在控制器中注入服务:
public class HomeController : Controller
{
private readonly IMyService _myService;
public HomeController(IMyService myService)
{
this._myService = myService;
}
public IActionResult Index()
{
var result = _myService.DoSomething(1);
return View();
}
}
这样就在ASP.NET Core中使用Autofac实现了依赖注入。但是此时ASP.NET Core只是从容器中创建控制器的构造函数参数,控制器本身并不是从容器中创建的。要想让控制器也从容器中创建需要使用AddControllersAsServices()。
public class Startup
{
// Omitting extra stuff so you can see the important part...
public void ConfigureServices(IServiceCollection services)
{
// Add controllers as services so they'll be resolved.
services.AddMvc().AddControllersAsServices();
}
public void ConfigureContainer(ContainerBuilder builder)
{
// If you want to set up a controller for, say, property injection
// you can override the controller registration after populating services.
builder.RegisterType<MyController>().PropertiesAutowired();
}
}
AOP
Autofac使用Autofac.Extras.DynamicProxy实现方法拦截,在Autofac 4.0.0版本之前,使用Autofac.Extras.DynamicProxy2。使用Nuget安装对应版本。
- 在注册时启动拦截器支持:
builder.RegisterType<MyService>().As<IMyService>().EnableInterfaceInterceptors();
- 编写拦截器。拦截器是一个继承IInterceptor的类。
public class CallLogger : IInterceptor
{
TextWriter _output;
public CallLogger(TextWriter output)
{
_output = output;
}
public void Intercept(IInvocation invocation)
{
_output.WriteLine("Calling method {0} with parameters {1}... ",
invocation.Method.Name,
string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()));
invocation.Proceed();
_output.WriteLine("Done: result was {0}.", invocation.ReturnValue);
}
}
- 注册拦截器。拦截器要注册到Autofac容器中。
builder.Register(c => new CallLogger(Console.Out));
- 使用拦截器。将Intercept特性标注在类或接口上对方法进行拦截。
[Intercept(typeof(CallLogger))]
public class MyService : IMyService
{
public int DoSomething(int id)
{
Console.WriteLine("DoSomething:"+id);
return 0;
}
}
拦截器成功调用:
实现一个SpringBoot风格的Cacheable特性
在SpringBoot中,有一个@Cacheable注解,将这个注解标注在方法上,会对方法的返回结果进行缓存,想要在ASP.NET Core中实现一个类似的功能。
首先我们需要一个自定义的特性,这个特性标注在方法上,将这个方法标注为需要缓存。然后,需要一个拦截器,在调用方法时查看这个方法的特性是否需要缓存,如果需要缓存,就将结果放入缓存,并在下次访问时从缓存中取数据。
自定义特性Cacheable
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CacheableAttribute:Attribute
{
}
新建拦截器CheckCache,这个拦截器检查方法是否需要缓存
public class CheckCache : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var cacheable =invocation.MethodInvocationTarget.GetCustomAttributestypeof(CacheableAttribute), true).Length > 0;
if(cacheable)
{
//执行方法前先检查缓存,如果缓存中有数据直接返回
}
invocation.Proceed();
if (cacheable)
{
//执行方法后将方法返回值写入缓存
}
}
}
注册这个拦截器:
builder.RegisterType<CheckCache>();
为了操作缓存,我们新建一个接口ICache,方便缓存与具体的实现解耦:
public interface ICache
{
object Get(string key);
void Put(string key, object value);
}
这个接口很简单,只有两个方法,分别是查询缓存与插入缓存。
为了简单,写一个没有任何操作的缓存实现NoOpCache:
public class NoOpCache : ICache
{
public object Get(string key)
{
Console.WriteLine("查询缓存:" + key);
return null;
}
public void Put(string key, object value)
{
Console.WriteLine("插入缓存:" + key);
}
}
这个缓存类啥也不干,既不会插入值,也不会返回值,只是为了测试,实际情况中可能要实现MemeryCache或RedisCache等。
注册这个类:
builder.RegisterType<NoOpCache>().As<ICache>().EnableInterfaceInterceptors();
将缓存服务注入到CheckCache类中,并使用:
public class CheckCache : IInterceptor
{
private readonly ICache _cache;
public CheckCache(ICache cache)
{
this._cache = cache;
}
public void Intercept(IInvocation invocation)
{
var cacheable =invocation.MethodInvocationTarget.GetCustomAttributestypeof(CacheableAttribute), true).Length > 0;
string key = "";
if(cacheable)
{
//执行方法前先检查缓存,如果缓存中有数据直接返回
key = invocation.Method.Name + string.Join(", ",invocation.Arguments.Select(a => (a ??"").ToString()).ToArray());
var cacheValue = _cache.Get(key);
if(cacheValue!=null)
{
if(invocation.Method.ReturnType.IsInstanceOfTpe(cacheValue))
{
invocation.ReturnValue = cacheValue;
return;
}
}
}
invocation.Proceed();
if (cacheable)
{
//执行方法后将方法返回值写入缓存
_cache.Put(key, invocation.ReturnValue);
}
}
}
在这里我们简单的使用方法名加参数名拼成一个key来使用,在SpringBoot中实际上可以使用表达式来指定key,这里我们为了简单就这么实现了,后面可以再改进。
最后将特性加到服务上:
[Intercept(typeof(CallLogger))]
[Intercept(typeof(CheckCache))]
public class MyService : IMyService
{
[Cacheable]
public int DoSomething(int id)
{
Console.WriteLine("DoSomething:"+id);
return id;
}
}
这样在执行DoSomething时就会查询缓存,如果缓存中有数据就用缓存中的数据,否则就执行方法,并将返回值插入缓存: