Asp.Net MVC5 How to ensure that a cookie exists?

2019-03-22 13:30发布

问题:

I'm new to MVC (5). In order to add localization support to my website I added a "Language" field to my ApplicationUser : IdentityUser

What's the best approach to now store this information in the browser and ensure that it gets re-created even if the user manually deletes it?


TL; but I've got time

What I've tried until now:

I started creating a cookie in my method private async Task SignInAsync(ApplicationUser user, bool isPersistent) but I notice that:

  1. This method is not used if the user is already authenticated and automatically logs in using the .Aspnet.Applicationcookie and my language cookie could be meanwhile expired (or been deleted).

  2. A user could manually delete the cookie, just for fun.

I thought about checking its existence in the controller (querying the logged user and getting it from the db) and it works but I'd need to do it in EVERY controller. I'm not sure is the correct way to do this.

Any suggestion about how to approach this problem and guarantee that the application has a valid "language cookie" on every request?

回答1:

It sounds to me like what you want here is a Custom Action Filter. You can override the OnActionExecuting method which means the logic is run before any action is called

public class EnsureLanguagePreferenceAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var langCookie = filterContext.HttpContext.Request.Cookies["LanguagePref"];
        if (langCookie == null)
        {
            // cookie doesn't exist, either pull preferred lang from user profile
            // or just setup a cookie with the default language
            langCookie = new HttpCookie("LanguagePref", "en-gb");
            filterContext.HttpContext.Request.Cookies.Add(langCookie);
        }
        // do something with langCookie
        base.OnActionExecuting(filterContext);
    }
}

Then register your attribute globally so it just becomes the default behaviour on every controller action

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());
    filters.Add(new EnsureLanguagePreferenceAttribute());
}


回答2:

To me, the easiest way would be to create your own Authorize attribute (since your language options are tied to an authenticated user account). Inside of your new authorize attribute, simply perform the check if the cookie exists. If it does, then life is good. Else, query the user's database profile and reissue the cookie with the stored value

public class MyAuthorization : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
         //no point in cookie checking if they are not authorized
        if(!base.AuthorizeCore(httpContext)) return false;
        var cookie = httpContext.Request.Cookies["LanguageCookie"];
        if (cookie == null) {
           CreateNewCookieMethod();
        }
        return true;
    }
}

To use, replace [Authorize] with [MyAuthorization] in your project.

If you don't want to mess with the [Authorize] attribute, you could create your own attribute that does the cookie checking and decorate your controller with that one as well.

One last alternative is to create your own Controller class that does the checking on the OnActionExecuting.

public class MyBaseController : Controller
{
    public string Language {get;set;}

protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
    var cookie = filterContext.HttpContext.Request.Cookies["LanguageCookie"];
    if(cookie == null){
       cookie = CreateNewCookieMethod();
       filterContext.HttpContext.Request.Cookies.Add(cookie);
    }
    Language = cookie.Value;
    base.OnActionExecuting(filterContext);
}

How to use (note that we inherit from MybaseController now)

public class HomeController : MyBaseController{
    public ActionResult Index(){
      //Language comes from the base controller class
      ViewBag.Language = Language;
      Return View();
        }

}

This method is neat because now that Language variable will be available in any controller that inherits from this new class.

Either of these will give you a single, cookie checking point. Additionally, you are only going back to the database only in the instance that the cookie does not exist.