65.9K
CodeProject 正在变化。 阅读更多。
Home

MVC 基本站点:第 1 步 - 多语言站点骨架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (94投票s)

2013年1月12日

Ms-PL

15分钟阅读

viewsIcon

436051

downloadIcon

16224

本文旨在作为本系列的第一篇文章,主要关注创建多语言 MVC 网站骨架。

MVC 基本站点

目录

引言

MVC 基本站点旨在成为一系列关于创建使用 ASP.NET MVC 的基本且可扩展网站的教程文章。

ASP.NET MVC 是微软的一个 Web 开发框架,它结合了模型-视图-控制器 (MVC) 架构模式的效率和整洁性、敏捷开发中的最新理念和技术,以及 ASP.NET 平台中的最佳实践。

MVC 应用程序包含以下组件

  • 模型 (Models):应用程序中实现应用程序数据域逻辑的部分。通常,模型对象会从数据库检索和存储其状态。
  • 视图 (Views):使用模型数据显示应用程序用户界面的组件。
  • 控制器 (Controllers):处理用户交互、与模型交互并最终选择视图进行渲染的组件。视图仅显示信息;控制器处理并响应用户输入和交互。

本文旨在作为本系列的第一篇文章,主要关注创建多语言 MVC 网站骨架。它还提供了从零开始实现的良好用户认证和注册机制的示例,以及数据实体框架和 LINQ 的用法。

提供的源代码注释清晰且整洁,应该能够轻松阅读和理解。

从用户界面的角度来看,本文提供了一个 MVC 网站的骨架,其中包括:主页(带菜单、页眉和页脚的主布局)、“主页”(Index)和“关于”(About)页面的骨架、“登录”(LogOn)和“注册”(Register)页面的完整实现,以及其他用于编辑用户和地址数据的常见部分页面。

整个用户界面已完全实现了以下三种语言的国际化和本地化:英语、罗马尼亚语 (Română) 和德语 (Deutch),并且可以扩展到其他语言。

图:MVC 基础站点 - 登录页面。

当用户从右上角的下拉列表中更改使用的语言时(参见上图),所使用的用户界面文本、数字、验证和消息将从那时起并针对指定的语言进行显示。

软件环境

  • .NET 4.0 框架
  • Visual Studio 2010(或 Express 版)
  • ASP.NET MVC 3.0
  • SQL Server 2008 R2(或 Express Edition 版本 10.50.2500.0,或更高版本)

站点骨架

提供的解决方案骨架包含两个项目

  • MvcBasicSite:这是主项目,类型为 ASP.NET MVC 3.0,包含一些使用的模型、所有视图、所有控制器以及其他使用的资源和源代码。本项目将在本章中详细介绍。
  • MvcBasic.Logic:这是一个类库项目,包含数据实体框架图、与生成的实体类相关的部分类以及其他业务逻辑使用的类。本项目将在下一章中介绍。

图:MVC 基础站点 - 站点骨架

正如您在上图中所看到的,在创建 ASP.NET MVC 3.0 项目后,Visual Studio 会将项目项生成并分组到此结构中

  • app_code:这是一个可选文件夹,如果使用,则包含可供整个项目访问的全局代码。在本例中,此文件夹包含 Content.cshtml 文件,该文件定义了一个 Razor 代码助手,用于更轻松地在视图中包含脚本。
  • App_Data:这是数据的物理存储。此文件夹在 ASP.NET 网站中使用 Web Forms 页面时具有相同的作用。在本例中,此文件夹为空,但默认情况下,Visual Studio 会添加用于成员资格和身份验证的 SQL 数据库。
  • App_GlobalResources:这是一个可选文件夹,包含用于实现多语言功能的资源文件。请注意,文件名非常重要,每个资源文件都包含特定语言的所有文本。在本例中,它包含以下三个与每种使用语言相关的文件
    • Resource.resx:与英语(不变区域性)关联的默认资源文件;
    • Resource.de.resx:与德语(区域性信息代码:de-DE)关联的资源文件;
    • Resource.ro.resx:与罗马尼亚语(区域性信息代码:ro-RO)关联的资源文件。
  • Content:这是用于存放级联样式表文件 (CSS)、图像、主题等静态内容文件的推荐位置。在本例中,此文件夹中有
    • Images:包含使用的图像的子文件夹;
    • theme:包含基础 jQueryUI 主题文件的子文件夹;
    • dd.css:用于更改使用的语言下拉列表的 CSS 文件;
    • site.css:站点的主要 CSS 文件。
  • Controllers:这是存放控制器的推荐位置。MVC 框架要求所有控制器的名称都以“Controller”结尾。在本例中,此文件夹中有三个控制器
    • BaseController:是我创建的,作为所有控制器类的基类。它包含所有控制器所需的通用数据和功能;
    • AccountController:管理用户注册、登录和注销;
    • HomeController:管理主页(Index)和关于(About)页面的操作。
  • Model:为代表 MVC Web 应用程序模型类的类提供。在本例中,实际的模型对象已分离到 MvcBasic.Logic 类库项目中,在此文件夹中,我们只有这两个类
    • LogOnModel:用于登录页面用户认证的模型;
    • SiteSession:用于存储已登录用户的站点会话特定数据的类:UserID、Username、UserRole 和 CurentUICulture。
  • Scripts:这是支持应用程序的脚本文件的推荐位置。默认情况下,此文件夹包含 ASP.NET AJAX 基础文件和 jQuery 库。
  • Views:这是存放视图的推荐位置。此文件夹包含每个控制器的一个子文件夹;文件夹名称为控制器名称前缀。默认情况下,还有一个名为 Shared 的子文件夹,它不对应任何控制器,用于存放跨多个控制器共享的视图。在本例中,Views 文件夹的内容如下
    • Account:包含与 AccountController 关联的视图
      • Logon.cshtml:用于用户认证的页面;
      • Register.cshtml:用于用户注册的主页面;
      • RegisterFinalized.cshtml:用于成功注册的页面。
    • Home:包含与 HomeController 关联的视图
      • About.cshtml:关于页面的骨架;
      • Index.cshtml:主页的骨架。
    • Shared:包含跨多个控制器共享的视图
      • _Address.cshtml:用于编辑地址数据的部分视图;
      • _Header.cshtml:用于渲染主页面页眉的部分视图;
      • _Layout.cshtml:包含站点主布局的主页面;
      • _UserAndAddress.cshtml:用于编辑用户数据及其地址的部分视图;
      • Error.cshtml:在发生意外异常时显示的错误页面。
    • _ViewStart.cshtml:定义整个站点使用的主要布局的 URL;
    • Web.config:定义仅适用于视图页面的 Web 配置。通常我们不应在此手动添加任何内容。
  • Global.asax:包含 MvcApplication 类(派生自 HttpApplication),用于设置全局 URL 路由默认值,以及管理其他全局事件,如 Session_End
  • packages.config:由 NuGet 基础结构管理,用于跟踪已安装的包及其版本。默认情况下,它会跟踪 jQuery、EntityFramework、Modernizr 等包;
  • Web.config:站点的主要配置文件以及与不同活动解决方案配置关联的配置文件;默认情况下,我们有
    • Web.Debug.config:仅在 Debug 版本的情况下适用于主配置文件的转换配置文件;
    • Web.Release.config:仅在 Release 版本的情况下适用于主配置文件的转换配置文件;

图:MVC 基础站点 - 站点骨架类图

正如您在上方的类图中所看到的,BaseController 类包含两个数据成员

  • _db:这是一个 MvcBasicSiteEntities 类型的对象,用于访问数据库中的站点实体数据(详情请参见下一章);
  • CurrentSiteSession:这是一个 SiteSession 类型的属性,用于访问在登录时缓存到 HTTP 会话上下文中的当前已登录用户的主要数据。这样,用户最重要的信息(包括 ID、角色和当前 UI 区域性)可以在回发之间保持不变。

MVC 站点中的所有控制器类都必须继承 BaseController 类,因为该类提供了上述通用数据以及用于国际化和本地化的功能,通过重写 ExecuteCore() 方法实现。未处理的异常也由该基类管理,但错误和异常管理将在其他文章中详细介绍。

从零开始的用户认证

当我们创建一个新的 ASP.NET MVC 项目时,Visual Studio 生成的骨架包含使用成员资格实现的用户的身份验证和一个包含所需表的关联 SQL 数据库。这对于许多应用程序来说可能没问题,但在实际情况中,您可能不想使用成员资格和生成的表和代码(也许您已经拥有与其它软件解决方案共享的用户表,并且必须重用它们);因此,在这些场景下,从零开始实现的自有身份验证机制可能会很有用。

LogOnModel 是用于认证的模型类。它有两个包含验证属性的属性,这些属性使用来自上述资源文件的错误消息文本。

注意 1:对于模型类中的每个验证消息,我们应该在每种使用的资源文件中具有具有相同键的关联文本;并且在每种资源文件中,文本必须被翻译成其关联的语言。

[Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName =  "ValidationRequired ")]
[DataType(DataType.Password)]
public string Password { get; set; }

在上例中,LogOnModel 类中的 Username 属性有两个属性

  • DataType:指定这是一个密码字段,因此文本不会显示;
  • Required:用于验证,有两个属性
    • ErrorMessageResourceType:指定验证错误消息将从指定的资源文件名读取(只需文件名,不带任何扩展名,如 .de.resx.ro.resx,因为当控制器中的当前线程 UI 区域性改变时,正确的资源文件会自动选择);
    • ErrorMessageResourceName:指定资源文件中资源键的名称。

AccountController 是一个类,它完全实现了用于用户认证和注册的操作,并且还管理用户在整个站点中使用的当前区域性的更改。当用户单击“登录”链接时,将调用 AccountController 的下一个方法

public ViewResult LogOn()
{
    //
    // Create the model.
    //
    LogOnModel model = new LogOnModel();
    model.Username = string.Empty;
    model.Password = string.Empty;
    //
    // Activate the view.
    //
    return View(model);
}

此方法创建 **LogOn 模型**,然后使用新创建的模型激活 **LogOn 视图**。

如您在下例中所见,在 **LogOn 视图**中,所有页面标题、标题、标签、链接和按钮的文本都是通过使用语法:@Resource.[ResourceKeyName] 来使用关联的资源键实现的。此外,对于所有文本框,都使用了两个指令,第一个指令使用 @Html.TextBoxFor 语法将模型中的属性与文本框链接起来,第二个指令使用 @Html.ValidationMessageFor 语法链接验证消息。

<div class="editor-label"/>
    @Resource.LogOnModelUsername
</div/>
<div class="editor-field"/>
    @Html.TextBoxFor(m => m.Username)
    @Html.ValidationMessageFor(m => m.Username)
</div/>

注意 2:对于解决方案中使用的所有视图,用户界面项(标题、页眉、链接、标签、按钮等)中使用的所有文本都应通过资源实现,并且这些文本的值应以其关联的语言插入到所有使用的资源文件中。

用户认证以基本的验证开始,该验证在浏览器中通过使用 LogOnModel 模型类中定义的验证规则来完成。然后,将调用 AccountController 的下一个方法。

[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 方法将验证用户名和密码,如果出现错误,错误消息将显示在 LogOn 视图中,否则,成功登录后,将创建一个新的站点会话对象并用于存储用户登录数据,然后整个站点会话对象将被缓存到当前 HTTP 会话中,最后,用户将被重定向到站点主页(HomeController 的 Index 视图)。

更改和缓存当前区域性

当用户更改用户界面中的当前区域性时,将调用 AccountController 的下一个方法

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

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 方法中调用 SiteSessionCurrentUICulture 属性。在每次回发和每个控制器上,ASP.NET MVC 框架都会在调用控制器任何操作之前自动调用 ExecuteCore 方法。

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 生成的骨架还包含使用成员资格实现的用户的注册。在本解决方案中,我为您提供了一个从零开始实现的、可扩展的用户注册功能,该功能使用了两个部分视图,这两个部分视图也被设计为可以在后续的 **MVC Basic Site** 文章中描述的用户管理中使用。

可以通过 **LogOn** 页面的 **“注册”** 链接激活用户注册页面。

图:MVC 基础站点 - 注册页面

在上图中,您可以看到所有菜单、标签文本、按钮文本以及用户注册验证消息都是德语显示的,因为在右上角选择的当前区域性是“Deutch”。此验证是通过使用 UserAddress 实体类中定义的验证规则来完成的(详情请参见下一章)。

因此,用户必须更正验证错误,然后用户注册将继续,并将调用 AccountController.Register() 方法。
[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 页面。

数据实体模型和逻辑类

MvcBasic.Logic 项目中,我添加了一个新的 ADO.NET 数据模型项,然后将其与使用的数据库表关联,最后,对于每个实体类,我在一个单独的文件中创建了一个关联的部分类,其中包含实体逻辑。

图:MVC 基础站点 - 数据实体模型图

因此,MvcBasic.Logic 项目包含与生成的实体类(来自上述图)关联的部分类,还有其他业务逻辑使用的类。该项目包含以下类

  • MvcBasicSiteEntities:这是用于访问数据库中数据作为实体对象集合的主要数据上下文。
  • User:这是与 Users 表关联的实体类,用于存储用户的基本数据。该类也用作 Register 视图和 _UserAndAddress 部分视图的模型。
  • Address:这是与 Addresses 表关联的实体类,用于存储地址数据。该类用作 _Address 部分视图的模型。
  • Country:这是与 Countries 表关联的实体类,用于存储国家/地区数据;
  • UserValidation:定义 User 实体的验证规则和消息。此验证类的所有属性都具有包含验证属性的属性,这些属性使用当前项目中的资源文件中的错误消息文本,如下例所示
    [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; }
  • AddressValidation:定义 Address 实体的验证规则和消息;
  • EmailAttribute:继承 RegularExpressionAttribute,并通过使用正则表达式实现电子邮件验证。此属性在 UserValidation 中用于验证用户电子邮件。
  • EmailValidationRule:继承 ModelClientValidationRule,并定义电子邮件验证规则。

请注意,在验证类的情况下,所有属性都使用 MvcBasic.Logic 项目中存在的资源文件中的验证消息,并且这些资源文件的名称与 MvcBasicSite 中描述的资源文件类似。

实体类和验证类之间的链接是通过在实体类定义中使用 MetadataType 属性来实现的。

在运行此代码之前

请注意,数据库和源代码所需的所有工具都作为链接提供在 **References** 部分,并且可以免费下载和使用(用于测试),因为它们是 Express 版本。

在运行此代码之前,您应该执行以下步骤:

  1. 在您的 SQL Server(或 SQL Express)中创建一个名为 MvcBasicSite 的数据库,然后将提供的 MvcBasicSiteDatabase.bak 数据库恢复到其中。
  2. 您可以选择在您的 SQL Server(或 SQL Express)中创建一个此数据库的登录用户。
  3. 根据第 2 步和第 3 步中的设置,修改 MvcBasicSite Web 应用程序的 Web.config 文件中的连接字符串。

参考文献

历史

  • 2013 年 1 月 11 日:版本 1.0.0.1 - 草稿版本。
  • 2013 年 1 月 13 日:版本 1.0.0.2 - 审阅后进行的小幅修改。
  • 2013 年 1 月 15 日:版本 1.0.0.2 - 在源代码中添加了缺失的行。
  • 2013 年 1 月 25 日:版本 1.0.1.1 - 文章中添加了更多详细信息。
  • 2013 年 1 月 30 日:版本 1.0.2.1 - 文章中添加了新的详细信息。
  • 2013 年 2 月 14 日:版本 1.0.2.2 - 链接至“MVC 基础站点:第二步 – 异常管理”
  • 2013 年 2 月 21 日:版本 1.0.2.3 - 基础站点步骤。
  • 2013 年 4 月 23 日:版本 1.0.2.4 - 更新基础站点步骤。
  • 2013 年 5 月 18 日:版本 1.0.2.5 - 更新基础站点步骤。
© . All rights reserved.