4-4.ASP.NET Core MVC 入門教學 - 更新資料與ViewModel

ASP.NET Core MVC 入門教學

這節我們來修改更新資料的頁面

順便講到一個重要的寫法,就是ViewModel

一開始我們可以看到最右邊,按下Edit進入編輯頁面

而編輯頁面也跟新增頁面在Controller有兩個方法,至於要走哪個,跟新增頁面的觀念是一樣的

那我們就來看一下,一進入頁面前的Controller裡的程式在做什麼

public async Task<IActionResult> Edit(Guid? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var news = await _context.News.FindAsync(id);
    if (news == null)
    {
        return NotFound();
    }
    return View(news);
}

一開始會有(Guid? id)傳進來,那這個id的值哪來?就是從網址取得的

接著我們再繼續往下看

public async Task<IActionResult> Edit(Guid? id)
{
    if (id == null)
    {
        return NotFound();  //如果id取不到就返回404找不到的頁面
    }

    var news = await _context.News.FindAsync(id);  //從資料庫找符合這個id的一筆資料
    
    if (news == null)
    {
        return NotFound();  //如果找不到一樣返回404找不到的頁面
    }
    return View(news);  //找到了就把資料丟去View顯示出來
}

接著我們再來看下面的編輯頁面有些什麼問題?

簡單來說就是很多欄位不該給使用者去輸入,所以這時候Dto又派上用場了

namespace Kcg.Dtos
{
    public class NewsEditDto
    {
        public Guid NewsId { get; set; }

        public string Title { get; set; }

        public string Contents { get; set; }

        public int DepartmentId { get; set; }

        public DateTime StartDateTime { get; set; }

        public DateTime EndDateTime { get; set; }        
        
        public Boolean Enable { get; set; }
    }
}

我們只撈出我們要輸入的欄位就好

public async Task<IActionResult> Edit(Guid? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var news = await (from a in _context.News
                      where a.NewsId == id
                      select new NewsEditDto
                      {
                          EndDateTime = a.EndDateTime,
                          NewsId = a.NewsId,
                          StartDateTime = a.StartDateTime,
                          Title = a.Title,
                          Contents = a.Contents,
                          DepartmentId = a.DepartmentId
                      }).SingleOrDefaultAsync();

    if (news == null)
    {
        return NotFound();
    }

    return View(news);
}

接著再修改View的Edit的Model跟刪掉不要的欄位

@model NewsEditDto

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>News</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="NewsId" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Contents" class="control-label"></label>
                <input asp-for="Contents" class="form-control" />
                <span asp-validation-for="Contents" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="DepartmentId" class="control-label"></label>
                <input asp-for="DepartmentId" class="form-control" />
                <span asp-validation-for="DepartmentId" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StartDateTime" class="control-label"></label>
                <input asp-for="StartDateTime" class="form-control" />
                <span asp-validation-for="StartDateTime" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="EndDateTime" class="control-label"></label>
                <input asp-for="EndDateTime" class="form-control" />
                <span asp-validation-for="EndDateTime" class="text-danger"></span>
            </div>
            <div class="form-group form-check">
                <label class="form-check-label">
                    <input class="form-check-input" asp-for="Enable" /> @Html.DisplayNameFor(model => model.Enable)
                </label>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

這樣雖然程式可以執行了,但還有個小問題

我部門的地方想要做成下拉選單,該怎麼做呢?

既然想要讓使用者可以選擇部門,那我們勢必要再撈出部門的資料往View送

但我們已經有送NewsEditDto了,要怎麼再送另外一包東西呢?用ViewData?這不會是一個好選擇,畢竟這算是頁面的重要資料之一

所以這個時候我們可以創一個ViewModel,那ViewModel是什麼呢?

其實ViewModel跟Dto一樣,沒有什麼額外的功能,只是一個帶有目的的類別

而ViewModel的目的就是,要傳到View裡的重要資料,都往這裡放的一個類別就會把它叫做ViewModel

所以第一節課用Dto傳到View是當時暫時的做法,真正的做法應該是要定義一個ViewModel然後傳值去View

那這邊我們的ViewModel要定義什麼內容呢?就是一包Dto一包部門的資料

namespace Kcg.ViewModels
{
    public class NewsEditViewModel
    {
        public NewsEditDto News { get; set; }
        public List<Department> Departments { get; set; }
    }
}

再修改一下Controller的程式

public async Task<IActionResult> Edit(Guid? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var NewsEditViewModel = new NewsEditViewModel();

    NewsEditViewModel.News = await (from a in _context.News
                                    where a.NewsId == id
                                    select new NewsEditDto
                                    {
                                        EndDateTime = a.EndDateTime,
                                        NewsId = a.NewsId,
                                        StartDateTime = a.StartDateTime,
                                        Title = a.Title,
                                        Contents = a.Contents,
                                        DepartmentId = a.DepartmentId,
                                        Enable = a.Enable
                                    }).SingleOrDefaultAsync();

    NewsEditViewModel.Departments = await _context.Department.ToListAsync();

    if (NewsEditViewModel.News == null)
    {
        return NotFound();
    }

    return View(NewsEditViewModel);
}

html的model改成

@model NewsEditViewModel

接著我們開始做下拉選單,我這邊先不用.net core MVC的Tag helper的寫法,這以後會再教的時候再示範

先單純用HTML原生的寫法

<div class="form-group">
    <label asp-for="News.DepartmentId" class="control-label"></label>
    <select name="News.DepartmentId" class="form-select">
        @foreach (var temp in Model.Departments)
        {
            <option value="@temp.DepartmentId">@temp.Name</option>
        }
    </select>
    <span asp-validation-for="News.DepartmentId" class="text-danger"></span>
</div>

修改完後畫面如,下拉選單就出現了

那可以來修改看看了嗎?當然還不行,我們更新的程式還沒調整完

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(Guid id, NewsEditDto news)
{
    if (id != news.NewsId)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        var update = _context.News.Find(news.NewsId);

        if (update != null)
        {
            update.Title = news.Title;
            update.Contents = news.Contents;
            update.DepartmentId = news.DepartmentId;
            update.StartDateTime = news.StartDateTime;
            update.EndDateTime = news.EndDateTime;
            update.Enable = news.Enable;

            update.UpdateEmployeeId = 1;
            update.UpdateDateTime = DateTime.Now;

            await _context.SaveChangesAsync();

            return RedirectToAction(nameof(Index));
        }
    }
    return View(news);
}

本來是無條件使用者傳什麼來就全部更新,改為特定更新部分欄位,影片會有更詳細的解說

接著我們就來試試看功能能不能正常運作,做一些小修改後按save

查看明細就說發現資料都正確的被更新了

代表我們這次的調整是沒有問題的

這邊難度開始有點提升了,越來越多觀念的綜合應用了,不懂得可能要再多看幾遍,多自己試幾遍了

這節總結

傳到View的資料往往可能不只有資料表的資料,可能還有頁面其他的資料

所以這個時候我們通常會用ViewModel把這些資料都裝進去再傳去View

而ViewModel的定義就是傳去View的Model資料我們會把它定義為ViewModel

這樣以後或者是別人在看,看到 ViewModel的命名檔案,就知道它的作用是這個

另外Bind的想法就跟新增是一樣的,將原有字串的Bind方式改為用Dto的方式進行

最後就是局部特定欄位更新的方法

 

範例檔:下載




Copyright © 凱哥寫程式 2022 | Powered by TalllKai ❤