ASP. net mvc globalization Solution

Source: Internet
Author: User

Because the project needs to learn ASP. net mvc recently. In practice, a website must support multiple languages and be global. Under MVC, I have implemented a global framework. I would like to share with you the shortcomings.

 

Global support for URLs

Friends who often visit Microsoft websites may be familiar with similar content .. \ ZH-CN \.. ,.. \ en-US \.. this is the global solution used in this article. Of course, there is also a scheme to use querystring to pass parameters. The basic idea is similar.

Because of the inherent URL routing principle of MVC, this solution is easy to accept.

 

Basic Ideas

The basic idea of this solution is:

1. When the user accessesWhen the URL contains valid culture ParametersCan be directly routed to the corresponding controller and set the thread culture during controller initialization;

2. When the user accessesWhen the URL does not contain the culture ParameterIs also routed to the corresponding controller, but the Controller redirects to the URL containing the culture before executing the action. Here, the culture followsCheck the cookie first, then the language browser settings, and finally use the default priority orderImplementation.

Let's take a look at the demo. Pay attention to the URL and click Download example.

 

Resource. resx

Review the resource file before proceeding. Web applications in Asp.netProgramThe resource file defined in (same as winform. resx is actually an xml configuration file. Generally, we only care about the key/Value configuration. We can create one or more. resx. resx generates a CS file. This CS file defines a class (which may be a resource class, depending on the name of your resource file ), you can access these keys by accessing the static properties of this class. the key to resx reading is the cultureinfo. As long as we set the current thread's cultureinfo, the resource will automatically identify the corresponding. resx configuration file. In the name of. resx, follow these rules:

Resource. zh-cn.resx (for simplified Chinese resource files)

Resource. en-us.resx (for US English resource files)

The culture name in the middle is very important.

Generally, only one default resource is required during development. resx. After the development is complete, copy the same resource. resx, change the name to the above format, and then manually or automatically translate all the values into the corresponding language.

 

Solve Routing Problems

In this solution, you must first consider the URL routing configuration. First, ideally, all our URLs are in the form of domain/culture/controller/Action/param1/..., you only need a route starting with culture. But in fact it is not that simple. If the user does not know this rule, he manually enters domain/controller/Action/param1.. Then this URL cannot be correctly routed. This is the most common case when accessing a website for the first time (we usually type www.microsoft.com without adding any culture parameters ). So do we need to write two routes for this scenario? Obviously not, or you do not need to do it manually. The first problem to be solved here arises. My solution is: manually write this route only for domain/controller/Action/param1.CodeConfiguration, which is also in line with the habits; then, traverse all the routes in the route table through a method, and add a parameter CI before each URL rule to indicate the culture, generate a new route and add it to the route table. Although this method does not reduce the number of routing rules, you do not have to manually write them one by one. Otherwise, no one will agree to this scheme. The code and explanation are as follows:

 

 
Protected void application_start () {arearegistration. registerallareas (); registerroutes (routetable. routes); registerglobalizationroutes ();...}

 

Private void registerglobalizationroutes () {// routetable. routes is the route table if (routetable. routes = NULL) return; // create a new route set to store the routecollection rc = new routecollection (); // skip routes here. ignoreroute ("{resource }. axd/{* pathinfo} "); // because ignorerouteinternal is a private class, it can only reflect // skip ignorerouteinternal var routes = routetable. routes. skipwhile (P => (P. getType (). name = "ignorerouteinternal"); int insertpoint = routetable. routes. count ()-routes. count (); // traverse all routes to be processed foreach (var r in routes) {route item = (R as route ); // The following Code creates a new route object, adds the CI parameter before the URL rule, and copies other settings route newitem = new route (// string. format (@ "{CI}/{0}", item. URL), @ "{CI}/" + item. URL, new mvcroutehandler (); newitem. defaults = new routevaluedictionary (item. defaults); newitem. constraints = new routevaluedictionary (item. constraints); // The CI parameter needs to be verified because only valid culture can be accepted by newitem. constraints. add ("Ci", new cultureprefixrule (); newitem. datatokens = new routevaluedictionary (); newitem. datatokens ["namespaces"] = item. datatokens ["namespaces"]; RC. add (newitem);} // The ingress with the CI parameter should be placed on the front, so insert it to the front foreach (var c in RC) {routetable. routes. insert (insertpoint ++, c );}}
 
 
// A private class that implements irouteconstraint. cultureprefixrule: irouteconstraint {ienumerable <string> cultureconllection = cultureinfo. getcultures (culturetypes. specificcultures ). select (P => P. name. tolower (); Public bool match (httpcontextbase httpcontext, route, string parametername, routevaluedictionary values, routedirection) {If (Values [parametername]! = NULL) return cultureconllection. Contains (Values [parametername]. tostring (). tolower (); else return false ;}}

 

Note the following points:

1. routes. ignoreroute ("{resource}. axd/{* pathinfo}"); An ignorerouteinternal route entry is added to the routing table, except that this route entry needs to be skipped. The relationships between the three classes are:

Routebase-> route-> ignorerouteinternal

Unfortunately, ignorerouteinternal is a private class, so it can only be reflected.

2. When you set the constraints attribute for a route, you actually specify an irouteconstraint for it. MVC has a class that implements irouteconstraint to accept regular expressions. We initialize constraints with a string in the maproute method, which is actually instantiated. Here, our requirements are obviously complex: we need to determine whether the CI parameter is supported, so we also have the implementation of irouteconstraint with the cultureprefixrule.

3. routes with CI parameters are more "special", so it is best to put them in front of the route table. I will not go into details about the reason.

  

Jump before the Controller's action execution

All controllers should have the same behavior: they can perform redirection for URLs without CI parameters. Therefore, we naturally want to implement a base class controller. Here I name it basecontroller and the code is as follows:

 
Public class basecontroller: controller {protected string redirecturl; protected override void initialize (system. web. routing. requestcontext) {base. initialize (requestcontext); object culturevalue; // check if (requestcontext. routedata. values. trygetvalue ("Ci", out culturevalue) {// set the current thread's culture try {thread. currentthread. currentuiculture = cultureprovider. getcultureinfo (culturevalue. tostring (); thread. currentthread. currentculture = cultureprovider. getcultureinfo (culturevalue. tostring ());
Response. Cookies. Add (New httpcookie (cultureprovider. culturecookiekey, culturevalue. tostring ();} catch {Throw new exception ("Culture error! ") ;}} Else // if no CI parameter {// check cookie httpcookie clang = requestcontext. httpcontext. Request. Cookies [cultureprovider. culturecookiekey]; If (Clang! = NULL) {culturevalue = clang. value;} else // check Brower setting {string [] Langs = requestcontext. httpcontext. Request. userages; If (Langs! = NULL & Langs. length> 0) {culturevalue = Langs [0]. split (';'). first () ;}} if (culturevalue = NULL) {culturevalue = cultureprovider. culturedefault;} // set redirecturl. If you do not need to redirect to redirecturl, set redirecturl to null redirecturl = string. format (@ "/{0} {1}", culturevalue. tostring (), requestcontext. httpcontext. request. rawurl) ;}} protected override iactioninvoker createactioninvoker () {return New customcontrolleracti Oninvoker (redirecturl) ;}// implementation of an iactioninvoker, MVC uses controlleractioninvoker by default, because in // redirecturl! = When null, You need to execute redirection internal class customcontrolleractioninvoker: controlleractioninvoker {string redirecturl; Public customcontrolleractioninvoker (string URL): Base () {redirecturl = URL ;} protected override actionresult invokeactionmethod (controllercontext, actiondescriptor, idictionary <string, Object> parameters) {object returnvalue; // inside childaction Cannot redirect if (! String. isnullorempty (redirecturl )&&! Controllercontext. ischildaction) returnvalue = new redirectresult (redirecturl); else returnvalue = actiondescriptor. execute (controllercontext, parameters); actionresult result = createactionresult (controllercontext, actiondescriptor, returnvalue); return result ;}} public static class cultureprovider {public const string culturecookiekey = "Lang "; public const string culturedefault = "En-us"; public static cultureinfo getcultureinfo (string CI) {try {return New cultureinfo (CI);} catch {return NULL ;}}}

As long as all controllers inherit the basecontroller.

Here we need to focus on the customcontrolleractioninvoker class. In fact, it took me a lot of time to solve the redirection problem from this class, so I had to debug the MVC source code. Of course, the original idea was to manually judge redirecturl during each action execution to guide redirection. But obviously, no one wants to take out all the actions they have already written and modify them one by one, so there is a small exploration.

 

Links and jumps on pages

Finally, I am very happy and worried about the problem: when I use this framework, almost all links and jump factors on the page can automatically add the CI parameter before the URL! Although I know that helper like HTML. actionlink has the ability to generate URLs from routing tables, but the ability to automatically add CI still makes me a little unexpected. However, it is important to note whether the URL of the link is correct. There are some special cases.

 

Use resources on the page

You can directly reference the resource class in the C # script by referencing resources on the page. Here we provide a helper. This html extension method.

Public static class resourceextensions {public static string resource (this controller, string expression, Params object [] ARGs) {resourceexpressionfields = getresourcefields (expression ,"~ /"); Return getglobalresource (fields, argS);} public static string resource (this htmlhelper, string expression, Params object [] ARGs) {string Path = "~ /"; Resourceexpressionfields fields = getresourcefields (string. format ("resource, {0}", expression), PATH); Return getglobalresource (fields, argS);} static string getglobalresource (resourceexpressionfields, object [] ARGs) {return string. format (string) httpcontext. getglobalresourceobject (fields. classkey, fields. resourcekey, cultureinfo. currentuiculture), argS);} static resourceexpressionfields getresourcefields (string expression, string virtualpath) {var context = new expressionbuildercontext (virtualpath); var builder = new resourceexpressionbuilder (); Return (response) builder. parseexpression (expression, typeof (string), context );}}

Note that this method considers resource as the class name of the resource by default, so you need to modify it if necessary.

Resourceexpressionfields fields = getresourcefields (string. Format ("resource, {0}", expression), PATH); In "resource, {0 }"

 

 

Conclusion

 

MVC beginners can even be called Web development beginners. The above is a solution I personally proposed. I don't know if there are any shortcomings. Please also ask the readers to give their opinions and discuss it.

Download example

Other related resources:

Http://blog.miniasp.com/post/2010/01/ASPNET-MVC-Developer-Note-Part-15-Globalization-and-Localization.aspx

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.