Thực hành (2) CRUD trong ASP.NET Core MVC

    2

    CRUD (Create-Retrieve-Update-Delete) là nhóm chức năng cơ bản trên dữ liệu mà mọi ứng dụng đều phải có. Trong ASP.NET Core MVC nhóm chức năng này được thực hiện một cách dễ dàng. ASP.NET Core MVC thậm chí còn cung cấp khả năng scafolding – tự động sinh code controller và view dựa trên thông tin của model.

    Bài học này nối tiếp bài thực hành (1) trình bày cách xây dựng một ứng dụng quản lý đơn giản. Trong bài học này bạn sẽ tiếp tục thực hiện chức năng Delete – Update – Create.

    Xóa dữ liệu

    Chức năng xóa dữ liệu thực hiện theo quy trình sau:

    1. Chọn lệnh xóa (từ Index hoặc Details view);
    2. Hiển thị thông tin chi tiết của dữ liệu và yêu cầu xác nhận xóa;
    3. Nếu người dùng xác nhận xóa => xóa dữ liệu và điều hướng về trang Index;
    4. Nếu người dùng hủy xóa => điều hướng về trang Index.

    Bước 1. Điều chỉnh BookController

    Bổ sung hai action sau vào BookController:

    public IActionResult Delete(int id) {
        var b = _service.Get(id);
        if (b == null) return NotFound();
        else return View(b);
    }
    
    [HttpPost]
    public IActionResult Delete(Book book) {
        _service.Delete(book.Id);
        _service.SaveChanges();
        return RedirectToAction("Index");
    }

    Hai action này tuân theo một mẫu xử lý phổ biến trong ASP.NET Core: Get – Post – Redirect.

    Trong chức năng xóa, mẫu này thể hiện như sau:

    1. Người dùng thông qua link gọi tới action Delete lần đầu tiên với truy vấn GET, phương thức Delete(int id) sẽ được gọi.
    2. Action Delete(int id) lựa chọn view tương ứng. Trong view này phải có một form gọi trở về action Delete với truy vấn POST.
    3. Ở lần gọi action Delete thứ hai từ form với truy vấn POST, action Delete(Book book) sẽ được gọi. Về kỹ thuật để yêu cầu kích hoạt action theo truy vấn POST, chúng ta đặt attribute [HttpPost] trước action đó.
    4. Khi action Delete(Book book) thực hiện xong người dùng sẽ được điều hướng về trang Index. Việc điều hướng này giúp tránh tình trạng post dữ liệu nhiều lần – vốn có thể xem như một vấn đề an ninh.

    Chú ý rằng [HttpPost] là bắt buộc đối với action Delete(Book book). ASP.NET Core MVC chỉ phân biệt các action theo tên gọi chứ không quan tâm đến danh sách tham số. Do vậy, Delete(int id) và Delete(Book book) trong ASP.NET Core MVC là như nhau.

    ASP.NET Core phân biệt phương thức action dựa trên loại truy vấn mà nó xử lý thông qua attribute [Http…], bao gồm [HttpPost], [HttpGet], v.v..

    Mẫu Get – Post – Redirect được sử dụng cho cả 3 loại chức năng Delete, Update và Create.

    Bước 2. Xây dựng Delete view

    Sử dụng scafolding hoặc tự mình tạo Delete view (/Views/Book/Delete.cshtml) với code như sau:

    @model BookMan.Mvc.Models.Book
    
    @{
        ViewData["Title"] = "Confirmation";
    }
    
    <p class="p-3 alert-danger">Are you sure you want to delete this?</p>
    
    <div>
        <p class="h3">@Model.Name</p>
        <hr />
        <dl class="row">
            <dt class="col-sm-2"><label asp-for="Name"></label></dt>
            <dd class="col-sm-10">@Model.Name</dd>
            <dt class="col-sm-2"><label asp-for="Authors"></label></dt>
            <dd class="col-sm-10">@Model.Authors</dd>
            <dt class="col-sm-2"><label asp-for="Publisher"></label></dt>
            <dd class="col-sm-10">@Model.Publisher</dd>
            <dt class="col-sm-2"><label asp-for="Year"></label></dt>
            <dd class="col-sm-10">@Model.Year</dd>
            <dt class="col-sm-2"><label asp-for="DataFile"></label></dt>
            <dd class="col-sm-10">@Model.DataFile</dd>
            <dt class="col-sm-2"><label asp-for="Description"></label></dt>
            <dd class="col-sm-10">@Model.Description</dd>
        </dl>
    </div>
    
    <hr />
    
    <form asp-action="Delete">
        <input type="hidden" asp-for="Id" />
        <input type="submit" value="Delete" class="btn btn-outline-danger btn-sm" /> |
        <a asp-action="Index" class="btn btn-sm btn-outline-secondary">Back to List</a>
    </form>

    Bước 3. Thêm nút lệnh Delete vào Index và Details view

    Để kích hoạt chức năng xóa dữ liệu, bổ sung thẻ sau vào Index view (vào cạnh nút Details):

    <a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-sm btn-outline-danger">Delete</a>

    Tương tự, bổ sung thẻ sau vào Details view (vào cạnh nút Back to List):

    <a asp-action="Delete" asp-route-id="@Model.Id" class="btn btn-sm btn-outline-danger">Delete</a>

    Bước 4. Chạy thử ứng dụng

    Kiểm tra xem bạn đã tạo thư mục Data trong project hay chưa. Nếu chưa, hãy tạo thư mục Data trực thuộc dự án và chạy chương trình.

    Bạn có thể gọi lệnh Delete từ Index hoặc Details view. Chức năng này hoạt động như chúng ta đã mô tả.

    Mỗi khi xóa một bản ghi, dữ liệu sẽ lưu vào file và điều hướng trở về trang Index.

    Cập nhật dữ liệu

    Chức năng cập nhật dữ liệu được chạy từ Index hoặc Details view và theo cùng quy trình như Delete:

    1. Hiển thị form cập nhật;
    2. Nếu người dùng chọn lưu kết quả => lưu vào file dữ liệu và điều hướng về Index;
    3. Nếu người dùng muốn hủy lưu => điều hướng về Index hoặc Details.

    Bước 1. Xây dựng action

    Bổ sung hai action sau vào BookController.

    public IActionResult Edit(int id) {
        var b = _service.Get(id);
        if (b == null) return NotFound();
        else return View(b);
    }
    
    [HttpPost]
    public IActionResult Edit(Book book) {
        if (ModelState.IsValid) {
            _service.Update(book);
            _service.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(book);
    }

    Để ý rằng hai phương thức Edit ở đây cũng tuân theo mô hình Get-Post-Redirect giống như Delete ở trên.

    Bước 2. Xây dựng Edit view

    @model BookMan.Mvc.Models.Book
    
    @{
        ViewData["Title"] = $"Editing {Model.Name}";
    }
    
    <p class="h3">You're editing: <strong>@Model.Name</strong></p>
    <hr />
    
    <form asp-action="Edit" enctype="multipart/form-data">
    
        <div class="row">
            <input type="hidden" asp-for="Id" />
            <div class="col-md-6">
                <div class="form-group">
                    <label asp-for="Name" class="control-label"></label>
                    <input asp-for="Name" class="form-control" />
                    <span asp-validation-for="Name" class="text-danger small"></span>
                </div>
                <div class="form-group">
                    <label asp-for="Authors" class="control-label"></label>
                    <input asp-for="Authors" class="form-control" />
                    <span asp-validation-for="Authors" class="text-danger small"></span>
                </div>
                <div class="form-group">
                    <label asp-for="Publisher" class="control-label"></label>
                    <input asp-for="Publisher" class="form-control" />
                    <span asp-validation-for="Publisher" class="text-danger small"></span>
                </div>
                <div class="form-group">
                    <label asp-for="Year" class="control-label"></label>
                    <input asp-for="Year" class="form-control" />
                    <span asp-validation-for="Year" class="text-danger small"></span>
                </div>
            </div>
            <div class="col-md-6">
                <div class="form-group">
                    <label asp-for="Description" class="control-label"></label>
                    <textarea asp-for="Description" class="form-control" rows="8"></textarea>
                    <span asp-validation-for="Description" class="text-danger small"></span>
                </div>
                <div class="form-group">
                    <input type="file" name="file" />
                    <input type="hidden" asp-for="DataFile" />
                </div>
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            </div>
        </div>
    
        @section Scripts {
            @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
        }
    
        <hr />
        <div class="form-group">
            <input type="submit" value="Save" class="btn btn-sm btn-outline-primary" /> |
            <a asp-action="Details" asp-route-id="@Model.Id" class="btn btn-sm btn-outline-info">Cancel</a> |
            <a asp-action="Index" class="btn btn-sm btn-outline-secondary">Back to List</a>
        </div>
    </form>

    Để ý một số thẻ span có tham số asp-validation-for. Đây là hỗ trợ thẩm định dữ liệu (input validation) trong ASP.NET Core. Cơ chế này giúp kiểm tra xem các property của model có phù hợp yêu cầu hay không.

    Một số yêu cầu chúng ta đã chỉ định khi xây dựng domain model bao gồm [Required][Range].

    Chúng ta sẽ học chi tiết về thẩm định dữ liệu trong một bài học riêng.

    Bước 3. Thêm nút lệnh Edit

    Thêm nút lệnh Edit với thẻ a như sau vào Index view:

    <a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-sm btn-outline-warning">Edit</a>

    Thêm nút lệnh sau vào Details view:

    <a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-sm btn-outline-warning">Edit</a>

    Giờ bạn có thể chạy thử ứng dụng với chức năng cập nhật dữ liệu.

    Chú ý, chức năng upload file chưa hoạt động. Chúng ta sẽ quay lại với chức năng này sau.

    Thêm dữ liệu mới

    Quy trình thêm dữ liệu diễn ra tương tự như Edit và Delete.

    Bước 1. Thêm controller action

    Thêm hai action sau đây vào BookController:

    public IActionResult Create() => base.View(_service.Create());
    
    [HttpPost]
    public IActionResult Create(Book book) {
        if (ModelState.IsValid) {
            _service.Add(book);
            _service.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(book);
    }

    Bước 3. Xây dựng view

    Tạo Create view với code như sau:

    @model BookMan.Mvc.Models.Book
    
    @{
        ViewData["Title"] = "Create a new book";
    }
    
    <p class="h3">Add a new book</p>
    <hr />
    
    <form asp-action="Create" enctype="multipart/form-data">
    
        <div class="row">
            <input type="hidden" asp-for="Id" />
            <div class="col-md-6">
                <div class="form-group">
                    <label asp-for="Name" class="control-label"></label>
                    <input asp-for="Name" class="form-control" />
                    <span asp-validation-for="Name" class="text-danger small"></span>
                </div>
                <div class="form-group">
                    <label asp-for="Authors" class="control-label"></label>
                    <input asp-for="Authors" class="form-control" />
                    <span asp-validation-for="Authors" class="text-danger small"></span>
                </div>
                <div class="form-group">
                    <label asp-for="Publisher" class="control-label"></label>
                    <input asp-for="Publisher" class="form-control" />
                    <span asp-validation-for="Publisher" class="text-danger small"></span>
                </div>
                <div class="form-group">
                    <label asp-for="Year" class="control-label"></label>
                    <input asp-for="Year" class="form-control" />
                    <span asp-validation-for="Year" class="text-danger small"></span>
                </div>
            </div>
            <div class="col-md-6">
                <div class="form-group">
                    <label asp-for="Description" class="control-label"></label>
                    <textarea asp-for="Description" class="form-control" rows="8"></textarea>
                    <span asp-validation-for="Description" class="text-danger small"></span>
                </div>
                <div class="form-group">
                    <input type="file" name="file" />
                    <input type="hidden" asp-for="DataFile" />
                </div>
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            </div>
        </div>
    
        @section Scripts {
            @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
        }
    
        <hr />
        <div class="form-group">
            <input type="submit" value="Create" class="btn btn-outline-primary btn-sm" /> |
            <a asp-action="Index" class="btn btn-sm btn-outline-secondary">Back to List</a>
        </div>
    </form>

    Bước 3. Thêm nút lệnh Create

    Thêm nút lệnh Create bằng khối html sau vào ngay trước bảng dữ liệu của Index view:

    <div class="mb-3">
        <a asp-action="Create" class="btn btn-outline-primary">Create New</a>
    </div>

    Đến đây chức năng thêm dữ liệu đã hoàn thành. Bạn có thể chạy thử chương trình:

    Lưu ý chức năng upload file chưa hoạt động. Chúng ta sẽ quay lại với chức năng này sau.

    Tái sử dụng code với partial view

    Khi thực hiện bài thực hành này bạn có thể để ý thấy rằng có vài chỗ đang bị lặp code. Cụ thể, Details và Delete view cùng chia sẻ một khối code razor hiển thị thông tin chi tiết của một cuốn sách. Create và Edit view cùng chia sẻ code của form nhập liệu.

    Details và Delete cùng chia sẻ một khối code
    Edit và Create có cùng khối code thân form

    Giữa Delete và Details view cùng chia sẻ một logic hiển thị. Tương tự, Edit và Create view có cùng logic hiển thị. Do vậy, sự lặp code ở đây không phải là vấn đề lớn. Chúng ta hoàn toàn có thể chấp nhận vấn đề này. Đặc biệt, nếu bạn mong muốn sự tiện lợi của scafolding, bạn có thể chấp nhận việc lặp code giữa hai cặp view trên.

    Tuy nhiên, nếu bạn ưa thích sự tối ưu, việc lặp code như trên là khó chấp nhận. Trong tình huống này bạn có thể giải quyết sự lặp code bằng cách áp dụng partial view.

    Hãy cùng thực hiện các bước sau đây để vận dụng partial view và xem cách chống lặp code với partial view.

    Bước 1. Tạo partial view chung cho Details và Delete view

    Tạo _Single.cshtml trong thư mục /Views/Book và copy đoạn code trùng nhau của Details và Delete view sang. Bạn thu được file _Single.cshtml như sau:

    @model BookMan.Mvc.Models.Book
    <div>
        <p class="h3">@Model.Name</p>
        <hr />
        <dl class="row">
            <dt class="col-sm-2"><label asp-for="Name"></label></dt>
            <dd class="col-sm-10">@Model.Name</dd>
            <dt class="col-sm-2"><label asp-for="Authors"></label></dt>
            <dd class="col-sm-10">@Model.Authors</dd>
            <dt class="col-sm-2"><label asp-for="Publisher"></label></dt>
            <dd class="col-sm-10">@Model.Publisher</dd>
            <dt class="col-sm-2"><label asp-for="Year"></label></dt>
            <dd class="col-sm-10">@Model.Year</dd>
            <dt class="col-sm-2"><label asp-for="DataFile"></label></dt>
            <dd class="col-sm-10">@Model.DataFile</dd>
            <dt class="col-sm-2"><label asp-for="Description"></label></dt>
            <dd class="col-sm-10">@Model.Description</dd>
        </dl>
    </div>

    Bước 2. Cập nhật Delete và Details view

    Xóa bỏ phần code bôi vàng và thay bằng

    <partial name="_Single" model="Model" />

    Khi này code của Details.cshtml và Delete.cshtml còn như sau:

    @model BookMan.Mvc.Models.Book
    
    @{
        ViewData["Title"] = Model.Name;
    }
    
    <partial name="_Single" model="Model" />
    
    <hr />
    
    <div>
        <a asp-action="Index" class="btn btn-sm btn-outline-secondary">Back to List</a>
        <a asp-action="Delete" asp-route-id="@Model.Id" class="btn btn-sm btn-outline-danger">Delete</a>
        <a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-sm btn-outline-warning">Edit</a>
    </div>
    @model BookMan.Mvc.Models.Book
    
    @{
        ViewData["Title"] = "Confirmation";
    }
    
    <p class="p-3 alert-danger">Are you sure you want to delete this?</p>
    
    <partial name="_Single" model="Model" />
    
    <hr />
    
    <form asp-action="Delete">
        <input type="hidden" asp-for="Id" />
        <input type="submit" value="Delete" class="btn btn-outline-danger btn-sm" /> |
        <a asp-action="Index" class="btn btn-sm btn-outline-secondary">Back to List</a>
    </form>

    Bước 3. Cập nhật Edit và Create view

    Tương tự, copy phần bôi vàng trùng nhau của Edit và Create view sang file mới _Form.cshtml trong /Views/Book. Bạn thu được file _Form.cshtml có code như sau:

    @model BookMan.Mvc.Models.Book
    
    <div class="row">
        <input type="hidden" asp-for="Id" />
        <div class="col-md-6">
            <div class="form-group">
                <label asp-for="Name" class="control-label"></label>
                <input asp-for="Name" class="form-control" />
                <span asp-validation-for="Name" class="text-danger small"></span>
            </div>
            <div class="form-group">
                <label asp-for="Authors" class="control-label"></label>
                <input asp-for="Authors" class="form-control" />
                <span asp-validation-for="Authors" class="text-danger small"></span>
            </div>
            <div class="form-group">
                <label asp-for="Publisher" class="control-label"></label>
                <input asp-for="Publisher" class="form-control" />
                <span asp-validation-for="Publisher" class="text-danger small"></span>
            </div>
            <div class="form-group">
                <label asp-for="Year" class="control-label"></label>
                <input asp-for="Year" class="form-control" />
                <span asp-validation-for="Year" class="text-danger small"></span>
            </div>
        </div>
        <div class="col-md-6">
            <div class="form-group">
                <label asp-for="Description" class="control-label"></label>
                <textarea asp-for="Description" class="form-control" rows="8"></textarea>
                <span asp-validation-for="Description" class="text-danger small"></span>
            </div>
            <div class="form-group">
                <input type="file" name="file" />
                <input type="hidden" asp-for="DataFile" />
            </div>
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        </div>
    </div>
    
    @section Scripts {
        @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
    }

    Bước 4. Cập nhật Edit và Create view

    Thay cho khối code đã di chuyển sang _Form.cshtml bằng lệnh sau:

    <partial name="_Form" model="Model" />

    Khi này, code của Edit và Create view trở thành như sau:

    @model BookMan.Mvc.Models.Book
    
    @{
        ViewData["Title"] = $"Editing {Model.Name}";
    }
    
    <p class="h3">You're editing: <strong>@Model.Name</strong></p>
    <hr />
    
    <form asp-action="Edit" enctype="multipart/form-data">
    
        <partial name="_Form" model="Model" />
    
        <hr />
        <div class="form-group">
            <input type="submit" value="Save" class="btn btn-sm btn-outline-primary" />
            <a asp-action="Details" asp-route-id="@Model.Id" class="btn btn-sm btn-outline-info">Cancel</a>
            <a asp-action="Index" class="btn btn-sm btn-outline-secondary">Back to List</a>
        </div>
    </form>
    @model BookMan.Mvc.Models.Book
    
    @{
        ViewData["Title"] = "Create a new book";
    }
    
    <p class="h3">Add a new book</p>
    <hr />
    
    <form asp-action="Create" enctype="multipart/form-data">
    
        <partial name="_Form" model="Model" />
    
        <hr />
        <div class="form-group">
            <input type="submit" value="Create" class="btn btn-outline-primary btn-sm" /> |
            <a asp-action="Index" class="btn btn-sm btn-outline-secondary">Back to List</a>
        </div>
    </form>

    Khi bạn chạy thử ứng dụng, các chức năng không thay đổi.

    Tuy nhiên, code của các view tương ứng đã được đơn giản hóa đáng kể và không còn lặp code nữa.

    Ở đây bạn đã gặp cách sử dụng partial view để tránh lặp code Razor. Bạn sẽ học chi tiết hơn về partial view trong một bài học riêng.

    Partial view là một tính năng của ASP.NET Core cho phép tái sử dụng một khối code Razor đặt trong một file riêng. Bạn có thể hình dung partial view không khác biệt gì so với một view thông thường trong MVC. Thay vì chỉ định trong action, bạn có thể chèn partial view vào một view khác qua thẻ <partial /> như chúng ta đã làm ở trên.

    Kết luận

    Trong bài học này chúng ta đã thực hành xây dựng một ứng dụng đơn giản với các chức năng CRUD dữ liệu cơ bản.

    Như bạn đã thấy, xây dựng các chức năng CRUD dữ liệu trong ASP.NET Core MVC tương đối đơn giản.

    Riêng đối với nhóm Delete – Update – Create bạn cần sử dụng mô hình Get – Post – Redirect.

    Một số chức năng cao cấp hơn chúng ta sẽ để dành cho bài thực hành thứ 3.

    Link tải mã nguồn: https://1drv.ms/u/s!Ar_aj4rIJ2qGkf8rCeoYjbiNZRbg4w?e=gHgJOg

    + Nếu bạn thấy site hữu ích, trước khi rời đi hãy giúp đỡ site bằng một hành động nhỏ để site có thể phát triển và phục vụ bạn tốt hơn.
    + Nếu bạn thấy bài viết hữu ích, hãy giúp chia sẻ tới mọi người.
    + Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần thảo luận cuối trang.
    Cảm ơn bạn!

    Subscribe
    Notify of
    guest
    2 Thảo luận
    Oldest
    Newest
    Inline Feedbacks
    View all comments
    Thiện

    Video bài này đâu bạn. Mình cần video

    kunchan

    3.Thêm nút lệnh Delete vào Index và Detail.
    asp-route-id=”Model.Id”