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)}");
}
}
}
Итак, что происходит, когда WebAPI получает запрос?Хук Autofac в конвейере WebAPI создает новую область видимости, помеченную константой Autofac.Core.Lifetime.MatchingScopeLifetimeTags.RequestLifetimeScopeTag
(это строка «AutofacWebRequest»).Эта область становится «корнем» для этого конкретного запроса.Контроллер и все его компоненты также разрешаются из этой области, и все попытки разрешить зависимости, зарегистрированные с InstancePerRequest()
, то есть с InstancePerMatchingLifetimeScope("AutofacWebRequest")
, доходят до этой области.
Теперь давайте вернемся к.NET Core.Microsoft внедрила собственное управление DI, и теперь оно также встроено в WebAPI.Он работает почти так же, но с одним исключением: в DI Microsoft нет концепции «помеченных» областей, поэтому у него просто нет средств для обеспечения функциональности, аналогичной InstancePerRequest()
.Вместо этого внутренние компоненты WebAPI просто создают новую обычную область видимости, то есть делают вызов BeginLifetimeScope()
в терминах autofac.Это позволяет контроллерам разрешать свои собственные экземпляры DbContext
s и другие вещи, уникальные для запроса.Таким образом, как уже указывал Трэвис, самый простой и простой способ обработки таких зависимостей в .NET Core - это использование InstancePerLifetimeScope()
регистраций (или «областей действия» в терминах DI-контейнера Microsoft), поскольку именно этим и занимается WebAPI.внутренне все равно.Это первый вариант, который вы должны рассмотреть в .NET Core. И именно здесь поведение отличается от областей действия autofac по запросу.
Однако, по моему мнению, концепция InstancePerRequest()
регистраций все еще действует и имеет право на существование, даже если онадолжно быть очень редко нужно.К счастью, это не очень сложно реализовать самостоятельно.Вам нужно несколько важных вещей:
- Autofac;
- Регистрация всех контроллеров как сервисов с использованием расширения
AddControllersAsServices()
; - Промежуточное программное обеспечение, которое получает запрос, создает теговое время жизниscope и помещает эту область в качестве контейнера DI в запросе, чтобы все нижестоящие компоненты - промежуточное ПО, контроллеры и т. д. - использовали этот контейнер для разрешения своих зависимостей; расширение
- для регистрации этого промежуточного ПО;
- нестандартное
InstancePerRequest()
расширение для регистрации зависимостей с помощью собственного тега времени жизни.
Итак, в целом возможная реализация может выглядеть следующим образом.
Testсервис для игры:
public interface ISomeService
{
Guid Id { get; }
}
public class SomeService : ISomeService
{
public Guid Id { get; }
public SomeService()
{
Id = Guid.NewGuid();
}
}
TestController.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);
}
}
Startup.cs
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();
}
}
Теперь к основной части.Я не вытащил его из своего рабочего проекта, поэтому он может быть не совсем корректным, но он работает и очень хорошо показывает идею.
RequestScopeMiddleware.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;
}
}
}
}
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);
}
}