MVC:使用模型来建立前台页面

Come fron old posts in MVC:使用模型来建立前台页面  , 2016-09-14

很久没有看MVC了,之前学了一点就搁置了,今天继续捡起来,毕竟Mvc还是很重要很重要的,也是接下去一段时间我应该着重花力气去学习的。

上一次的MVC博客已经把实体框架的运用交代了,那么接下来我需要让网页真正成为网页,而不再是一堆数据表。

————–以上是草稿,二十三天前的,太颓废了!————–

下面正经开始写。

在.NET MVC 中可以通过几种方式来构建前台网页,比如强类型,动态或者使用内置的ViewBag来风骚地构建。我因为之前写UWP的关系所以还是比较适应用强类型来写,辅以ViewBag看来偷个懒,感觉还是很舒服的。动态的话似乎就是更纯粹地web编程习惯了,不是很了解,先不管。我们来看看怎么使用强类型地逻辑构建我们的网页

通过前几次的blog,我们已经建立了实体类,并且搞定了数据库,所有需要的数据都已经完备了。那么基于EntityFramework,我们需要建立一个控制器(controller),来驱动数据和前台的交互。在VS可以通过建立控制器之间完成前台页面增删改查地建立,实在是太偷懒了,也不适合我学习,所以我使用的是单纯建立控制器,而不建立视图,自己一句一句写。

以SchoolData模型来建立控制器,得到SchoolDataController:

namespace MVCTest001.Controllers {
    public class SchoolDataController : Controller {
        SCDBContext scdb = new SCDBContext();

    }
}

先来里面什么都没有啊,但是考虑到数据库上下文肯定是要经常用到的,所以在字段里面先声明好,以备后续操作的使用。

考虑到数据库的四个基本操作,我们需要建立4个页面和一个默认主页,分别负责增删改查部分,这里就不写了,级的建立的是*.cshtml类型的网页,使用Razor语法来实现。

首先从首页-Index.cshtml来开始

1.DropDownList的实现

因为主页名是Index,所以我们需要在控制器里增加一个Index方法,来通过控制器路由指向这个页面,这个地方很好理解。我想要在Index页面显示数据库里面SchoolData部分的信息展示,还希望通过姓名来筛选显示,需要一个DropDownList来实现。需求很明确,我们该怎么做呢?

先是一个客户端思维地实现DropDownList地方式,彻底的强类型。首先形成list内容地数据源,并通过ViewBag交给页面部分:

var FirstNamesList = new List<string>();
            var FirstSearchData = from item in scdb.Students
                             orderby item.FirstMidName
                             select item.FirstMidName;

            FirstNamesList.AddRange(FirstSearchData.Distinct());
            ViewBag.FirstNamsList = new SelectList(FirstNamesList);

然后在页面部分取出转化成对应的类型,通过内置辅助类形成DropDownList,并构建Get表单:

@using (Html.BeginForm("Index", "SchoolData", FormMethod.Get)) {

        <div class="col-md-10">
            姓: @Html.DropDownList("firstName", ViewBag.FirstNamsList as SelectList, htmlAttributes: new { @class = "form-control" })
            名: @Html.TextBox("lastName", null, htmlAttributes: new { @class = "form-control" })
            <br />
            <br />
            <input type="submit" value="筛选" class="btn btn-default" />
        </div>
    }

上面的做法我是完全按照自己的思路来写的,和web正常的写法相比,的确是很古怪,但的确可以运行实现。

接下来我们看看官方的实现方法,这里是CourceData地实现,逻辑是类似的:

var listResource = db.Departments.ToList();
            ViewBag.SelectedDepartment = new SelectList(listResource, "DepartmentID", "Name", SelectedDepartment);
@using (Html.BeginForm()) {
    <p>
        Select Department: @Html.DropDownList("SelectedDepartment", "All")
        <input type="submit" value="Filter" />
    </p>
}

不同的地方是很明显的,这个list,显示的内容是“Name”字段,但每个Item实际上是“DepartmentID”,选中的也是这个,最终提交地也是ID。而我那个做法显示地内容和值内容都是相同的,显然通过控制器,后者更能适应Web。

2.按照DropDownList的选择来GET数据

这里还没有结束,因为Submit之后将重新GET一次页面信息,所以我们需要根据GET的内容来完成Index方法地其余部分。

如果是我的写法,那么接下来:

// GET: SchoolData
        [HttpGet]
        public ActionResult Index(string firstName,string lastName) {
            // for DropdownList source
            var FirstNamesList = new List<string>();
            var FirstSearchData = from item in scdb.Students
                             orderby item.FirstMidName
                             select item.FirstMidName;

            FirstNamesList.AddRange(FirstSearchData.Distinct());
            ViewBag.FirstNamsList = new SelectList(FirstNamesList);

            // for Content Data in tsble
            var studentData = from item in scdb.Students
                              orderby item.FirstMidName
                              select item;

            if (!string.IsNullOrEmpty(firstName))
                studentData = studentData
                    .Where(i => i.FirstMidName.Contains(firstName))
                    .OrderBy(s=>s.FirstMidName);

            if (!string.IsNullOrEmpty(lastName))
                studentData = studentData
                    .Where(i => i.LastName.Contains(lastName))
                    .OrderBy(s => s.FirstMidName);

            return View(studentData);
        }

我传入的参数分别是“firstName”和“lastName”,经过判断,重新查询数据库上下文,并返回正确的视图,当然如果没有参数的情况也要考虑到。

如果按照默认的方式,Get方法应该是这样的:

// GET+POST: CourseData/Index
        public ActionResult Index(int? SelectedDepartment) {
            var listResource = db.Departments.ToList();
            ViewBag.SelectedDepartment = new SelectList(listResource, "DepartmentID", "Name", SelectedDepartment);
            int value = SelectedDepartment.GetValueOrDefault();

            var courses = db.Courses
                .Where(i => !SelectedDepartment.HasValue || i.DepartmentID == value)
                .OrderBy(s => s.CourseID)
                .Include(c => c.Department);

            return View(courses.ToList());
        }

由于传入的是ID,所以使用ID(ID是当前数据表的主键,所以很方便)获取试图更新就显得非常简单了。

3.将GET到的数据以表格方式呈现

那么通过GET方法得到了我们需要去呈现的数据,下一步该怎么做?

如果是我的GET方法,获取到的是一个SchoolData(IOrderedQueryable<Student>)对象,如果通过内置方法,得到的是courses.ToList(),就是一个List对象。都是一个类似List逻辑的集合对象,而且通过了View返回到前台视图了,这就好办了。

由于是强类型方式,我们可以在前台定义内置的视图模型Model

@model IEnumerable<MVCTest001.Models.Course>

这里强制将我传送过来的集合数据类型进行转化,公开了枚举数,这样很利于foreach进行集合迭代。这里使用Razor语法,而不是脚本来编程构建网页的表格内容:

<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.FullName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.FirstMidName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.LastName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.EnrollmentDate)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.ID)
        </th>
        <th></th>
    </tr>

    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.FullName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ID)
            </td>
            <td>
                @Html.ActionLink("编辑", "Edit", new { id = item.ID }) |
                @Html.ActionLink("细节", "Details", new { id = item.ID }) |
                @Html.ActionLink("删除", "Delete", new { id = item.ID })
            </td>
        </tr>
    }

</table>

最后获取到的是这样的HTML显示效果:

 

jhvcvcjkhc

通过POST将修改提交到服务器

如果需要从客户端修改服务端的数据,就可以使用HttpPost的方式,那么POST在MVC是如何来构建的呢?

我们先以创建数据条目来进入话题

1.首先以GET获得Create准备页面

首先我们需要通过控制器将页面导航到Create页面,我们需要在Index页面加入一个链接:

<p>@Html.ActionLink("添加学生信息", "Create")</p>

这里调用的是ActionLink的一个重载方法,省略了控制器,那么就表示在本控制器内部进行跳转。显示的Html内容是“添加学生信息”,指向的ActionName(方法名)是“Create”。

所以我们在控制其内部增加一个“Create”方法,返回“Create.cshtml”页面视图:

// 很简单......这的确有点智障
public ActionResult Create() {
            return View();
        }

当然光有这样一个方法其实是不够的,因为导航到的Create页面,所有的内容都还没有所以必须完成一个Post方法来提交数据到服务器。

2.完成POST方法

不过首先我们应该来看看前台的Html的结构:

@model MVCTest001.Models.Student

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

<h2>添加学生信息</h2>
@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()

    @Html.ValidationSummary(true, "", new { @class = "text-danger" })

    <div class="form-group">
        @Html.LabelFor(model=>model.FirstMidName, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model=>model.FirstMidName, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model=>model.FirstMidName, null, new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.LastName, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.LastName, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.LastName, null, new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.EnrollmentDate, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.EnrollmentDate, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.EnrollmentDate, null, new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        <input type="submit" value="确认" class="btn btn-default" />
    </div>

}

<div class="form-group">
    @Html.ActionLink("放弃并退回", "Index")
</div>

Html.ValidationMessageFor()是错误检查,通过定义模型时候的特性来检查输入是否符合规则,剩下的很好理解了,一个条目一个坑,自己填上去然后Submit。那么如何来构造Post的后台方法呢?

如果是按照我的写法,那应该是把输入的个条数据通过表单传到后台,写入模型实例对应的字段,然后调用SaveChanges来保存数据。然而MVC在这方面是有完全不同的变化的,MVC提供了一整套简化的操作,不再需要一一手动去操作字段了。具体是怎样的我们来看这段代码:

[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(
            [Bind(Include = "FirstMidName,LastName,ID,EnrollmentDate,FullName,Enrollments")]
            Student student) {
            if (ModelState.IsValid) {
                scdb.Students.Add(student);
                scdb.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(student);
        }

首先通过特性来限定方法体是一个HttpPost方法,在通过特性声明出一个AntiForgeryToken产生一个虚拟的表框以简单地防止非网页地POST提交,是对应前台HTML代码中@Html.AntiForgeryToken()。

这里有看到了传入的参数是一个Student实例,并且通过特性,提供了有关于绑定到模型地信息。

[Bind(Include = "FirstMidName,LastName,ID,EnrollmentDate,FullName,Enrollments")]

MVC框架通过指明给定的字段栏位,和前台一一对应,将改变先缓存到上下文中地当前model,在调用SaveChange的时候完成数据的更新,而不再需要意义手动去修改或者创建。

ModelState.IsValid 当模型状态字典返回的实力经检查有效的时候,我们就可以调用数据库操作并保存我们的改动,并将页面重定向到Index页面;否则,返回当前的实力到前台,就像前面的GET一样,让用户有机会修改,当然还会提示错误的信息。

Create部分就这样完成了,那同理修改删除也就类似了,只是操作数据库变更的那条指令会有区别。整个数据库单表增删改查功能就算是完成了。

226 thoughts on “MVC:使用模型来建立前台页面”

  1. Hello, Neat post. There’s a problem with your site in internet explorer, would check this?

    IE still is the market leader and a big component of folks will leave out your fantastic writing because of this problem.

  2. This is the right blog for everyone who wants to understand this
    topic. You understand a whole lot its almost hard to argue with you
    (not that I really will need to?HaHa). You definitely put a fresh spin on a subject that’s been discussed for years.
    Great stuff, just wonderful!

  3. First of all I want to say terrific blog! I had a quick question in which I’d like
    to ask if you do not mind. I was curious to know how you center yourself and clear your thoughts prior to writing.
    I have had a hard time clearing my thoughts in getting my thoughts out.
    I truly do take pleasure in writing however it just seems
    like the first 10 to 15 minutes tend to be wasted just trying to figure
    out how to begin. Any ideas or tips? Thank you!

Leave a Reply

Your email address will not be published. Required fields are marked *