設計與使用ASP.NET Core Web API 3 - CRUD增刪查改

by vivid 25. 十一月 2020 02:32

.NET Magazine國際中文電子雜誌
作 者:許薰尹
文章編號: N201122402
出刊日期: 2020/11/25

這篇文章是《設計與使用ASP.NET Core Web API》系列的文章中的第三篇,延續《設計與使用ASP.NET Core Web API 1-使用Entity Framework Core存取資料》、《設計與使用ASP.NET Core Web API 2- Swagger》兩篇文章的情境,我們將使用ASP.NET Core Web API與Entity Framework Core設計CRUD功能。

查詢單一商店資料

在ASP.NET Core MVC「MyWeb」專案中,修改「HomeController」類別程式碼,加入一個「Details」方法,接收「id」當參數,在方法中建立「PublisherService」物件,並叫用「GetStoreByIdAsync」方法傳入「id」參數來叫用Web API取得編號相符的商店資料,請參考以下程式列表:

  • MyWeb\Controllers\HomeController.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MyWeb.Models;

namespace MyWeb.Controllers {
  public class HomeController : Controller {
    private readonly ILogger<HomeController> _logger;
    private readonly IHttpClientFactory _httpClientFactory;
    public HomeController( ILogger<HomeController> logger, IHttpClientFactory httpClientFactory ) {
      _logger = logger;
      _httpClientFactory = httpClientFactory;
    }

    public async Task<IActionResult> Index( ) {

      PublisherService client =
        new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );
      ICollection<Store> stores = await client.GetStoresAsync( );
      return View( stores );
    }
    public async Task<IActionResult> Details( string id ) {
      if ( id == null ) {
        return NotFound( );
      }

      PublisherService client =
        new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );

      Store store = await client.GetStoreByIdAsync( id );


      if ( store == null ) {
        return NotFound( );
      }
      return View( store );
    }

    public IActionResult Privacy( ) {
      return View( );
    }

    [ResponseCache( Duration = 0, Location = ResponseCacheLocation.None, NoStore = true )]
    public IActionResult Error( ) {
      return View( new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier } );
    }
  }
}

 

建立「Details」檢視

建立「Details」檢視。將游標停留在「HomeController」控制器程式設計畫面「Details」方法之中,按一下滑鼠右鍵,從快捷選單選取「Add View」,請參考下圖所示:

clip_image002

圖 1:建立「Details」檢視。

在「Add Razor View」對話盒中,設定以下項目:

  • · 「View name」:「Details」。
  • · 「Template」:「Details」。
  • · 「Model class」:選取「Store」類別。
  • · 勾選「Use a layout page」核取方塊。

然後按下「Add」按鈕。Visual Studio 2019便會在「Views\Home」資料夾下,新增一個「Details.cshtml」檔案,請參考下圖所示:

clip_image004

圖 2:建立「Details」檢視。

修改產生的「Details」檢視程式碼,修改「@Html.ActionLink」這行程式,第三個參數傳入一個匿名物件,指定要編輯的商店編號,以方便切換到資料編輯畫面,請參考以下程式列表:

  • MyWeb\Views\Home\Details.cshtml

@model MyWeb.Store

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

<h1> Details </h1>

<div>
    <h4> Store </h4>
    <hr />
    <dl class = "row">
        <dt class = "col-sm-2">
            @Html.DisplayNameFor( model => model.StorId )
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor( model => model.StorId )
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor( model => model.StorName )
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor( model => model.StorName )
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor( model => model.StorAddress )
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor( model => model.StorAddress )
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor( model => model.City )
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor( model => model.City )
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor( model => model.State )
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor( model => model.State )
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor( model => model.Zip )
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor( model => model.Zip )
        </dd>
    </dl>
</div>
<div>
    @Html.ActionLink( "Edit", "Edit", new {  id = Model.StorId }) |
    <a asp-action = "Index"> Back to List </a>
</div>

 

修改「Index」檢視程式碼,同樣在叫用「Html.ActionLink」方法產生「Details」超連結時,第三個參數傳入一個匿名物件,指定要編輯的商店編號,以方便從「Index」檢視切換到「Details」資料畫面:

Html.ActionLink( "Details", "Details", new { id item.StorId } )

目前「Index」檢視的程式碼看起來如下:

  • MyWeb\Views\Home\Index.cshtml

@model IEnumerable<MyWeb.Store>

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

<h1>Index</h1>

<p>
    <a asp-action = "Create"> Create New </a>
</p>
<table class = "table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor( model => model.StorId )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.StorName )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.StorAddress )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.City )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.State )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.Zip )
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor( modelItem => item.StorId )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.StorName )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.StorAddress )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.City )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.State )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.Zip )
            </td>
            <td>
                @Html.ActionLink( "Edit", "Edit", new {/* id = item.PrimaryKey */ } ) |
                @Html.ActionLink( "Details", "Details", new { id = item.StorId } ) |
                @Html.ActionLink( "Delete", "Delete", new { /* id = item.PrimaryKey */ }  )
            </td>
        </tr>
}
    </tbody>
</table>

 

選取Visual Studio 2019開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯,完成後可以同時執行兩個網站,只要在Visual Studio 2019開發工具,按「CTRL」+「F5」組合鍵來執行網站應用程式,請參考下圖所示:

clip_image006

圖 3:查詢一筆商店資料。

點選任一筆資料後方的「Details」超連結,便可看到資料呈現在畫面上,請參考下圖所示:

clip_image008

圖 4:顯示一筆商店資料。

新增資料

在ASP.NET Core MVC「MyWeb」專案中「HomeController」類別中加入兩個「Create」方法,第一個「Create」方法沒有參數;第二個「Create」方法接收一個「Store」型別的參數,方法上方標識「HttpPost」 Attribute。在標識「HttpPost」 Attribute的「Create」方法中,叫用「PublisherService」物件的「CreateStoreAsync」方法透過Web API新增資料,請參考以下程式列表:

  • MyWeb\Controllers\HomeController.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MyWeb.Models;

namespace MyWeb.Controllers {
  public class HomeController : Controller {
    private readonly ILogger<HomeController> _logger;
    private readonly IHttpClientFactory _httpClientFactory;
    public HomeController( ILogger<HomeController> logger, IHttpClientFactory httpClientFactory ) {
      _logger = logger;
      _httpClientFactory = httpClientFactory;
    }

    public async Task<IActionResult> Index( ) {

      PublisherService client =
        new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );
      ICollection<Store> stores = await client.GetStoresAsync( );
      return View( stores );
    }
    public async Task<IActionResult> Details( string id ) {
      if ( id == null ) {
        return NotFound( );
      }

      PublisherService client =
        new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );

      Store store = await client.GetStoreByIdAsync( id );


      if ( store == null ) {
        return NotFound( );
      }
      return View( store );
    }
    public IActionResult Create( ) {
      return View( );
    }

    [HttpPost]
    public async Task<IActionResult> Create( Store store ) {
      if ( ModelState.IsValid ) {
        PublisherService client =
         new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );
        await client.CreateStoreAsync( store );
        return RedirectToAction( nameof( Index ) );
      }
      return View( store );
    }


    public IActionResult Privacy( ) {
      return View( );
    }

    [ResponseCache( Duration = 0, Location = ResponseCacheLocation.None, NoStore = true )]
    public IActionResult Error( ) {
      return View( new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier } );
    }
  }
}

 

建立「Create」檢視

下一個步驟建立「Create」檢視,將游標停留在「HomeController」控制器程式設計畫面「Create」方法之中,按一下滑鼠右鍵,從快捷選單選取「Add View」。在「Add Razor View」對話盒中,設定:

  • · 「View name」:「Create」。
  • · 「Template」:「Create」。
  • · 「Model class」:選取「Store」類別。
  • · 勾選「Use a layout page」核取方塊。

然後按下「Add」按鈕。Visual Studio 2019便會在「Views\Home」資料夾下,新增一個「Create.cshtml」檔案,請參考下圖所示:

clip_image010

圖 5:建立「Create」檢視。

選取Visual Studio 開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯。在Visual Studio開發工具,按CTRL+F5執行網站,點選「Index」檢視上的「Create New」連結,或在瀏覽器輸入以下網址(URL)進入新增資料畫面:

https://localhost:44303/Home/Create

輸入商店資料,然後按「Create」按鈕:

clip_image012

圖 6:輸入要新增的資料。

此時你會得到一個HTTP狀態碼201例外錯誤,請參考下圖所示:

clip_image014

圖 7:HTTP狀態碼201例外錯誤。

這是因為我們設計的ASP.NET Core Web API在資料新增後,會回傳HTTP 201狀態碼,與「NSwagStudio」工具產生的「PublisherService」類別程式碼預期的狀態碼「200」不一致的關係,你可以修改「PublisherService」類別「CreateStoreAsync」方法內「if (status_ == 200 )」這段程式碼為「if (status_ == 201 || status_ == 200)」。「CreateStoreAsync」方法目前的程式碼如下:

  • MyWeb\PublisherService.cs

public async System.Threading.Tasks.Task<Store> CreateStoreAsync(Store body, System.Threading.CancellationToken cancellationToken)
{
     var urlBuilder_ = new System.Text.StringBuilder();
     urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/Stores/CreateStore");

     var client_ = _httpClient;
     try
     {
         using (var request_ = new System.Net.Http.HttpRequestMessage())
         {
             var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value));
             content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
             request_.Content = content_;
             request_.Method = new System.Net.Http.HttpMethod("POST");
             request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));

             PrepareRequest(client_, request_, urlBuilder_);
             var url_ = urlBuilder_.ToString();
             request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
             PrepareRequest(client_, request_, url_);

             var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
             try
             {
                 var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
                 if (response_.Content != null && response_.Content.Headers != null)
                 {
                     foreach (var item_ in response_.Content.Headers)
                         headers_[item_.Key] = item_.Value;
                 }

                 ProcessResponse(client_, response_);

                 var status_ = (int)response_.StatusCode;
                 if (status_ == 201 || status_ == 200)
                 {
                     var objectResponse_ = await ReadObjectResponseAsync<Store>(response_, headers_).ConfigureAwait(false);
                     if (objectResponse_.Object == null)
                     {
                         throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
                     }
                     return objectResponse_.Object;
                 }
                 else
                 {
                     var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
                     throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
                 }
             }
             finally
             {
                 if (response_ != null)
                     response_.Dispose();
             }
         }
     }
     finally
     {
     }
}

 

選取Visual Studio 2019開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯,完成後可以同時執行兩個網站,只要在Visual Studio 2019開發工具,按「CTRL」+「F5」組合鍵來執行網站應用程式,試著新增一筆資料,若資料沒有問題,應該會成功的新增到資料庫,並跳轉到「Index」檢視顯示出新增的資料,請參考下圖所示:

clip_image016

圖 8:成功新增資料。

HTTP狀態碼201例外錯誤另一種解法

若不修改「PublisherService」的程式碼,要解決上述HTTP 201狀態碼例外錯誤的話,另一種解決方法是修改在ASP.NET Core MVC「MyWebAPI」專案中「StoresController」類別的「PostStore」方法,在成功將資料寫到資料庫之後,直接回傳「Task.FromResult( store )」這樣ASP.NET Core API在資料新增之後將會回傳HTTP 200狀態碼,請參考以下程式列表:

  • MyWebAPI\Controllers\StoresController.cs

[HttpPost("CreateStore")]
public async Task<ActionResult<Store>> ( Store store ) {
  _context.Stores.Add( store );
  try {
    await _context.SaveChangesAsync( );
  }
  catch ( DbUpdateException ) {
    if ( StoreExists( store.StorId ) ) {
      return Conflict( );
    }
    else {
      throw;
    }
  }
  return await Task.FromResult( store );
  //return CreatedAtAction( "GetStore", new { id = store.StorId }, store );
}

 

修改資料

在ASP.NET Core MVC「MyWeb」專案中「HomeController」類別中加入兩個「Edit」方法程式碼,第一個「Edit」方法接收一個「string」型別的「id」參數;第二個「Edit」方法接收一個「string」型別的「id」參數與一個「Store」型別的「store」參數。在標識「HttpPost」 Attribute的「Edit」方法中,叫用「PublisherService」的「UpdateStoreAsync」方法將「Store」物件送到ASP.NET Core Web API做修改,然後利用「RedirectToAction」方法回到「Index」檢視,請參考以下程式列表:

  • MyWeb\Controllers\HomeController.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MyWeb.Models;

namespace MyWeb.Controllers {
  public class HomeController : Controller {
    private readonly ILogger<HomeController> _logger;
    private readonly IHttpClientFactory _httpClientFactory;
    public HomeController( ILogger<HomeController> logger, IHttpClientFactory httpClientFactory ) {
      _logger = logger;
      _httpClientFactory = httpClientFactory;
    }

    public async Task<IActionResult> Index( ) {

      PublisherService client =
        new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );
      ICollection<Store> stores = await client.GetStoresAsync( );
      return View( stores );
    }
    public async Task<IActionResult> Details( string id ) {
      if ( id == null ) {
        return NotFound( );
      }

      PublisherService client =
        new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );

      Store store = await client.GetStoreByIdAsync( id );


      if ( store == null ) {
        return NotFound( );
      }
      return View( store );
    }

    public IActionResult Create( ) {
      return View( );
    }

    [HttpPost]
    public async Task<IActionResult> Create( Store store ) {
      if ( ModelState.IsValid ) {
        PublisherService client =
         new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );
         await client.CreateStoreAsync( store );
        return RedirectToAction( nameof( Index ) );
      }
      return View( store );
    }

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

      PublisherService client =
        new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );
     
      Store store = await client.GetStoreByIdAsync( id );


      if ( store == null ) {
        return NotFound( );
      }
      return View( store );
    }

    [HttpPost]
    public async Task<IActionResult> Edit( string id, Store store ) {
      if ( id != store.StorId) {
        return NotFound( );
      }

      if ( ModelState.IsValid ) {
          PublisherService client =
        new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );
        await client.UpdateStoreAsync(id, store );
        return RedirectToAction( nameof( Index ) );
      }
      return View( store );
    }

    public IActionResult Privacy( ) {
      return View( );
    }

    [ResponseCache( Duration = 0, Location = ResponseCacheLocation.None, NoStore = true )]
    public IActionResult Error( ) {
      return View( new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier } );
    }
  }
}

 

建立「Edit」檢視

將游標停留在「HomeController」控制器程式設計畫面「Edit」方法之中,按一下滑鼠右鍵,從快捷選單選取「Add View」。在「Add Razor View」對話盒中,設定以下項目:

  • · 「View name」:「Edit」。
  • · 「Template」:「Edit」。
  • · 「Model class」:選取「Store」類別。
  • · 勾選「Use a layout page」核取方塊。

然後按下「Add」按鈕。Visual Studio 2019便會在「Views\Home」資料夾下,新增一個「Edit.cshtml」檔案,請參考下圖所示:

clip_image018

圖 9:建立「Edit」檢視。

目前「Edit」檢視的程式碼看起來如下:

  • MyWeb\Views\Home\Edit.cshtml

@model MyWeb.Store

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

<h1> Edit </h1>

<h4> Store </h4>
<hr />
<div class = "row">
    <div class = "col-md-4">
        <form asp-action = "Edit">
            <div asp-validation-summary = "ModelOnly" class = "text-danger"> </div>
            <div class = "form-group">
                <label asp-for = "StorId" class = "control-label"> </label>
                <input asp-for = "StorId" class = "form-control" />
                <span asp-validation-for = "StorId" class = "text-danger"> </span>
            </div>
            <div class = "form-group">
                <label asp-for = "StorName" class = "control-label"> </label>
                <input asp-for = "StorName" class = "form-control" />
                <span asp-validation-for = "StorName" class = "text-danger"> </span>
            </div>
            <div class = "form-group">
                <label asp-for = "StorAddress" class = "control-label"> </label>
                <input asp-for = "StorAddress" class = "form-control" />
                <span asp-validation-for = "StorAddress" class = "text-danger"> </span>
            </div>
            <div class = "form-group">
                <label asp-for = "City" class = "control-label"> </label>
                <input asp-for = "City" class = "form-control" />
                <span asp-validation-for = "City" class = "text-danger"> </span>
            </div>
            <div class = "form-group">
                <label asp-for = "State" class = "control-label"> </label>
                <input asp-for = "State" class = "form-control" />
                <span asp-validation-for = "State" class = "text-danger"> </span>
            </div>
            <div class = "form-group">
                <label asp-for = "Zip" class = "control-label"> </label>
                <input asp-for = "Zip" class = "form-control" />
                <span asp-validation-for = "Zip" class = "text-danger"> </span>
            </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>

 

修改「Index」檢視,同樣在叫用「Html.ActionLink」方法產生「Edit」超連結時,第三個參數傳入一個匿名物件,指定要編輯的商店編號,以方便切換到「Edit」資料畫面:

Html.ActionLink( "Edit ", " Edit ", new { id = item.StorId } )

目前「Index」檢視的程式碼看起來如下:

  • MyWeb\Views\Home\Index.cshtml

@model IEnumerable<MyWeb.Store>

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

<h1> Index </h1>

<p>
    <a asp-action = "Create"> Create New </a>
</p>
<table class = "table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor( model => model.StorId )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.StorName )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.StorAddress )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.City )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.State )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.Zip )
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor( modelItem => item.StorId )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.StorName )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.StorAddress )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.City )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.State )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.Zip )
            </td>
            <td>
                @Html.ActionLink( "Edit", "Edit", new { id = item.StorId }) |
                @Html.ActionLink( "Details", "Details", new { id = item.StorId }) |
                @Html.ActionLink( "Delete", "Delete", new { /* id = item.PrimaryKey */ })
            </td>
        </tr>
}
    </tbody>
</table>

 

選取Visual Studio 2019開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯,完成後可以同時執行兩個網站,只要在Visual Studio 2019開發工具,按「CTRL」+「F5」組合鍵來執行網站應用程式,點選任一筆資料後方的「Edit」連結,便可看到資料呈現在編輯畫面上,請參考下圖所示:

clip_image020

圖 10:儲存編輯的資料。

和新增資料的結果類似,這次資料儲存時會發生HTTP狀態碼204號錯誤,執行結果請參考下圖所示:

clip_image022

圖 11: HTTP狀態碼204例外錯誤。

修改「PublisherService」類別「UpdateStoreAsync」方法內「if (status_ == 200 )」這段程式碼為「if (status_ == 200 || status_ == 204)」。「UpdateStoreAsync」方法目前的程式碼如下:

  • MyWeb\PublisherService.cs

public async System.Threading.Tasks.Task UpdateStoreAsync(string id, Store body, System.Threading.CancellationToken cancellationToken)
{
    var urlBuilder_ = new System.Text.StringBuilder();
    urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/Stores/UpdateStore/{id}");
    urlBuilder_.Replace("{id}", System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture)));

    var client_ = _httpClient;
    try
    {
        using (var request_ = new System.Net.Http.HttpRequestMessage())
        {
            var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value));
            content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
            request_.Content = content_;
            request_.Method = new System.Net.Http.HttpMethod("PUT");

            PrepareRequest(client_, request_, urlBuilder_);
            var url_ = urlBuilder_.ToString();
            request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
            PrepareRequest(client_, request_, url_);

            var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
            try
            {
                var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
                if (response_.Content != null && response_.Content.Headers != null)
                {
                    foreach (var item_ in response_.Content.Headers)
                        headers_[item_.Key] = item_.Value;
                }

                ProcessResponse(client_, response_);

                var status_ = (int)response_.StatusCode;
                if (status_ == 200 || status_ == 204 )
                {
                    return;
                }
                else
                {
                    var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
                    throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
                }
            }
            finally
            {
                if (response_ != null)
                    response_.Dispose();
            }
        }
    }
    finally
    {
    }
}

 

選取Visual Studio 2019開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯,完成後可以同時執行兩個網站,只要在Visual Studio 2019開發工具,按「CTRL」+「F5」組合鍵來執行網站應用程式,試著修改一筆資料,若資料沒有問題,這次應該會成功的修改資料,並跳轉到「Index」檢視顯示出修改過的資料。

刪除資料

在ASP.NET Core MVC「MyWeb」專案中「HomeController」類別中加入「Delete」方法與「DeleteConfirm」方法。由於這兩個方法的參數都是一個「string」型別的「id」變數,因此方法名稱不能命名為相同,不符合程式語言方法多載(Method Overload)的原則。解決方式是將兩個方法分別取不同名稱:「Delete」與「DeleteConfirm」,並在「DeleteConfirm」方法上方套用「ActionName("Delete")」Attribute表示這是資料使用HTTP POST提交時,要執行的「Delete」方法。

在標識「HttpPost」 Attribute的「Delete」方法中,叫用「PublisherService」的「DeleteStoreAsync」方法讓Web API刪除資料庫的資料,然後利用「RedirectToAction」方法回到「Index」檢視,請參考以下程式列表:

  • MyWebAPI\Controllers\StoresController.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MyWeb.Models;

namespace MyWeb.Controllers {
  public class HomeController : Controller {
    private readonly ILogger<HomeController> _logger;
    private readonly IHttpClientFactory _httpClientFactory;
    public HomeController( ILogger<HomeController> logger, IHttpClientFactory httpClientFactory ) {
      _logger = logger;
      _httpClientFactory = httpClientFactory;
    }

    public async Task<IActionResult> Index( ) {

      PublisherService client =
        new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );
      ICollection<Store> stores = await client.GetStoresAsync( );
      return View( stores );
    }
    public async Task<IActionResult> Details( string id ) {
      if ( id == null ) {
        return NotFound( );
      }

      PublisherService client =
        new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );

      Store store = await client.GetStoreByIdAsync( id );


      if ( store == null ) {
        return NotFound( );
      }
      return View( store );
    }

    public IActionResult Create( ) {
      return View( );
    }

    [HttpPost]
    public async Task<IActionResult> Create( Store store ) {
      if ( ModelState.IsValid ) {
        PublisherService client =
         new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );
        await client.CreateStoreAsync( store );
        return RedirectToAction( nameof( Index ) );
      }
      return View( store );
    }

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

      PublisherService client =
        new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );

      Store store = await client.GetStoreByIdAsync( id );


      if ( store == null ) {
        return NotFound( );
      }
      return View( store );
    }

    [HttpPost]
    public async Task<IActionResult> Edit( string id, Store store ) {
      if ( id != store.StorId ) {
        return NotFound( );
      }

      if ( ModelState.IsValid ) {
        PublisherService client =
      new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );
        await client.UpdateStoreAsync( id, store );
        return RedirectToAction( nameof( Index ) );
      }
      return View( store );
    }

    public async Task<IActionResult> Delete( string id ) {
      if ( id == null ) {
        return NotFound( );
      }
      PublisherService client =
        new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );
      Store store = await client.GetStoreByIdAsync( id );

      if ( store == null ) {
        return NotFound( );
      }
      return View( store );
    }

    [HttpPost, ActionName( "Delete" )]
    public async Task<IActionResult> DeleteConfirmed( string id ) {
      PublisherService client =
         new PublisherService( "https://localhost:44311/", _httpClientFactory.CreateClient( ) );
      await client.DeleteStoreAsync( id );
      return RedirectToAction( nameof( Index ) );
    }

    public IActionResult Privacy( ) {
      return View( );
    }

    [ResponseCache( Duration = 0, Location = ResponseCacheLocation.None, NoStore = true )]
    public IActionResult Error( ) {
      return View( new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier } );
    }
  }
}

 

建立「Delete」檢視

將游標停留在「HomeController」控制器程式設計畫面「Delete」方法之中,按一下滑鼠右鍵,從快捷選單選取「Add View」。在「Add Razor View」對話盒中,設定以下項目:

  • · 「View name」:「Delete」。
  • · 「Template」:「Delete」。
  • · 「Model class」:選取「Store」類別。
  • · 勾選「Use a layout page」核取方塊。

然後按下「Add」按鈕。Visual Studio 2019便會在「Views\Home」資料夾下,新增一個「Delete.cshtml」檔案,請參考下圖所示:

clip_image024

圖 12:建立「Delete」檢視。

然後按下「Add」按鈕。Visual Studio 2019便會在「Views\Home」資料夾下,新增一個「Delete.cshtml」檔案。目前「Delete」檢視的程式碼看起來如下:

  • MyWeb\Views\Home\Delete.cshtml

@model MyWeb.Store

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

<h1> Delete </h1>

<h3> Are you sure you want to delete this? </h3>
<div>
    <h4> Store </h4>
    <hr />
    <dl class = "row">
        <dt class = "col-sm-2">
            @Html.DisplayNameFor( model => model.StorId )
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor( model => model.StorId )
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor( model => model.StorName )
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor( model => model.StorName )
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor( model => model.StorAddress )
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor( model => model.StorAddress )
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor( model => model.City )
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor( model => model.City )
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor( model => model.State )
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor( model => model.State )
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor( model => model.Zip )
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor( model => model.Zip )
        </dd>
    </dl>
   
    <form asp-action = "Delete">
        <input type = "submit" value = "Delete" class = "btn btn-danger" /> |
        <a asp-action = "Index"> Back to List </a>
    </form>
</div>

 

修改「Index」檢視,同樣在叫用「Html.ActionLink」方法產生「Delete」超連結時,第三個參數傳入一個匿名物件,指定要刪除的商店編號,以方便切換到「Delete」資料確認畫面:

Html.ActionLink( "Delete ", " Delete ", new { id = item.StorId } )

目前「Index」檢視的程式碼看起來如下:

  • MyWeb\Views\Home\Index.cshtml

@model IEnumerable<MyWeb.Store>

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

<h1> Index </h1>

<p>
    <a asp-action = "Create"> Create New </a>
</p>
<table class = "table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor( model => model.StorId )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.StorName )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.StorAddress )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.City )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.State )
            </th>
            <th>
                @Html.DisplayNameFor( model => model.Zip )
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor( modelItem => item.StorId )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.StorName )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.StorAddress )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.City )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.State )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.Zip )
            </td>
            <td>
                @Html.ActionLink( "Edit", "Edit", new { id=item.StorId }) |
                @Html.ActionLink( "Details", "Details", new { id=item.StorId }) |
                @Html.ActionLink( "Delete", "Delete", new { id = item.StorId } )
            </td>
        </tr>
}
    </tbody>
</table>

 

選取Visual Studio 2019開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯,完成後可以同時執行兩個網站,只要在Visual Studio 2019開發工具,按「CTRL」+「F5」組合鍵來執行網站應用程式,點選任一筆資料後方的「Delete」連結,便可看到資料呈現在刪除確認畫面上,點選「Delete」按鈕便會將資料刪除,請參考下圖所示:

clip_image026

圖 13:確認刪除。

Tags:

.NET Magazine國際中文電子雜誌 | ASP.NET Web API | C# | 許薰尹Vivid Hsu

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List