Contents

.Net Core 表單 Helper 使用紀錄

表單 Taghelper大概有這幾項

  • Form Tag Helper
  • Input Tag Helper
  • Label Tag Helper
  • Select Tag Helper
  • Textarea Tag Helper
  • Validation Tag Helper

表單

前置作業

_layout.cshtml 加入 bootstrap 選單

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    
    <environment include="Development">
        <link href="~/lib/bootstrap/dist/css/bootstrap.css" rel="stylesheet"/>
    </environment>

    <environment exclude="Development">
        <link rel="stylesheet"
                integrity="ha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
                crossorigin="anonymous"
                href="https://sstackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
                asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.css"
                asp-fallback-test-class="sr-only" asp-fallback-test-property="position"
                asp-fallback-test-value="absolute"
                asp-suppress-fallback-integrity="true"/>
    </environment>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
    <div class="container">
        <a class="navbar-brand" href="#">Navbar</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="navbarSupportedContent">
        <ul class="navbar-nav mr-auto">
            <li class="nav-item active">
            <a class="nav-link" asp-controller="home" asp-action="index">狗清單 </a>
            </li>
            <li class="nav-item">
            <a class="nav-link" asp-controller="home" asp-action="create">新增狗</a>
            </li>
        </ul>

        </div>
    </div>
</nav>

    <div class="container">
        @RenderBody()
    </div>



    @RenderSection("Scripts",required: false)
</body>
</html>

重點說明

  1. @RenderBody() 會載入當下 View 的內容回傳到這個位置。
  2. @RenderSection("Scripts",required: false) 會載入當下 View 自定的@Section Scripts 內容,很適合載入當下頁面需要載入一些js/css,第二個參數假如當前 View 沒定義 @Section Scripts 就會報錯
https://i.imgur.com/ZE9Io4a.png

上面敘述看不懂可以看一下這張圖,來源:c# - Determine what View is going to be rendered in @RenderBody() - Stack Overflow

推薦可以看認識View - View的種類 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天這篇。

新增表單

在 Controller 建立 Create 方法

1
2
3
4
        public IActionResult Create()
        {
            return View();
        }

建立 Create View

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@model WebApplication3.Models.Dog

<form asp-controller="home" asp-action="create" method="post">
    <div class="form-group row">
        <label asp-for="Name" class="col-sm-2 col-form-label"></label>
        <div class="col-sm-10">
            <input asp-for="Name" />
        </div>
    </div>
    <div class="form-group row">
        <label asp-for="Address" class="col-sm-2 col-form-label"></label>
        <div class="col-sm-10">
            <input asp-for="Address" />
        </div>
    </div>
    <div class="form-group row">
        <button class="btn btn-primary">送出</button>
    </div>
</form>

select 選單

建立 enum
WebApplication3\Models\AddressEnum.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace WebApplication3.Models
{
    public enum AddressEnum
    {
        tw,
        jp,
        cn,
        us,
    }
}
1
2
3
4
<div class="form-group row">
    <label asp-for="Address"class="col-sm-2 col-form-label" ></label>
    <select asp-for="Address" asp-items="Html.GetEnumSelectList<AddressEnum>()"></select>
</div>

Html.GetEnumSelectList可參考:HtmlHelper.GetEnumSelectList 方法 (Microsoft.AspNetCore.Mvc.ViewFeatures) | Microsoft Docs

https://i.imgur.com/Tw1WjBU.png

原始碼結果
https://i.imgur.com/atFmUNL.png

模型綁定

模型綁定是將 HTTP request 的資料映射到 Controller 操作法法上對應的參數。

操作方法中的參數可以是簡單類別,如整數、字串等,也可以是Class。

程式讀值順序:

  1. Form values(表單值)
  2. Route values (路由值)
  3. Query String (url參數值)

IRepository 增加 add

新增public Dog Add(Dog dog);

WebApplication3\Models\IDogRepository.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
using System.Collections.Generic;

namespace WebApplication3.Models
{
    public interface IDogRepository
    {
        public Dog getDog(int id);

        public IEnumerable<Dog> GetAllDogs();

        public Dog Add(Dog dog);
    }
}

新增這個方法

1
2
3
4
5
6
public Dog Add(Dog dog)
{
    dog.Id = _dogList.Max(s => s.Id) + 1;
    _dogList.Add(dog);
    return dog;
}

完整程式碼
WebApplication3\Models\MockDogRepository.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System.Collections.Generic;
using System.Linq;

namespace WebApplication3.Models
{
    public class MockDogRepository : IDogRepository
    {

        private List<Dog> _dogList;

        public MockDogRepository()
        {
            _dogList = new List<Dog>()
            {
                new Dog(){Id=1,Name ="dog1",Address="tw"},
                new Dog(){Id=2,Name ="dog2",Address="tw"},
                new Dog(){Id=3,Name ="dog2",Address="tw"},
            };
        }

        public Dog Add(Dog dog)
        {
            dog.Id = _dogList.Max(s => s.Id) + 1;
            _dogList.Add(dog);
            return dog;
        }

        public IEnumerable<Dog> GetAllDogs()
        {
            return _dogList;
        }

        public Dog getDog(int id)
        {
            return _dogList.FirstOrDefault(a => a.Id == id);
        }
    }
}

Controller 建立 Create 增加資料方法(Post)

  1. 添加 HttpGetHttpPost Attribute 到 Create 方法
  2. RedirectToActionResult 傳回放到 Post Create 方法
  3. RedirectToAction("Details", new { id = newDog.Id });
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
        [HttpGet]
        public IActionResult Create()
        {
            return View();
        }
        
        [HttpPost]        
        public RedirectToActionResult Create(Dog dog)
        {
            Dog newDog = _dogRepostory.Add(dog);
            return RedirectToAction("Details", new { id = newDog.Id });
        }

AmbiguousActionException: Multiple actions matched.

https://i.imgur.com/7tvnAuX.png

遇到這個錯誤不用緊張,是遇到路由重複宣告。這時候標註 HttpGetHttpPost 可以解決這個問題。

模型驗證

表單做簡單基本驗證。

必填(Required)

1
2
        [Required]
        public string Name { get; set; }

完整程式如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
using System.ComponentModel.DataAnnotations;

namespace WebApplication3.Models
{
    public class Dog
    {
        public int Id { get; set; }
        [Required]
        public string Name { get; set; }
        public string Address { get; set; }
    }
}

確認驗證是否正確

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
        [HttpPost]
        public IActionResult Create(Dog dog)
        {
            if (ModelState.IsValid)
            {
                Dog newDog = _dogRepostory.Add(dog);
                return RedirectToAction("Details", new { id = newDog.Id });
            }
            return View();
        }

完整寫法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using Microsoft.AspNetCore.Mvc;
using WebApplication3.Models;
using WebApplication3.ViewModels;

namespace WebApplication3.Controllers
{

    public class HomeController : Controller
    {
        private readonly IDogRepository _dogRepostory;

        public HomeController(IDogRepository dogRepository)
        {
            _dogRepostory = dogRepository;
        }


        public IActionResult Index()
        {
            var model = _dogRepostory.GetAllDogs();

            return View(model);
        }

        public IActionResult Details(int? id)
        {

            HomeDetailsViewModel homeDetailsViewModel = new HomeDetailsViewModel()
            {
                Dog = _dogRepostory.getDog(id??1),
                PageTitle = "狗的詳情"
            };

            return View(homeDetailsViewModel);
        }

        [HttpGet]
        public IActionResult Create()
        {
            return View();
        }

        [HttpPost]
        public IActionResult Create(Dog dog)
        {
            if (ModelState.IsValid)
            {
                Dog newDog = _dogRepostory.Add(dog);
                return RedirectToAction("Details", new { id = newDog.Id });
            }
            return View();
        }
    }
}

新增錯誤訊息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@model WebApplication3.Models.Dog

<form asp-controller="home" asp-action="create" method="post">

    <div asp-validation-summary="All"></div>

    <div class="form-group row">
        <label asp-for="Name" class="col-sm-2 col-form-label"></label>
        <div class="col-sm-10">
            <input asp-for="Name" class="form-control" />
            <span asp-validation-for="Name"></span>
        </div>
    </div>
    <div class="form-group row">
            <label asp-for="Address" class="col-sm-2 col-form-label" ></label>
        <div class="col-sm-10">
            <select asp-for="Address" asp-items="Html.GetEnumSelectList<AddressEnum>()" class="form-control" ></select>
        </div>
    </div>
    <div class="form-group row">
        <button class="btn btn-primary">送出</button>
    </div>
</form>

重點:

  1. <div asp-validation-summary="All"></div> 錯誤訊息全部顯示在這邊。
  2. 新增<span asp-validation-for="Name"></span>顯示單個錯誤訊息在這邊。

https://i.imgur.com/VtQY6Ur.png

定義自訂錯誤訊息

WebApplication3\WebApplication3\Models\Dog.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
using System.ComponentModel.DataAnnotations;

namespace WebApplication3.Models
{
    public class Dog
    {
        public int Id { get; set; }
        [Required(ErrorMessage = "請輸入姓名")]
        public string Name { get; set; }
        public string Address { get; set; }
    }
}

https://i.imgur.com/WNyeqaQ.png

其他驗證

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12

    [ValidateNever]: Indicates that a property or parameter should be excluded from validation.
    [CreditCard]: Validates that the property has a credit card format. Requires jQuery Validation Additional Methods.
    [Compare]: Validates that two properties in a model match.
    [EmailAddress]: Validates that the property has an email format.
    [Phone]: Validates that the property has a telephone number format.
    [Range]: Validates that the property value falls within a specified range.
    [RegularExpression]: Validates that the property value matches a specified regular expression.
    [Required]: Validates that the field isn't null. See [Required] attribute for details about this attribute's behavior.
    [StringLength]: Validates that a string property value doesn't exceed a specified length limit.
    [Url]: Validates that the property has a URL format.
    [Remote]: Validates input on the client by calling an action method on the server. See [Remote] attribute for details about this attribute's behavior.

可參考:Model validation in ASP.NET Core MVC | Microsoft Docs

Email驗證可以用

1
 [RegularExpression(@"^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$", ErrorMessage = "郵箱格式不正確")]

可以參考:Deali-Axy/AspNetCore-Learning-Mvc: ☀AspNetCore學習筆記(Mvc篇),學生管理系統

顯示 label 自訂名稱

WebApplication3\WebApplication3\Models\Dog.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
using System.ComponentModel.DataAnnotations;

namespace WebApplication3.Models
{
    public class Dog
    {
        public int Id { get; set; }
        [Display( Name = "姓名")]
        [Required(ErrorMessage = "請輸入姓名")]
        public string Name { get; set; }
        [Display(Name = "居住地")]
        public string Address { get; set; }
    }
}

https://i.imgur.com/NEBfKzZ.png

錯誤訊息累加

可以 Attribute 寫在一起。

1
2
        [Required(ErrorMessage = "請輸入姓名"),MaxLength(30,ErrorMessage ="姓名不能超過30個字")]
        public string Name { get; set; }

也可以分開都能

1
2
3
4
        [Display( Name = "姓名")]
        [Required(ErrorMessage = "請輸入姓名")]
        [MaxLength(30, ErrorMessage = "姓名不能超過30個字")]
        public string Name { get; set; }

發現 .Net Core 還會好心幫你改好 html 東西。
https://i.imgur.com/txlQS03.png

不知道是好事還壞事XD

下拉選單文字調整

WebApplication3\WebApplication3\Models\AddressEnum.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17

using System.ComponentModel.DataAnnotations;

namespace WebApplication3.Models
{
    public enum AddressEnum
    {
        [Display(Name = "台灣")]
        tw,
        [Display(Name = "日本")]
        jp,
        [Display(Name = "中國")]
        cn,
        [Display(Name = "美國")]
        us,
    }
}

WebApplication3\WebApplication3\Models\Dog.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
using System.ComponentModel.DataAnnotations;

namespace WebApplication3.Models
{
    public class Dog
    {
        public int Id { get; set; }
        [Display( Name = "姓名")]
        [Required(ErrorMessage = "請輸入姓名")]
        [MaxLength(30, ErrorMessage = "姓名不能超過30個字")]
        public string Name { get; set; }
        [Display(Name = "居住地")]
        public AddressEnum Address { get; set; }
    }
}

WebApplication3\Models\MockDogRepository.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System.Collections.Generic;
using System.Linq;

namespace WebApplication3.Models
{
    public class MockDogRepository : IDogRepository
    {

        private List<Dog> _dogList;

        public MockDogRepository()
        {
            _dogList = new List<Dog>()
            {
                new Dog(){Id=1,Name ="dog1",Address=AddressEnum.tw},
                new Dog(){Id=2,Name ="dog2",Address=AddressEnum.tw},
                new Dog(){Id=3,Name ="dog2",Address=AddressEnum.jp},
            };
        }

        public Dog Add(Dog dog)
        {
            dog.Id = _dogList.Max(s => s.Id) + 1;
            _dogList.Add(dog);
            return dog;
        }

        public IEnumerable<Dog> GetAllDogs()
        {
            return _dogList;
        }

        public Dog getDog(int id)
        {
            return _dogList.FirstOrDefault(a => a.Id == id);
        }
    }
}

https://i.imgur.com/h6xmZoP.png

我們這邊加個請選擇選項。

1
2
3
4
5
6
7
8
9
    <div class="form-group row">
            <label asp-for="Address" class="col-sm-2 col-form-label" ></label>
        <div class="col-sm-10">
            <select asp-for="Address" asp-items="Html.GetEnumSelectList<AddressEnum>()" class="form-control" >
                <option value="">請選擇</option>
            </select>
            <span asp-validation-for="Address"  class="text-danger"></span>
        </div>
    </div>

送出會遇到

https://i.imgur.com/vXNRkCq.png

解決方法,在Enum加個問號。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
using System.ComponentModel.DataAnnotations;

namespace WebApplication3.Models
{
    public class Dog
    {
        public int Id { get; set; }
        [Display( Name = "姓名")]
        [Required(ErrorMessage = "請輸入姓名")]
        [MaxLength(30, ErrorMessage = "姓名不能超過30個字")]
        public string Name { get; set; }
        [Display(Name = "居住地")]
        public AddressEnum? Address { get; set; }
    }
}

相關文章

這邊以前學 Laravel View 時候,我還記得他有方法可以控制 Boostrap 選單是不是當頁 selected 狀態,這邊原生沒有,但可以透過自己擴充來解決這個問題

1
2
3
4
$(document).ready(function() {
    $('ul.nav.navbar-nav').find('a[href="' + location.pathname + '"]')
        .closest('li').addClass('active');
});