Alan Tsai 的學習筆記


學而不思則罔,思而不學則殆,不思不學則“網貸” 為現任微軟最有價值專家 (MVP)、微軟認證講師 (MCT) 、Blogger、Youtuber:記錄軟體開發的點點滴滴 著重於微軟技術、C#、ASP .NET、Azure、DevOps、Docker、AI、Chatbot、Data Science

[iThome 第七屆鐵人賽 21] 搜索頁面 - ViewModel的定義

在上一篇介紹完了搜索功能的概念和思路之後,在這一篇開始要看實作的部份。

通常寫Mvc都是從Model開始,因此這一篇將來看一下搜索功能所會使用到的ViewModel

ViewModel的內容

首先,搜索的ViewModel必然會有兩個Property:

  1. 搜索條件
  2. 搜索結果

因此,我們會先從這兩個部份的Property來看起。

搜索條件的ViewModel

搜索條件會有一定有的欄位和各個domain所需要的欄位,因此會先定義一個Base,好方便之後domain來繼承並且提供其他相關欄位。

一定會有的欄位像是:

  1. 每頁筆數
  2. 目前頁數
  3. 排序欄位
  4. 排序順序

Domain相關的欄位就依照各自的需求,例如假設是一篇文章,可能會有以“標題”做搜索或者以“內文”做搜索。

因此,程式碼會如下:

/// <summary>
/// 搜索 Form 的 ViewModel base。定義搜索必須要有的相關欄位。
/// </summary>
public abstract class SearchFormViewModelBase : ISearchFormViewModelBase
{
    /// <summary>
    /// 目前頁數的值
    /// </summary>
    private int page;

    /// <summary>
    ///  取得或設定目前頁數。最小值是1。
    /// </summary>
    /// <value>
    /// 目前頁數
    /// </value>
    public virtual int Page
    {
        get
        {
            if (this.page < 1)
            {
                this.page = 1;
            }

            return this.page;
        }

        set { this.page = value; }
    }

    /// <summary>
    /// 每頁筆數的值
    /// </summary>
    private int pageSize;

    /// <summary>
    /// 取得或設定每頁筆數。最小值是15。
    /// </summary>
    /// <value>
    /// 每頁筆數
    /// </value>
    public virtual int PageSize
    {
        get
        {
            if (this.pageSize < 1)
            {
                this.pageSize = 15;
            }

            return this.pageSize;
        }

        set { this.pageSize = value; }
    }

    /// <summary>
    /// 欄位排序的值
    /// </summary>
    protected string orderByColumnName;

    /// <summary>
    /// 取得或設定要依照那個欄位做排序。
    /// </summary>
    /// <value>
    /// 依照那個欄位做排序.
    /// </value>
    public abstract string OrderByColumnName { get; set; }

    /// <summary>
    /// 取得或設定排序的方向。
    /// </summary>
    /// <value>
    /// <c>true</c> 表示用 ascending排序; otherwise, <c>false</c>.
    /// </value>
    public bool IsAscending { get; set; }
}

這個ViewModel實作的Interface就不看了,基本上就是這些Property的定義。


這邊有個地方可以注意到就是排序的欄位。因為我們這個SearchFormViewModelBase沒有形態的概念,而通常來說搜索條件會和某一個DB的Table對應。因此,為了方便之後框架幫忙做搜索,這邊又定義一個有強型別的SearchFormViewModelBase

/// <summary>
/// 搜索 Form 的 ViewModel base。有帶上形態,以第一個欄位做排序
/// </summary>
/// <typeparam name="T">Entity Framework裡面Table Entity</typeparam>
public abstract class SearchFormViewModelBase<T> : SearchFormViewModelBase
{
    /// <summary>
    /// 取得或設定要依照那個欄位做排序。
    /// </summary>
    /// <value>
    /// 依照那個欄位做排序.
    /// </value>
    public override string OrderByColumnName
    {
        get
        {
            if (string.IsNullOrEmpty(this.orderByColumnName))
            {
                this.orderByColumnName = typeof(T).GetProperties().First().Name;
            }

            return this.orderByColumnName;
        }

        set
        {
            this.orderByColumnName = value;
        }
    }
}

可以注意到這個Class的定義是Abstract,表示之後的class應該要依照自己的Domain去做繼承,例如如果今天是Post的搜索ViewModel,希望有一個 “標題”的搜索欄位,和排序要以“建立時間”欄位,那麼ViewModel就會是:
public class SearchFormViewModel : SearchFormViewModelBase<Post>
{
    [DisplayName("標題")]
    public string Title { get; set; }

    public override string OrderByColumnName
    {
        get
        {
            return "CreateDateTime";
        }
    }
}

搜索結果


這個部份就比較簡單,就是某一個和Detail頁面一樣ViewModel不過是被一個IPagedList包住。因此他不會像搜索ViewModel一樣有個base而是直接看定義的 ViewModel然後以泛型的方式傳入。



PagedList.Mvc - 好用的分頁套件



搜索ViewModel的主檔


有了這兩個Property的ViewModel之後,就可以建立搜索的ViewModel的Base:

/// <summary>
/// 搜索頁面的ViewModel需要繼承這一個Base。
/// 方便處理Paging和搜索條件相關。
/// 這個方法就兩個Property,用作於表示搜索的form和搜索結果的result。
/// </summary>
/// <typeparam name="TSearchForm">搜索的form ViewModel type。
/// 必須是繼承<see cref="MvcInfrastructure.Common.Base.SearchFormViewModelBase"/>
/// </typeparam>
/// <typeparam name="TPageResult">搜索的結果ViewModel type</typeparam>
public class SearchViewModelBase<TSearchForm, TPageResult> : 
	ISearchViewModelBase<TSearchForm, TPageResult>
    where TSearchForm : Core.Common.Base.ISearchFormViewModelBase, new() 
{
    private TSearchForm searchForm;

    /// <summary>
    /// 取得或設定搜索的Form。如果是null,會實例一個。
    /// </summary>
    /// <value>
    /// 搜索的Form
    /// </value>
    public TSearchForm SearchForm
    {
        get
        {
            if (this.searchForm == null)
            {
                this.searchForm = new TSearchForm();
            }

            return this.searchForm;
        }

        set { this.searchForm = value; }
    }

    /// <summary>
    /// 取得或設定搜索結果的ViewModel。
    /// </summary>
    /// <value>
    /// 搜索結果的ViewModel。用<see cref="PagedList.IPagedList"/>包住,方便做分頁
    /// </value>
    public IPagedList<TPageResult> Result { get; set; }
}


Post的搜索ViewModel


定義好了搜索的BaseViewModel之後,假設今天是Post頁面要做搜索的ViewModel,就可能會像是:

public class Index : SearchViewModelBase<SearchFormViewModel, SearchResult>
{
}

public class SearchResult : IMapFrom<Post>
{
    public int Id { get; set; }
    [DisplayName("標題")]
    public string Title { get; set; }
    [DisplayName("內文")]
    public string PostContent { get; set; }

}

public class SearchFormViewModel : SearchFormViewModelBase<Post>
{
    [DisplayName("標題")]
    public string Title { get; set; }

    public override string OrderByColumnName
    {
        get
        {
            return "CreateDateTime";
        }
    }
}

結語


有了搜索的ViewModel之後,在下一篇將會介紹框架的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