Alan Tsai 的學習筆記


學而不思則罔,思而不學則殆,不思不學則“網貸” 記錄軟體開發的點點滴滴 著重於微軟技術、網頁開發、DevOps、C#, Asp .net Mvc、Azure、AI、Chatbot、Docker、Data Science

[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層的自動搜索在使用上面還是會有些問題,最大的問題是,目前搜索只支援完全符合。但是,這樣的搜索一點都不好用。


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


如果文章對您有幫助,就請我喝杯飲料吧
街口支付QR Code
街口支付QR Code
街口支付QR Code
支付寶QR Code
街口支付QR Code
微信支付QR Code
comments powered by Disqus