More

    搜索頁面 – Service層的工作 – 自動套用一般搜索條件 (.NET MVC 5 Ch-23)

    在上一篇介紹完了如何動態產生Linq條件之後,在這一篇,將會透過Reflection和Dynamic Linq Query來讓Service層,能夠在不做任何事情的情況下,自動對資料做過濾,並且轉成對應的ViewModel配上分頁。

    同步發表於我的部落格:http://alantsai2007.blogspot.com/2014/10/BuildYourOwnApplicationFrameworkOnMvc-23-IndexPage-AutomaticSsearch.html

    Service層的處理

    在處理搜索的部份,Service層將會需要:

    1. 透過Reflection取得要搜索的欄位 – 這邊要記得是不要base的欄位(不要那些例如目前在第幾頁,和一頁幾筆的那種)
    2. 依照Reflection的欄位和Dynamic LInq Query組成搜索條件
    3. 做搜索並且用Automapper把Entity 對應到ViewModel搭配分頁

    Service處理搜索的方法

    首先,之前的那個GenericService將會多一個方法叫做ProcessIndexViewModel

    /// <summary>
    /// 通用行的Service layer實作
    /// </summary>
    /// <typeparam name="T">主要的Entity形態</typeparam>
    public class GenericService<T> : IService<T>
        where T : class
    {
    ....
    	/// <summary>
        /// 處理在Index ViewModel所需要做的事情
        /// </summary>
        /// <typeparam name="TSearchForm">搜索form的形態</typeparam>
        /// <typeparam name="TPageResult">搜索結果的形態</typeparam>
        /// <param name="searchViewModel">搜索相關的viewmodel</param>
        /// <param name="includes">需要Include進來的其他Entity</param>
        public virtual void ProcessIndexViewModel<TSearchForm, TPageResult>(ISearchViewModelBase<TSearchForm, TPageResult> searchViewModel,  
            params System.Linq.Expressions.Expression<Func<T, object>>[] includes) 
            where TSearchForm : ISearchFormViewModelBase, new()
        {
            var data = db.Repository<T>().Reads();
    
            foreach (var item in includes)
            {
                data.Include(item);
            }
    		
    		SearchViewModelProcess.ApplySearchForm<T, TSearchForm, TPageResult>(data,
                        searchViewModel);
        }
    ....
    }
    

    基本上ProcessIndexViewModel會接受一個之前定義過的SearchViewModelBase的形態,同時假設搜索的內容需要做到Include的話,可以設定。

    再來就詳細看一下實際做搜索的邏輯。

    /// <summary>
    /// 把Search Form Viewmodel的OrderBy和Where條件apply上去。
    /// 會把最終內容儲存到SearchViewModelBase.Result裡面。
    /// </summary>
    /// <typeparam name="T">EF 的Entity</typeparam>
    /// <typeparam name="TSearchForm">Search Form ViewModel的Type</typeparam>
    /// <typeparam name="TPageResult">Search結果的VieModel type</typeparam>
    /// <param name="data">原始的IQueryable</param>
    /// <param name="searchForm">Search Form ViewModel</param>
    public static void ApplySearchForm<T, TSearchForm, TPageResult>(IQueryable<T> data, ISearchViewModelBase<TSearchForm, TPageResult> searchForm)
         where TSearchForm : ISearchFormViewModelBase, new()
    {
        data = data.DynamicWhere(searchForm.SearchForm);
    
        ApplyOrderByAndToPageResult<T, TSearchForm, TPageResult>(data, searchForm);
    }
    

    ApplySearchForm會在呼叫兩個方法,一個是下搜索條件,一個是做實際的搜索的呼叫AutoMapper,先來看一下實際的搜索。

    Reflection取得欄位值和組成搜索條件

    /// <summary>
    /// 依照Search Form ViewModel的值來設定Where的內容。
    /// </summary>
    /// <typeparam name="T">通常是EF的Entity</typeparam>
    /// <param name="data">要被處理的資料</param>
    /// <param name="searchForm">Search Form Viewmodel的值</param>
    /// <returns>有增加OrderBy的IQueryable</returns>
    public static IQueryable<T> DynamicWhere<T>(this IQueryable<T> data, ISearchFormViewModelBase searchForm)
    {
        var properties = searchForm.GetType().GetProperties
    						(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public)
                            .Where(x => (x.GetGetMethod().GetBaseDefinition() == x.GetGetMethod())).ToArray(); // where 條件是用來避免override的property (例如:OrderByColumnName)也被算進去。
    
        string whereCalus = string.Empty;
        string andOperator = string.Empty;
        List<object> propertiesValue = new List<object>();
    
        for (int i = 0; i < properties.Length; i++)
        {
            var value = properties[i].GetValue(searchForm);
    
            if (string.IsNullOrEmpty(value.NonNullString()) == false)
            {
                whereCalus = string.Format("{0}{1} {2} = @{3}", whereCalus, andOperator,
                        properties[i].Name, propertiesValue.Count);
    
                andOperator = " and";
    
                propertiesValue.Add(value);
            }
        }
    
        if (string.IsNullOrEmpty(whereCalus) == false)
        {
            data = data.Where(whereCalus, propertiesValue.ToArray());
        }
    
        return data;
    }
    

    這邊注意到核心是取得properties的部分,只需要後來繼承的欄位,和注意不要取得複寫的欄位,例如OrderByColumnName。之後,就是用Dynamic Linq Query來組裝搜索條件。

    執行完了DynamicWhere,就有了搜索的條件,接下來就是執行搜索條件並且轉成對應的ViewModel和分頁。

    做搜索和用AutoMapper對應

    /// <summary>
    /// Apply Orderby並且把最後結果塞到SearchViewModelBase.Result裡面。
    /// </summary>
    /// <typeparam name="T">EF 的Entity</typeparam>
    /// <typeparam name="TSearchForm">Search Form ViewModel的Type</typeparam>
    /// <typeparam name="TPageResult">Search結果的VieModel type</typeparam>
    /// <param name="data">原始的IQueryable</param>
    /// <param name="searchForm">Search Form ViewModel</param>
    private static void ApplyOrderByAndToPageResult<T, TSearchForm, TPageResult>(IQueryable<T> data, 
        ISearchViewModelBase<TSearchForm, TPageResult> searchForm) where TSearchForm : ISearchFormViewModelBase, new()
    {
        if (searchForm.SearchForm.IsAscending)
        {
            data = data.OrderBy(searchForm.SearchForm.OrderByColumnName);
        }
        else
        {
            data = data.OrderBy(searchForm.SearchForm.OrderByColumnName + " descending");
        }
    
        searchForm.Result = data.Project().To<TPageResult>().ToPagedList(searchForm.SearchForm.Page, searchForm.SearchForm.PageSize);
    }
    

    可以看到,先依照欄位做排序(這邊需要注意到OrderBy也是Dynamic Linq Query的方法),然後在把資料轉型把結果放到Result裡面。

    Controller呼叫搜索

    在前面呼叫就變得比較簡單:

    public ActionResult Index(Index searchViewModel)
    {
        service.ProcessIndexViewModel(searchViewModel);
    
        return View(searchViewModel);
    }
    

    Service是和之前一樣注入進來的。這邊把Index ViewModel作為方法參數是有兩個用意:

    1. 如果要做搜索或者按下下一頁的時候,會直接post back到這一邊,因此要接住才可以。
    2. 當第一次get的時候,為了邏輯一致,因此還是要呼叫ProcessIndexViewModel,但是如果Index ViewModel沒有被實例化,會造成裡面Property是null 導致出錯。

    結語

    透過這一篇,可以讓框架幫我們針對一般的欄位和條件自動做搜索和分頁,不過這一篇沒有介紹在View上面如何使用。

    在下一篇,將會介紹如何透過template讓產生的搜索欄位在不同功能看起來一致,並且一些helper來幫助產生url。

    Source : https://ithelp.ithome.com.tw/articles/10160094

    Recent Articles

    spot_img

    Related Stories

    Stay on op - Ge the daily news in your inbox