Alan Tsai 的學習筆記


學而不思則罔,思而不學則殆,不思不學則“網貸” 為現任微軟最有價值專家 (MVP)、微軟認證講師 (MCT) 、Blogger、Youtuber:記錄軟體開發的點點滴滴 著重於微軟技術、C#、ASP .NET、Azure、DevOps、Docker、AI、Chatbot、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
台灣 Pay QR Code
台灣 Pay QR Code
Line Pay 一卡通 QR Code
Line Pay 一卡通 QR Code
街口支付QR Code
支付寶QR Code
街口支付QR Code
微信支付QR Code
comments powered by Disqus