Han*_*lse 1 .net entity-framework dependency-injection autofac asp.net-web-api
我正在尝试使用Autofac、EF Core和AutoMapper设置我的ASP.NET Core Web API项目。
目前,我可以通过以下两种设置使我的项目工作:
SingleInstance() (EF 上下文的并发问题,因为它不是线程安全的)Resolve()调用时都会获得一个新实例=> InstancePerDependency() (额外开销)不幸的是,我无法使其与InstancePerRequest()范围一起使用。
出于某种原因,我收到以下错误消息:
这听起来有点像我会InstancePerRequest()在singelton服务的某个地方注入这些依赖项......
一旦我将所有内容更改为SingleInstance()或InstancePerDependency()一切正常。与InstancePerRequest().
/// <summary>
/// Is called by the ASP.NET Core application for all environments.
/// </summary>
/// <param name="builder">Builder container.</param>
private static void ConfigureContainer(ContainerBuilder builder, ApplicationName applicationName)
{
// For all environments and for only user service specific registrations
if (applicationName == ApplicationName.XY)
{
// Auto mapper
builder.RegisterAutoMapper();
// Register db context
builder.RegisterType<MyDbContext>().InstancePerRequest();
// Repositories
builder.RegisterType<RepositoryA>().As<IRepositoryA>().InstancePerRequest();
// Services
builder.RegisterType<ServiceA>().As<IServiceA>().InstancePerRequest();
}
}
/// <summary>
/// Register AutoMapper to the DI container.
/// </summary>
public static void RegisterAutoMapper(this ContainerBuilder builder)
{
builder.Register(c => new MapperConfiguration(cfg =>
{
// Mapping
cfg.CreateMap<EntityA, EntityB>();
})).AsSelf().SingleInstance();
// Register mapper
builder.Register(c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve)).As<IMapper>().InstancePerLifetimeScope();
}
Run Code Online (Sandbox Code Playgroud)
public class ServiceA : IServiceA
{
private readonly IMapper _mapper;
private readonly IRepositoryA _repository;
public ServiceA (IRepositoryA _repository, IMapper mapper)
{
_repository = _repository;
_mapper = mapper;
}
/// <summary>
/// Return all categories.
/// </summary>
public async Task<List<XY>> GetAllAsync()
{
return await _repository.XY.ToListAsync();
}
}
Run Code Online (Sandbox Code Playgroud)
public class RepositoryA : RepositoryBase<XY, int, RepositoryA>, IRepositoryA
{
public RepositoryA(MyDbContext myDbContext, ILogger<RepositoryA> logger) : base(myDbContext, logger)
{
}
}
}
Run Code Online (Sandbox Code Playgroud)
public abstract class RepositoryBase<TEntityType, TIdType, TLoggingType>
where TEntityType : class
where TLoggingType : class
{
private readonly MyDbContext _myDbContext;
protected readonly ILogger<TLoggingType> _logger;
protected DbSet<TEntityType> _dbSet;
private bool _disposed = false;
public RepositoryBase(MyDbContext myDbContext, ILogger<TLoggingType> logger)
{
_myDbContext = myDbContext;
_dbSet = _myDbContext.Set<TEntityType>();
_logger = logger;
}
// All the boilerplate code has been left out for the sake of brevity
}
Run Code Online (Sandbox Code Playgroud)
TL;DR - 这是可能的,但InstancePerLifetimeScope()仍然应该是第一个考虑的选项。此外,autofac 的文档并不完全正确 - 行为不会完全相同。
首先,请阅读InstancePerRequest() 如何在完整的 .NET Framework WebAPI 应用程序中工作。正如链接下所述,它实际上InstancePerMatchingLifetimeScope()在幕后使用来完成工作。
下一个问题是 - 如何InstancePerMatchingLifetimeScope()运作?:) 反过来,这里描述。下面的例子展示了它背后的想法。
public interface IMyService { }
public class MyService: IMyService
{
public MyService()
{
}
}
internal class Program
{
public static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterType<MyService>().As<IMyService>().InstancePerMatchingLifetimeScope("ScopeName");
var container = builder.Build();
using (var scope = container.BeginLifetimeScope("ScopeName"))
{
var instance1 = scope.BeginLifetimeScope().Resolve<IMyService>();
var instance2 = scope.BeginLifetimeScope().Resolve<IMyService>();
// This outputs "True" since both instances are actually resolved from the same lifetime scope
// tagged with string "ScopeName".
// If none of the parent scopes are tagged with the "ScopeName"
// then such an attempt to resolve IMyService will throw an exception.
Console.WriteLine($"References are the same: {object.ReferenceEquals(instance1, instance2)}");
}
}
}
Run Code Online (Sandbox Code Playgroud)
那么,当 WebAPI 收到请求时会发生什么?WebAPI 管道中的 Autofac 挂钩创建了标有Autofac.Core.Lifetime.MatchingScopeLifetimeTags.RequestLifetimeScopeTag常量(它是“AutofacWebRequest”字符串)的新范围。此范围成为此特定请求的“根”。Controller 及其所有内容也从该范围解析,并且所有解析注册的依赖项的尝试InstancePerRequest(),即 with InstancePerMatchingLifetimeScope("AutofacWebRequest"),都在该范围内。
现在,让我们回到 .NET Core。微软实现了他们自己的 DI 管理,现在它也嵌入在 WebAPI 中。它的工作方式几乎相同,但有一个例外:Microsoft 的 DI 没有“标记”范围的概念,因此它没有任何方法提供类似于InstancePerRequest(). 相反,WebAPI 内部只是创建新的普通作用域,即BeginLifetimeScope()用 autofac 的术语进行调用。这允许控制器解析他们自己的DbContexts实例和其他对于请求是唯一的东西。因此,正如 Travis 已经指出的那样,在 .NET Core 中处理此类依赖项的最简单、最直接的方法是使用InstancePerLifetimeScope()注册(或 Microsoft 的 DI 容器中的“范围”),因为无论如何这就是 WebAPI 在内部所做的。这是您在 .NET Core 中应该考虑的第一个选项。这就是行为与 autofac 的每个请求范围不同的地方。
然而,在我看来,InstancePerRequest()注册的概念仍然有效并且有权存在,即使它应该很少被需要。幸运的是,自己实现它并不难。你需要一些主要的东西:
AddControllersAsServices()扩展将所有控制器注册为服务;InstancePerRequest()使用您自己的生命周期范围标签注册依赖项的自定义扩展。因此,所有可能的实现可能如下所示。
测试服务:
public interface ISomeService
{
Guid Id { get; }
}
public class SomeService : ISomeService
{
public Guid Id { get; }
public SomeService()
{
Id = Guid.NewGuid();
}
}
Run Code Online (Sandbox Code Playgroud)
测试控制器.cs
[Route("api/[controller]")]
public class TestController : Controller
{
private readonly ILifetimeScope _scope;
public TestController(ILifetimeScope scope)
{
_scope = scope;
}
[HttpGet]
public IActionResult Get()
{
var service1 = _scope.Resolve<ISomeService>();
ISomeService service2;
using (var newScope = _scope.BeginLifetimeScope())
{
service2 = newScope.Resolve<ISomeService>();
}
return Ok(service1.Id == service2.Id);
}
}
Run Code Online (Sandbox Code Playgroud)
启动文件
public class Startup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddControllersAsServices();
var builder = new ContainerBuilder();
// just to play with another option and see how it works
// builder.RegisterType<SomeService>().As<ISomeService>().InstancePerLifetimeScope();
builder.RegisterType<SomeService>().As<ISomeService>().InstancePerRequest();
builder.Populate(services);
return new AutofacServiceProvider(builder.Build());
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// !!!!!
app.UsePerRequestScopes();
app.UseMvc();
}
}
Run Code Online (Sandbox Code Playgroud)
现在到了重要的部分。我没有从我的工作项目中提取它,所以它可能不完全正确但它有效并且它很好地展示了这个想法。
请求范围中间件.cs
public class RequestScopeMiddleware
{
private readonly RequestDelegate _next;
private readonly string _scopeName;
public RequestScopeMiddleware(RequestDelegate next, string scopeName)
{
_next = next;
_scopeName = scopeName;
}
public async Task InvokeAsync(HttpContext context)
{
var originalServiceProvider = context.RequestServices;
var currentLifetimeScope = originalServiceProvider.GetRequiredService<ILifetimeScope>();
using (var requestScope = currentLifetimeScope.BeginLifetimeScope(_scopeName))
{
context.RequestServices = new AutofacServiceProvider(requestScope);
try
{
await _next(context);
}
finally
{
context.RequestServices = originalServiceProvider;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
RequestScopeExtensions.cs
public static class RequestScopeExtensions
{
private const string ScopeName = "RequestScope";
public static void InstancePerRequest<T1, T2, T3>(this IRegistrationBuilder<T1, T2, T3> builder)
{
builder.InstancePerMatchingLifetimeScope(ScopeName);
}
public static void UsePerRequestScopes(this IApplicationBuilder builder)
{
builder.UseMiddleware<RequestScopeMiddleware>(ScopeName);
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1678 次 |
| 最近记录: |