[iThome 第七屆鐵人賽 14] 把目前的CRUD功能抽到Service層 @ Alan Tsai 的學習筆記|An Asp .Net Mvc Web Developer Blog

Alan Tsai 的學習筆記


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

[iThome 第七屆鐵人賽 14] 把目前的CRUD功能抽到Service層

在上一篇介紹完了什麽是Service層,和爲什麽要使用Service層。在這一篇,將會把CRUD裡面的方法先抽到Service層裡面,因此Controller不在直接和Data Access Layer溝通,而是透過Service層來和Data Access Layer溝通。

在定義Service功能的時候,將會使用泛型的方式讓程式碼能夠通用,然後各自在繼承通用型的方法來提供個別功能的客制。

IService<T>的定義

在定義實作之前,先來定義好相對應的功能有什麽。

/// <summary>
/// Service服務層內容的Interface
/// </summary>
/// <typeparam name="T">主要要儲存的Entity Type</typeparam>
public interface IService<T>
    where T : class
{

    /// <summary>
    /// 取得符合條件的Entity並且轉成對應的ViewModel
    /// </summary>
    /// <typeparam name="TViewModel">ViewModel的形態</typeparam>
    /// <param name="wherePredicate">過濾邏輯</param>
    /// <param name="includes">需要Include的Entity</param>
    /// <returns>取得轉換過的ViewModel List</returns>
    List<TViewModel> GetListToViewModel<TViewModel>(Expression<Func<T, bool>> wherePredicate, 
        params Expression<Func<T, object>>[] includes);

    /// <summary>
    /// 取得某一個條件下面的某一筆Entity並且轉成對應的ViewModel
    /// </summary>
    /// <typeparam name="TViewModel">ViewModel的形態</typeparam>
    /// <param name="wherePredicate">過濾邏輯</param>
    /// <param name="includes">需要Include的Entity</param>
    /// <returns>取得轉換過的ViewModel或者是null</returns>
    TViewModel GetSpecificDetailToViewModel<TViewModel>(Expression<Func<T, bool>> wherePredicate, 
        params Expression<Func<T, object>>[] includes);


    /// <summary>
    /// 依照某一個ViewModel的值,產生對應的Entity並且新增到資料庫
    /// </summary>
    /// <typeparam name="TViewModel">ViewModel的形態</typeparam>
    /// <param name="viewModel">ViewModel的Reference</param>
    /// <returns>是否儲存成功</returns>
    void CreateViewModelToDatabase<TViewModel>(TViewModel viewModel);

    /// <summary>
    /// 依照某一個ViewModel的值,更新對應的Entity
    /// </summary>
    /// <typeparam name="TViewModel">ViewModel的形態</typeparam>
    /// <param name="viewModel">ViewModel的值</param>
    /// <param name="wherePredicate">過濾條件 - 要被更新的那一筆過濾調價</param>
    /// <returns>是否刪除成功</returns>
    void UpdateViewModelToDatabase<TViewModel>(TViewModel viewModel, 
		Expression<Func<T, bool>> wherePredicate);

    /// <summary>
    /// 刪除某一筆Entity
    /// </summary>
    /// <param name="wherePredicate">過濾出要被刪除的Entity條件</param>
    /// <returns>是否刪除成功</returns>
    void Delete(Expression<Func<T, bool>> wherePredicate);
}

GenericService<T> - IService<T>的實作


這個部份將會實作IService裡面的服務。

/// <summary>
/// 通用行的Service layer實作
/// </summary>
/// <typeparam name="T">主要的Entity形態</typeparam>
public class GenericService<T> : IService<T>
    where T : class
{
    protected IUnitOfWork db;

      /// <summary>
    /// 取得符合條件的Entity並且轉成對應的ViewModel
    /// </summary>
    /// <typeparam name="TViewModel">ViewModel的形態</typeparam>
    /// <param name="wherePredicate">過濾邏輯</param>
    /// <param name="includes">需要Include的Entity</param>
    /// <returns>取得轉換過的ViewModel List</returns>
    public virtual List<TViewModel> GetListToViewModel<TViewModel>(System.Linq.Expressions.Expression<Func<T, bool>> wherePredicate, 
        params System.Linq.Expressions.Expression<Func<T, object>>[] includes)
    {
         var data = db.Repository<T>().Reads();

        foreach (var item in includes)
        {
            data.Include(item);
        }

        return AutoMapper.Mapper.Map<List<TViewModel>>(data.Where(wherePredicate));
    }

      /// <summary>
    /// 取得某一個條件下面的某一筆Entity並且轉成對應的ViewModel
    /// </summary>
    /// <typeparam name="TViewModel">ViewModel的形態</typeparam>
    /// <param name="wherePredicate">過濾邏輯</param>
    /// <param name="includes">需要Include的Entity</param>
    /// <returns>取得轉換過的ViewModel或者是null</returns>
    public virtual TViewModel GetSpecificDetailToViewModel<TViewModel>(System.Linq.Expressions.Expression<Func<T, bool>> wherePredicate, 
        params System.Linq.Expressions.Expression<Func<T, object>>[] includes)
    {
         var data = db.Repository<T>().Reads();

        foreach (var item in includes)
        {
            data.Include(item);
        }

        return AutoMapper.Mapper.Map<TViewModel>(data.Where(wherePredicate).FirstOrDefault());
    }


     /// <summary>
    /// 依照某一個ViewModel的值,更新對應的Entity
    /// </summary>
    /// <typeparam name="TViewModel">ViewModel的形態</typeparam>
    /// <param name="viewModel">ViewModel的值</param>
    /// <param name="wherePredicate">過濾條件 - 要被更新的那一筆過濾調價</param>
    /// <returns>是否刪除成功</returns>
    public virtual void UpdateViewModelToDatabase<TViewModel>(TViewModel viewModel, System.Linq.Expressions.Expression<Func<T, bool>> wherePredicate)
    {
        var entity = db.Repository<T>().Read(wherePredicate);

        AutoMapper.Mapper.Map(viewModel, entity);

        db.Repository<T>().Update(entity);

        db.SaveChange();
    }


    /// <summary>
    /// 刪除某一筆Entity
    /// </summary>
    /// <param name="wherePredicate">過濾出要被刪除的Entity條件</param>
    /// <returns>是否刪除成功</returns>
    public virtual void Delete(Expression<Func<T, bool>> wherePredicate)
    {
        var data = db.Repository<T>().Read(wherePredicate);
        db.Repository<T>().Delete(data);

        db.SaveChange();
    }


    /// <summary>
    /// 依照某一個ViewModel的值,產生對應的Entity並且新增到資料庫
    /// </summary>
    /// <typeparam name="TViewModel">ViewModel的形態</typeparam>
    /// <param name="viewModel">ViewModel的Reference</param>
    /// <returns>是否儲存成功</returns>
    public void CreateViewModelToDatabase<TViewModel>(TViewModel viewModel)
    {
        var entity = AutoMapper.Mapper.Map<T>(viewModel);

        db.Repository<T>().Create(viewModel);

        db.SaveChange();
    }
}

定義專案相關的Service和實作


透過這種方式,在之後需要擴充功能將會變的簡單。

public interface IPostService: IService<Post>
{
}

public class PostService 
    : GenericService<Post>, IPostService
{
    public PostService(IUnitOfWork inDb)
        : base(inDb)
    {
    }
}

在使用上,IService和GenericService可以作為框架的基礎方法。而個別專案則透過繼承和實作這個通用的服務來達到擴充或複寫以符合專案的需求。

Controller裡面的修改


Service做好了之後,第一件事情就是把它拿來用,因此會需要:


註冊到Autofac


記得註冊服務到Autofac才有DI的功能:

builder.RegisterType<PostService>().As<IPostService>();

Controller - CR的修改

...
private IPostService service;

public PostsController(IPostService inService)
{
    service = inService;
}

// GET: Posts
public ActionResult Index()
{
    //return View(db.Post.ToList());
    return View(service.GetListToViewModel<Index>());
}

// GET: Posts/Details/5
public ActionResult Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    // Post post = db.Post.Find(id);
    Detail post = service.GetSpecificDetailToViewModel&lt;Detail&gt;(x =&gt; x.Id == id);
    if (post == null)
    {
        return HttpNotFound();
    }
    return View(post);
}

// GET: Posts/Create
public ActionResult Create()
{
    return View();
}

// POST: Posts/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Create post)
{
    if (ModelState.IsValid)
    {
        service.CreateViewModelToDatabase(post);
        return RedirectToAction(&quot;Index&quot;);
    }

    return View(post);
}

Controller - UD的修改

// GET: Posts/Edit/5
public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Edit post = service.GetSpecificDetailToViewModel<Edit>(x => x.Id == id);
    if (post == null)
    {
        return HttpNotFound();
    }
    return View(post);
}

// POST: Posts/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Edit post)
{
    if (ModelState.IsValid)
    {
        service.UpdateViewModelToDatabase(Edit, x => x.Id == post.Id);
       
        return RedirectToAction("Index");
    }
    return View(post);
}

// GET: Posts/Delete/5
public ActionResult Delete(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Detail post = service.GetSpecificDetailToViewModel<Detail>(x => x.Id == id);
    if (post == null)
    {
        return HttpNotFound();
    }
    return View(post);
}

// POST: Posts/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
    service.Delete(x => x.Id == id);

    return RedirectToAction("Index");
}

結語


當Controller都和Service層溝通的時候,這裡面就多了一層的abstraction,意味著邏輯能夠重複使用,並且未來要替換邏輯也會變的更加容易。


不過相信看到這邊,對於這個初步的Service肯定不會很滿意,畢竟只提供了最基本的功能。


在之後的篇章,將會慢慢的把這個Service補上更多功能。


在下一篇,將會先看有了Service層之後,對一些檔案上傳的處理有哪些幫助。

comments powered by Disqus