Asp.Net MVC4系列--進階篇之Helper(1)

mybwu_com發表於2014-04-13

從本章開始,將為讀者介紹MVC4中的helper使用方法

從sample開始

準備controller和Action

   public class TestController : Controller
    {
        public ActionResult Index()
        {
           ViewBag.Fruits = new[] { "Apple", "Orange","Pear" };
           ViewBag.Cities = new[] { "New York", "London","Paris" };
            string message = "This is an HTML element: <input>";
            return View("List",(object)message);
 
        }
    }


程式碼說明:準備了一個TestController,包含了一個Index Action(預設Action),Action中存了一些stringArray在ViewBag,返回了一個字串(包含了htmltag)。

準備View

<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
Here are the fruits:
@foreach (string str in (string[])ViewBag.Fruits) {
<b>@str </b>
}
</div>
<div>
Here are the cities:
@foreach (string str in (string[])ViewBag.Cities) {
<b>@str </b>
}
</div>
<div>
Here is the message:
<p>@Model</p>
</div>
</body>
</html>


程式碼說明:把Viewbag中的兩個string Array列印出來,並列印出傳來的message。

測試:


可以看到,View列印出了string array,並且htmltag被識別為了string。

下一步,建立一個inline的helper

現在希望對列印字串陣列的View中的程式碼做一下封裝,這樣可以很好的控制列印的這部分實現。

於是給出實現:

@helper  ListArrayItems(string[] items) {
foreach(string str in items) {
<b>@str </b>
}
}


在列印時候,就像呼叫C#函式那樣呼叫就可以了,傳入ViewBag中的Array:

<div>
Here are the fruits:
@ListArrayItems(ViewBag.Fruits)
</div>
<div>
Here are the cities:
@ListArrayItems(ViewBag.Cities)
</div>


使用helper的好處顯而易見,其實就是封裝帶來的好處,我們可以集中對行為進行控制,例如希望顯示為列表,只需要給helper改為:

@helper  ListArrayItems(string[] items) {
<ul>
@foreach(string str in items) {
<li>@str</li>
}
</ul>
}


測試:


實現ExternalHelper method

不想把程式碼coding在View裡,希望寫在C#裡來管理,那麼可以考慮實現一個External版本的:

第一步,實現一個customizehelper

Public  static class MyHelpers
    {
        public staticMvcHtmlString ListArrayItems(this HtmlHelper html, string[] list)
        {
            var  tag =new  TagBuilder("ul");
            foreach(var str in list)
            {
                var itemTag = new  TagBuilder("li");
               itemTag.SetInnerText(str);
               tag.InnerHtml += itemTag.ToString();
            }
            return  new MvcHtmlString(tag.ToString());
        }
}


可以看到,實際上是對HtmlHelper的擴充套件,接收一個stringarray引數,列印為<ul></li>列表,使用的是TagBuilder,返回值是MvcHtmlString。

1.在View中comment掉以前的inline版本,應用external版本的實現:

@*@helper ListArrayItems(string[] items) {
<ul>
@foreach(string str in items) {
<li>@str</li>
}
</ul>
} *@
@using MVCHelperStudy.CustomizeHelper


2.呼叫

<div>
Here are the fruits:
@Html.ListArrayItems((string[])ViewBag.Fruits)
</div>
<div>
Here are the cities:
@Html.ListArrayItems((string[])ViewBag.Cities)
</div>


HtmlHelper中常用的property:

RouteCollection

Application定義的路由

ViewBag

從呼叫的Controller/Action傳進來的ViewBag

ViewContext

包含了請求資訊,Controller,View等等

ViewContext中常用的property

Controller

當前請求呼叫的Controller

HelperContext

包含了當前請求的詳細資訊(Request,Reponse等等)

IsChildAction

呼叫是否來自ChildAction

RouteData

當前請求的路由資訊

View

呼叫當前helper的view

TagBuilder的常用函式,property

InnerHtml

設定控制元件內部html

SetInnerText(string)

設定控制元件包含的text

AddCssClass(string)

設定控制元件的css class

MergeAttribute(string,string,bool)

設定attribute,指定是否覆蓋舊值

Partial View , Child Action , Helper Method

前面章節講過了PartialView,ChildAction,這次也演示了Helper的用法,那麼什麼時候用哪個?

一般的,Helper會對Html的封裝多一些,如果希望封裝部分html的展示方法,可以考慮helper,一般會使用inline形式的,因為個人認為mvc後端寫external版本的helper比較蹩腳。

如果對於場景:來了一個請求,找到了action,action希望停留在當前view,完成一個區域性重新整理(ajaxcall),那麼可考慮partialview最合適了,這種場景,只涉及到action接到請求,來選擇view。

如果再複雜一些,view中一定要callaction,操作一下model,反過來render一個view,那麼就考慮採用childaction。不過一般情況下,接收請求返回partialview足以cover大部分需要應用ajax的場景,如果不夠,對於比較特殊的頁面,例如需要SPA(singlepage application ) , 那麼js template + JsonResult也是個不錯的方案。

字串Encoding

對於上例,Action中返回的字串中包含了”<input>”,如果希望mvc自動識別為一個輸入框,如何做?

準備一個helper

        public static  MvcHtmlStringDisplayMessage(this HtmlHelper html, string msg)
        {
            string result =String.Format("This is the message: <p>{0}</p>", msg);
            return new  MvcHtmlString(result);
        }


與直接顯示出model(字串型別)不同,我們包了一層,構造一個MvcHtmlString物件丟給mvcframework,這樣它就會識別為一個html輸出到瀏覽器。

改一下view

把剛才的View程式碼刪了,新增以下程式碼:

@model string
@using MVCHelperStudy.CustomizeHelper
 
<html>
<head>
<meta name="viewport" content="width=device-width"/>
<title>Index</title>
</head>
<body>
    <p>This is the contentfrom the view:</p>
<div style="border: thin solid black; padding: 10px">
Here is the message:
<p>@Model</p>
</div>
<p>This is the content from the helper method:</p>
<div style="border: thin solid black; padding: 10px">
@Html.DisplayMessage(Model)
</div>


程式碼說明:我們列印了兩個字串,一個是直接取@Model的,一個是呼叫了新的helper(Wrap為MvcHtmlString物件)

測試


可以看到我們的html字串被mvcframework識別為了控制元件render在瀏覽器上

現在改一下,如果我們的目的就是希望mvcframework把我的html字串識別為字串,那麼helper怎麼寫,簡單的返回字串就好了:

Public  static string DisplayMessage(this HtmlHelper html, string msg) {
return String.Format("This is the message:<p>{0}</p>", msg);
}


第二種方法

使用Html.Encode,依然返回MvcHtmlString:



Public  static MvcHtmlString DisplayEncodeMessage(this HtmlHelper html, string msg) { 
String  encodedMessage = html.Encode(msg); 
string result = String.Format("This is the message: <p>{0}</p>", encodedMessage); 
return new  MvcHtmlString(result); 
}



View呼叫一下:

Remove掉所有程式碼新增這行,@Html.DisplayEncodeMessage(@Model)

測試:


使用自帶的helper來構造一個表單

Mvc framework給我們提供了很多自帶的生成html的方法,讓我們可以生成表單。

從一個例子開始,

在Model資料夾新增Person類

public class Person
    {
        public int PersonId { get;set; }
        public string FirstName {get; set; }
        public string LastName {get; set; }
        public DateTime BirthDate {get; set; }
        public Address HomeAddress{ get; set; }
        public bool IsApproved {get; set; }
        public Role Role { get;set; }
    }
    public class Address
    {
        public string Line1 { get;set; }
        public string Line2 { get;set; }
        public string City { get;set; }
        public string PostalCode {get; set; }
        public string Country {get; set; }
    }
    public enum Role
    {
        Admin,
        User,
        Guest
}


一共三個類:person,address,role。

準備一個Controller,程式碼

public  class PersonController : Controller
    {
        public ActionResult CreatePerson()
        {
           return  View(new Person());
        }
 
       [HttpPost]
        public ActionResult  CreatePerson(Person person)
        {
           return  View(person);
        }
    }


程式碼說明:

準備兩個action,一個是不帶資料的,主要針對第一次來到這個頁面的情況;一個是接收資料的,針對提交表單的情況,並使用了httppost對請求訪問進行限制。

View的實現

<html>
@model MVCHelperStudy.Models.Person
@{
ViewBag.Title = "CreatePerson";
}
<h2>CreatePerson</h2>
<form action="/Person/CreatePerson" method="post">
<div class="dataElem">
<label>PersonId</label>
<input  name="personId"  value="@Model.PersonId"/>
</div>
<div  class="dataElem">
<label>First Name</label>
<input  name="FirstName" value="@Model.FirstName"/>
</div>
<div  class="dataElem">
<label>Last Name</label>
<input  name="lastName" value="@Model.LastName"/>
</div>
<input  type="submit" value="Submit" />
</form>


程式碼說明:

在不使用任何html helper 函式的情況下,手動coding一個form,表單指向Person/CreatePerson,method為post。


測試

1.第一次訪問這個頁面,可以看到View拿到了一個空的Person物件


2.Fill這個form:


3.提交表單

筆者在Action設定了斷點,為了說明請求成功被路由到了post版本的CreatePersonAction中。



注意,我們手動為每一個input設定了name屬性,這個很重要,因為mvcFramework在提交表單時會拿input的name屬性和model進行匹配並賦值,如果把name拿掉會怎樣?提交表單時這個input的值就沒法帶到action了。例如拿掉lastName的name屬性:

   <input  value="@Model.LastName"/>


然後按照剛才的流程提交表單,


在除錯狀態下檢視LastName的值:


可以看到,FirstName和Id的數值都被帶過來了,但是LastName由於name屬性沒有設定導致mvcframework沒有辦法匹配,因此資料沒有被正確賦值導致binding失敗,關於modelbinding,後面章節會講到。

使用Html.BeginForm, Html.EndForm 生成表單控制元件

顧名思義,這個函式就是生成<form>tag的,下面的列表是幾種過載和說明:

BeginForm()

指向當前的controller和當前view對應的action, http method :Get

BeginForm(action,controller)

指定一個controller/action

http method : Get

BeginForm(action,controller,method)

指定controller/action和

Http method

BeginForm(action,controller,method,attributes)

指定controller/action 和

http method ,並設定form的一些屬性(例如 class)

BeginForm(action,controller,routeValues,method,attributes)

指定controller/action,設定method,傳route的匿名物件,並可以設定form attribute

我們可以看到,從第一個到最後一個過載,我們可以自定義的事情越來越多,現在以一個最複雜的最後一個為例,看看生成的form是怎樣的,這樣我們可以考慮不同場景來決定是否使用這個方法:

@using (Html.BeginForm("CreatePerson", "Person",
new { id = "MyIdValue" }, FormMethod.Post,
new { @class = "personClass",data_formType="person"})) {
////html
}


生成的form

<form action="/Person/CreatePerson/MyIdValue" class="personClass" data-formType="person"
method="post">


生成form時,specify一個路由

如果有需要,告訴mvcframework在生成表單時,按照指定的路由配置來生成,也是可以做到的:

對於路由配置:

            routes.MapRoute(
name: "FormRoute",
url: "app/forms/{controller}/{action}"
);


編寫View程式碼

   @using(Html.BeginRouteForm("FormRoute", new {},FormMethod.Post,
new { @class = "personClass",data_formType="person"})) {
//html
}


生成的表單html(Form部分)

<form  action="/app/forms/Person/CreatePerson" class="personClass"  data-formType="person"method="post"><div class="dataElem">

這樣就完成了手動根據路由來生成form的實現。


使用html helper

常用的用於html helper生成控制元件

CheckBox

Html.CheckBox(“name”,false)

<input id="myCheckbox" name="myCheckbox" type="checkbox" value="true" />

<input name="myCheckbox" type="hidden" value="false" />

Hidden Field

Html.Hidden("myHidden", "val")

<input id="myHidden" name="myHidden" type="hidden" value="val" />

Radio Button

Html.RadioButton("myRadiobutton", "val", true)

<input checked="checked" id="myRadiobutton" name="myRadiobutton"

type="radio" value="val" />

Password

Html.Password("myPassword", "val")

<input id="myPassword" name="myPassword" type="password" value="val" />

Text Area

Html.TextArea("myTextarea", "val", 5, 20, null)

<textarea cols="20" id="myTextarea" name="myTextarea" rows="5">

val</textarea>

Text box

Html.TextBox("myTextbox", "val")

<input id="myTextbox" name="myTextbox" type="text" value="val" />

使用html helper重構剛才的View

@using(Html.BeginRouteForm("FormRoute",new {}, FormMethod.Post,
new { @class ="personClass", data_formType="person"})) {
<div class="dataElem">
<label>PersonId</label>
@Html.TextBox("personId",@Model.PersonId)
</div>
<div class="dataElem">
<label>FirstName</label>
@Html.TextBox("firstName",@Model.FirstName)
</div>
<div class="dataElem">
<label>LastName</label>
@Html.TextBox("lastName",@Model.LastName)
</div>
<input type="submit" value="Submit" />
}


執行,檢視瀏覽器生成的html:

<form action="/app/forms/Person/CreatePerson" class="personClass" data-formType="person" method="post"><div class="dataElem">
<label>PersonId</label>
<input data-val="true" data-val-number="The field PersonId must be anumber." data-val-required="The PersonId field is required." id="personId" name="personId" type="text"value="0" />
</div>
<div class="dataElem">
<label>FirstName</label>
<input id="firstName" name="firstName" type="text"value="" />
</div>
<div class="dataElem">
<label>LastName</label>
<input id="lastName"  name="lastName"  type="text" value="" />
</div>
<input type="submit"  value="Submit" />
</form>


結果:

1.form標籤按照我們希望的方式生成了(根據路由配置生成)

2.htmlhelper生成了input,每個都有name,名稱為我們傳入的那個名字。

關於Html.TextBox(“”),mvcframework會分別從ViewData, ViewBag,和@Model.DataValue中查詢指定的property名稱,找到第一個就直接返回,完成匹配。


強型別的form表單

如果使用Html.TextBox(“name”)傳字串不太滿意,希望傳一個Member表示式,可以使用Html.TextBoxFor來達到目的:

支援強型別生成html控制元件的常用方法

CheckBox

Html.CheckBoxFor(x => x.IsApproved)

<input id="IsApproved" name="IsApproved" type="checkbox" value="true" />

<input name="IsApproved" type="hidden" value="false" />

HiddenField

Html.HiddenFor(x => x.FirstName)

<input id="FirstName" name="FirstName" type="hidden" value="" />

RadioButton

Html.RadioButtonFor(x => x.IsApproved, "val")

<input id="IsApproved" name="IsApproved" type="radio" value="val" />

Password

Html.PasswordFor(x => x.Password)

<input id="Password" name="Password" type="password" />

Text Area

Html.TextAreaFor(x => x.Bio, 5, 20, new{})

<textarea cols="20" id="Bio" name="Bio" rows="5">Bio value</textarea>

Text Box

Html.TextBoxFor(x => x.FirstName)

<input id="FirstName" name="FirstName" type="text" value="" />

幾乎和字串版本的類似,區別就是簽名多了一個For,接收的引數為一個MemberExpression。

使用強型別的版本重構我們的View:

@using(Html.BeginRouteForm("FormRoute",new {}, FormMethod.Post,
new { @class = "personClass",data_formType="person"})) {
<div class="dataElem">
<label>PersonId</label>
@Html.TextBoxFor(m => m.PersonId)
</div>
<div class="dataElem">
<label>First Name</label>
@Html.TextBoxFor(m => m.FirstName)
</div>
<div class="dataElem">
<label>Last Name</label>
@Html.TextBoxFor(m => m.LastName)
</div>
<input  type="submit"value="Submit" />
}


驗證一下瀏覽器生成的html是我們希望的:

<form action="/app/forms/Person/CreatePerson" class="personClass" data-formType="person" method="post"><div class="dataElem">
<label>PersonId</label>
<input  data-val="true" data-val-number="The field PersonId must be a number." data-val-required="The PersonId field is required." id="PersonId" name="PersonId" type="text" value="0" />

<div class="dataElem">
<label>First Name</label>
<input id="FirstName" name="FirstName" type="text" value="" />
</div>
<div class="dataElem">
<label>Last Name</label>
<input id="LastName" name="LastName" type="text" value="" />
</div>
<input type="submit" value="Submit" />
</form>


可以看到,mvcframework為我們生成了希望的html。

DropDownList

前面的例子沒有提到Dropdown,是希望以其為特例演示一下強型別的顯示。

1.先看一下html自帶的關於drop-down生成的方法

Html.DropDownList("myList", new SelectList(new [] {"A", "B"}), "Choose")

<select id="myList" name="myList">

<option value="">Choose</option>

<option>A</option>

<option>B</option>

</select>

Html.DropDownListFor(x => x.Gender, new SelectList(new [] {"M", "F"}))

<select id="Gender" name="Gender">

<option>M</option>

<option>F</option>

</select>

Html.ListBox("myList", new MultiSelectList(new [] {"A", "B"}))

<select id="myList" multiple="multiple" name="myList">

<option>A</option>

<option>B</option>

</select>

Html.ListBoxFor(x => x.Vals, new MultiSelectList(new [] {"A", "B"}))

<select id="Vals" multiple="multiple" name="Vals">

<option>A</option>

<option>B</option>

</select>

可以看到,mvc framework提供了不同的過載,我們可以生成multiselect和select。

生成帶有role的表單,修改View程式碼:

   @using(Html.BeginRouteForm("FormRoute", new {},FormMethod.Post,
new {@class = "personClass", data_formType="person"})) {
<div class="dataElem">
<label>PersonId</label>
@Html.TextBoxFor(m=> m.PersonId)
</div>
<div class="dataElem">
<label>FirstName</label>
@Html.TextBoxFor(m=> m.FirstName)
</div>
<div class="dataElem">
<label>LastName</label>
@Html.TextBoxFor(m=> m.LastName)
</div>
<div class="dataElem">
<label>Role</label>
@Html.DropDownListFor(m=> m.Role,
new SelectList(Enum.GetNames(typeof(MVCHelperStudy.Models.Role))))
</div>
<input  type="submit"value="Submit" />
}


這次我們新增了Role,我們使用了Enum的靜態方法獲得指定Enum型別中的所有值。傳遞給mvcframework的是Member Expression和string array。

測試:


瀏覽器生成的select標籤

<div class="dataElem">
<label>Role</label>
<select  data-val="true"  data-val-required="The Role field isrequired."  id="Role" name="Role">
<option selected="selected">Admin</option>
<option>User</option>
<option>Guest</option>
</select>
</div>


相關文章