面向桌面 (WPF/WinForms) 开发者的 ASP.NET MVC - 第二部分






4.94/5 (9投票s)
导航、表单和数据表
引言
这是系列文章的第二部分。第一部分在这里。我们将从上次中断的地方开始。既然我们已经有了 ASP.NET MVC 的“Hello World”网站,就让它变得有用起来。在这一部分中,我们将
- 为我们的网站添加一个菜单栏。
- 创建一个数据录入表单。
- 显示一个数据表。
背景
本文是该系列文章的一部分,旨在向硬核桌面应用程序开发者介绍 Web 编程概念。该系列试图提供 WinForms/WPF 开发者可以轻松理解的信息。随着概念的引入,我们将深入探讨“为什么”和“如何”的问题,以深入了解幕后发生的事情。重点在于保持控制,并能够决定你的 ASP.NET MVC 网站应该如何运行,而不是感到意外。
导航栏
作为一名桌面应用程序开发者,我首先想到的是如何为我的网站添加菜单栏?在 Web 世界中,这被称为导航栏。根据网站的设计,导航栏可以放置在网站的任何位置。为了简单起见,我们将它放在顶部。我们的 Index.cshtml 是用户访问网站时调用的默认视图。我们将在此视图上进行操作,以添加导航栏并调整其他一些项目。
所以,是时候启动图形编辑器,将菜单栏拖放到我们的视图上了,对吧?错了!这时就会出现令人震惊的一点。ASP.NET MVC 中没有图形化的拖放编辑器。至少在本文发表时是没有的。你可以在 Visual Studio 编辑器中输入 HTML,然后通过刷新 Web 浏览器来查看结果。这真是倒退!WinForms 内置了拖放编辑器,对于 WPF,我们有令人惊叹的 Expression Blend。“但是我们有 Expression Web!”不幸的是,它不适用于 ASP.NET MVC。Expression Web 4.0 甚至无法打开 CSHTML 文件。深呼吸,闭上眼睛,喝杯咖啡或去散散步。尽一切可能克服这一点,然后回来继续。这不像听起来那么糟糕,第一次的时候。我最终还是克服了!
现在,你已经克服了叹气、沮丧和震惊,让我们开始让导航栏工作起来。将以下代码输入到 Index.cshtml 中。
@{
ViewBag.Title = "My first Hello World website!";
}
<h2>Welcome to my Hello World website!</h2>
<table>
<tr>
<td><a href="">Home</a></td>
<td> </td>
<td> </td>
<td><a href="/login">Login</a></td>
<td> </td>
<td> </td>
<td><a href="">About & Help</a></td>
</tr>
</table>
在第一行,你会注意到 @{} 部分。这是 Razor 视图引擎的部分,它允许我们在视图中插入 C# 或 VB.NET 代码。在这个部分,你可以编写 C#/VB.NET 代码来为视图生成动态 HTML。目前,它主要用于设置网页标题。标题通常由 Web 浏览器显示在窗口框架和选项卡标题中。这种神奇的效果是通过位于 Views/Shared 文件夹中的 _Layout.cshtml 中的高亮代码实现的。
<!DOCTYPE html>
<html>
<head>
....
<title>@ViewBag.Title</title>
....
</head>
<body>
@RenderBody()
....
....
</body>
</html>
中的第二个条目是页面标题,后面是一个用于布局导航栏的 HTML 表格。使用表格进行布局是 Web 编程界激烈争论的话题。我不是评论权威,但我在这里想保持简单,因此我使用了它。现在运行网站,你应该会看到类似下面的内容。
点击“Home”和“About & Help”链接只会一遍又一遍地刷新同一个页面。在我们使这些链接生效之前,必须为它们创建视图。
在我们继续创建登录视图之前,我们需要一个模型来存储用户名和密码。右键单击 Models 文件夹,然后单击 Add->Class。将文件命名为 LoginModel.cs。在 LoginModel.cs 中输入以下代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication1.Models
{
public class LoginModel
{
public string Username { get; set; }
public string Password { get; set; }
}
}
为简单起见,我们不会深入探讨处理明文密码的安全隐患。
登录表单
现在让我们创建一个登录视图。右键单击 View 文件夹,然后单击 Add->New Folder。将文件夹命名为 Login。右键单击新的 Login 文件夹,然后单击 Add->View。将视图名称键入为 Login。

Solution Explorer 中的条目应该如下所示。
创建数据录入表单/登录对话框的 HTML 加上 C# 代码如下。
@using MvcApplication1.Models;
@model LoginModel
@{
ViewBag.Title = "Login";
}
<h2>User Login Page</h2>
<table>
@using(Html.BeginForm("DoLogin","Login", FormMethod.Post))
{
<tr>
<td>Username</td>
<td></td>
<td>@Html.EditorFor(lm => lm.Username)</td>
</tr>
<tr>
<td>Password</td>
<td></td>
<td>@Html.PasswordFor(lm=>lm.Password)</td>
</tr>
<tr>
<td></td>
<td></td>
<td><input type="submit" value="Login" /></td>
</tr>
}
</table>
让我们逐行分析上面的代码。第一个条目与其他 C# 源文件中的 using 指令一样,它允许我们在不显式限定的情况下使用命名空间内的类型。在这种情况下,我们希望引用 LoginModel,而无需在所有地方都进行完全限定。@ 符号指示 Razor 视图引擎将后续文本视为 C# 代码而不是 HTML。有关更多详细信息,请搜索Razor 视图引擎和Razor 语法。
@model 指令用于指定一个强类型模型,该模型将由该视图引用。在这种情况下,我们的模型名为 LoginModel。请注意,此指令后面没有分号。如果不小心加上一个,请准备好花费数小时来弄清楚哪里出错了。我以亲身经历为例!你应该熟悉接下来的两个条目(网页标题和标题),所以我将跳过解释它们。
最后一条目是用于显示登录表单的 HTML 表格。在桌面世界中,这被称为登录对话框。我们在表格内有一个 Razor 块,以启用 HtmlHelper 类的使用。顾名思义,此类有助于我们编写实际上生成 HTML 的 C# 代码。在 Web 世界中,如果你需要使用 Web 浏览器收集用户输入,我们需要使用 HTML 表单。一旦用户单击“Submit/Login”按钮,数据就会从 Web 浏览器传输到 Web 服务器。所有这些都内置在 HTML 协议中。
BeginForm 有助于生成在 Web 浏览器中渲染表单所需的 HTML,并将数据定向回选定控制器中的某个操作。在这种情况下,我们希望在 Login 控制器中调用 DoLogin 操作。并且我们希望表单使用 Post 方法发送数据。搜索HTML Form GET vs POST以了解更多详细信息。EditorFor 和 PasswordFor 有助于生成将 HTML 控件与 LoginModel 绑定的 HTML。这确保了用户输入的值会自动绑定到属性。PasswordFor 创建一个文本框,该文本框会掩码用户输入。
现在是时候创建登录控制器了。请记住,Web 服务器接收的所有请求都会被定向到 MVC 中的控制器。右键单击 Controllers 文件夹,然后单击 Add->Controller。将其命名为 LoginController。

用以下内容替换 LoginController.cs 的内容。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class LoginController : Controller
{
//
// GET: /Login/
public ActionResult Index()
{
return View("Login");
}
}
}
Index 操作将显式返回 Login 视图,因为我们将其命名为 Login.cshtml。
现在运行网站,然后单击主页上的“Login”链接。你应该会看到类似这样的内容。我已经输入了用户名和密码来突出显示掩码密码。
如果你单击“Login”按钮,应该会收到预期的错误。这是因为我们还没有在登录控制器中编写 DoLogin 操作。在此之前,让我们为 Home 页面/控制器创建一个模型,该模型将保存登录用户名的列表。右键单击 Models 文件夹,单击 Add->Class,并将文件命名为 HomeModel.cs。在 HomeModel.cs 中输入以下代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication1.Models
{
public class HomeModel
{
public List<string> UsersOnline { get; set; }
public HomeModel()
{
UsersOnline = new List<string>();
}
}
}
数据表
现在我们需要修改相应的视图 Index.cshtml 来使用这个模型。
@using MvcApplication1.Models;
@model HomeModel
@{
ViewBag.Title = "My first Hello World website!";
}
<h2>Welcome to my Hello World website!</h2>
<table>
<tr>
<td><a href="">Home</a></td>
<td> </td>
<td> </td>
<td><a href="/login">Login</a></td>
<td> </td>
<td> </td>
<td><a href="">About & Help</a></td>
</tr>
</table>
<hr />
<h3>List of users currently online</h3>
<table>
@foreach (string username in Model.UsersOnline)
{
<tr>
<td>@username</td>
</tr>
}
</table>
我正在使用 Razor 来生成 HTML 表格条目,以显示已登录的用户。请注意 Razor 语法如何允许我们混合 HTML 和 C#。这是一个非常强大的功能。如果你现在运行网站,我们还没有完成,你将收到一个恼人的Object reference not set to an instance of an object.错误!这是因为我们还没有创建 HomeModel 的对象并将其传递给这个视图。执行此操作的地方是 HomeController 类。像这样修改 HomeController.cs 文件中的代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
var homeModel = new HomeModel();
return View(homeModel);
}
}
}
我所做的就是创建 HomeModel 类的实例并将其传递给 View。现在运行网站,应该会产生类似这样的结果。
现在是时候编写最后一些代码来使这一切正常工作了。在现实世界中,你会将用户会话信息持久化到数据库中。但为了简单起见,我将偷懒,将这些数据保存在 Web 服务器缓存中,我们将用它来代替真正的数据库。
在 LoginController.cs 文件中的 Login 控制器中添加一个 DoLogin 操作。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class LoginController : Controller
{
//
// GET: /Login/
public ActionResult Index()
{
return View("Login");
}
public ActionResult DoLogin(LoginModel model)
{
TempData["username"] = model.Username;
return RedirectToAction("ShowUsers", "Home");
}
}
}
DoLogin 操作有一个 LoginModel 类型的参数模型。那么它是如何工作的呢?还记得我们在 Login.cshtml 文件中将 LoginModel 绑定到了视图,并使用了 Htmlhelper 类的 BeginForm 方法吗?在后台,ASP.NET MVC 提供了一个模型绑定器来绑定值。TempData 是一个用于在控制器之间传递数据的字典。我们将用户名保存在字典中,以便其他控制器使用。
在这种情况下,我们将 Login 单击操作重定向到主页,用户可以在那里看到当前在线用户列表。另外请注意,我们将调用 Home 控制器中的 ShowUsers 操作。由于它不存在,我们将继续编写它。用以下代码替换 HomeController.cs 中的代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
private const string CACHE_KEY = "HomeModelData";
//
// GET: /Home/
public ActionResult Index()
{
var homeModel = new HomeModel();
return View(homeModel);
}
public ActionResult ShowUsers()
{
var homeModel = (HomeModel)HttpContext.Cache[CACHE_KEY];
if (homeModel == null)
{
homeModel = new HomeModel();
HttpContext.Cache[CACHE_KEY] = homeModel;
}
string username = TempData["username"] as string;
if (username != null)
{
homeModel.UsersOnline.Add(username);
}
return View("Index", homeModel);
}
}
}
ShowUsers 操作尝试从 Web 服务器缓存(我们用于替代实际数据库的)检索用户会话信息。然后将新登录的用户添加到在线用户列表中。用户名从 TempData 中检索。由于我们要重用 Index 视图,因此我在 View 构造函数中以及模型对象一起明确指定了它。
现在运行代码并使用你的名字登录。如果你再次单击“Login”链接并以另一个名字登录,现在你应该会看到两个名字,如下所示。
我将把 About & Help 链接留给你作为练习!
这结束了本系列的第二部分。如果你喜欢,请告诉我?你希望我在下一部分涵盖什么内容?
关注点
虽然本文涵盖了很多内容,但仍有许多工作要做。尤其是在构建供数千用户使用的公共网站时。当前的网站与其现代对应物相去甚远。像 Gmail 这样的前沿网站就像桌面应用程序一样,几乎在一个页面上运行。如果时间允许并且有足够的鼓励,我将在未来尝试涵盖它们!
历史
首次发布于 2014 年 4 月 4 日