我有一个ASP.NET WEB API项目,该项目使用存储库和工作单元模式以及UnityContainer。我向PerRequestLifeTimeManager注册了所有存储库和工作单元类,并且一切正常。每个对服务器的客户端请求都在与其他客户端请求分开的单个事务中工作。将请求返回给客户端后,将处理DbContext。
当我将响应返回给客户端后,我需要执行沉重的动作时,我的问题就开始了。 DbContext已处置,我无法为新实例请求Unity容器(因为不存在HttpContext)。
我读了这篇文章PerRequestLifetimeManager can only be used in the context of an HTTP request 并尝试实现2种不同的UnityContainer
我以为我解决了问题,但是我注意到我从CoreContainer请求的每个存储库都使用不同的DbContext。另外,UnitOfWork具有不同的DbContext。当然,这会导致我的代码出现很多错误。
这是我用来在UnityContainer中注册实体的代码。我有2个数据库,因此某些存储库使用第一个数据库的DbContext,其他使用第二个DbContext。使用两个DbContext的我的UnitOfWork
public static class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> perRequestContainer =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterCommonTypes<PerRequestLifetimeManager>(container);
return container;
});
private static Lazy<IUnityContainer> perResolveContainer =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterCommonTypes<PerResolveLifetimeManager>(container);
//CNSDeployerService can
container.RegisterType<ICNSDeployerService, CNSDeployerService>(new PerResolveLifetimeManager());
return container;
});
public static IUnityContainer WebContainer => perRequestContainer.Value;
public static IUnityContainer CoreContainer => perResolveContainer.Value;
#endregion
/// <summary>
/// Please notice this configuration exist only inside the scope of a single request
/// See UnityWebApiActivator
/// </summary>
/// <param name="container"></param>
public static void RegisterCommonTypes<T>(IUnityContainer container) where T: LifetimeManager
{
container.RegisterType<ApplicationDbContext, ApplicationDbContext>(Activator.CreateInstance<T>());
container.RegisterType<DbContext, ApplicationDbContext>(Activator.CreateInstance<T>());
container.RegisterType<capNsubContext, capNsubContext>(Activator.CreateInstance<T>());
container.RegisterType<IDataContextAsync, capNsubContext>("capNsubContext", Activator.CreateInstance<T>());
container.RegisterType<IDataContextAsync, LinetServerContext>("linnetDataContext", Activator.CreateInstance<T>());
container.RegisterType<IRepository<Entity>, Repository<Entity>>(Activator.CreateInstance<T>());
container.RegisterType<IRepositoryAsync<VideoTargetLanguage>, Repository<VideoTargetLanguage>>(Activator.CreateInstance<T>());
//Unity understand array by defaults so we just need to map it to IEnumerable
container.RegisterType<IEnumerable<IDataContextAsync>, IDataContextAsync[]>();
container.RegisterType<IUnitOfWorkAsync, UnitOfWork>(Activator.CreateInstance<T>());
container.RegisterType<UserManager<ApplicationUser>>(Activator.CreateInstance<T>());
container.RegisterType<RoleManager<IdentityRole>>(Activator.CreateInstance<T>());
container.RegisterType<AccountController>(Activator.CreateInstance<T>());
container.RegisterType<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>(Activator.CreateInstance<T>());
container.RegisterType<IOrderService, OrderService>(Activator.CreateInstance<T>());
container.RegisterType<IFFMpegService, FFMpegService>(Activator.CreateInstance<T>());
container.RegisterType<IVideoService, VideoService>(Activator.CreateInstance<T>());
container.RegisterType<IOrderItemService, OrderItemService>(Activator.CreateInstance<T>());
container.RegisterType<ILanguageService, LanguageService>(Activator.CreateInstance<T>());
container.RegisterType<IUserService, UserService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSCaptionsService, CNSCaptionsService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSTranslationsService, CNSTranslationsService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSCapMoviesService, CNSMovieService>(Activator.CreateInstance<T>());
container.RegisterType<HttpClient, HttpClient>(Activator.CreateInstance<T>());
container.RegisterType<SimpleRefreshTokenProvider, SimpleRefreshTokenProvider>(Activator.CreateInstance<T>());
var capNsubEntityTypes = GetEntityFrameworkEntityTypesByContext<capNsubContext>();
var linnetEntityTypes = GetEntityFrameworkEntityTypesByContext<LinetServerContext>();
RegisterEntitiesRepostiories<T>(container, capNsubEntityTypes, "capNsubContext");
RegisterEntitiesRepostiories<T>(container, linnetEntityTypes, "linnetDataContext");
}
private static void RegisterEntitiesRepostiories<T>(IUnityContainer container, IEnumerable<Type> entities, string contextName)
where T:LifetimeManager
{
var iGenericRepositoryTypes = new[] { typeof(IRepositoryAsync<>), typeof(IRepository<>) };
foreach (var iGenericRepositoryType in iGenericRepositoryTypes)
{
foreach (var entityType in entities)
{
var iSpecificRepositoryType = iGenericRepositoryType.MakeGenericType(entityType);
var genericRepositoryType = typeof(Repository<>);
var specificRepositoryType = genericRepositoryType.MakeGenericType(entityType);
container.RegisterType(iSpecificRepositoryType, Activator.CreateInstance<T>(), new InjectionFactory(c =>
{
return Activator.CreateInstance(specificRepositoryType, c.Resolve<IDataContextAsync>(contextName), c.Resolve<IUnitOfWorkAsync>());
}));
}
}
}
private static IEnumerable<Type> GetEntityFrameworkEntityTypesByContext<T>() where T : DataContext
{
var capNsubContextType = typeof(T);
var capNsubDataAssembly = Assembly.GetAssembly(capNsubContextType);
var ef6EntityType = typeof(Repository.Pattern.Ef6.Entity);
return capNsubDataAssembly.GetTypes()
.Where(t => String.Equals(t.Namespace, capNsubContextType.Namespace, StringComparison.Ordinal) &&
t.IsSubclassOf(ef6EntityType));
}
}
[System.Web.Http.Authorize(Roles = "admin")]
[System.Web.Http.RoutePrefix("api/job")]
public class JobController : BaseApiController {
[System.Web.Http.Route("Create", Name = "Create")]
[System.Web.Http.HttpPost]
public IHttpActionResult Create(JobBindingModel createJobModal)
{
//We have to use the CoreContainer since cnsDeployer scope runs outside of the request
var cnsDeployer = UnityConfig.CoreContainer.Resolve<ICNSDeployerService>();
if (!ModelState.IsValid)
{
return BadRequest();
}
try
{
//This runs in the backround after we return the response to client
cnsDeployer.Deploy(createJobModal.ItemIds);
return Ok();
}
catch(Exception err)
{
return InternalServerError(err);
}
}
}
public class CNSDeployerService : ICNSDeployerService
{
private readonly IOrderItemService orderItemService;
private readonly ICNSCapMoviesService cnsMoviesService;
private readonly ICNSTranslationsService cnsTranslationsService;
private readonly IFFMpegService ffMpegService;
private readonly IUnitOfWorkAsync unitOfWorkAsync;
private readonly IVideoService videoService;
public CNSDeployerService(IOrderItemService orderItemService,
ICNSCapMoviesService cnsCapMoviesService,
ICNSTranslationsService cnsTranslationsService,
IFFMpegService ffMpegService,
IUnitOfWorkAsync unitOfWorkAsync,
IVideoService videoService)
{
this.orderItemService = orderItemService;
this.cnsMoviesService = cnsCapMoviesService;
this.cnsTranslationsService = cnsTranslationsService;
this.ffMpegService = ffMpegService;
this.unitOfWorkAsync = unitOfWorkAsync;
this.videoService = videoService;
}
public void Deploy(IEnumerable<Guid> orderItemIds)
{
try
{
InnerDeploy(orderItemIds);
}
catch
{
unitOfWorkAsync.Dispose();
}
}
private void InnerDeploy(IEnumerable<Guid> orderItemIds)
{
var orderItems = orderItemService.Queryable()
.Where(orderItem => orderItemIds.Any(itemId => orderItem.Id == itemId)
&& orderItem.IsInProcessQueue == false
&& !orderItem.TranslationId.HasValue)
.ToList();
if (orderItems.Count == 0)
{
unitOfWorkAsync.Dispose();
throw new ArgumentNullException("No valid orders was provided");
}
foreach ( var orderItem in orderItems)
{
orderItem.IsInProcessQueue = true;
orderItemService.Update(orderItem);
}
unitOfWorkAsync.SaveChanges();
var translationId = Guid.NewGuid();
var movieId = Guid.NewGuid();
var connectedMoviePath = cnsMoviesService.GetMoviePath(movieId);
var videosUrlList = orderItems
.Select(orderItem => orderItem.VideosTable.VideoUrl)
.ToList();
//Don't await on this task since we want concat to continue after request is returned
Task.Run(async () =>
{
try
{
await ffMpegService.ConcatVideos(videosUrlList, connectedMoviePath);
VideoUtils.CreateVideoImageAndReturnPath(connectedMoviePath);
var videosTotalDuration = videoService.GetVideosTotalDuration(orderItemIds);
var durationInSeconds = Convert.ToInt32((int)(videosTotalDuration / 1000));
await cnsMoviesService.CreateMovieRecordAsync(movieId, durationInSeconds);
await cnsTranslationsService.CreateTranslationRecordAsync(movieId, translationId, language: 1);
var index = 0;
foreach (var orderItem in orderItems)
{
orderItem.TranslationId = translationId;
orderItem.TranslationIndex = index++;
orderItem.IsInProcessQueue = false;
orderItemService.Update(orderItem);
}
await unitOfWorkAsync.SaveChangesAsync();
}
catch (Exception err)
{
//TODO: Handle error
}
finally
{
//Dispose db context
unitOfWorkAsync.Dispose();
}
});
}
}