暂无图片
暂无图片
暂无图片
暂无图片

ASP.NET MVC+EF从入门到精通(一)

51ASPNET 2019-02-19
147

新建项目

打开 VS2015 ,找到菜单项 [ 文件 -> 新建 -> 项目 ] ,打开向导对话框:



注意我们的选择项:

1. 运行平台: .NET FrameWork 4.5

2. 项目模板: ASP.NET Web Application (.NET Framework)

3. 项目名称: AspNetMvc.QuickStart ,如果你在跟着本教程练习,建议起相同的项目名称,方便直接拷贝代码到你的项目中。

点击 [ 确定 ] 按钮,向导会带我们到另一个选择对话框:

由于本教程是快速入门,所以我们从最简单的入手,只勾选必需的选项:

1. 不进行身份验证。 ASP.NET MVC 提供了完善的身份验证方案,我们会有单独的文章讲解。

2. 仅勾选 MVC 引用。

点击 [ 确定 ] VS2015 会创建一个可直接运行的项目,按下快捷键 [Ctrl+F5] ,不调试直接运行:


默认的目录结构如下:

如果你之前在 WebForms 下进行开发,对其中的一些文件夹和文件应该很熟悉了:

1. Web.config :项目配置文件,里面保存项目配置参数以及数据库连接字符串。

2. packages.config Nuget 配置文件

3. Global.asax :全局代码文件,提供应用程序级别以及会话级别的事件处理函数,可以在 Application_Start 中注册全局变量。

4. favicon.ico :浏览器地址栏图标,在 HTML head 标签中引用。

5. App_Data :放置本地数据库文件,比如 LocalDB 生成的数据库文件。

下面几个文件夹,用来放置静态文件,从名称就可以方便的猜出其用途:

1. Scripts :放置静态脚本文件,比如 jQuery 等。

2. fonts :放置图标字体文件,比如流行的 FontAwesome 字体等。

3. Content :放置静态文件,比如 xml 文件、 Bootstrap CSS 库。


下面几个文件是 ASP.NET MVC 新引入的:

1. App_Start :用来放置应用初始化类,这个是 MVC4 引入的一个命名约定,其实这就是一个普通的文件夹,没有特殊的含义。

2. Controllers :控制器类。

3. Models :模型类,比如 EF CodeFirst 的模型定义。

4. Views :视图文件,最初的视图引擎是 WebForms View Engine ,使用和 ASPX 文件相同的语法,而现在用的 Razor 视图引擎是 MVC3 引入的,以 cshtml 为后缀。

页面流程

首先看下 [About] 页面:


这个页面之所以能够呈现在我们眼前,经历了三个主要流程:

1. MVC 的路由引擎根据 URL 查找相应的控制器( HomeController.cs )。

2. 控制器的操作方法 About 准备数据,然后传入视图 Home/About.cshtml

3. 视图准备 HTML 片段,放入布局页面并返回浏览器。


路由引擎 -> 控制器

一切还得从 Global.asax 中说起,在其中的应用程序启用事件中,我们需要注册路由处理器:


protected void Application_Start()
       AreaRegistration.RegisterAllAreas();
       FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);       RouteConfig.RegisterRoutes(RouteTable.Routes);
       BundleConfig.RegisterBundles(BundleTable.Bundles);
}


RouteConfig.cs 类位于 App_Start 文件夹中,我们来看下内容:


public class RouteConfig
{       public static void RegisterRoutes(RouteCollection routes)
              routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
              routes.MapRoute(
                     name: "Default",                     url: "{controller}/{action}/{id}",
                     defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
}



这里注册一个名为 Default 的路由规则,对应的 URL {controller}/{action}/{id} ,这里三个占位符分别表示:

  1. 1. {controller} :控制器,默认是 Home ,对应的控制器类是 HomeController.cs

  2. 2. {action} :控制器里面的方法,默认是 Index 。所以如果用户直接通过 http://localhost/ 访问系统时,默认调用 Home 控制器中的 Index 方法处理。

  3. 3. {id} :参数 ID ,可选项,这个参数对应于操作方法中的 id 参数。

控制器方法 -> 视图

通过上面的介绍,我们就知道了 http://localhost:55654/Home/About 网址对应于 Home 控制器的 About 方法。

我们在 Controllers/HomeController.cs 中找到相应的方法:


public ActionResult About()
       ViewBag.Message = "Your application description page."; 
       return View();
}


ViewBag 是一个动态对象( dynamic ),可以用来存储任意参数,用来从控制器向视图传递数据。

从控制器向视图传递数据一般有两种方法:

1. 传入模型,然后在视图中通过 Model 对象访问,这是一种强类型的方式,也是推荐的做法。其局限性就是只能传入一个模型,如果需要传入多个模型对象,就需要自定义类来包含多个模型,另一种方法就是 ViewBag

2. ViewBag ,视图包传递数据非常方便,但是在视图中可能需要进行强制类型转换。在常见的传入一个主模型和多个次模型时,可以把多次模型放到 ViewBag 中,从而避免自定义类的麻烦。

作为命名约定,这个操作方法会自动调用相应名称的视图文件 About.cshtml

视图 -> 浏览器

下面来看 About.cshtml 视图文件:


@{
    ViewBag.Title = "About";
}<h2>@ViewBag.Title.</h2><h3>@ViewBag.Message</h3>
 <p>Use this area to provide additional information.</p>


@ 开头用来输出 C# 代码的运行结果, MVC 会自动判断于何处结束 C# 代码,并转入 HTML 代码。

需要注意,页面第一行的 @{ } 用来执行一段 C# 代码,不会输出内容,这里定义了一个 ViewBag.Title 的变量,并在下面的代码中使用 @ViewBag.Title 输出到页面中。

很多初学者可能有些疑惑,为啥控制器中定义了 ViewBag.Message ,而在视图中定义了 ViewBag.Title ,这两者有啥区别?

一般来说是没有功能的区别,仅仅是语义的区别。在视图中定义的变量仅在视图中使用,比如这里定义的 ViewBag.Title 不仅在 About.cshtml 中使用,而且在布局视图 Shared/­_Layout.cshtml 中也用到了。

布局视图

布局视图类似于 WebForms 中的母版页,具体的视图页面会作为一部分嵌入到布局视图中,然后返回到浏览器形成一个完整的页面。

每一个视图页面默认会使用 Views/_ViewStart.cshtml 中的定义的内容:


@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}


这里面指定了布局视图的位置,我们来简单看下布局视图的内容:


<!DOCTYPE html><html><head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")</head><body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("Home", "Index", "Home")</li>
                    <li>@Html.ActionLink("About", "About", "Home")</li>
                    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>
    </div>
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)</body></html>


其中 head 标签下面的 title 中使用了在 About 视图中定义的 ViewBag.Title 属性。

这个布局视图使用 Bootstrap 库定义的 CSS 样式来完成,包含标题栏,导航菜单,以及页脚的定义,具体的内容会嵌入 @RenderBody() 的地方,最终形成完整的 HTML 页面返回。


数据库操作

上面从控制器传入视图的数据是硬编码的一个字符串,实际项目中则经常需要从数据库中读取数据,我们使用微软推荐的 Entity Framework CodeFirst 开发模式来创建和使用数据库。

安装 Entity Framework

首先需要安装 EF ,在 VS2015 中找到 [ 工具 ] 菜单,然后找到 NuGet 包管理器:


转到 [ 浏览 ] 选项卡,可以搜索 Entity Framework ,安装其最新稳定版到项目中:

安装后,会自动更改 Web.config 添加相应的配置信息。

创建模型

我们计划完成一个简单的学生管理系统,包含基本的增删改查( CRUD )。

首先在 Models 文件,创建学生( Student )的模型类:


public class Student
{       
   public int ID { get; set; }      
   public string Name { get; set; }      
   public int Gender { get; set; }      
   public string Major { get; set; }      
   public DateTime EntranceDate { get; set; } }


然后创建数据库操作上下文, EF 需要这个文件来创建和访问数据库:

public class StudentDbContext : DbContext
{       public DbSet<Student> Students { get; set; }
}


由于这个类继承自 EF DbContext 基类,因此需要在文件头部添加如下引用:

using System.Data.Entity;


创建完这两个文件,需要重新编译项目(快捷键 Ctrl+Shift+B ),否则下面添加控制器时会出错。

添加控制器

Controllers 目录上点击右键,添加控制器,弹出向导对话框:


这里选择 MVC 5 Controller with views, using Entity Framework ,然后进入设置对话框:

在这个对话框中,我们需要指定刚才创建的模型类( Student )和数据访问上下文类( StudentDbContext ),然后 VS 不仅可以自动创建视图,而且使用 EF 自动创建 CRUD 的全部代码,是不是很酷!

全部功能完成了!

是不是很惊奇,我们甚至没来得及写视图代码,没有配置数据库,没有写 CRUD 的逻辑代码, VS 模板帮我们生成了一切,现在运行一下( Ctrl+F5 ),并在浏览器中输入 /Students

表格页面

表格页面对应于 Students 控制器下的 Index 操作方法:


public class StudentsController : Controller
{       private StudentDbContext db = new StudentDbContext(); 
       // GET: Students
       public ActionResult Index()
       {              return View(db.Students.ToList());
}


首先,我们看到控制器内部定义了一个私有变量 db ,并进行初始化。这是数据库操作上下文实例,所有的 CRUD 操作都讲依赖于这个实例。

Index 方法中,通过向 View 方法传递学生列表的方式,把模型数据传递到了视图,在 Views/Students/Index.cshtml 视图文件中,我们声明了传入模型的类型:


@model IEnumerable<AspNetMvc.QuickStart.Models.Student>


在视图中, Model 属性的类型就确定为强类型 IEnumrable<Student> ,配合 VS 提供的智能感知,不仅可以快速编写代码,并且在编译时还检查代码的有效性。

完整的 Index.cshtml 代码:


@model IEnumerable<AspNetMvc.QuickStart.Models.Student>
    ViewBag.Title = "Index";
<h2>Index</h2>
    @Html.ActionLink("Create New", "Create")</p><table class="table">
            @Html.DisplayNameFor(model => model.Name)        </th>
            @Html.DisplayNameFor(model => model.Gender)        </th>
            @Html.DisplayNameFor(model => model.Major)        </th>
            @Html.DisplayNameFor(model => model.EntranceDate)        </th>
        <th></th>
 @foreach (var item in Model) {    <tr>
            @Html.DisplayFor(modelItem => item.Name)        </td>
            @Html.DisplayFor(modelItem => item.Gender)        </td>
            @Html.DisplayFor(modelItem => item.Major)        </td>
            @Html.DisplayFor(modelItem => item.EntranceDate)        </td>
            @Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
            @Html.ActionLink("Details", "Details", new { id=item.ID }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.ID })        </td>
    </tr>} 
</table>


看着很有古老的 ASP 的感觉吧,不过这里的 Model 属性是强类型的,因此在 foreach 循环中, VS 明确知道 item 类型是 Student ,从而方便代码编写:


@Html 里面都是 MVC 提供的辅助方法,用来辅助生成 HTML 代码:

1. ActionLink :用来生成超链接,链接到本控制器内的某个操作方法(也可以是其他控制器的方法,有重载函数),可以指定路由参数,通过对象初始化语法来创建,比如 new {id=item.ID}

2. DisplayNameFor :显示模型属性的名称。强类型辅助方法,允许我们使用一个 lambda 表达式来指定某个模型属性,而不用写字符串。好处不仅有智能感知,编译时检查,而且也方便代码重构,比如我们在更改模型的属性名称时,视图中的相应代码也会改变。

3. DisplayFor :显示模型属性的值。

新增页面



新增页面对应于 Students 控制器下的 Create 操作方法:


public ActionResult Create()
{       return View();
}


对应的视图文件:

@model AspNetMvc.QuickStart.Models.Student
    ViewBag.Title = "Create";
<h2>Create</h2>
 @using (Html.BeginForm())
    @Html.AntiForgeryToken()   
    <div class="form-horizontal">
        <h4>Student</h4>
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })        <div class="form-group">
            @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })            <div class="col-md-10">
                @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })            </div>
        </div>
              @* 省略 Gender Major EntranceDate *@       
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>} 
    @Html.ActionLink("Back to List", "Index")</div>
 @section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}


首先定义了视图中使用的模型类型是 Student ,这样 LabelFor 强类型辅助方法就可以从模型元数据中获取需要显示的文本。

页面打开时,由于并未传入任何模型对象,所以 Model 为空对象,如下所示:

所以页面上默认的输入框都是空的,截图中是作者输入值后的效果。

Html.BeginForm() 会在页面上生成一个 form 标签,默认的提交地址还是当前页面( action=/Students/Create ),默认的请求方法是 post ,如下所示:


因此,点击 [Create] 按钮时,会发出一个 POST 请求到后台,对应于 Students 控制器的 Create 方法。

保存数据与模型绑定

下面我们来看下拥有 [HttpPost] 元数据的 Create 方法:


[HttpPost]
[ValidateAntiForgeryToken]public ActionResult Create([Bind(Include = "ID,Name,Gender,Major,EntranceDate")] Student student)
{       if (ModelState.IsValid)
              db.Students.Add(student);
              db.SaveChanges();              return RedirectToAction("Index");
       return View(student);
}


这里面有两个安全措施:

1. ValidateAntiForgeryToken :用来阻止 CSRF (跨站请求伪造)。

2. Bind :用来阻止 Over-Posting (过多提交攻击)。

这两个安全手段我们会在以后的文章中详细介绍,这里就先略过。

我们先看下本次请求的 POST 参数:




但是 Create 方法中只有一个 Student 对象参数,是不是很神奇,其实这是一个重要的概念 模型绑定

如果在 WebForms 中,我们可以会写一堆代码来从 Request.Form 中获取参数,并重建 Student 对象,类似如下代码:


Student student = new Student();