Mvc: Accessing the request's cookie in Controller's constructor

Created on 1 Dec 2016  路  9Comments  路  Source: aspnet/Mvc

Since I'm not happy with the default Localization system in ASP.NET Core, I've written my own. (Main problem is that you have to create separate files for all views and controllers)

It can select the language based on Cookies. To cut down on typing I wanted to be able to get the Cookie in the Controller's constructor, but Request, Response and HttpContext are all null in the constructor of the Controller. I've read that past versions of ASP.NET had methods that were called before executing Actions, but I can't seem to find these in ASP.NET Core.

My code works, but I have to get the cookie and pass it to a new localizer instance in each request.

I've often seen middleware mentioned to solve this kind of problem, but I'm somewhat certain it won't work for my case. Reason being that I want to set the localizer instance of the controller to the one I configure with the cookie. This does not seem to be possible, since the middleware doesn't know that my controllers have a localizer instance (I could try to figure out the controller type and cast it in the middleware, but that's just silly)

If there's a way to do what I want with middleware, I'd love to hear it. If not, is there any way to execute code before requests to a certain controller? Constructor seems to be too early in the request cycle, since Request, Response and HttpContext are all null in the constructor.

Thanks in advance!
Simon

Most helpful comment

[Authorize]
    public class ManageController : Controller
    {
        private Lazy<Localizer> _localizer;

        public ManageController()
        {
            _localizer = new Lazy<Localizer>( () => {
                        var cookies = this.HttpContext.Request.Cookies;
                                return new Localizer(cookies);
                        });
        }

        public Index()
        {
            Viewdata["localizedString"] = _localizer.Value["example string"];
            return View();
        }
    }

This way HttpContext property will be set by the time you access it.

All 9 comments

Showing some code would make it easier to talk about.

Sure, this is the relevant part of my localizer:
```C#
public class Localizer
{
public static string DefaultLanguage { get; set; }
private static Dictionary> _languageStrings;
public static List SupportedLanguages{get; private set;}

    //This is what I want to be able to set for every request. Preferably once per controller, so that I could override it on the controller level.
    public string CurrentLanguage { get; private set; }

    public Localizer()
    {

    }

    public Localizer(string language)
    {
        SetLanguage(language);
    }

    public Localizer(HttpRequest request)
    {
        SetLanguage(request);
    }

    public void SetLanguage(string lang)
    {
        CurrentLanguage = lang;
    }

    public void SetLanguage(HttpRequest lang)
    {
        CurrentLanguage = lang.Cookies["lang"];
    }
}
And here is an example of how I want to use it in a controller:

```C#
    [Authorize]
    public class ManageController : Controller
    {
        private Localizer _localizer;

        public ManageController()
        {
            //This is what I expected to be able to do. But Request, Response and HttpContext are all null in the constructor
            var cookies = HttpContext.Request.Cookies;
            _localizer = new Localizer(cookies);
        }

        public Index()
        {
            Viewdata["localizedString"] = _localizer["example string"];
            return View();
        }
    }

Why don't you inject localizer as dependency in constructor? It could then read HttpContext and initialize itself.

@ShadowDancer I'm not quite sure how that would work. In the Constructor the HttpContext is null, and in my Localizer I don't have an instance of the HttpContext.

@Saticmotion
you may init Localizer on demand, not in constructor (so httpContext will be set).

Another option is to extend DefaultControllerActivator, and pass it to property there.

Yet another possibility is to get IHttpContextAccessor in contructor of Localizer (but first You must register it in startup.cs)

Option 1 is best IMO.

@ShadowDancer Option 1 is what I'm currently doing. I was just wondering if I could do it at the Controller level instead of at the Action level :) I'll look into the other options, maybe they work more like I expected.

[Authorize]
    public class ManageController : Controller
    {
        private Lazy<Localizer> _localizer;

        public ManageController()
        {
            _localizer = new Lazy<Localizer>( () => {
                        var cookies = this.HttpContext.Request.Cookies;
                                return new Localizer(cookies);
                        });
        }

        public Index()
        {
            Viewdata["localizedString"] = _localizer.Value["example string"];
            return View();
        }
    }

This way HttpContext property will be set by the time you access it.

@ShadowDancer That's cool! Not a huge fan of the .Value though, but it's almost exactly what I want! Thanks for the help!

You may cache it in private field, just using lazy prevents user from accessing backing field.

Was this page helpful?
0 / 5 - 0 ratings