多语言网站框架
本文简介
本博文介绍了 Visual Studio 工具生成的 ASP.NET MVC3 站点的基本框架;怎样实现网站的语言的国际化与本地化功能,从零开始实现用户身份认证机制,从零开始实现用户注册机制,实现自定义的MVC验证特性……
本系列包含四篇,在codeproject中都评级为5颗星。我会全部翻译出来,感兴趣的园友可以关注我的博客。
1) MVC网站教程(一):多语言网站框架
2) MVC Basic Site: Step 2 - Exceptions Management
本博文示例下载:
介绍
“MVC网站教程”系列的目的是教你如何使用 ASP.NET MVC 创建一个基本的、可扩展的网站。
ASP.NET MVC 是微软提供的一个web开发框架,它整合MVC(Model View Controller)架构模式的高效与简洁,并从敏捷开发中吸取了最新的概念和技术……融入ASP.NET平台。
MVC应用程序包含下面几个组件:
1) Models:模型对象,做为应用程序的数据域,通常,模型对象在数据库中检索和存储它们的状态。
2) Views:视图,(模型对象提供数据,)显示应用程序的用户界面。
3) Controllers:控制器,处理用户交互信息,与model协同工作,最后选择一个view显示处理结果页。
本文是“MVC网站教程”系列的首篇,主要讲解如何去创建一个多语言MVC网站,同时也讲解了用户认证和注册机制的实现。使用了微软的Entity Framework框架和LINQ查询技术。
从用户界面的角度来看,本文提供的MVC网站框架包括:主页面(主要布局项:菜单,页眉,页脚),“Home”和“About”导航索引,实现“LogOn(登陆)”和“Register(注册)”功能页,还提供通用的用户信息和地址信息编辑的部分功能页(_UserAndAddress.cshtml)。
整个用户界面语言完全实现国际化与本地化功能,提供三种语言支持:English、Romanian (Română) 和 German (Deutch)。并且支持扩展其他语言。
当用户通过上图右上角的下拉列表改变用户语言时,用户界面的文本、数字、验证信息和错误提示消息都将从这一刻更改为特定的语言。
开发环境
- .NET 4.0 Framework
- Visual Studio 2010 (or Express edition)
- ASP.NET MVC 3.0
- SQL Server 2008 R2 (or Express Edition version 10.50.2500.0, or higher version)
网站框架
本文示例的解决方案包含两个项目:
- MvcBasicSite:是ASP.NET MVC 3站点项目。包含部分Models、所有Views和所有Controllers,还包含其他用户资源文件和相关代码。下面会进行详细介绍。
- MvcBasic.Logic:是一个类库项目。包含Entity Framework的实体映射图(.edmx),以及自动生成的相关实体部分类,和手动创建的逻辑实体部分类。这个项目将在“MVC网站教程”系列的第二篇中详细介绍。
上图,是ASP.NET MVC3 项目自动生成的文件架构:
- App_code:可选特殊文件夹,包含应用程序级别代码。如在本示例中,该文件夹包含一个Content.cshtml文件,它定义了一个Razor语法的@helper帮助器方法,编译器会将其编译为“Content”(和文件名一样)的类,你可以在应用程序的任何视图模版中使用该帮助器方法。(该文件夹下的文件在应用程序运行时动态编译为程序集并且自动链接到应用程序,而不需要手动添加程序集引用。但是,需要注意的是该文件夹的更改会导致整个应用程序重新编译。对于大型项目,因为很耗时,这可能不受欢迎。为此,鼓励大家将代码进行模块化处理到不同的类库中)
- App_Data:可选特殊文件夹,包含数据的物理存储文件。本示例中为空文件夹。
- App_GlobalResources:可选特殊文件夹,包含用于实现应用程序多语言特性的资源文件。注意,文件的命名非常重要,并且每一个资源文件包含站点中每个要显示文本的特定语言版本。在本示例中包含3个特定语言的资源文件:
1) Resource.resx:是默认资源文件,关联 English 语言。
2) Resource.de.resx:是关联 German 语言的资源文件。(语言代码:de-DE)
3) Resource.ro.resx:是关联 Romanian 语言的资源文件。(语言代码:ro-RO)
- Content:是静态文件推荐存放的文件夹,比如CSS,图片,主题等等。在本示例中,该文件夹存放有:
1) Images:子文件夹,包含图片文件(gif、png…)。
2) Theme:子文件夹,包含基本的jQueryUI主题的css文件。
3) dd.css:改变语言的下拉列表控件的样式文件。
4) site.css:站点主要的css文件。
- Controllers:这是控制器Controller推荐存放的文件夹,在MVC架构中控制器需要以”Controller”的文件名结尾。在本示例中该文件夹包含三个控制器:
1) BaseController:基本控制器,是本示例中其他Controller的基类,提供公共数据和公共函数。
2) AccountController:管理用户注册,登陆和退出。
3) HomeController:管理导航页“Home”和“About”的操作。
- Model:MVC网站应用程序的数据模型集合。在本示例中真实的模型对象被放在MvcBasic.Logic类库项目中(即与数据库表对应的模型对象Domain Model)。在Model文件夹下只包含2个类文件。
1) LogOnModel:此模型用于LogOn页面的用户身份认证功能。
2) SiteSession:此模型用于保存站点的特定用户会话信息,eg:UserId,Username,UserRole和CurentUICulture。
- Scripts:这是应用程序脚本文件推荐存放的文件夹。创建MVC项目默认会包含ASP.NET AJAX脚本库和jQuery脚本库。
- Views:这是应用程序视图推荐存放的文件夹。该文件夹针对每个控制器都对应一个子文件夹,子文件夹命名为对应控制器的前缀。默认情况下,还有个命名为Shared的子文件夹,它不对应任何控制器,但包含被多个控制器共享的视图。在本示例中该文件夹包含以下子文件夹:
1) Account:控制器AccountController对应的视图。
a) Logon.cshtml:用于用户的身份认证。
b) Register.cshtml:用于用户的注册。
c) RegisterFinalized.cshtml:用于用户成功注册。
2) Home:控制器HomeController对应的视图。包含About.cshtml和Index.cshtml页面。
3) Shared:包含被多个控制器共享的视图。
a) _Address.cshtml:部分视图,用于地址数据的编辑。
b) _Header.cshtml:部分视图,用于网站的页眉。
c) _Layout.cshtml:母版页,是站点的主要布局风格。
d) _UserAndAddress.cshtml:部分视图,用于用户数据和其对应的地址数据的编辑。
e) Error.cshtml:错误页面,用于显示站点的异常信息。
4) _ViewStart.cshtml:指定整个站点使用的布局模版页URL。
5) Web.config:网站配置文件,仅仅作用于视图页面。通常情况下无需手动更改。
- Global.asax:包含MvcApplication类(继承自HttpApplication),用于设置全局URL路由规则,也管理一些全局事件如Session_End。
10.packages.config:是NuGet的基础设施,用于跟踪扩展安装包以及版本信息。
11.Web.config:网站的主要配置文件。包含Web.Debug.config和Web.Release.config两个版本。
从上图中,我们得知BaseController类包含两个成员:
1) _db:是一个MvcBasicSiteEntities类型的上下文对象,用于从数据库中访问站点的实体数据。
2) CurrentSiteSession:是一个SiteSession类型的属性,用于访问当前登录用户的重要数据,它缓存在Session会话中。通过这种方式保存了用户的重要信息,包括:UserId,Username,UserRole和CurentUICulture等。
所有该MVC站点的控制器都将继承自BaseCOntroller类,因为这个类提供了公共数据SiteSession和通过重写 ExecuteCore() 方法实现程序国际化和本地化功能,该类还通过重写 OnException() 方法管理未处理异常。
从零开始实现用户身份认证机制
当你创建一个新的 ASP.NET MVC 项目时,Visual Studio 工具可以帮助我们生成默认的用户认证机制,即membership机制与生成其关联的SQL数据库及表。这种方式在大部分应用程序中是可以的,但是在项目实际情况下你可能不想使用membership机制(eg:你已经有与其他软件公用用户数据表),所以在这些场景从零开始实现自己的用户认证机制是非常有用的。
LogOnModel是用户认证的模型对象。它包含两个标注验证特性的属性,这些验证特性使用多语言资源文件的文本描述错误信息。
Note 1:对于模型对象的每个验证信息,我们应该在每个语言资源文件中添加相同的资源键并关联到模型。资源文件中的文本必须转换为其对应的语言版本。
1
2
3
|
[Required(ErrorMessageResourceType = typeof (Resource), ErrorMessageResourceName = "ValidationRequired" )] [DataType(DataType.Password)] public string Password { get ; set ; } |
在上例,Password属性标注了两个特性:
1) DataType:指定这是密码字段,所以文本不能显示为明文。
2) Required:用于验证,并有两个属性:
a) ErrorMessageResourceType:指定验证错误信息将从特定的资源文件中读取。(注意命名是不带任何后缀的(如.de.resx或.ro.resx),因为选择哪个资源文件是由用户动态选择的,应用程序将在BaseController中根据用户会话信息进行匹配)
b) ErrorMessageResourceName:指定资源文件中要显示的资源键。
AccountController控制器实现了完整的用户身份认证和注册机制,并且管理整个站点当前语言环境的改变。当用户单击右上角的Logon连接时将触发AccountController控制器LogOn操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public ViewResult LogOn() { // // Create the model. // LogOnModel model = new LogOnModel(); model.Username = string .Empty; model.Password = string .Empty; // // Activate the view. // return View(model); } |
这个方法创建一个新的LogOnModel模型对象,并将它传递给LogOn.cshtml视图。
请看下面例子,在LogOn.cshtm视图中,所有文本信息如title、head、label、link和button都是通过语法 @Resource.ResourceKeyName将显示文本与资源文件文本关联起来。所有文本框textbox都拥有两个指令,第一个指令通过 @Html.TextBoxFor 语法关联到模型对象属性;第二个指令通过 @Html.ValidationMessageFor 语法关联到验证信息。
1
2
3
4
5
6
7
|
<div class = "editor-label" /> @Resource.LogOnModelUsername </div/> <div class = "editor-field" /> @Html.TextBoxFor(m => m.Username) @Html.ValidationMessageFor(m => m.Username) </div/> |
Note 2:解决方案中的每个视图,所有在用户界面使用的文本项信息(eg:title、head、link、label、button等等)都应该通过资源文件获取。并且这些文本项信息在每个特定语言的资源文件中都应该存在对应键。
用户认证功能先使用在LogOnModel模型对象上设置的验证规则做基本的数据验证,然后再触发AccountController控制器上的LogOn方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
[HttpPost] public ActionResult LogOn(LogOnModel model) { if (ModelState.IsValid) { // // Verify the user name and password. // User user = _db.Users.FirstOrDefault(item => item.Username.ToLower() == model.Username.ToLower() && item.Password == model.Password); if (user == null ) { ModelState.AddModelError( "" , Resources.Resource.LogOnErrorMessage); // return View(model); } else { // // User logined succesfully ==> create a new site session! // FormsAuthentication.SetAuthCookie(model.Username, false ); // SiteSession siteSession = new SiteSession(_db, user); Session[ "SiteSession" ] = siteSession; // Cache the user login data! // return RedirectToAction( "Index" , "Home" ); } } // // If we got this far, something failed, redisplay form! // return View(model); } |
LogOn方法将验证用户的name和password,并在出错的时候将错误信息显示在LogOn.cshtml视图页面。若无错则登陆成功,会创建一个SiteSession对象用于存储用户登陆数据,然后将SiteSession对象缓存到当前的HTTP会话中,最后用户被重定向到home站点页面(即Index.cshtml视图)。
改变并且缓存当前环境语言
当用户通过用户界面改变当前语言环境时,将触发AccountController控制器的ChangeCurrentCulture操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public ActionResult ChangeCurrentCulture( int culture) { // // Change the current culture for this user. // SiteSession.CurrentUICulture = culture; // // Cache the new current culture into the user HTTP session. // Session[ "CurrentUICulture" ] = culture; // // Redirect to the same page from where the request was made! // return Redirect(Request.UrlReferrer.ToString()); } |
在上面代码中,SiteSession类中静态属性CurrentUICulture被调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public static int CurrentUICulture { get { if (Thread.CurrentThread.CurrentUICulture.Name == "ro-RO" ) return 1; else if (Thread.CurrentThread.CurrentUICulture.Name == "de-DE" ) return 2; else return 0; } set { // // Set the thread's CurrentUICulture. // if (value == 1) Thread.CurrentThread.CurrentUICulture = new CultureInfo( "ro-RO" ); else if (value == 2) Thread.CurrentThread.CurrentUICulture = new CultureInfo( "de-DE" ); else Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; // // Set the thread's CurrentCulture the same as CurrentUICulture. // Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture; } } |
从上面代码可以得知,CurrentUICulture属性可以获取或设置当前使用语言环境,并且返回具有特定意义的int值(即:0 = InvariantCulture, 1 = ro-RO, 2 = de-DE)。
注意,当前使用的语言环境是设置在服务器响应请求的当前线程上,并且客户端每次请求都会丢失。
为了重新找回当前语言环境,我们在BaseController类中重写保护方法ExecuteCore方法,并且设置SiteSession对象的CurrentUICulture属性值。ASP.NET MVC会在操作控制器上任何一个操作之前自动调用ExecuteCore方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
protected override void ExecuteCore() { int culture = 0; if ( this .Session == null || this .Session[ "CurrentUICulture" ] == null ) { int .TryParse(System.Configuration.ConfigurationManager.AppSettings[ "Culture" ], out culture); this .Session[ "CurrentUICulture" ] = culture; } else { culture = ( int ) this .Session[ "CurrentUICulture" ]; } // SiteSession.CurrentUICulture = culture; // // Invokes the action in the current controller context. // base .ExecuteCore(); } |
我们还能从上面代码中得知,当用户第一次访问应用程序时,会从web.config配置文件中读取默认的语言环境。
从零开始实现用户注册机制
当我们创建一个新的ASP.NET MVC项目时,Visual Studio开发工具自动生成的架构也会包含用户注册机制,和用户验证机制一样使用ASP.NET的 membership机制实现。在这个解决方案中,我从零开始实现一个可扩展的用户注册机制,它使用两个可重用的部分视图。将在“MVC网站教程”系列的第二篇中详细介绍。
用户注册页面可以通过LogOn页面的”Register”链接触发。
在上图,你能看到所有菜单、label文本、button文本和用户注册验证信息被显示为German语言。因为在页面右上角将当前环境语言设置为”Deutch”。这些验证机制是通过在User和Address实体类上设置验证规则实现的。(将在“MVC网站教程”系列的第二篇中详细介绍)
所以用户必须填写正确的信息,然后继续注册,控制器会调用AccountController.Register()方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
[HttpPost] public ViewResult Register(User model, Address modelAddress) { if (ModelState.IsValid) { // // Verify if exists other user with the same username. // User existUser = _db.Users.FirstOrDefault(item => item.Username.ToLower() == model.Username.ToLower()); if (existUser == null ) { // // Save the user data. // MvcBasic.Logic.User user = new MvcBasic.Logic.User(); user.Username = model.Username; user.Password = model.Password; user.UserRole = UserRoles.SimpleUser; user.Email = model.Email; // if (modelAddress.CountryID <= 0) modelAddress.CountryID = null ; // user.Address = modelAddress; // _db.Users.AddObject(user); _db.SaveChanges(); // // Go to RegisterFinalized page! // return View( "RegisterFinalized" ); } else { // // Exists other user with the same username, so show the error message. // ModelState.AddModelError( "" , Resources.Resource.RegisterInvalidUsername); model.Address = modelAddress; // return View(model); } } // // If we got this far, something failed, redisplay form! // model.Address = modelAddress; // return View(model); } |
该方法会检查是否已经存在相同用户名的用户,如果存在,注册页面将收到一条错误提示信息,并且用户需要更改其用户名;若不存在,将创建一个新用户并保存到数据库中,然后显示RegisterFinalized.cshtml页面。
数据实体模型和逻辑类
在MvcBasic.Logic项目中,我添加了一个“ADO.NET 实体数据模型”的新项(*.edmx),然后将其与数据库表进行关联,最后对每个实体对象都创建了一个单独的扩展部分类文件,这些部分类包含了实体额外的操作逻辑。
因此,MvcBasic.Logic项目中包含的手动创建的部分类与自动生成的实体部分类相关联,手动创建的部分类中还包含其他一些业务逻辑。这个项目包含了如下类:
1) MvcBasicSiteEntities:是主要数据上下文,用于从数据库中获取数据存入实体对象集合。
2) User:是与数据库中存储用户数据的Users表相关联的实体类,这个模型类用于Register视图和_UserAndAddress部分视图。
3) Address:是与数据库中存储地址数据的Addresses表相关联的实体类,这个模型类用于_Address部分视图。
4) Country:是与数据库中存储城市数据的Countries表相关联的实体类。
5) UserValidation:为User实体定义验证规则和错误提示信息。所有属性都有验证特性,它们的错误文本都来自当前项目的资源文件,就像下面示例:
1
2
3
4
5
6
|
[Required(ErrorMessageResourceType = typeof (Resource), ErrorMessageResourceName = "ValidationRequired" )] [Email(ErrorMessageResourceType = typeof (Resource), ErrorMessageResourceName = "ValidationEmail" )] [Compare( "Email" , ErrorMessageResourceType = typeof (Resource), ErrorMessageResourceName = "ValidationComfirmEmail" )] [DataType(DataType.EmailAddress)] [StringLength(128)] public string ComfirmEmail { get ; set ; } |
6) AddressValidation:为Address实体定义验证规则和错误提示信息。
7) EmailAttribute:扩展基类RegularExpressionAttribute并且通过正则表达式实现邮箱验证。这个特性用于UserValidation类的Email属性来验证用户邮箱。
8) EmailValidationRule:扩展基类ModelClientValidationRule并且定义邮箱验证规则。
注意,在示例中的验证类中,所有特性使用的验证信息都来自MvcBasic.Logic项目中的资源文件。
实体类和验证类是通过在实体类上定义MetadataType特性将它们关联起来的。
原文链接:MVC Basic Site: Step 1 – Multilingual Site Skeleton
作者:Raul Iloc