Reputation: 17415
I want to override value of an item in the RouteData
that named lang
based on a user claim
in the User.Identity
.
For example if the lang
value in the route data is en
(example.com/en/login), I want read the lang
claim
of the user and override the lang
in the roue data by it. So other links in the login page should follow the new lang
value (not en
).
Where I set the language:
In my project I set the language of of the UiThread
in Application_AuthenticateRequest
. So I changed the lang
route value there but it seems if has not the expected result (Why?):
requestContext.RouteData.Values["lang"] = langName;
Where should I write the code to override Route values of a request? and What is your suggestion?
Upvotes: 4
Views: 2632
Reputation: 56869
Application_AuthenticateRequest
is too late in the MVC life cycle to have any effect on route values.
MVC uses value providers to resolve the values in the ModelBinder very early in the request before action filters run, so action filters are not a solution to this.
You basically have a couple of options.
Route
or RouteBase
subclass. Routes are responsible for turning the request into route values, so you can put conditional logic here to build your request.IAuthorizationFilter
. Authorization filters run before the ModelBinder and can therefore affect the values that are passed into models and action methods.However, as a dissatisfied user of other web sites that take control over the language selection process instead of just letting the user decide based on the URL/selection, I have to protest. You think you are helping the user by doing this, but in fact you are making your web site more frustrating to use.
- Most users will enter your site through a search engine or one of your site's marketing channels. In each of these cases, they will automatically enter your site in their own language because they are searching in their own language. At least that is what will happen if you have the culture in the URL and don't use any cookies or session state to block search engines from indexing your localized pages.
- Users who are returning through their browser history or bookmarks will also always come back in their own language.
- A (usually) very small fraction of users may enter your site by typing
www.somesite.com
, in which case they won't come in under their own language. They will have no problem recognizing that they are seeing the wrong language and will immediately look for some sort of language dropdown or flag icon to switch to their language. Unless you are using some sort of remember me feature, this step will always happen before they log in.So, there is little point in "overriding" the culture when the user logs in. They will have already selected the right culture in 99.999% of all cases that reach this stage.
If you still insist on "helping" your users by doing this, don't override the culture from the URL. Put a RedirectToAction
on the post portion of your login action to redirect them to a URL with the new culture exactly once and only when logging in. This will allow your (now frustrated) users to be able to override your override and change back to the language they intend to view the site in.
If you give them no option to view the site in the language they want by choosing it in the URL or in some sort of language selector, don't be surprised when they don't return.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
// Change to the culture of the signed in user by replacing the
// first segment of the URL.
returnUrl = ChangeCultureInUrl(returnUrl);
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
Again, my recommendation is to just let the user select their culture, but if you still think for some reason this is necessary the above solution is far better than hijacking the culture and taking control from the user.
Upvotes: 3
Reputation: 468
Create a class that implements IActionFilter
, override "lang" value in it, and configure MVC to use that IActionFilter
using System.Web.Mvc;
namespace WebApplication1.Filters
{
public class LangOverrideFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.RouteData.Values["lang"] = "en";
//Access userclaims through filterContext.HttpContext.User.
}
}
}
In App_Start/FilterConfig.cs:
using System.Web;
using System.Web.Mvc;
using WebApplication1.Filters;
namespace WebApplication1
{
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new LangOverrideFilter());
filters.Add(new HandleErrorAttribute());
}
}
}
My App_Start/RouteConfig.cs for testing:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace WebApplication1
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{lang}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}
Upvotes: 0