1

I have an application that is written using c# on the top of the ASP.NET MVC 5 framework.

I created a custom action filter (i.e. ValidateCookieValueAction) by inheriting the ActionFilterAttribute class. I am using ValidateCookieValueAction attribute for multiple of my action methods.

The filter's purpose is to make sure a user has a cookie value before allowing them in to the action. Despite the security concern, the filter works great. However, the cookie value itself need to be validated before the user is let in.

To validate the cookie value, I need an instance of the DbContext so I can query the database and validate the cookie value.

I know I can create an new instance of DbContext directly inside the ActionFilter. However, I want to avid that. I want to be able to pass the DbContext instance that I already created from the controller to allow me to reuse the connection that I already have established in the controller.

Here are how my controllers setup

public class BaseController 
{
    protected IDbContext Context { get; private set; }

    protected override void Initialize(RequestContext requestContext)
    {
        base.Initialize(requestContext);

        Context = new DbContext();
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            Context.Dispose();
        }

        base.Dispose(disposing);
    }
}

public class TestController : BaseController 
{
    [ValidateCookieValueAction]
    public ActionResult Index()
    {
        // the uses is in!
        return View();
    }
}

Here is my action filter class

public class ValidateCookieValueAction : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        var cookie = new CookieJar(filterContext.HttpContext);
        if (!cookie.Has(ContactListCookieName))
        {
            var Url = new UrlHelper(filterContext.RequestContext);
            var url = Url.Action("Guest", "Test");

            filterContext.Result = new RedirectResult(url);
        }
    }
}

How can I pass instance of my Context to the ValidateCookieValueAction?

Junior
  • 11,602
  • 27
  • 106
  • 212
  • 1
    If there's no other concerns except for connection reuse, then go ahead and just spin up a new DbContext in your filter. EF keeps a pool of open connections to the database and will grab one of these to service the new context. – Sam Axe Aug 31 '17 at 21:29
  • @SamAxe but the framework will then issues 2 different instances of the DbContaxt I am also trying to avoid that. – Junior Aug 31 '17 at 21:30
  • Why? What is the concern? – Sam Axe Aug 31 '17 at 21:31
  • You could isolate the database access (and its dbContext) in a seperate 'datalayer' class that gets injected into both the controller and the filter. – Johan Donne Aug 31 '17 at 21:33
  • Isn't there a penalty for creating/using the `DbContext` multiple time? A connection open/close fee. If I create a new instance of the `DbContext` class and wrapping it with `using(...)` method inside my filter, a connection will be oped and the close. + the other instance that I am using in the controller. – Junior Aug 31 '17 at 21:34
  • 3
    DbContexts are *cheap* and should have short lifespans. You **should** be creating a new instance inside your action filter. – Dai Aug 31 '17 at 21:35
  • @Dai why **should** I rather than just passing the instance that is being managed/disposed by the controller? – Junior Aug 31 '17 at 21:36
  • 1
    @Mike: I would think that wrapping it inside a using block is preferable to keeping the instance and passing it around (how will you dispose it if e.g. an exception is thrown in the filter, if you do that in the filter itself, how will the controller detect that the dbcontext is disposed...). furthermore: I strongly agree with Dai. – Johan Donne Aug 31 '17 at 21:37
  • @JohanDonne wouldn't the dispose method get called by the controller even if an exception took place? – Junior Aug 31 '17 at 21:41
  • 1
    if you want to use a dbcontext per request you will need DI container with a per request lifetime manager. similar to this: https://stackoverflow.com/questions/27065538/unity-perrequestlifetimemanager-re-using-object-in-different-requests also is a good idea to isolate your data access logic to a "datalayer". – Aldo Aug 31 '17 at 21:42
  • @Mike: only if your controller instance 'survives' the exception and is still in a state where it can call the Dispose method... – Johan Donne Aug 31 '17 at 21:54
  • There is no native way in MVC5 you need a DI container. There is a native way in MVC6. – jamesSampica Sep 01 '17 at 04:13

2 Answers2

1

This is not directly related to original question, but it seems like you are create an authentication mechanism by yourself. If not, please let me know; I'll just delete this answer.

ASP.NET already has ASP.NET Identity which is more secure and more reliable than create it by ourselves. However, if you want use your existing custom tables, you can use OWIN Middleware which basically is a subset of ASP.NET Identity.

The primary advantage of using OWIN Middleware is that you could use Authorize attribute come with ASP.NET MVC. Implementation is a lot easier than you think.

Sample code -

OWIN Authentication Middle-ware

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = "ApplicationCookie",
            LoginPath = new PathString("/Account/Login")
        });
    }
}

Store access as role claim in Principle object

public void SignIn(User user, IList<string> roleNames)
{
    IList<Claim> claims = new List<Claim>
    {
        new Claim(ClaimTypes.Sid, user.Id.ToString()),
        new Claim(ClaimTypes.Name, user.UserName),
        new Claim(ClaimTypes.GivenName, user.FirstName),
        new Claim(ClaimTypes.Surname, user.LastName),
    };

    foreach (string roleName in roleNames)
    {
        claims.Add(new Claim(ClaimTypes.Role, roleName));
    }

    ClaimsIdentity identity = new ClaimsIdentity(claims, AuthenticationType);

    IOwinContext context = _context.Request.GetOwinContext();
    IAuthenticationManager authenticationManager = context.Authentication;

    authenticationManager.SignIn(identity);
}

Usage

[Authorize(Roles = "CanViewHome")]
public class IndexController : Controller
{
    [Authorize(Roles = "CanEditHome")]
    public ActionResult Edit()
    {
        return View();
    }
}
Win
  • 61,100
  • 13
  • 102
  • 181
  • Is there a way to add a new claim to the user after login? What I am doing can be changed to use claims if I can add a.l new claim to the users existing claims (way after authentication) – Junior Aug 31 '17 at 22:01
  • Yes, you just call [SignOut](https://github.com/WinLwinOoNet/AspNetMvcActiveDirectoryOwin/blob/master/src/Presentation/AspNetMvcActiveDirectoryOwin.Web.Common/Security/OwinAuthenticationService.cs#L43) method first to signout, and then call [SignIn](https://github.com/WinLwinOoNet/AspNetMvcActiveDirectoryOwin/blob/master/src/Presentation/AspNetMvcActiveDirectoryOwin.Web.Common/Security/OwinAuthenticationService.cs#L20) method again to create new authentication cookie for same user within the same request. – Win Sep 01 '17 at 18:52
  • Although, this does not answer my question on "Is there a way to pass an instance of DbContext from the controller to an ActionFilter?" But did lead to a solution may be a better than what I was looking to do. Thank you for your answer – Junior Sep 01 '17 at 22:15
  • @MikeA You can add claims without needing to call this sign-in/sign-out again, but I suppose doing so is fairly straightforward. If you want an alternative, see https://stackoverflow.com/questions/24587414/how-to-update-a-claim-in-asp-net-identity – Tieson T. Sep 03 '17 at 08:20
0

there is already a context stored in the current context by default.

in Startup.Configuration(IAppBuilder app)

app.CreatePerOwinContext(ApplicationDbContext.Create); // this already stores a DbContext

you can fetch it with

var dbContext = HttpContext.GetOwinContext().Get<ApplicationDbContext>();

Alternatively you can store the Context in the [TempData] or [Session].
If you refetch it in the same Request, then just throw the DbContext in the Context.Items[], these only last for the current Request.

in OnActionExecuting:

filterContext.Controller.TempData

in the Controller:

this.TempData