MVC: Unit Testing in MVC

Today is the second day since I changed my blog websites in WordPress, so it’s time to add something new into my poor repository.

I have described my opinions on ASP.NET MVC5 by the old posts, and then I began learning this open-source framework for some weeks, today I will write something that I have learnt about the Unit-Test feature in ASP.NET MVC5. Now, let’s begin.

As we all know that unit-test is very important to an application in the patterns or opinions of extensibility, flexibility and modern.

There is a description to Unit-Testing in Wikipedia:

In computer programming, unit testing is a software testing method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine whether they are fit for use. Intuitively, one can view a unit as the smallest testable part of an application. In procedural programming, a unit could be an entire module, but it is more commonly an individual function or procedure. In object-oriented programming, a unit is often an entire interface, such as a class, but could be an individual method. Unit tests are short code fragments created by programmers or occasionally by white box testers during the development process. It forms the basis for component testing.
Ideally, each test case is independent from the others. Substitutes such as method stubs, mock objects,[5] fakes, and test harnesses can be used to assist testing a module in isolation. Unit tests are typically written and run by software developers to ensure that code meets its design and behaves as intended.

So the problem here is what should we do if we want to add the feature of unit-testing into our MVC project. Come with me, and the steps are over here:

  1. A demo of ASP.NET MVC prepared;
  2. Visual Studio 2015 or any other versions supported;
  3. Network is necessary, that’s all.

Now if all of them prepared, we are ready to begin.

1st: Create your code logic

The first step to do is creating a new controller with empty-module because I assume that the demo you have created is empty.

Like this:

namespace EssentialTools.Controllers {
    public class HomeController : Controller {
        public ActionResult Index() {
            return View();
        }
    }
}

Oh, it’s so empty that it makes you feel terrible, right? Well, it’s none of my business, aha. And in fact, we really needn’t too much code here because it’s only a demo. Then we should create a model class which we will create logic with. The name of this class is not important, just like what I have done:

namespace EssentialTools.Models {
    public class Product {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }

    public class MinimumDiscountHelper : IDiscountHelper {
            public decimal ApplyDiscount(decimal totalParam) {
                if (totalParam < 0)
                    throw new ArgumentOutOfRangeException();
                if (totalParam < 10)
                    return totalParam;
                if (totalParam <= 100)
                    return totalParam - 5;
                else
                    return totalParam * 0.9M;
            }
        

}
The interface here you can ignore because  here doesn’t involve the concept of Inversion of control, Dependency injection or the loosely-coupled components.
I have add a new Helper in this class because it’s the guy that well will test in the Unit-Testing after. It means that , if you cost more than 100 dollars, you will enjoy 90 percent discount, and if you cost between 10 and 100 dollars, the consumption will minus 5 dollars, and if you cost less than 10 dollars, you won’t enjoy discount any more. But it’s obviously that your consumption can’t less than zero, it’s impossible, so the negative numbers input is invalid and system will throws an ArgumentOutOfRangeException.

 

Ok, the preparation is well done, and we will enter the next.

2nd: Add Unit-Testing project in this solution

Add a new project which pattern is “unit-test”, and add the necessary reference to it. I will use the default framework of unit-testing in ASP.NET MVC , but any other third-party framework is also allowed because the MVC is extensible.

The new test project will have a default class like this:

namespace EssentialTools.Tests {
    [TestClass]
    public class UnitTest1 {

        [TestMethod]
        public void TestMethod01() {

        }
     }
}

It’s empty as well, so we add some code into it:

namespace EssentialTools.Tests {
    [TestClass]
    public class UnitTest1 {

        private MinimumDiscountHelper getTestObject() {
            return new MinimumDiscountHelper();
        }

        [TestMethod]
        public void Discount_Above_100() {

            MinimumDiscountHelper target = getTestObject();
            decimal total = 200;

            var discountTotal = target.ApplyDiscount(total);

            Assert.AreEqual(total * 0.9M, discountTotal);

        }

        [TestMethod]
        public void Discount_Between_10_And_100() {

            MinimumDiscountHelpertarget = getTestObject();

            var TenDollarDiscount = target.ApplyDiscount(10);
            var HundredDollarDiscount = target.ApplyDiscount(100);
            var FiftyDollarDiscount = target.ApplyDiscount(50);

            Assert.AreEqual(5, TenDollarDiscount);
            Assert.AreEqual(95, HundredDollarDiscount);
            Assert.AreEqual(45, FiftyDollarDiscount);
        }

        [TestMethod]
        public void Discount_Blow_10() {

            MinimumDiscountHelper target = getTestObject();

            var FiveDollarDiscount = target.ApplyDiscount(5);
            var ZeroDollarDiscount = target.ApplyDiscount(0);

            Assert.AreEqual(5, FiveDollarDiscount);
            Assert.AreEqual(0, ZeroDollarDiscount);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void Discount_Nagetive_Total() {

            MinimumDiscountHelper target = getTestObject();

            target.ApplyDiscount(-1);
        }
    }
}

Join the so long code here in sudden must makes confused, so I will give a explanation for this.

“[TestClass]” and “[TestMethod]” are attributes defined by the test-framework, and are the magic source of this unit-testing project. If you miss them, your classes or methods will be the same as the common classes and methods, and the unit-testing will not works. Of course, you can add common methods and classes in the test class, it’s allowed because you may need something to help building your test-project and this common code is necessary.

In unit-testing, there should be three steps to be done before running the tests. It’s Arrange-Act-Assert(AAA).

Arrange:

Your should make everything ready here, such as create instance and initialize data.

For example:

// Arrange
MinimumDiscountHelper target = getTestObject();
decimal total = 200;

We create a new instance of MinimumDiscountHelper , and initialize the price as 200 dollars.

Act:

After arranged, do the test at this place:

// Act
var discountTotal = target.ApplyDiscount(total);

Check the discount and get the result here.

Here is the main party of the test method, which contains the logic of this founction or feature, so write the code correctly or you will get a wrong result.

Assert:

After actions done, you should make a assertions here to check if the result equal to the expectations, if not, test failed.

// Assert
Assert.AreEqual(total * 0.9M, discountTotal);

The “Assert” is a static class which contains many static methods for check the assertions in this test period. Each of these methods has a override one to contain a special error-message for failed-result shown to the tester. Each of these return a Boolean value shown if the test passed or not.

In the last test method, there is a”[ExpectedException(typeof(ArgumentOutOfRangeException))]” defined at the head of the method, it means that the method will only throws “ArgumentOutOfRangeException” as a expected exception, and will fetch it, so you don’t need to use “try-catch” to get the exceptions. It’s obviously make your code clean and tidy.

We should follow these rules so that your code will be more readable and specific. Anyway, it’s only a suggestion.

3rd: Start Unit-Test work

Now, we can start the test work.

Click the panel “TEST” in the VS, and open the Test resource window. If you can’t see your test methods here, you should build your project first.

Select a method to run or run all of the test methods, to check if some error throws.

If some error throws, review your code and resolve the problems, until your screen change to the view like this:

screenshot-168

Well, my dashboard is not the same as it should be because I also have some different unit-testing work to do with. But in a word, all tests should be passed.

Anyway, if you build your unit-testing feature like this, it’s still weak and inflexible because it may go wrong if you change the Helper’s name, or even some small changes in the source code. Design by the loosely-coupled components and using interface is a good way to resolve this trouble, but it not the point here.

That’s all, so many syntax error because my horrible English….

I think I should have a rest, still in fever .

Continuous learning ASP.NET MVC5

Well, I admit that it is a meaningless post… or just a piece of status.

Got the ASP.NET MVC5 Master this Monday, I have read one-fifth of the content. There is no doubt that this book is excellent and the plenty of my puzzling problems have been resolved. I will keep taking notes .

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

MVC:对输入数据添加验证

Come from old posts in MVC:对输入数据添加验证  , 2016-08-01

数据验证是很常见的,比如网页需要你输入你的姓名,但你输入了一串数字(不知道数字是不是也可以当姓名( ▼-▼ )),这明显是不符合规则的,也不能任由这种数据存入数据存储

MVC框架里已经集成了JQuery验证功能,我们来一探究竟

1)建立数据验证条件

首先,我们看看如何给数据库条目添加限制,我还是采用用EntityFramework的独特方法来进行

打开定义好的数据实体类,比如有这些字段:

        public int ID { get; set; }

        public string Title { get; set; }

        public DateTime ReleaseDate { get; set; }

        public string Genre { get; set; }

        public decimal Price { get; set; }

        public string Rating { get; set; }

我们把他们添加标签限制如下:

        public int ID { get; set; }

        [StringLength(60,MinimumLength = 3)]
        public string Title { get; set; }


        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",ApplyFormatInEditMode = true)]
        public DateTime ReleaseDate { get; set; }


        [RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
        [Required]
        [StringLength(30)]
        public string Genre { get; set; }


        [Range(1,100)]
        [DataType(DataType.Currency)]
        public decimal Price { get; set; }


        [RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
        [StringLength(5)]
        public string Rating { get; set; }

[StringLength(*,MinimumLength = *)]:限制了字符串的最大长度和最小长度

[Display(Name = “******”)]:定义了字符串的显示名称

[DataType(******.**)]:定义了数据的存储类型

[DisplayFormat(DataFormatString = “{******}”,ApplyFormatInEditMode = true)]:定义了数据存储的格式,而且允许按照EditMode定义好的格式来显示

[RegularExpression(@”********”)]:需要匹配特定的正则表达式

[Required]:需要Value值,但比如一些值类型本身需要,就不用填了

[Range(1,100)]:值的取值范围

还有一些其他限制可以定义,可以阅读MSDN等获取具体的内容

想要数据库架构变更生效你需要这么做:

add-migration DataAnnotations
update-database

依然是在PM控制台进行,具体参考上一篇博客内容 [ MVC:Code First Migrations  ],这里不赘述了

验证规则会自动执行由.NET 框架有助于使您的应用程序更加健壮。它还确保,你不能忘验证一些东西,无意中让坏的数据到数据库

2)错误UI的显示与处理

JQuery真正的好处在于我们不需要在代码中增加哪怕一条判断信息,当信息检查不通过,是无法触发POST提交的,只会重新导行到当前页面

只需要这样组织HTML部分:

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

ValiMess部分就会按照预定检测Edit中的内容是否符合规范,当所有检测通过,当前页面状态变为Valid,才允许POST发送表单内容,并相应数据库变更:

// GET: PersonalModels/Create
        public ActionResult Create() {
            return View();
        }

        // POST: PersonalModels/Create
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "ID,Title,ReleaseDate,Genre,Price,Rating")] PersonalModel personalModel) {
            if(ModelState.IsValid) {
                db.Personal.Add(personalModel);
                db.SaveChanges();
                return RedirectToAction("Index");
            }

            return View(personalModel);
        }

如果想要在以后更改的验证逻辑,你可以在一个地方通过将验证属性添加到模型,而不必担心不符合规则 。 如何强制执行的应用程序的不同部分 — — 所有的验证逻辑会在一个地方定义和使用。这使代码非常干净,并使它容易维护和发展。这也意味着你会充分尊重DRY原则(单一,不重复)。

3)一些需要注意的地方

对于多浏览器支持还有些地方需要注意

1.如果你使用日期字段的DataType属性,你必须也指定 DisplayFormat属性,以确保该字段在 Chrome 浏览器中都能正确呈现

2.jQuery 验证不能与范围属性和日期时间。例如,下面的代码将始终显示客户端验证错误,即使在指定范围内的日期是︰

[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]

需要禁用 jQuery 日期验证与日期时间使用范围的属性。但通常在模型中编译硬日期不是很好的做法。

3.可以将代码放在一行上显示:

[Display(Name = “Release Date”),DataType(DataType.Date)]

MVC:Code First Migrations

Come from old posts in MVC:Code First Migrations , 2016-08-01

有必要先来了解一下什么叫做CodeFirstMigrations(代码先行的迁移 ):

应用程序开发领域中的代码先行(Code First)方法指的是先在代码中创建对象模型,然后按照模型生成数据库结构。这与“模型先行开发”类似,那种方式会使用Visual Studio中的EF设计器创建数据模型。Code First Migrations这种工具可以基于代码中所做的改变,以递增的方式更新已存的数据库结构。这可以按照自动或者手动的方式进行,采用哪种方式依赖于对模型所做出的更新的类型。能够自动完成的变更类型有:

  • 增加属性或者类
  • 对属性和类重命名(想要使其正常工作,需要编写一些脚本)
  • 对列(column)或者表(table)重命名,而不对属性或类重命名
  • 删除属性

在任何一种情况下,执行命令Update-Database就会自动把变更应用到数据库结构上。

对于所有其它更新,Code First Migrations会使用Add-Migration命令创建变更数据库的脚本。这些迁移操作都会保存,并加上时间戳,让我们可以回滚到数据库结构的上一个版本。除了能够对数据库结构做出变更之外,迁移操作中还可以包括用于操作数据的SQL命令。想要提交迁移操作,你只需要再次运行Update-Database命令。为了对数据库进行复制,Code First Migrations也可以使用Update-Database来创建SQL脚本:

Update-Database –TargetDatabase:"NewDatabase" –Script

1)CodeFirstMigrations

执行CodeFirstMigrations 代码先行的迁移 ,我们需要这么做:

1.打开数据迁移功能(Enable Code First Migrations)

Nuget管理器->程序包管理控制台:

Enable-Migrations -ContextTypeName MVCTest001.Models.DBContext
正在检查上下文的目标是否为现有数据库...
已为项目 MVCTest001 启用 Code First 迁移。

这里需要注意,针对旧blog没有提及的问题说明,当Context上下文不在MVC主项目中的时候,需要改变指令的语法如下:

PM> Enable-Migrations -ProjectName MVCTest001.CoreProjectXXX.Models
正在检查上下文的目标是否为现有数据库...
已为项目 MVCTest001 启用 Code First 迁移。
PM>

开启数据迁移设置完成,后续的操作都按照上述的规则来处理

2.如果尝试再次进行迁移设置指令,就会得到这样的反馈:

PM> Enable-Migrations -ContextTypeName MVCTest001.Models.DBContext
已在项目“MVCTest001”中启用迁移。若要覆盖现有迁移配置,请使用 -Force 参数。

可以发现,指令不被允许,因为迁移已经开启

3.初始化数据迁移基架,并建立时间节点

PM> add-migration Initial
正在为迁移“Initial”搭建基架。
此迁移文件的设计器代码包含当前 Code First 模型的快照。
在下一次搭建迁移基架时,将使用此快照计算对模型的更改。
如果对要包含在此迁移中的模型进行其他更改,
则您可通过再次运行“Add-Migration Initial”重新搭建基架。
PM>

提示迁移完成,并按时间创立了新类,并包含了创建数据库架构的代码

4.进行数据库架构刷新,并运行Seed方法

PM> update-database
指定“-Verbose”标志以查看应用于目标数据库的 SQL 语句。
正在应用显式迁移: [201607311640461_Initial]。
正在应用显式迁移: 201607311640461_Initial。
正在运行 Seed 方法。
PM>

迁移结束,数据库架构初始化完成

2)为数据表增加条目

1.在实体类中添加新的属性(条目)

2.在View中进行所有必要的修改,在后台同样

以添加Rating为例,比如:

public ActionResult Create([Bind(Include = "ID,Title,ReleaseDate,Genre,Price,Rating")] PersonalModel personalModel) {
......

当然可能不止一处,需要全部增补完毕

在前台部分:

<td>
            <!-- ...... -->
</td>
        <td>
            @Html.DisplayFor(modelItem => item.Rating)
        </td>

以及所有需要增补的地方

3.这时候编译运行会报错

先看两个错误,(1):

自创建数据库,支持 ‘MovieDBContext’ 上下文模型已更改。考虑使用代码第一次迁移来更新数据库 (http://go.microsoft.com/fwlink/?LinkId=238269)。

(2):

Server error in “/” application ,the model backing the “……” context has changed since the database was created……

4.解决方案

因为数据库架构和实体类已经不同,这里我们通过数据库迁移的方法来解决

首先在Seed中添加你增补的条目,比如“Rating”,每一个单元都要增补

然后记得生成解决方案

接着在Nuget管理器->程序包管理控制台的输入和输出:

PM> add-migration Rating
正在为迁移“Rating”搭建基架。
此迁移文件的设计器代码包含当前 Code First 模型的快照。
在下一次搭建迁移基架时,将使用此快照计算对模型的更改。
如果对要包含在此迁移中的模型进行其他更改,
则您可通过再次运行“Add-Migration Rating”重新搭建基架。

这里要注意,“add-migration ******”的命名是随意的,处于可读性考虑,建议要像GitHub一样为commit的命名一样来处理

这样数据库就在基架框架中更新了条目,然后只要update数据库框架:

PM> update-database
指定“-Verbose”标志以查看应用于目标数据库的 SQL 语句。
正在应用显式迁移: [201608010340559_Rating]。
正在应用显式迁移: 201608010340559_Rating。
正在运行 Seed 方法。
PM>

这样数据库刷新完成,打开网页就能正常运行了,功能当然也正常