I can't find any documentation on how to use Autofac together with Lazy and lifetime scopes. Getting an error about
"No scope with a Tag matching 'transaction' is visible from the scope
in which the instance was requested..."
In my Controller constructor:
public HomeController(Lazy<ISalesAgentRepository> salesAgentRepository, Lazy<ICheckpointValueRepository> checkpointValueRepository)
{
_salesAgentRepository = new Lazy<ISalesAgentRepository>(() => DependencyResolver.Current.GetService<ISalesAgentRepository>());
_checkpointValueRepository = new Lazy<ICheckpointValueRepository>(() => DependencyResolver.Current.GetService<ICheckpointValueRepository>());
}
In my Action:
using (var transactionScope = AutofacDependencyResolver.Current.ApplicationContainer.BeginLifetimeScope("transaction"))
{
using (var repositoryScope = transactionScope.BeginLifetimeScope())
{
// ....
}
}
Are lifetime scopes incompatible with Lazy or did I got it completely wrong?
Yes, you are barking up the wrong tree.
A new controller is created for every new application request. Hence no need to try to manage the lifetime of the dependencies separately.
Configure your repositories to have a scoped lifetime. Do the same for the transaction scope.
When done both repositories will have the same shared transactionScope.
You can also move the transaction commit to an action filter, like this:
public class TransactionalAttribute : ActionFilterAttribute
{
private IUnitOfWork _unitOfWork;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.Controller.ViewData.ModelState.IsValid && filterContext.HttpContext.Error == null)
_unitOfWork = DependencyResolver.Current.GetService<IUnitOfWork>();
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Controller.ViewData.ModelState.IsValid && filterContext.HttpContext.Error == null && _unitOfWork != null)
_unitOfWork.SaveChanges();
base.OnActionExecuted(filterContext);
}
}
(replace IUnitOfWork with transactionscope). Source: http://blog.gauffin.org/2012/06/05/how-to-handle-transactions-in-asp-net-mvc3/
Related
I was working with SignalR, and created timer that will execute following code where I need to get the number of unread messages from database
[Route("api/[controller]")]
[ApiController]
public class PorukeHubController : ControllerBase
{
private readonly IHubContext<PorukeHub> _hub;
private readonly TimerManager _timer;
private DnevnikContext _context { get; set; }
public PorukeHubController(IHubContext<PorukeHub> hub,
TimerManager timer, DnevnikContext context)
{
_hub = hub;
_timer = timer;
_context = context;
}
[HttpGet]
public IActionResult Get()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(() =>
{
var unreadMessages = _context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
_hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
});
return Ok(new { Message = "Request Completed" });
}
Unfortunately, I get the following error when trying to access _context:
System.ObjectDisposedException: 'Cannot access a disposed context
instance. A common cause of this error is disposing a context instance
that was resolved from dependency injection and then later trying to
use the same context instance elsewhere in your application. This may
occur if you are calling 'Dispose' on the context instance, or
wrapping it in a using statement. If you are using dependency
injection, you should let the dependency injection container take care
of disposing context instances. ObjectDisposed_ObjectName_Name'
I'm very confused on what steps should I take to solve this, I'm not yet familiar that much with DI too
Any help would be appreciated
You should turn your method into an async method and await the database call.
public async Task<IActionResult> Get()
{
...
var unreadMessages = await _context.Messages.Where(p => p.RecieverID == currentUserId && p.isRead == false).Count();
...
}
A DbContext only lives for a short period of time and its lifetime ends at the end of the web request for which it is created.
The delegate, however, that you use to initialize the TimerManager stores a reference to that DbContext likely for the duration of the application (assuming that TimerManager is a Singleton). But since that DbContext is disposed of soon after you initialized the TimerManager it becomes unusable.
In general, you should prevent moving injected dependencies from thread to thread (except in case those threads are part of a sequentially executed asynchronous operation), because the consuming code (i.e. your controller) doesn't know whether or not it is safe to do so.
This means that, instead of reusing the same DbContext, the timer should get its own instance and, preferable, get a fresh DbContext instance every time the timer is triggered. You should see each timer tick as a new request, just like every call to a controller is a new (web) request. And the general rule of thumb is that each new request gets its own container scope.
What this means is that, instead of reusing the same DbContext instance, you should wrap each tick in a container scope / IServiceScope and resolve the DbContext from that scope.
For instance:
private readonly TimerManager _timer;
private readonly IServiceProvider _provider;
public PorukeHubController(TimerManager timer, IServiceProvider provider)
{
_timer = timer;
_provider = provider;
}
[HttpGet]
public IActionResult Get()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(() =>
{
using (var scope = this.provider.CreateScope())
{
var sp = scope.ServiceProvider;
var context = sp.GetRequiredService<DnevnikContext>();
var hub = sp.GetRequiredService<IHubContext<PorukeHub>>();
var unreadMessages = context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
}
});
return Ok(new { Message = "Request Completed" });
}
Although the code above fixes the initial problem of letting the DbContext go out of scope, it poses a new problem, which is more fundamental in nature, which is that application components shouldn't depend on the (IServiceProvider) DI Container. This is called the Service Locator anti-pattern.
This means that you shouldn't do this type of initialization inside your controllers, but instead move this to the application startup path; a.k.a. the Composition Root.
This can be done, for instance, by introducing a new abstraction:
private readonly IMessageInitializer _initializer;
public PorukeHubController(IMessageInitializer initializer)
{
__initializer = _initializer;
}
[HttpGet]
public IActionResult Get()
{
_messageInitializer.Initialize();
return Ok(new { Message = "Request Completed" });
}
In this code example the new IMessageInitializer hides the complexity of initialization of the timer, the querying of the database and the calling of the hub from the controller.
Inside your Composition Root you can now define an implementation with the original code:
public class ScheduledMessageInitializer : IMessageInitializer
{
private readonly TimerManager _timer;
private readonly IServiceProvider _provider;
public ScheduledMessageInitializer(
TimerManager timer, IServiceProvider provider)
{
_timer = timer;
_provider = provider;
}
public void Initialize()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(() => {
using (var scope = this.provider.CreateScope())
{
var sp = scope.ServiceProvider;
var context = sp.GetRequiredService<DnevnikContext>();
var hub = sp.GetRequiredService<IHubContext<PorukeHub>>();
var unreadMessages = context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
}
});
}
}
This class can be registered as follows:
// This assumes that TimerManager is a singleton as well.
services.AddSingleton<IMessageInitializer, ScheduledMessageInitializer>();
This still poses a (smaller) design issue, which is that with DI you should strive to keep the application's business logic out of the Composition Root. It should only contain the required infrastructural code for the application to execute. The querying of the database and sending it to the hub can be considered business logic; not infrastructure.
That would mean that one last refactoring is in place: you should extract that logic out of the ScheduledMessageInitializer and place it in a new application component:
public class UnreadMessageChecker
{
private readonly DnevnikContext _context;
private readonly IHubContext<PorukeHub> _hub;
public UnreadMessageChecker(DnevnikContext context, IHubContext<PorukeHub> hub)
{
_context = context;
_hub = hub;
}
public async Task CheckAsync()
{
var unreadMessages = context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
// I noticed how you called SendAsync. You should await it, otherwise
// you'll get into the same trouble as where you started out with:
// with an ObjectDisposedExcetion.
await hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
}
}
services.AddTransient<UnreadMessageChecker>();
This new component can be resolved from the ScheduledMessageInitializer, which reduces it to the following:
public class ScheduledMessageInitializer : IMessageInitializer
{
private readonly TimerManager _timer;
private readonly IServiceProvider _provider;
public ScheduledMessageInitializer(
TimerManager timer, IServiceProvider provider)
{
_timer = timer;
_provider = provider;
}
public void Initialize()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(async () =>
{
using (var scope = this.provider.CreateScope())
{
var checker = scope.ServiceProvider
.GetRequiredService<UnreadMessageChecker>();
await checker.CheckAsync();
}
});
}
}
There might still be other issues with your code. For instance, it seems weird to me that you have a currentUserId (which is runtime data, changing on each request), while using it to initialize the timer with; unless that timer isn't a Singleton. But if the timer isn't a singleton, that would mean that would be initializing an endless number of timers, which likely isn't a good idea as well.
Another issue is that, if the TimerManager is indeed singleton, there might be a race condition while initializing. Is the TimerManager thread-safe? What would happen when it gets initialized twice simultaneously? Would that cause problems. I, unfortunately, can't answer this.
I'm trying to create a UnitOfWork Action Filter.
I'm hooking into the OnActionExecuted method where i want to save all changes to the DBContext depending on three rules :-
The Context is NOT NULL.
The Context ChangeTracker HasChanges
No Exceptions have been caught throughout the lifetime of the ActionMethods existence.
Throughout the action methods in the WEB API, I only ever attach entities to the DbContext, its only when the action itself has completed without any errors do I commit changes.
The DbContext in setup using Ninject witha lifestyle of "InRequestScope".
Here is the UnitOfWork ActionFilterAttribute :-
public class UnitOfWorkActionFilterAttribute : ActionFilterAttribute
{
public virtual IActionTransactionHelper ActionTransactionHelper
{
get { return WebContainerManager.Get<IActionTransactionHelper>(); }
}
public override bool AllowMultiple
{
get { return false; }
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
ActionTransactionHelper.BeginTransaction();
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
ActionTransactionHelper.EndTransaction(actionExecutedContext);
ActionTransactionHelper.CloseSession();
}
}
In Ninject, the DbContext is configured like this :-
container.Bind<MyDbContext>().ToSelf().InRequestScope();
Here is the ActionTransactionHelper Class :-
public class ActionTransactionHelper : IActionTransactionHelper
{
public bool TransactionHandled { get; private set; }
public bool SessionClosed { get; private set; }
public void BeginTransaction()
{
var sessionContext = WebContainerManager.Get<MyDbContext>();
if (sessionContext == null)
{
throw new NullReferenceException("sessioncontext");
}
}
public async Task EndTransaction(HttpActionExecutedContext filterContext)
{
var sessionContext = WebContainerManager.Get<MyDbContext>();
if (sessionContext != null && sessionContext.ChangeTracker.HasChanges() && filterContext.Exception == null)
{
var x = await sessionContext.SaveChangesAsync();
}
TransactionHandled = true;
}
public void CloseSession()
{
var sessionContext = WebContainerManager.Get<MyDbContext>();
if (sessionContext == null) return;
sessionContext.Dispose();
SessionClosed = true;
}
}
I have an Action method that attaches enities to the DBContext like this :-
context.Claims.Add(entity);
When the EndTransaction() Method is fired on ActionExecuted, the context object has no record in the ChangeTracker of any changes and the SaveChangesAsync() method is never fired.
However, If i change the Ninject Binding for the DbContext to a Singleton, the code works fine, the change is tracked and the object is saved to the database.
I don't understand why this isn't working per web request?
I dont want to use Singletons in this case.
This problem was resolved by adding the Ninject.WeApi2 Nuget Package.
In the NinjectWebCommon i replaced the NinjectDependencyResolver Implementation with the one referenced in the Ninject.WeApi2
**Ninject.Web.WebApi.NinjectDependencyResolver**
InRequestScope() now works fine with Action Filters and the DBContext tracks changes.
I am working on an asp.net mvc web application. now i have created multiple repositories classes, for example i have the following abstract repository classes:-
public interface ISkillRepository : IDisposable
{//code goes here..
&
public interface IStaffRepository : IDisposable
{//code goes here
and the model Repositories:-
public class SkillRepository : ISkillRepository , IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
//code goes here
&
public class StaffRepository : IStaffRepository , IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
now inside y controller i am intializing and creating the repo as follow:-
public class SkillController : Controller
{
private ISkillRepository skillRepository;
public SkillController() : this(new SkillRepository()) {}
public SkillController(ISkillRepository repository)
{
skillRepository = repository;
}
but currently i got the following error inside my application:
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
and the problem is that i need to be passing the same context accross the repos and controllers. so can anyone adivce on this:-
how i can inside one model repo to reference another repo using the same context class. for example inside the Staff repositoryto referecne the skill repository?
how i can inside a controller class to refer multiple repos , but at the same time pass the same context object among them , so if i issue a save() it will wrap all the statements inside one transaction. for example insie my skillController to reference both the skill & staff repos using the same context object ?
Thanks
Edit
I have created the following Unit of work class:-
public class UnitOfWork : IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
private SkillRepository skillRepository;
private StaffRepository staffRepository;
private SecurityRoleRepository securityroleRepository;
public SkillRepository SkillRepository
{
get
{
if (this.skillRepository == null)
{
this.skillRepository = new SkillRepository(context);
}
return skillRepository;
}
}
public StaffRepository StaffRepository
{
get
{
if (this.staffRepository == null)
{
this.staffRepository = new StaffRepository(context);
}
return staffRepository;
}
}
public SecurityRoleRepository SecurityRoleRepository
{
get
{
if (this.staffRepository == null)
{
this.staffRepository = new SecurityRoleRepository(context);
}
return securityroleRepository;
}
}
public async Task Save()
{
await context.SaveChangesAsync();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
and then inside my repo i did the following:-
public class SecurityRoleRepository : ISecurityRoleRepository , IDisposable
{
private SkillManagementEntities context;// = new SkillManagementEntities();
public SecurityRoleRepository(SkillManagementEntities context)
{
this.context = context;
and on the controller class i will be referencing the UnitOfWork as follow:-
public class SecurityRoleController : Controller
{
private UnitOfWork unitOfWork = new UnitOfWork();
public async Task<ActionResult> Index(string filter = null, int page = 1, int pageSize = 20, string sort = "Name", string sortdir = "ASC")
{
try
{
var records = new PagedList<SecurityRole>();
ViewBag.filter = filter;
records.Content = await unitOfWork.SecurityRoleRepository.GetSecurityRoleForGrid(filter, page, pageSize, sort, sortdir).ToListAsync();
now i am facing a problem is that how i can referecne a repo from another Repo ? for example how i can reference the Skill repo inside the SecurityRole repo ?
EDIT Final
i did the following steps:-
1. i install
Install-Package Ninject.MVC5
2. then i created the following dependency class:-
public class YourDependencyResolverClass : IDependencyResolver
{
private IKernel kernel;
public YourDependencyResolverClass()
{
kernel = new StandardKernel();
AddBindings();
}
public object GetService(Type serviceType)
{
return kernel.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return kernel.GetAll(serviceType);
}
private void AddBindings()
{
kernel.Bind<ISkillRepository>().To<SkillRepository>();
kernel.Bind<IStaffRepository>().To<StaffRepository>();
kernel.Bind<ISecurityRoleRepository>().To<SecurityRoleRepository>();
kernel.Bind<ICustomerRepository>().To<CustomerRepository>();
kernel.Bind<ISkillVersionHistoryRepository>().To<SkillVersionHistoryRepository>();
}
}
}
3.now inside my SkillRepository class i will be referencing the StaffRepository as follow:-
public class SkillRepository : ISkillRepository , IDisposable
{
private SkillManagementEntities context ;
private IStaffRepository staffrepo = (IStaffRepository)DependencyResolver.Current.GetService(typeof(IStaffRepository));
public SkillRepository(SkillManagementEntities context)
{
this.context = context;
}
Finally inside my action method i will be calling the Uiteofwork class as follow:-
public class StaffController : Controller
{
//private SkillManagementEntities db = new SkillManagementEntities();
UnitOfWork unitofwork = new UnitOfWork();
public async Task<ActionResult> AutoComplete(string term)
{
var staff = await unitofwork.StaffRepository.GetAllActiveStaff(term).Select(a => new { label = a.SamAccUserName }).ToListAsync();
return Json(staff, JsonRequestBehavior.AllowGet);
and the unite of work class is :-
public class UnitOfWork : IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
private SkillRepository skillRepository;
private StaffRepository staffRepository;
private SecurityRoleRepository securityroleRepository;
private CustomerRepository customerRepository;
private SkillVersionHistoryRepository SVH;
public SkillRepository SkillRepository
{
get
{
if (this.skillRepository == null)
{
this.skillRepository = new SkillRepository(context);
}
return skillRepository;
}
}
public StaffRepository StaffRepository
{
get
{
if (this.staffRepository == null)
{
this.staffRepository = new StaffRepository(context);
}
return staffRepository;
}
}
public CustomerRepository CustomerRepository
{
get
{
if (this.customerRepository == null)
{
this.customerRepository = new CustomerRepository(context);
}
return customerRepository;
}
}
public SecurityRoleRepository SecurityRoleRepository
{
get
{
if (this.securityroleRepository == null)
{
this.securityroleRepository = new SecurityRoleRepository(context);
}
return securityroleRepository;
}
}
public SkillVersionHistoryRepository SkillVersionHistoryRepository
{
get
{
if (this.SVH == null)
{
this.SVH = new SkillVersionHistoryRepository(context);
}
return SVH;
}
}
public async Task Save()
{
await context.SaveChangesAsync();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
So can you adivce if my approach of using unitefwork and DI will guarantee that all my statements will be warped inside a single DB transaction ? thnaks?
We handle this by sharing a context using a singleton that is scoped to the request using HttpContext:
public MyContext GetContext()
{
if (System.Web.HttpContext.Current.Items["MyScopedContext"] == null)
{
System.Web.HttpContext.Current.Items["MyScopedContext"] = new MyContext();
}
return (MyContext)System.Web.HttpContext.Current.Items["MyScopedContext"];
}
The context object (repository) itself essentially houses a Unit of Work. The code I added above just gives you a way to share a single repository across all code running within a request. If your repository classes are defined in the scope of a web application, you can just replace your direct instantiation of SkillManagementEntities() with a call to a GetContext() method.
On the other hand if your repositories are defined in a layer shared by heterogeneous applications, you may need to get your context from a factory object that you can inject as needed. Either way, creating a new context object per repository is what's causing your issue.
Not an answer: this "use DI" suggestion answers a bit different question - OP is looking for "unit-of-work" pattern - while basic case (lifetime of unit of work matches lifetime of request/controller) can easily be solved with any DI framework, managing multiple units of work or units of work with longer lifetime is much harder and dedicated "unit of work factory" (sample usage) is likely the solution.
Usually when you go that far with interfaces/repositories and constructor dependency injection you have some Dependency Injection framework. There is a good chance that one you are using already provides "per HTTP request" resolution or allows to easily add one.
I.e. if you using Unity there is PerRequestLifetime lifetime manager that makes all .Resolve calls for the same interface/object to return the same instance for given request. See more info in DI with Unity MSDN article.
Approximate sample:
container.RegisterType<ISkillRepository, SkillRepository>();
container.RegisterType<IOtherRepository, OtherRepository>();
container.RegisterType<TheContext, TheContext>(new PerRequestLifetime());
With such registration and assuming you've configured ASP.Net MVC to use Unity to resolve types when controller is created it will get necessary dependencies (new instances as registered with default lifetime), but both will share the same context (assuming each depends on TheContext class either via constructor or property injection).
I am using Fluent NHibernate as our ORM and I am getting an Error of memory Leak.
I have observerd it in Task Manager that, whenever I tried to access the Home page from different web Browsers in the same PC, CPU usage is 2-3% but Memory usage get 80-90% which results in slowing of website and leads to system Hang.
And to run my webisite again I have to End the process from Task Manager. One more thing, when I access it from a Browser it use some memory but when i close it , It do no release all resources(that memory).
I have made the architecture of my webiste in this way :-
I have made a class "ParentObject" in which i have created Repository object as a static member.
All Entities Which I have created have been Inherited from "ParentObject" Class.
I have made one more class BaseController which I have inherited from "Controller" class and in Base class I have used this code :-
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
EdustructRepository Repository; // My Repository Class where I have written logic for opening and closing Save and Update Session.I have mentioned my this logic below
if (Session["Repository"] != null)
{
Repository = (EdustructRepository)Session["Repository"];
if (!Repository.GetSession().Transaction.IsActive)
Repository.GetSession().Clear();
}
else
{
Repository = new EdustructRepository(typeof(ActivityType), FluentNhibernateRepository.DataBaseTypes.MySql);
Session["Repository"] = Repository;
}
if (ParentObject._repository == null)
{
ParentObject._repository = new EdustructRepository(); // Here i have set the ParentObject's static variable "_repository" by this i have accessed repository in all my Entities .
}
}
And I have Inherited all my controller with BaseController Class. By this I have got the "_repository" object with every Action hit.
My session management Logic
public class EdustructRepository : NHibernetRepository
{
public void Save<T>(T item, bool clearSession)
{
if (typeof(T).GetProperty("Created_at").GetValue(item, null).ToString() == DateTime.MinValue.ToString())
{
typeof(T).GetProperty("Created_at").SetValue(item, MySqlDateTime.CurrentDateTime(), null);
}
typeof(T).GetProperty("Updated_at").SetValue(item, MySqlDateTime.CurrentDateTime(), null);
base.CheckAndOpenSession();
using (var transaction = base.GetSession().BeginTransaction())
{
try
{
base.GetSession().SaveOrUpdate(item);
transaction.Commit();
if (clearSession)
{
Session.Clear();
}
}
catch
{
base.Evict(item);
base.Clear();
throw;
}
}
//base.Save<T>(item, clearSession);
}
public void Save<T>(T item)
{
Save<T>(item, false);
}
}
public class NHibernetRepository : IDisposable
{
public static ISessionFactory _SessionFactory = null;
protected ISession Session = null;
private ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(MySQLConfiguration.Standard.ConnectionString(c => c.FromConnectionStringWithKey("DBConnectionString")))
.Mappings(m =>m.FluentMappings.AddFromAssembly((Assembly.Load("Edustruct.Social.DataModel"))).Conventions.Add<CascadeConvention>())
.ExposeConfiguration(cfg => cfg.SetProperty(NHibernate.Cfg.Environment.CurrentSessionContextClass,"web"))
.BuildSessionFactory();
}
protected void CheckAndOpenSession()
{
if (_SessionFactory == null)
{
_SessionFactory = CreateSessionFactory();
}
if (Session == null)
{
Session = _SessionFactory.OpenSession();
Session.FlushMode = FlushMode.Auto;
}
if (!Session.IsOpen)
Session = _SessionFactory.OpenSession();
else if (!Session.IsConnected)
Session.Reconnect();
}
}
Note: We haven't closed Session in our Repository it is because I am using lazy Initialization also I have used it at Views so if I close session here I get an error showing "Session not found".
This is how I have made flow of my website.
Would you please review this code and let me know why I am getting this Error.
Thanking you in advance.
Problems:
you basicly hold one session per entity open forever. However ISession implements the unit of work pattern which is not meant for this.
CheckAndOpenSession() is not thread safe but webserving is inherently threaded: each request typically gets its own thread.
Whats the use
Every business operation should have its own session which is disposed of at the end of it. Business operation is typically a controller action or web method.
sample
// on appstart
GlobalSessionFactory = CreateSessionFactory();
// in basecontroller befor action
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
DatabaseSession = GlobalSessionFactory.OpenSession();
}
// in basecontroller after action (pseudocode)
protected override void OnActionExecuted()
{
DatabaseSession.Dispose();
}
In my System.Web.Mvc Action filters I previously used TempData to store an instance of my unitOfWork service like so:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.TempData[UnitOfWorkRequestKey] = UnitOfWork;
UnitOfWork.Begin();
}
then to commit the transaction I retreived it from temp data like this..
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var unitOfWork = (IUnitOfWork)filterContext.Controller.TempData[UnitOfWorkRequestKey];
try
{
if (filterContext.Exception == null)
{
unitOfWork.Complete();
}
}
finally
{
unitOfWork.Dispose();
filterContext.Controller.TempData[UnitOfWorkRequestKey] = null;
}
}
So my question is:
In the System.Web.Http Web Api Action Filter (using HttpActionContext) - is there an equivalent location to store my instance of a service, so I can retrieve the same instance when the action has executed?
In the System.Web.Http Web Api Action Filter (using HttpActionContext)
- is there an equivalent location to store my instance of a service, so I can retrieve the same instance when the action has executed?
No, there isn't. The whole point of an API is that it should be stateless. That's rule number 1. If you need to use Session or TempData in an API you are probably doing something very wrong from a design perspective.
Also you shouldn't be using TempData in your MVC application for this task. TempData is used when you need to persist information between more than one request. In your case it is the same request. So you should have used the HttpContext to store this information:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Items[UnitOfWorkRequestKey] = UnitOfWork;
}
and then:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var unitOfWork = (IUnitOfWork) filterContext.HttpContext.Items[UnitOfWorkRequestKey];
try
{
if (filterContext.Exception == null)
{
unitOfWork.Complete();
}
}
finally
{
unitOfWork.Dispose();
filterContext.Controller.TempData[UnitOfWorkRequestKey] = null;
}
}
Alright, now that we have fixed your MVC application here's how to achieve the same in the Web API using the Request.Properties collection:
public override void OnActionExecuting(HttpActionContext actionContext)
{
actionContext.Request.Properties[UnitOfWorkRequestKey] = UnitOfWork;
}
and then:
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var unitOfWork = (IUnitOfWork) actionExecutedContext.Request.Properties[UnitOfWorkRequestKey];
try
{
if (actionExecutedContext.Exception == null)
{
unitOfWork.Complete();
}
}
finally
{
unitOfWork.Dispose();
}
}