Question

I have an ASP.Net Web API project. I am using NHibernate in this project; Fluent NHibernate to be specific. I am handling NHib session management using a custom ActionFilterAttribute. It looks like this:

public class SessionManagement : ActionFilterAttribute
{
    public SessionManagement()
    {
        SessionFactory = WebApiApplication.SessionFactory;
    }

    private ISessionFactory SessionFactory { get; set; }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var session = SessionFactory.OpenSession();
        CurrentSessionContext.Bind(session);
        session.BeginTransaction();
    }

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        var session = SessionFactory.GetCurrentSession();
        var transaction = session.Transaction;
        if (transaction != null && transaction.IsActive)
        {
            transaction.Commit();
        }
        session = CurrentSessionContext.Unbind(SessionFactory);
        session.Close();
    }

This was working well for my needs. However, I have recently added a custom JSON.NET MediaTypeFormatter to format the my action's resulting JSON. The problem I am having is that my ActionFilter OnActionExecuted() method is called before the MediaTypeFormatter's WriteToStreamAsync can do it's job. The result is that lazily loaded (the problem) collections are now not available because the session is closed. What is the best way to handle this? Should I remove the ActionFilter's OnActionExecuted method and just close my session in the MediaTypeFormatter?

Thanks!!

Was it helpful?

Solution

The MediaTypeFormatter is the wrong layer to close the session at because this behavior really has nothing to do with the particular formatter you're using. Here's what I recommend doing:

  1. Derive from HttpContent and create a class that derives from ObjectContent. Override the SerializeToStreamAsync implementation to await the base implementation's SerializeToStreamAsync then close the session:

    public class SessionClosingObjectContent : ObjectContent
    {
        private Session _session;
    
        public SessionClosingObjectContent(Type type, object value, MediaTypeFormatter formatter, Session session)
            : base(type, value, formatter)
        {
            _session = session;
        }
    
        protected async override Task SerializeToStreamAsync(Stream stream, TransportContext context)
        {
            await base.SerializeToStreamAsync(stream, context);
            // Close the session and anything else you need to do
            _session.Close();
        }
    }
    
  2. Now in your action filter, instead of closing the session, you want to replace the response Content with your new class that closes the session:

    public class SessionManagement : ActionFilterAttribute
    {
        public SessionManagement()
        {
            SessionFactory = WebApiApplication.SessionFactory;
        }
    
        private ISessionFactory SessionFactory { get; set; }
    
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            var session = SessionFactory.OpenSession();
            CurrentSessionContext.Bind(session);
            session.BeginTransaction();
        }
    
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            var session = SessionFactory.GetCurrentSession();
            var response = actionExecutedContext.Response;
            if (response.Content != null)
            {
                ObjectContent objectContent = response.Content as ObjectContent;
                if (objectContent != null)
                {
                    response.Content = new SessionClosingObjectContent(objectContent.ObjectType, objectContent.Value, objectContent.Formatter, session);
                    foreach (KeyValuePair<string, IEnumerable<string>> header in objectContent.Headers)
                    {
                        response.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
                    }
                }
            }
        }
    }
    

You could also choose instead to return an HttpResponseMessage with your new Content directly from your controller code wherever you need it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top