zoukankan      html  css  js  c++  java
  • 【第四篇】ASP.NET MVC快速入门之完整示例(MVC5+EF6)

    目录

    【第一篇】ASP.NET MVC快速入门之数据库操作(MVC5+EF6)

    【第二篇】ASP.NET MVC快速入门之数据注解(MVC5+EF6)

    【第三篇】ASP.NET MVC快速入门之安全策略(MVC5+EF6)

    【第四篇】ASP.NET MVC快速入门之完整示例(MVC5+EF6)

    【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)

     

    请关注三石的博客:http://cnblogs.com/sanshi

     

    完善数据注解

    到目前为止的表格页面效果:

     

     

    我们需要更多的数据注解,来限制各个属性,以及提供显示用的名称(而不是英文字符串):

     
    public class Student
    {
           public int ID { get; set; }
     
           [Display(Name = "姓名")]
           [Required]
           [StringLength(200, MinimumLength = 2)]
           public string Name { get; set; }
           
           [Display(Name = "性别")]
           [Required]
           [Range(0, 1)]
           public int Gender { get; set; }
     
     
           [Display(Name = "所学专业")]
           [Required]
           [StringLength(200)]
           public string Major { get; set; }
     
     
           [Display(Name = "入学日期")]
           [DataType(DataType.Date)]
           [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
           public DateTime EntranceDate { get; set; }
    }
     

     

     

    再次运行,表格页面效果:

     

     

    完善性别的显示

    表格页面-性别列显示为中文

    这个比较简单,将原来的:

    @Html.DisplayFor(modelItem => item.Gender)

     

     

    修改为:

     
    @if (item.Gender == 1)
    {
           @:男
    } else
    {
           @:女
    }
     

     

     

    新建编辑页面-性别显示为下拉列表

    原来的编辑页面:

     

     

    性别字段的编辑框是通过如下方式生成的:

    @Html.EditorFor(model => model.EntranceDate, new { htmlAttributes = new { @class = "form-control" } })

     

     

    Html辅助方法EditorFor会查看模型属性的类型,自动生成对应的表单输入框。由于性别字段是整形,所以这里默认会生成一个数字输入框。

     

    为了更加友好的显示,我们将性别改为下拉列表,并且仅允许用户从下拉项中选择。首先我们需要准备下拉列表选项的集合,并通过控制器传递给视图使用:

     

    定义获取性别集合的函数,由于需要多个地方使用,所以提取成一个公共方法:

     
    private List<SelectListItem> GetGenderList()
    {
           return new List<SelectListItem>() {
                  new SelectListItem
                  {
                         Text = "男",
                         Value = "1"
                  },new SelectListItem
                  {
                         Text = "女",
                         Value = "0"
                  }
           };
    }
     

     

     

    通过ViewBag.GenderList传入视图:

     
    // GET: Students/Edit/5
    public ActionResult Edit(int? id)
    {
           if (id == null)
           {
                  return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
           }
           Student student = db.Students.Find(id);
           if (student == null)
           {
                  return HttpNotFound();
           }
     
           ViewBag.GenderList = GetGenderList();
           return View(student);
    }
     

     

     

    视图中通过DropDownListFor强类型辅助方法,来显示下拉列表以及选中项:

    @Html.DropDownListFor(model => model.Gender,
    ViewBag.GenderList as IEnumerable<SelectListItem>, new { @class = "form-control" })

     

     

     

     

    表单提交时的代码和之前一样,多了一个对GetGenderList的调用:

     
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit([Bind(Include = "ID,Name,Gender,Major,EntranceDate")] Student student)
    {
           if (ModelState.IsValid)
           {
                  db.Entry(student).State = EntityState.Modified;
                  db.SaveChanges();
                  return RedirectToAction("Index");
           }
     
           ViewBag.GenderList = GetGenderList();
           return View(student);
    }
     

     

     

    这一点非常重要,虽然正常的提交操作不会再次返回当前视图(RedirectToAction直接指定了页面跳转),但是在模型绑定失败时(尝试禁用JavaScript,姓名留空,然后提交表单),如果不重新设置ViewBag.GenderList参数就会出错:

     

     

    表单检索

    下面我们为表格页面增加一个搜索表单,用来对表格数据进行过滤。

     

    先增加一些记录:

     

     

    添加表单检索字段:

     
    @using (Html.BeginForm())
    {
        @Html.AntiForgeryToken()
        <p>
            所学专业: @Html.DropDownList("Major",
    ViewBag.MajorList as IEnumerable<SelectListItem>, "全部")
            姓名: @Html.TextBox("Name")
            <input type="submit" value="检索" />
        </p>
    }
     

     

     

    由于本示例比较简单,没有单独的表来存储所学专业,因此我们需要从用户表中检索,并存储到ViewBag.MajorList中传入视图:

     
    private List<SelectListItem> GetMajorList()
    {
           var majors = db.Students.OrderBy(m => m.Major).Select(m => m.Major).Distinct();
     
           var items = new List<SelectListItem>();
           foreach(string major in majors)
           {
                  items.Add(new SelectListItem {
                         Text = major,
                         Value = major
                  });
           }
           return items;
    }
     
    // GET: Students
    public ActionResult Index()
    {
           ViewBag.MajorList = GetMajorList();
           return View(db.Students.ToList());
    }
     

     

     

    页面运行效果:

     

     

    增加POST请求的处理方法:

     
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Index(string Major, string Name)
    {
           var students = db.Students as IQueryable<Student>;
           if (!String.IsNullOrEmpty(Name))
           {
                  students = students.Where(m => m.Name.Contains(Name));
           }
     
           if (!String.IsNullOrEmpty(Major))
           {
                  students = students.Where(m => m.Major == Major);
           }
     
           ViewBag.MajorList = GetMajorList();
           return View(students.ToList());
    }
     

     

     

    此时的运行效果:

     

     

    数据库分页

    分页工具条

    首先改造视图代码,增加分页工具条:

     
    <div id="pagebar">
        @for (var i = 0; i < ViewBag.PageCount; i++)
        {
            if (i == ViewBag.PageIndex)
            {
                <span class="currentpagenumber">@(i + 1)</span>
            }
            else
            {
                <a class="pagenumber" href="javascript:;">@(i + 1)</a>
            }
        }
    </div>
     

     

     

    其中ViewBag.PageIndex和ViewBag.PageCount是由控制器传入的分页参数,我们需要这两个数据来构造分页链接,如果是当前分页就显示为文本,如果是其他页就显示为超链接,然后通过客户端JavaScript来注册点击事件。

     

    EF的数据库分页

    后台控制器代码:

     
    private static readonly int PAGE_SIZE = 3;
     
    private int GetPageCount(int recordCount)
    {
           int pageCount = recordCount / PAGE_SIZE;
           if (recordCount % PAGE_SIZE != 0)
           {
                  pageCount += 1;
           }
           return pageCount;
    }
     
    private List<Student> GetPagedDataSource(IQueryable<Student> students,
    int pageIndex, int recordCount)
    {
           var pageCount = GetPageCount(recordCount);
           if (pageIndex >= pageCount && pageCount >= 1)
           {
                  pageIndex = pageCount - 1;
           }
     
           return students.OrderBy(m => m.Name)
          .Skip(pageIndex * PAGE_SIZE)
          .Take(PAGE_SIZE).ToList();
    }
     
    // GET: Students
    public ActionResult Index()
    {
           var students = db.Students as IQueryable<Student>;
           var recordCount = students.Count();
           var pageCount = GetPageCount(recordCount);
          
           ViewBag.PageIndex = 0;
           ViewBag.PageCount = pageCount;
     
           ViewBag.MajorList = GetMajorList();
           return View(GetPagedDataSource(students, 0, recordCount));
    }
     

     

     

    EF为我们封装了大部分的细节,所以上面的数据库分页代码非常直观和容易理解:

    students

           .OrderBy(m => m.Name)

           .Skip(pageIndex * PAGE_SIZE)

           .Take(PAGE_SIZE).ToList()

     

    完成一个典型的数据库分页需要如下几部:

    1. OrderBy:指定排序列
    2. Skip:跳过多少条记录
    3. Take:返回的最大记录数

     

    上面的OrderBy是必须指定的,否则就会报错:

     

     

    分页SQL语句

    完成上面的代码,分页效果已经出来了:

     

     

    下面,我们使用第三方Express Profiler工具来检查EF生成的数据库分页SQL语句。

     

    首先下载工具:

    http://expressprofiler.codeplex.com/

     

    打开Express Profiler,在Server文本框中输入(LocalDb)MSSQLLocalDB,如果你使用的VS2013,这个字符串可能是:(LocalDb)v11.0,点击绿色的启用按钮:

     

     

    运行我们的示例,转到学生列表页面,然后清空Express Profiler中的全部显示,再点击第二页:

     

     

    可以看到这里有3次SQL查询,这个和我们的心理预期是一样的:

    1. 第一次SQL查询:总记录数

    SELECT

        [GroupBy1].[A1] AS [C1]

        FROM ( SELECT

            COUNT(1) AS [A1]

            FROM [dbo].[Students] AS [Extent1]

        )  AS [GroupBy1]

    go

     

    对应的C#代码:

    var students = db.Students as IQueryable<Student>;

    var recordCount = students.Count();

     

    1. 第二次SQL查询:所学专业集合(去除重复)

    SELECT

        [Distinct1].[Major] AS [Major]

        FROM ( SELECT DISTINCT

            [Extent1].[Major] AS [Major]

            FROM [dbo].[Students] AS [Extent1]

        )  AS [Distinct1]

    go

     

    对应的C#代码:

    var majors = db.Students.OrderBy(m => m.Major).Select(m => m.Major).Distinct();

     

    1. 第三次SQL查询:分页数据

    SELECT

        [Extent1].[ID] AS [ID],

        [Extent1].[Name] AS [Name],

        [Extent1].[Gender] AS [Gender],

        [Extent1].[Major] AS [Major],

        [Extent1].[EntranceDate] AS [EntranceDate],

        [Extent1].[Job] AS [Job]

        FROM [dbo].[Students] AS [Extent1]

        ORDER BY [Extent1].[Name] ASC

        OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY

    go

     

    对应的C#代码:

    return students.OrderBy(m => m.Name)

    .Skip(pageIndex * PAGE_SIZE)

    .Take(PAGE_SIZE).ToList();

     

    这个查询顺序也和前面的EF代码的执行顺序一模一样,可以再回过头看下控制器Index方法。

     

    同时处理表单检索和数据库分页

    不过目前遇到点难题,我们希望实现如下两个功能:

    1. 点击分页链接时会发出HTTP POST请求,在请求参数中带上表单检索值。
    2. 表单检索时,在请求参数中带上当前所在的分页索引。

     

    实现这两个功能才算完善,否则表单检索时如果丢失分页参数,就会回到第一页;而分页时如果丢失表单参数,就会清空表单输入框。

     

    是不是开始怀念WebForms了,在WebForms中整个页面都被包含在一个表单中,因此回发时根本不需要考虑哪些参数后台需要。而MVC中这个就需要我们操心了,毕竟在灵活性的面前,便利性就会有所打折。

     

    我们采取的办法是扩充前面的form标签,加入PageIndex隐藏字段,然后点击分页链接时提交表单即可:

     
    @using (Html.BeginForm("Index", "Students", FormMethod.Post, new { id = "searchForm" }))
    {
        @Html.AntiForgeryToken()
        <p>
            所学专业: @Html.DropDownList("Major",
    ViewBag.MajorList as IEnumerable<SelectListItem>, "全部")
            姓名: @Html.TextBox("Name")
            <input type="hidden" id="PageIndex" name="PageIndex" value="0" />
            <input type="button" id="searchButton" value="检索" />
        </p>
    }
     

     

     

    注册JavaScript脚本来处理点击[检索]按钮和分页链接:

     
    @section scripts {
        <script>
            function submitForm(pagenumber) {
                pagenumber = parseInt(pagenumber, 10);
                $('#PageIndex').val(pagenumber - 1);
                $('#searchForm').submit();
            }
     
            $(function () {
     
                $('#searchButton').click(function () {
                    submitForm($('#pagebar .currentpagenumber').text());
                });
     
                $('#pagebar .pagenumber').click(function () {
                    submitForm($(this).text());
                });
     
            });
        </script>
    }
     

     

     

    现在看下效果,首先检索所学专业:

     

     

    然后点击第二页,会发出一个POST请求:

     

     

    可以看到本次请求,上面的用户输入和PageIndex都发送到了控制器处理方法:

     
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Index(string Major, string Name, int PageIndex)
    {
           var students = db.Students as IQueryable<Student>;
           if (!String.IsNullOrEmpty(Name))
           {
                  students = students.Where(m => m.Name.Contains(Name));
           }
     
           if (!String.IsNullOrEmpty(Major))
           {
                  students = students.Where(m => m.Major == Major);
           }
     
     
           var recordCount = students.Count();
           var pageCount = GetPageCount(recordCount);
           if (PageIndex >= pageCount && pageCount >= 1)
           {
                  PageIndex = pageCount - 1;
           }
     
           students = students.OrderBy(m=>m.Name)
                .Skip(PageIndex * PAGE_SIZE).Take(PAGE_SIZE);
     
           ViewBag.PageIndex = PageIndex;
           ViewBag.PageCount = pageCount;
     
           ViewBag.MajorList = GetMajorList();
           return View(students.ToList());
    }
     

     

     

    这里需要注意一点:先进行表单过滤,然后执行获取总记录数的查询,最后再获取分页数据。这个顺序不能变,因为表单过滤后总记录才能确定下来。

     

    小结

    本篇文章对示例进行了完善,首先是添加更多的数据注解,然后将显示的性别由数字改为字符串,对于编辑和新建页面,将性别渲染为下拉列表。然后为表格页面增加表单检索功能,可以根据[所学专业]和[姓名]对表格过滤,最后完成了数据库分页功能。

    本系列文章至此已经完成了,下面我们简单总结下用到的关键词:路由引擎、控制器向视图传值、强类型辅助方法、模型绑定、数据注解、数据迁移、客户端验证、服务器端模型验证、模拟POST请求、表单身份验证、跨站请求伪造、过多提交攻击、表单检索、数据库分页。

    下载示例源代码

    文章出处:http://www.cnblogs.com/sanshi/p/6211259.html

  • 相关阅读:
    典型用户与场景
    团队工作总结
    《构建之法》第8,9,10章
    冲刺1,2,3,4,5,天
    Github以及推广
    四则运算安卓版ver.mk3
    软件工程APP进度更新
    团队会议之事后工作总结
    构建之法 13-17章有感
    对于其他小组评论的反馈
  • 原文地址:https://www.cnblogs.com/net-sky/p/9236009.html
Copyright © 2011-2022 走看看