8

I recently made changes to my MVC3 Application in attempt to properly dispose of the DbContext objects [1]. This worked great in development, but once the application was pushed to my production server, I started intermittently getting some funny exceptions which would persist until the AppPool was recycled. The exceptions can be traced back to code in my custom AuthorizeAttribute and look like:

System.InvalidOperationException: The 'Username' property on 'User' could not be set to a 'Int32' value. You must set this property to a non-null value of type 'String'.

System.InvalidOperationException: The 'Code' property on 'Right' could not be set to a 'String' value. You must set this property to a non-null value of type 'Int32'. 

(Database schema looks like this: Users: [Guid, String, ...], Rights: [Guid, Int32, ...])

It is as if some "wires are getting crossed", and the application is mixing up results from the database: trying to materialize the Right result as a User and vise versa.

To manage the disposal of DbContext, I put code in to store this at a per-controller level. When the controller is disposed, I dispose the DbContext as well. I know it's hacky, but the AuthorizeAttribute uses the same context via filterContext.Controller.

Is there something wrong with handling the object lifecycle of DbContext in this manor? Are there any logical explanations as to why I am getting the crisscross exceptions above?

[1] Although I understand that it is not necessary to dispose of DbContext objects, I recently came across a number of sources stating that it was best practice regardless.

Edit (per @MikeSW's comment)

A property of the AuthorizeAttribute representing the DbContext is being set in the OnAuthorization method, when the AuthorizationContext is in scope. This property is then later used in the AuthorizeCore method.

Mac Attack
  • 952
  • 10
  • 21
  • Can you share some of the relevant code of your custom AuthorizeAttribute? Note that an attribute is used as a singleton by asp.net mvc. Also are you using a DI Container? – MikeSW Apr 10 '13 at 12:25
  • @MikeSW I added information about the usage above. I am not using a DI container. With the information I provided above, it would seem as if these errors are occurring due to concurrency: in the time between `OnAuthorization` and `AuthorizeCore`, another request triggers `OnAuthorization` and clobbers the `DbContext` property. Does this follow? – Mac Attack Apr 10 '13 at 15:07
  • Yes, that's your problem. The [Authorize] is basically a singleton and you're changing the dbcontext property with every request. I suggest using a DI Container, register DbContext with HttpPerInstance lifetime then use DependencyResolver.Current.GetService() in OnAuthorization method. The container should handle the dispose of DbContext as well – MikeSW Apr 10 '13 at 15:24
  • @MikeSW We are considering adding a DI Container, but are looking for alternative solutions first. Would the use of a synclock on `this` inside of `OnAuthorization` produce any problems, at least to relieve the problem in the short-term? It's just that adding a DI Container is not a small task at the current scale of our application. – Mac Attack Apr 12 '13 at 16:25
  • No, I would use a DI Container. It's the simplest and cleanest solution. Asp.Net mvc knows how to work with one, really you can use it for this problem only. – MikeSW Apr 12 '13 at 16:39
  • P.S. we're using DI, with PerHttpRequest Life time, and still get the following ex.. System.InvalidOperationException: The 'ParentId' property on 'Account' could not be set to a 'String' value. You must set this property to a non-null value of type 'Int32'. at System.Data.Common.Internal.Materialization.Shaper.ErrorHandlingValueReader`1.GetValue(DbDataReader reader, Int32 ordinal) at System.Data.Common.Internal.Materialization.Shaper.GetPropertyValueWithErrorHandling[TProperty](Int32 ordinal, String propertyName, String typeName)... – Marty Feb 20 '14 at 12:29
  • @Marty, I think it was a mistake to put a bounty on this question as opposed to asking your own. The situations were somewhat similar, but not enough. And your comment was completely missed. I raised a flag a week ago asking that your bounty be removed and you be allowed to re-post a question of your own. The moderators have no idea what to do with that request. Maybe you should flag the question and make similar request (sighting my flag) – Dave Alperovich Mar 01 '14 at 20:26
  • Need any more help with this? If so I'll update my answer. – SilverlightFox Jul 29 '14 at 09:47

2 Answers2

1

Do you actually need to dispose the context?

According to this post by Jon Gallant who has been in touch with the Microsoft ADO.NET Entity Framework team:

Do I always have to call Dispose() on my DbContext objects? Nope

Before I talked with the devs on the EF team my answer was always a resounding “of course!”. But it’s not true with DbContext. You don’t need to be religious about calling Dispose on your DbContext objects. Even though it does implement IDisposable, it only implements it so you can call Dispose as a safeguard in some special cases. By default DbContext automatically manages the connection for you.

Community
  • 1
  • 1
SilverlightFox
  • 32,436
  • 11
  • 76
  • 145
0

First i recommend that you get "really" familiar with ASP.NET Application Life Cycle Overview for IIS 7.0 as it's fundamental to good MVC application design.

Now to try and "mimic" your code base

Let's say you have a similar custom MembershipProvider as described here https://stackoverflow.com/a/10067020/1241400

then you would only need a custom Authorize attribute

public sealed class AuthorizeByRoles : AuthorizeAttribute
    {
        public AuthorizeByRoles(params UserRoles[] userRoles)
        {
            this.Roles = AuthorizationHelper.GetRolesForEnums(userRoles);
        }
    }

public static class AuthorizationHelper
{       
    public static string GetRolesForEnums(params UserRoles[] userRoles)
    {
        List<string> roles = new List<string>();
        foreach (UserRoles userRole in userRoles)
        {
            roles.Add(GetEnumName(userRole));
        }
        return string.Join(",", roles);
    }

    private static string GetEnumName(UserRoles userRole)
    {
        return Enum.GetName(userRole.GetType(), userRole);
    }        
}

which you can use on any controller or specific action

 [AuthorizeByRoles(UserRoles.Admin, UserRoles.Developer)]
 public class MySecureController : Controller
 {
      //your code here
 }

If you want you can also subscribe to the PostAuthorizeRequest event and discard the results based on some criteria.

 protected void Application_PostAuthorizeRequest(Object sender, EventArgs e)
        {

            //do what you need here
        }

As for the DbContext, i have never run into your situation and yes per request is the right approach so you can dispose it in the controller or in your repository.

Of course it's recommended that you use filters and then add [AllowAnonymous] attribute to your actions.

Community
  • 1
  • 1
Matija Grcic
  • 12,963
  • 6
  • 62
  • 90