I am just beginning to localize an ASP.NET MVC application. Most of the strings will be defined in resource files and retrieved via Matt's Localization Helpers. Other strings must be stored in a database.
My Question:
Should I set CurrentUICulture early in the request pipeline and use that throughout the application, or directly use Request.UserLanguages[0] whenever needed?
Right now I'm thinking that I should set CurrentUICulture in Application_BeginRequest. The implementation would look something like this:
protected void Application_BeginRequest(object sender, EventArgs e)
{
var cultureName = HttpContext.Current.Request.UserLanguages[0];
Thread.CurrentThread.CurrentUICulture = new CultureInfo(cultureName);
}
Is this the best place to set CurrentUICulture and is Request.UserLanguages[0] the best place to get that info?
Update:
Ariel's post shows this can be defined without code, using web.config
<system.web>
<!--If enableClientBasedCulture is true, ASP.NET can set the UI culture and culture for a Web page automatically, based on the values that are sent by a browser.-->
<globalization enableClientBasedCulture="true" culture="auto:en-US" uiCulture="auto:en"/>
Here is a sample using an HttpModule:
http://weblogs.manas.com.ar/smedina/2008/12/17/internationalization-in-aspnet-mvc/
Other options, create a base Controller class and implement the localization logic there.
Or use an action filter attribute, but you'll have to remember to add it on every controller or combine this approach with the base Controller class.
Request.UserLanguages[0] can only be a hint what language the users wishes to see. Most users dont know where to change the browser language.
Another point: Dont be sure that Request.UserLanguages[0] is a valid language. It can even be null. (Not sure what bots have there)
You usually have a Language chooser on the page. Once a user has selected a language there, it is stored in a cookie, session or url. I like to use url because I think it looks pretty.
If a user sees your page without having set a language on your page, you should check if Request.UserLanguages[0] is a language you support and set Thread.CurrentThread.CurrentUICulture.
I use a filter to set Thread.CurrentThread.CurrentUICulture. Thats ok as long as no other filter is using Thread.CurrentThread.CurrentUICulture. Otherwise you would need to set the right execution order for filters.
I also use Matts helper and it worked very well so far.
Related
I have to make vanity urls for an already existing site. The requirements are:
The existing controllers are a kind of hierarchical structure and can't go away or be overridden in any way. Examples: domain.com/Dog/vanity and domain.com/Cat/vanity.
Keep existing actions. Any existing actions must take priority so that the page for that action is not stomped on by the vanity url.
take future pages and actions into account so that the above requirement is met (a new vanity url is ignored and the action/view executed instead)
To date, I have tried various solutions with routing that allow me to have domain.com/vanity which is nice but the marketing guys don't like because of the different departments within the company. I've tried routing which will override the existing actions and treats them all as vanities (also not feasible). I've a solution in place that programmatically deals with the url that was requested and redirects to a page that actually exists but this is not scalable in any way.
So far, I know that the vanity portion can be treated as a parameter to the action so that I can fire off the default page in the route (Index) but this is, so far, doesn't preserve the structure.
TL;DR: I need to have a solution that allows for domain/controller/vanity structure while also allowing domain/controller/action
Using AttributeRouting for MVC4 you can accomplish a working solution until you ramp up the replacement project. It'll allow you to keep existing routes while adding new, custom ones with little impact.
[Route("my-vanity/is-cool/post/{id}")]
public ActionResult Index(int id)
{
}
The important part is to remember priority, so you write routes that don't overwrite/are overwritten by existing routes. You can steer this to some degree with properties on the attribute. Below is just an example that will put the added route last in priority for the entire site.
[Route("my-vanity/is-cool", SitePrecedence = -1)]
public ActionResult Index()
{
}
ASP.NET WebApi2 have built in support for attribute routing. With it you can define URL's in whatever way you like (instead of following the /controller/action pattern)
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
I'm going to implement an ASP.NET MVC3 powered site which will be multilingual with distinct Urls for different languages (ie. http://acme.com/en/faq, http://acme.com/de/faq etc).
Is the way outlined in this article still the way to go with ASP.Net MVC3?
I haven't use a route handler like the author of the blog suggest but it seems like a good idea.
I typically just add the language as a parameter to the route, though.
routes.MapRoute("someroute", "{language}/some/path/{p1}/{p2}",
new { controller = "SomeController", action = "SomeAction"});
You can default the language parameter right there on the route definition but I usually do it on the base controller since I try to default to the language that the user has defined on their browser preferences (it comes in the HTTP request.)
One caveat of the approach described in the blog post is that is changes the main "CurrentCulture". You don't want to change the main "CurrentCulture" on every request, you only need to change the "CurrentUICulture". Changing the main "CurrentCulture" would affect the way your server behaves. For example, when talking to the database it will be using the user's culture and that's probably not what you want.
People tend to change the main CurrentCulture to gain formatting on dates and numbers (which is nice) but don't realize there are some side effects to doing that. Instead of changing the main CultureThread you'll need to pass the users' culture to your number and formatting functions (e.g. someDate.ToString(format, culture)
Yes, nothing have changed from the routing perspective.
There were questions about multilingual apps in MVC here on SO but they were mostly answered by giving details about implementing Resource files and then referencing those Resource strings in Views or Controller. This works fine for me in conjunction with the detection of user's locale.
What I want to do now is support localized routes. For instance, we have some core pages for each website like the Contact Us page.
What I want to achieve is this:
1) routes like this
/en/Contact-us (English route)
/sl/Kontakt (Slovenian route)
2) these two routes both have to go to the same controller and action and these will not be localized (they will be in English because they are hidden away from the user since they are part of the site's core implementation):
My thought is to make the Controller "Feedback" and Action "FeedbackForm"
3) FeedbackForm would be a View or View User control (and it would use references to strings in RESX files, as I said before, I already have set this up and it works)
4) I have a SetCulture attribute attached to BaseController which is the parent of all of my controllers and this attribute actually inherits FilterAttribute and implements IActionFilter - but what does it do? Well, it detects browser culture and sets that culture in a Session and in a cookie (forever) - this functionality is working fine too. It already effects the Views and View User Controls but at this time it does not effect routes.
5) at the top of the page I will give the user a chance to choose his language (sl|en). This decision must override 4). If a user arrives at our website and is detected as Slovenian and they decide to switch to English, this decision must become permanent. From this time on SetCulture attribute must somehow loose its effect.
6) After the switch, routes should immediately update - if the user was located at /sl/Kontakt
he should immediately be redirected to /en/Contact-us.
These are the constraints of the design I would like. Simply put, I do not want English routes while displaying localized content or vice-versa.
Suggestions are welcome.
EDIT:
There's some information and guidance here - Multi-lingual websites with ASP.NET MVC, but I would still like to hear more thoughts or practices on this problem.
Translating routes (ASP.NET MVC and Webforms)
How about this?
Create custom translate route class.
Localization with ASP.NET MVC using Routing
Preview:
For my site the URL schema should look
like this in general:
/{culture}/{site}
Imagine there is a page called FAQ,
which is available in different
languages. Here are some sample URLs
for these pages:
/en-US/FAQ /de-DE/FAQ /de-CH/FAQ
Why not create the action names desired and simply RedirectToAction for the single, real implementation?
public ActionResult Kontakt() {
return RedirectToAction("Contact");
}
public ActionResult Contact() {
return View();
}
I just used a simple solution with "Globalization Resources", like this:
routes.MapRoute(
"nameroute", // Route name
App_GlobalResources.Geral.Route_nameroute+"/{Obj}", // URL with parameters
new { controller = "Index", action = "Details", Obj = UrlParameter.Optional } // Parameter defaults
);
But, you could customize as needed.
I have searched for examples and found several but they are whole large projects. I am looking for some sample on how to get started building an MVC multi-tenant application. I think, the first part would be to decipher the url.
In ASP.Net this is how I did it. I got this from looking at DNN code. How would I do the same in MVC?
Global.asax
private void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
string domainName = string.Empty;
// domaName now contains 'example' if application.Request was www.example.com
domainName = GetDomainName(application.Request);
// Using domain, get the info for example from the database
object myPortal = // get from database
// Save in context for use on other pages
context.Items.Add("PortalSettings", myPortal);
}
Then in my basepage I get the value from the context.
I think an even more robust means would be to define a custom route. In that custom route is where you extract the domain and put it into the route values.
You then can have the base controller (as Josh described) which defines a Domain property or the like and stores that value there for convenience (or just extracts it on demand; either way).
By pulling it into the route values up front like that, you can make use of that information anywhere in the app along the request path, not just in the controller, so you get more re-use out of it that way. You can, for example, make use of it in a custom Authorize-like filter to handle the user's rights to that domain, and so on.
Get the domain name. You are on the right track with the DNN code. Just poke around the Request static variable in the debugger; there's all kinds of cool stuff there.
You'll probably need a user store. I use a custom database, but you could use the Microsoft membership provider and profile provider. Make the domain a property of the user, or a property of an organization, and the organization a property of the user.
Store the user's domain in the cookie, encrypted. Read the cookie at the beginning of the request, and make the user has access to that org/domain.
Make a BaseController that extends Controller, then have all your controllers inherit from it. In the BaseController, override OnActionExecuting. This is a much easier place to do your initial request rigging than the Global.asax.cs's Begin_request, because you can define protected members which will be available form every controller.
Currently I use [Authorize(Roles = ".....")] to secure my controller actions on my ASP.NET MVC 1 app, and this works fine. However, certain search views need to have buttons that route to these actions that need to be enabled/disabled based on the record selected on the search list, and also the security privs of the user logged in.
Therefore I think I need to have a class accessing a DB table which cross-references these target controller/actions with application roles to determine the state of these buttons. This will, obviously, make things messy as privs will need to be maintained in 2 places - in that class/DB table and also on the controller actions (plus, if I want to change the access to the action I will have to change the code and compile rather than just change a DB table entry).
Ideally I would like to extend the [Authorize] functionality so that instead of having to specify the roles in the [Authorize] code, it will query the security class based on the user, controller and action and that will then return a boolean allowing or denying access. Are there any good articles on this - I can't imagine it's an unusual thing to want to do, but I seem to be struggling to find anything on how to do it (could be Monday-morning brain). I've started some code doing this, looking at article http://schotime.net/blog/index.php/2009/02/17/custom-authorization-with-aspnet-mvc/ , and it seems to be starting off ok but I can't find the "correct" way to get the calling controller and action values from the httpContext - I could possibly fudge a bit of code to extract them from the request url, but that doesn't seem right to me and I'd rather do it properly.
Cheers
MH
I found this on another forum and so will post it here in case anyone finds it useful. Note that how you do this changes depending on whether you are using MVC 1 or 2
the class you create needs to implement
public void OnAuthorization(AuthorizationContext filterContext)
and then you can use
string controllerName = filterContext.RouteData.Values["controller"].ToString();
and the same, substituting "action" for "controller" (make sure you check for nulls in these values first). In MVC 2 this can be changed to filterContext.ActionDescriptor.ActionName and .ActionDescriptor.ControllerDescriptor.ControllerName and you won't have to check for nulls