在 Mac 上使用 Asp.Net 5 与 Entity Framework 建立第一个 Blog 网站

几乎学习所有Web开发程序语言后的第一个综合练习课题就是如何建立一个 Blog ,这也是因为 Blog 的功能刚好满足了使用程序语言与数据库连接的基本操作,而在 Asp.Net 5 之后,由于跨平台支持的特性,也代表着我们不需要开 Windows 就可以撰写 Asp.Net 的应用程序,但离开 Windows 之后,也意味着没有了 Visual Studio 来帮助我们进行建立项目和开发,所有的开发方法和步骤都是新的尝试,所以再度拿出这个练习题来熟悉一下再好不过了,以下范例的进行主要是以在 Mac 上使用 Visual Studio Code 和 Console,搭配 Asp.Net 5 和 Entity Framework 7 的 RC1 版来进行操作,练习如何从无到有撰写一个 Blog 的网站。


Asp.Net 版本确认

在开始进行开发之前,必须要先确认 Asp.Net 5 的版本是否已确实安装至 Coreclr 的 RC1 版,由于目前版本变动的幅度还比较大,如果之前就已经有在 Mac 上安装好 Asp.Net 5 环境的话,可能必须要再确认是否有升级到最新版本,特别需要注意不要使用到 Mono 版的 Runtime ,必须要使用 Coreclr 版的 Runtime。

  1. 打开 Terminal ,使用以操作升级 dnvm

    dnvm update-self
  2. 升级 dnx coreclr 版本的 runtime 至 Rc1

    dnvm upgrade -r coreclr
  3. 确认使用的版本为 1.0.0-rc1-final , Runtime 是 Coreclr

    dnvm list

    dnvm list

建立 Blog 网站框架

在 Mac 环境上,由于没有 Visual Studio ,所以微软基于 yoeman 开发了一套使用 Command 产生开发范本的工具 generator-aspnet ,所有建立网站或是新增 Class 的工作都可以在 Terminal 透过 Command 来进行新增,这一点也会跟原本的操作习惯比较不一样,但是与其他语言(例如 Ror )的使用方式更贴近,目前 Visual Studio Code 还没有将新增程序范本的功能整合进去,或许未来会加到 Visual Studio Code 之中,不过如果想要在其他平台上开发或使用 .Net 应用程序的话,还是尽早的熟悉一下 Command 的操作会更好一些。

  1. 打开 Terminal ,使用命令产生 Asp.Net 5的项目程序,选择 Web Application Basic [without Membership and Authorization]

    yo aspnet

    New Website

  2. 输入网站名称 Blogging

    Website Name

  3. 切换至网站目录,还原套件并执行网站

    cd Blogging
    dnu restore #还原套件
    dnx web
  4. 打开浏览器,开启 http://localhost:5000 ,可以看到网站成功执行

    Launch Website

安装 EntityFramework 7

在 Windows 上使用 EntityFramework 进行网站开发时,我们通常会选择使用 Localdb 来搭配作为数据库的测试环境,而在 Mac 上是没有 SqlServer 可以使用的,所以在这次的范例中,我会像大家介绍如何使用 EntityFramework 与 Sqlite 进行搭配,作为开发环境使用测试。

  1. 打开 Terminal ,安装 EntityFramework 与 Sqlite 所需要的 Nuget 套件

    dnu install EntityFramework.Commands
    dnu install EntityFramework.Sqlite
  2. 安装成功之后,可以在 project.json 中,看到这两项出现在 dependencies

    Install EntityFramework

  3. 为了方便之后进行数据库操作,在 project.json 建立 EntityFramework 操作的捷径,在 project.json 的 commands 区段新增以操作

    "ef": "EntityFramework.Commands"

    Add Command

  4. 在 Terminal 测试命令是否正确

    dnx ef

    Test EntityFramework Command

建立 Blog 数据库与字段

  1. 打开 Terminal ,建立 Models 数据夹,以及建立 BlogDbContext 作为操作数据库的媒介

    mkdir Models
    cd Models
    yo aspnet:Class BlogDbContext

    这边我们也使用了 generator-aspnet 来帮助我们建立 Class ,建立的 Class 会像这样

    BlogDbContext

  2. 修改 BlogDbContext 继承 DbContext ,新增 Post Property,记得 using Microsoft.Data.Entity

    using Microsoft.Data.Entity;
    
    public class BlogDbContext: DbContext
    {
        public DbSet Posts { get; set; }
    }
  3. 新增 Post Class,设定所需要的字段

    yo aspnet:Class Post

    Post 的数据字段

    using System.ComponentModel.DataAnnotations;
    
    public class Post
    {
      [Key]
      public int Id { get; set; }
    
      [Required]
      public string Title { get; set; }
    
      public string Body { get; set; }
    
      public DateTime CreatedAt { get; set; }
    }
  4. 打开网站根目录的 Startup.cs ,修改 ConfigureServices 方法,将 BlogDbContext 注册到 Asp.Net 5 所内建的 DI Framework 中,并设定使用 Sqlite

    using System.IO;
    using Microsoft.Data.Entity;
    using Microsoft.Extensions.PlatformAbstractions;
    
    public void ConfigureServices(IServiceCollection services)
    {        
          // Add Entity Framework
       var path = PlatformServices.Default.Application.ApplicationBasePath;
       services.AddEntityFramework()
           .AddSqlite()
           .AddDbContext(
               option =>
               option.UseSqlite("Filename=" + Path.Combine(path, "blog.db")));
    
       // Add framework services.
       services.AddMvc();
    }
  5. 在网站根目录打开 Terminal ,建立新的 Migration 并更新数据库

    dnx ef migrations add InitialDatabase
    dnx ef database update

    操作画面

    Create Database

  6. 我们可以在网站根目录下发现建立了 blog.db ,如果在 Mac 上想要浏览 sqlite 数据库的话,也可以使用这套 Open Source 的 sqlite 浏览工具 sqlitebrowser

新增及浏览 Blog 功能

准备完数据库以及数据表之后,接下来我们要继续把 Blog 的新删修查功能完成,这部分与原本的 Asp.Net Mvc 使用方式几乎是一模一样。

(注:这边要自己写程序是因为在 Mac 上,目前还没有支持 Scaffold 的功能,如果使用 Windows 的 Visual Studio 是一样享有 Scaffold 尊荣级的待遇喔!)
  1. 在根目录打开 Terminal ,新增 PostController

    yo aspnet:MvcController PostController
  2. 打开 PostController ,增加新删修查的程序

    public class PostController : Controller
    {
        private BlogDbContext blogDbContext;
    
        public PostController(BlogDbContext dbContext){
            this.blogDbContext = dbContext;
        }
    
        // GET: //
        public IActionResult Index()
        {
            var posts = this.blogDbContext.Posts.ToList();
    
            return View(posts);
        }
    
         // GET: Posts/Details/5
        public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return new BadRequestResult();
            }
    
            Post post = this.blogDbContext.Posts.Where(i=> i.Id == id).FirstOrDefault();
            if (post == null)
            {
                return HttpNotFound();
            }
    
            return View(post);
        }
    
        // GET: Posts/Create        
        public IActionResult Create()
        {
            return View();
        }
    
        // POST: Posts/Create
        // 若要免于过量张贴攻击,请启用想要系结的特定属性,如需
        // 详细资讯,请参阅 http://go.microsoft.com/fwlink/?LinkId=317598。
        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Create([Bind("Id,Title,Body,CreatedAt")] Post post)
        {
            if (ModelState.IsValid)
            {
                this.blogDbContext.Posts.Add(post);
                this.blogDbContext.SaveChanges();
                return RedirectToAction("Index");
            }
    
            return View(post);
        }
    
        // GET: Posts/Edit/5
        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new BadRequestResult();
            }
    
            Post post = this.blogDbContext.Posts.Where(i=> i.Id == id).FirstOrDefault();
            if (post == null)
            {
                return HttpNotFound();
            }
    
            return View(post);
        }
    
        // POST: Posts/Edit/5
        // 若要免于过量张贴攻击,请启用想要系结的特定属性,如需
        // 详细资讯,请参阅 http://go.microsoft.com/fwlink/?LinkId=317598。
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind("Id,Title,Body,CreatedAt")] Post post)
        {
            if (ModelState.IsValid)
            {
                this.blogDbContext.Entry(post).State = EntityState.Modified;
                this.blogDbContext.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(post);
        }
    
        // GET: Posts/Delete/5
        public ActionResult Delete(int? id)
        {
            if (id == null)
            {
                return new BadRequestResult();
            }
    
            Post post = this.blogDbContext.Posts.Where(i=> i.Id == id).FirstOrDefault();
            if (post == null)
            {
                return HttpNotFound();
            }
    
            return View(post);
        }
    
        // POST: Posts/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            Post post = this.blogDbContext.Posts.Where(i=> i.Id == id).FirstOrDefault();
            this.blogDbContext.Posts.Remove(post);
            this.blogDbContext.SaveChanges();
            return RedirectToAction("Index");
        }
    
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                this.blogDbContext.Dispose();
            }
            base.Dispose(disposing);
        }
    }
  3. 建立新增页面

    yo aspnet:MvcView Post/Create

    Create.cshtml

    @model Blogging.Models.Post
    @{
        ViewBag.Title = "Create";
    }
    
    

    Create

    @using (Html.BeginForm()) { @Html.AntiForgeryToken()
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" })
    @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" })
    @Html.LabelFor(model => model.Body, htmlAttributes: new { @class = "control-label col-md-2" })
    @Html.EditorFor(model => model.Body, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Body, "", new { @class = "text-danger" })
    @Html.LabelFor(model => model.CreatedAt, htmlAttributes: new { @class = "control-label col-md-2" })
    @Html.EditorFor(model => model.CreatedAt, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.CreatedAt, "", new { @class = "text-danger" })
    }

  4. 建立查询页面

    yo aspnet:MvcView Post/Details

    Details.cshtml

    @model Blogging.Models.Post
    @{
        ViewBag.Title = "Details";
    }
    
    

    Details

    Post

    @Html.DisplayNameFor(model => model.Title)
    @Html.DisplayFor(model => model.Title)
    @Html.DisplayNameFor(model => model.Body)
    @Html.DisplayFor(model => model.Body)
    @Html.DisplayNameFor(model => model.CreatedAt)
    @Html.DisplayFor(model => model.CreatedAt)

    @Html.ActionLink("Edit", "Edit", new { id = Model.Id }) | @Html.ActionLink("Back to List", "Index")

  5. 建立修改页面

    yo aspnet:MvcView Post/Edit

    Edit.cshtml

    @model Blogging.Models.Post
    
    @{
        ViewBag.Title = "Edit"; 
    }
    
    

    Edit

    @using (Html.BeginForm()) { @Html.AntiForgeryToken()

    Post

    @Html.ValidationSummary(true, "", new { @class = "text-danger" }) @Html.HiddenFor(model => model.Id)
    @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" })
    @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" })
    @Html.LabelFor(model => model.Body, htmlAttributes: new { @class = "control-label col-md-2" })
    @Html.EditorFor(model => model.Body, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Body, "", new { @class = "text-danger" })
    @Html.LabelFor(model => model.CreatedAt, htmlAttributes: new { @class = "control-label col-md-2" })
    @Html.EditorFor(model => model.CreatedAt, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.CreatedAt, "", new { @class = "text-danger" })
    }
    @Html.ActionLink("Back to List", "Index")

  6. 建立删除页面

    yo aspnet:MvcView Post/Delete

    Delete.cshtml

    @model Blogging.Models.Post
    
    @{
        ViewBag.Title = "Delete";
    }
    
    

    Delete

    Are you sure you want to delete this?

    Post

    @Html.DisplayNameFor(model => model.Title)
    @Html.DisplayFor(model => model.Title)
    @Html.DisplayNameFor(model => model.Body)
    @Html.DisplayFor(model => model.Body)
    @Html.DisplayNameFor(model => model.CreatedAt)
    @Html.DisplayFor(model => model.CreatedAt)
    @using (Html.BeginForm()) { @Html.AntiForgeryToken()
    | @Html.ActionLink("Back to List", "Index")
    }

  7. 执行程序,可以成功的操作 Blog 的新删修查功能

    Blog

小结

新版本的 Asp.Net 5 开放了在 Windows 之外的平台也能进行开发的新功能,也因为不同操作系统和平台的关系,在开发上的习惯和操作方式也不一样,但这也让我们看到未来 Asp.Net 5 可能在更多不同环境的操作下产生更多应用与发展的可能性,如果对于在非 Windows 平台上开发 Asp.Net 5 的朋友,可以趁早熟悉并习惯一下这些新的开发尝试与体验喔!关于今天的内容,欢迎大家一起讨论!

参考数据

  1. OmniSharp/generator-aspnet
  2. ASP.NET 5 Application to New Database
  3. Entity Framework, Create a new project