MVC 5 How to define Owin LoginPath with localized routes

s0nica picture s0nica · Jan 16, 2014 · Viewed 21.8k times · Source

I have a MVC 5 website with localized routes defined as

routes.MapRoute(
            name: "Default",
            url: "{culture}/{controller}/{action}/{id}",
            defaults: new { culture = CultureHelper.GetDefaultCulture(), controller = "Home", action = "Index", id = UrlParameter.Optional }
        );

Where the default culture results in "en-US".


The problem arises when on startup I have to define the login url using the LoginPath property, that is set once and it will always use the provided value, e.g. the default culture if "/en-Us/Account/Login" is the specified value. I then tried to use the UrlHelper class in the hope of experience some magic but the result is obviously the same:

var httpContext = HttpContext.Current;
        if (httpContext == null) {
          var request = new HttpRequest("/", "http://example.com", "");
          var response = new HttpResponse(new StringWriter());
          httpContext = new HttpContext(request, response);
        }

        var httpContextBase = new HttpContextWrapper(httpContext);
        var routeData = new RouteData();
        var requestContext = new RequestContext(httpContextBase, routeData);
        UrlHelper helper = new UrlHelper(requestContext);

        var loginPath = helper.Action("Login", "Account");

        // Enable the application to use a cookie to store information for the signed in user
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,                
            LoginPath = new PathString(loginPath)
        });

My question is: is there a way to hack this mechanism to dynamically retrieve the current culture or am I forced to set the current culture into a cookie and, when I'm redirected to the login page, use the cookie value to set the current culture before rendering the page?

Thanks

Answer

martinoss picture martinoss · Feb 4, 2014

I exactly had the same problem and figured out a way to overcome this limitation.

In the CookieAuthenticationOptions options, there is a "Provider" property which is initialized with the CookieAuthenticationProvider. This implements a method called ApplyRedirect and a delegate OnApplyRedirect. My first idea was to overwrite this ApplyRedirect and implement the required logic to handle localized routes. But unfortunately it can't be overriden. Passing my logic to OnApplyRedirect causes to overwrite the default behavior. You theoretically can grab the source of this behavior, copy it into your project and modify it to your needs, but this is obviously not a good practice. First, I decided to make a wrapper around the CookieAuthenticationProvider with two extension points using delegates and preserving the default behavior except of the url that is used - or easier, work around the wrapper (thx to lafi).

Then in the auth configuration I added my custom logic to the provider:

public void ConfigureAuth(IAppBuilder app)
{
    UrlHelper url = new UrlHelper(HttpContext.Current.Request.RequestContext);

    CookieAuthenticationProvider provider = new CookieAuthenticationProvider();

    var originalHandler = provider.OnApplyRedirect;

    //Our logic to dynamically modify the path (maybe needs some fine tuning)
    provider.OnApplyRedirect = context =>
    {
        var mvcContext = new HttpContextWrapper(HttpContext.Current);
        var routeData = RouteTable.Routes.GetRouteData(mvcContext);

        //Get the current language  
        RouteValueDictionary routeValues = new RouteValueDictionary();
        routeValues.Add("lang", routeData.Values["lang"]);

        //Reuse the RetrunUrl
        Uri uri = new Uri(context.RedirectUri);
        string returnUrl = HttpUtility.ParseQueryString(uri.Query)[context.Options.ReturnUrlParameter];
        routeValues.Add(context.Options.ReturnUrlParameter, returnUrl);

        //Overwrite the redirection uri
        context.RedirectUri = url.Action("login", "account", routeValues);
        originalHandler.Invoke(context);
    };

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString(url.Action("login", "account")),
        //Set the Provider
        Provider = provider
    });
}

See also this code:

Hope it fits your needs.

UPDATE: For less confusion, I updated my answer to use @Lafis enhancement, not using a wrapper class to apply the extended behavior. Please also give credit to @Lafis when upvoting.