在 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<Post> 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<BlogDbContext>(
               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: /<controller>/
        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";
    }
    
    <h2>Create</h2>
    
    @using (Html.BeginForm()) 
    {
        @Html.AntiForgeryToken()
        
        <div class="form-horizontal">
            @Html.ValidationSummary(true, "", new { @class = "text-danger" })
            <div class="form-group">
                @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" })
                </div>
            </div>
    
            <div class="form-group">
                @Html.LabelFor(model => model.Body, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.Body, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.Body, "", new { @class = "text-danger" })
                </div>
            </div>
    
            <div class="form-group">
                @Html.LabelFor(model => model.CreatedAt, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.CreatedAt, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.CreatedAt, "", new { @class = "text-danger" })
                </div>
            </div>
    
            <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>
    }
    

     

  4. 建立查詢頁面

    yo aspnet:MvcView Post/Details

    Details.cshtml

    @model Blogging.Models.Post
    @{
        ViewBag.Title = "Details";
    }
    
    <h2>Details</h2>
    
    <div>
        <h4>Post</h4>
        <hr />
        <dl class="dl-horizontal">
            <dt>
                @Html.DisplayNameFor(model => model.Title)
            </dt>
    
            <dd>
                @Html.DisplayFor(model => model.Title)
            </dd>
    
            <dt>
                @Html.DisplayNameFor(model => model.Body)
            </dt>
    
            <dd>
                @Html.DisplayFor(model => model.Body)
            </dd>
    
            <dt>
                @Html.DisplayNameFor(model => model.CreatedAt)
            </dt>
    
            <dd>
                @Html.DisplayFor(model => model.CreatedAt)
            </dd>
    
        </dl>
    </div>
    <p>
        @Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
        @Html.ActionLink("Back to List", "Index")
    </p>

     

  5. 建立修改頁面

    yo aspnet:MvcView Post/Edit

    Edit.cshtml
     

    @model Blogging.Models.Post
    
    @{
        ViewBag.Title = "Edit"; 
    }
    
    <h2>Edit</h2>
    
    
    @using (Html.BeginForm())
    {
        @Html.AntiForgeryToken()
        
        <div class="form-horizontal">
            <h4>Post</h4>
            <hr />
            @Html.ValidationSummary(true, "", new { @class = "text-danger" })
            @Html.HiddenFor(model => model.Id)
    
            <div class="form-group">
                @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" })
                </div>
            </div>
    
            <div class="form-group">
                @Html.LabelFor(model => model.Body, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.Body, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.Body, "", new { @class = "text-danger" })
                </div>
            </div>
    
            <div class="form-group">
                @Html.LabelFor(model => model.CreatedAt, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.CreatedAt, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.CreatedAt, "", new { @class = "text-danger" })
                </div>
            </div>
    
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Save" class="btn btn-default" />
                </div>
            </div>
        </div>
    }
    
    <div>
        @Html.ActionLink("Back to List", "Index")
    </div>

     

  6. 建立刪除頁面

    yo aspnet:MvcView Post/Delete

    Delete.cshtml

    @model Blogging.Models.Post
    
    @{
        ViewBag.Title = "Delete";
    }
    
    <h2>Delete</h2>
    
    <h3>Are you sure you want to delete this?</h3>
    <div>
        <h4>Post</h4>
        <hr />
        <dl class="dl-horizontal">
            <dt>
                @Html.DisplayNameFor(model => model.Title)
            </dt>
    
            <dd>
                @Html.DisplayFor(model => model.Title)
            </dd>
    
            <dt>
                @Html.DisplayNameFor(model => model.Body)
            </dt>
    
            <dd>
                @Html.DisplayFor(model => model.Body)
            </dd>
    
            <dt>
                @Html.DisplayNameFor(model => model.CreatedAt)
            </dt>
    
            <dd>
                @Html.DisplayFor(model => model.CreatedAt)
            </dd>
    
        </dl>
    
        @using (Html.BeginForm()) {
            @Html.AntiForgeryToken()
    
            <div class="form-actions no-color">
                <input type="submit" value="Delete" class="btn btn-default" /> |
                @Html.ActionLink("Back to List", "Index")
            </div>
        }
    </div>
    

     

  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