\( \newcommand{\N}{\mathbb{N}} \newcommand{\R}{\mathbb{R}} \newcommand{\C}{\mathbb{C}} \newcommand{\Q}{\mathbb{Q}} \newcommand{\Z}{\mathbb{Z}} \newcommand{\P}{\mathcal P} \newcommand{\B}{\mathcal B} \newcommand{\F}{\mathbb{F}} \newcommand{\E}{\mathcal E} \newcommand{\brac}[1]{\left(#1\right)} \newcommand{\abs}[1]{\left|#1\right|} \newcommand{\matrixx}[1]{\begin{bmatrix}#1\end {bmatrix}} \newcommand{\vmatrixx}[1]{\begin{vmatrix} #1\end{vmatrix}} \newcommand{\lims}{\mathop{\overline{\lim}}} \newcommand{\limi}{\mathop{\underline{\lim}}} \newcommand{\limn}{\lim_{n\to\infty}} \newcommand{\limsn}{\lims_{n\to\infty}} \newcommand{\limin}{\limi_{n\to\infty}} \newcommand{\nul}{\mathop{\mathrm{Nul}}} \newcommand{\col}{\mathop{\mathrm{Col}}} \newcommand{\rank}{\mathop{\mathrm{Rank}}} \newcommand{\dis}{\displaystyle} \newcommand{\spann}{\mathop{\mathrm{span}}} \newcommand{\range}{\mathop{\mathrm{range}}} \newcommand{\inner}[1]{\langle #1 \rangle} \newcommand{\innerr}[1]{\left\langle #1 \right \rangle} \newcommand{\ol}[1]{\overline{#1}} \newcommand{\toto}{\rightrightarrows} \newcommand{\upto}{\nearrow} \newcommand{\downto}{\searrow} \newcommand{\qed}{\quad \blacksquare} \newcommand{\tr}{\mathop{\mathrm{tr}}} \newcommand{\bm}{\boldsymbol} \newcommand{\cupp}{\bigcup} \newcommand{\capp}{\bigcap} \newcommand{\sqcupp}{\bigsqcup} \newcommand{\re}{\mathop{\mathrm{Re}}} \newcommand{\im}{\mathop{\mathrm{Im}}} \newcommand{\comma}{\text{,}} \newcommand{\foot}{\text{。}} \)

Wednesday, August 21, 2019

ASP.NET 中的 Form

可能有些 old-school,現在我們都 post json format 到 server,但 project 裏有遇見過 form data 處理速度比 json 快的事例 (我還懵懂的在奇怪為甚麼 project 裏的 post request 在 network 的 xhr section 裏甚麼也看不到,多按幾次才發現按 "all" 就找到了,原來變成了 form data 在傳輸 ...),本着熟習 .net 的心態來學一下 form。

首先我們為一個 form 的頁面 (一個 view) 定義以下 ViewModel:
using ReallyTrue.Models;
using System.Collections.Generic;

namespace ReallyTrue.ViewModel
{
    public class NewCustomerViewModel
    {
        public Customer Customer { get; set; }
        public IEnumerable<Membershiptype> MembershipTypes { get; set; }  
    }
}
其中 Customer 跟 MembershipType 為
namespace ReallyTrue.Models
{
    public class Customer
    {
        public int Id { get; set; }
        [Required]
        [StringLength(255)]
        public string Name { get; set; }
        public bool IsSubscribedToNewsletter { get; set; }
        public DateTime? Birthday { get; set; }
        public MembershipType MembershipType { get; set; }
        public int? MembershipTypeId { get; set; }
    }
}
namespace ReallyTrue.Models
{
    public class MembershipType
    {
        public int Id { get; set; }
        public short SignUpFee { get; set; }
        public string Name { get; set; }
        public byte DurationInMonths { get; set; }
        public byte DiscountRate { get; set; }
    }
}
在 Home controller 我們定義 New method 為
        public ActionResult New()
        {
            var membershipTypes = _context.MembershipTypes.ToList();
            var viewModel = new NewCustomerViewModel()
            {
                MembershipTypes = membershipTypes
            };
            return View(viewModel);
        }
利用這個 viewModel (它會被儲存到 ViewResult.ViewData.Model),我們可以建立 ./home/create的 view (cshtml)
@model ReallyTrue.ViewModel.NewCustomerViewModel

@{
    ViewBag.Title = "New Customer";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>New Customer</h2>

@using (Html.BeginForm("Create", "Home"))
{
    <div class="form-group">
        @Html.LabelFor(m => m.Customer.Name)
        @Html.TextBoxFor(m => m.Customer.Name, new { @class = "form-control" })
    </div>
    <div class="form-group">
        <label>Date of Birth</label>
        @Html.TextBoxFor(m => m.Customer.Birthday, new { @class = "form-control" })
    </div>
    <div class="checkbox">
        <label>
            @Html.CheckBoxFor(m => m.Customer.IsSubscribedToNewsletter) Subscribed to Newsletter?
        </label>
    </div>


    <div class="form-group">
        <label>Membership Type</label>
        @Html.DropDownListFor(m => m.Customer.MembershipTypeId, new SelectList(Model.MembershipTypes, "Id", "Name"), "Select", new { @class = "form-control" })
    </div>
    <button type="submit" class="btn btn-primary">Save</button>
}
它會被編譯成這個様子:

其中 save 按下時,它會把 form data 以 post method 方式傳送到 /home/create 這個 route。因此,我們定義 home controller 中的 create method 為 (attribute 為 HttpPost)
       [HttpPost]
        public ActionResult Create(Customer customer)
        {
            return new EmptyResult();
        }
好,重點來了,form data 的 request 有甚麼,我們收到的東西又會是甚麼?我們利用 debugger 來看看:


一些小實驗,我們同樣可以用 NewCustomerViewModel 來定義 Create method 的 parameter,在 debugger 裏可以看到相似的結果,當然有一些差異,紀錄下來研究內裏的 data binding 怎様運作:


我們可以注意到,id 根本沒有 pass 進去,因此基於 int 不是 nullable 的特性,我們會得到 0 這個結果。

還有一些 form 的 key concept 要學,因為暫時沒有機會應用到所以也不詳細做紀錄了。
  1. Client side: @Html.ValidationMessageFor(m => m,Customer.Name),及 render jquery 的 validation bundle。

    Server side 的簡易 validation:
    在 Create method 中寫上
                if (!ModelState.IsValid)
                {
                    //do something, say return View(New ViewModel 
                    //{ Customer = customer, 
                    //  MembershipTypes = _context.MembershipTypes.ToList()
                    //}
                    //)
                }
    
  2. Data Annotation: [Required], [StringLength(int)], [Range(1,2019)], [Phone], [EmailAddress], [Url], [RegularExpression("...")], ...
  3. Anti-forgery Token  用來防止 malicious attack。

No comments:

Post a Comment