2014年10月23日 星期四

[iThome 第七屆鐵人賽 24] 搜索頁面 - Service層的工作 - View方面的處理

在上一篇介紹完了Service如何自動處理搜索和Controller如何呼叫這個Service之後,接下來就要看view將會如何呼叫,並且透過一些Helper方便產生有正確RouteValue的連接。

功能描述

先看一下這一篇會介紹的內容,再來才詳細的描述。在這一篇會有以下的內容:

  1. 專門顯示SearchViewModelBase的EditorTemplate - 方便有一個統一的搜索顯示畫面
  2. Helper方便產生SearchViewModel看的懂的RouteValue
  3. PagedList.Mvc 顯示搜索結果的分頁清單

顯示SearchViewModelBase的EditorTemplate

通常來說,一個網站裡面的搜索風格會一致。也就是說,基本的內容都一樣,但是不同之處在於實際的搜索欄位,才會有整個網站的一致性。

透過搭配EditorTemplate,這個能夠很容易的做到。

首先,會在Views -> Shared ->EditorTemplates ->SearchFormViewModelBase.cshtml建立出搜索ViewModel的一個Template,裡面的內容如下:

@model SearchFormViewModelBase
@{
@*用Reflection取得目前這個ViewModel屬於搜索條件的Properties Name -
注意,這裡並不包含BaseViewModel的Property,而是只有繼承下來的Property
*@
var searchablePropertiesName = ReflectionHelper.
GetPropertiesOfCurrentType(Model.GetType()).
Select(x => x.Name);
var properties = ViewData.ModelMetadata.Properties.
Where(x => searchablePropertiesName.Contains(x.PropertyName));
}
@if(properties.Count() > 0)
{
<h3 class="box-title">搜索條件</h3>
@using (Html.BeginForm())
{
<div class="form-horizontal">
@foreach (var prop in properties)
{
@*因為搜索的ViewModel屬於Index ViewModel的一個Property,因此需要加上Prefix避免ModelBinding不到
- Prefix就是SearchViewModel在Index裡面的名字 - SearchForm*@

@*假設這個搜索欄位是一個下拉選單,在ViewData應該會有資料,要不然就是一般的輸入框*@
if (ViewData.ContainsKey(PagingHelper.PropertyNamePrefix + prop.PropertyName))
{
<div class="form-group">
@Html.Label(prop.PropertyName,
htmlAttributes: new { @class = "control-label col-md-4" })
<div class="col-md-8">
@Html.DropDownList(prop.PropertyName,
ViewData[PagingHelper.PropertyNamePrefix
+ prop.PropertyName] as IEnumerable<SelectListItem>,
"", htmlAttributes: new { @class = "form-control" })
</div>
</div>
}
else
{
<div class="form-group">
@Html.Label(prop.PropertyName,
htmlAttributes: new { @class = "control-label col-md-4" })
<div class="col-md-8">
@Html.Editor(prop.PropertyName,
new { htmlAttributes = new { @class = "form-control" } })
</div>
</div>
}
}
@Html.HiddenFor(x => x.Page)
@Html.HiddenFor(x => x.PageSize)
<div class="box-footer">
<input type="submit" value="搜索" class="btn btn-default" />
</div>
</div>
}
}


假設搜索欄位有什麼特別的實際,可以直接在這裡面修改,最後,每一頁要呈現搜索條件只需要在Index.cshtml寫:

@Html.EditorFor(x => x.SearchForm)

把SearchViewModel內容轉成RouteValueDictionary的Helper


有時候如果需要產生目前的搜索內容的值用作於產生鏈接的時候,一個Helper產生RouteValueDictionary是非常重要的。


可以在定義一個如下的Helper:

/// <summary>
/// 依照SearchModel的值,產生出RouteValueDictionary
/// </summary>
/// <param name="model">SearchModel的Instance</param>
/// <param name="rvd">需要增加到RouteValueDictionary的額外值</param>
/// <returns>返回產生的RouteValueDictionary</returns>
public static RouteValueDictionary GenRVDForSearchModel(object model,
RouteValueDictionary rvd = null)
{
if (rvd == null)
{
rvd = new RouteValueDictionary();
}

var properties = ReflectionHelper.GetPropertiesOfCurrentType(model.GetType());

for (int i = 0; i < properties.Length; i++)
{
var value = properties[i].GetValue(model);

if (string.IsNullOrEmpty(value.NonNullString()) == false)
{
rvd.Add(PropertyNamePrefix + properties[i].Name, value);
}
}

return rvd;
}
基本上就是把Model的內容產生對應的RouteValueDictionary。如果還不清楚這個方法的作用,接下來介紹建立分頁鏈接的時候就會清楚的看到如何使用這個方法。

為搜索結果產生分頁


PagedList.Mvc裡面有一個Helper可以方便產生分頁:

 @Html.PagedListPager(Model.Result, page =>
{
return Url.Action("Index", new {SearchForm.page = page});
});


基本上這個分頁的Helper會幫忙處理一些基本的Layout,我們需要做的就是告訴他每一個分頁連接如何產生即可。


不過這邊要注意到,上面的範例是當搜索條件不存在的情況下,這個連接是對的,如果在搜索條件有作用的情況下,每一個分頁的連接應該要包括目前的搜索條件才對。


因此,這個時候上一篇提到的Helper就有幫助,不過因為每一次建立的分頁會把要建立的分頁頁數帶進來,因此,需要建立新的Helper,來包住上面介紹產生RouteValueDictionary的Helper:

/// <summary>
/// 依照Search Form來產生 Route Value Dictionary
/// </summary>
/// <param name="model">Search Form的ViewModel</param>
/// <param name="page">那一頁</param>
/// <param name="pageSize">頁數</param>
/// <returns>
/// 返回產生的RouteValueDictionary
/// </returns>
public static RouteValueDictionary GenRVDBaseOnSearchFormModel(SearchFormViewModelBase model,
int? page = null, int? pageSize = null)
{
RouteValueDictionary rvd = new RouteValueDictionary();

rvd.Add(PropertyNamePrefix + "Page", page ?? model.Page);
rvd.Add(PropertyNamePrefix + "PageSize", pageSize ?? model.PageSize);

return GenRVDForSearchModel(model, rvd);
}

有了上面的Helper之後,我們產生分頁的方法就能夠變成:

@Html.PagedListPager(Model.Result, page =>
{
return Url.Action("Index",
PagingHelper.GenRVDBaseOnSearchFormModel(Model.SearchForm, page));
});


結語


到目前為止,在框架自動處理搜索的部分從Controller,到Service層到View裡面的使用都介紹了。


相信了解之後,對於要做基本的搜索不會有太大問題。


不過,在實務上面,Service層的自動搜索在使用上面還是會有些問題,最大的問題是,目前搜索只支援完全符合。但是,這樣的搜索一點都不好用。


因此,在下一篇,將會介紹如何把自動處理搜索的部分在進化一步。

沒有留言 :

張貼留言