2013年11月12日 星期二

ASP.NET MVC 4 WebApi 的 BaseApiController 之 Repository 模式

在 ASP.NET MVC 4 WebApi 專案底下,常常在 Controller 需要寫許多很類似的程式碼,只是對象 Model 不一樣做些許的改變。

在此介紹如何在 Controller 之下建置底層,使 Controller 變得只需要宣告、繼承、實作即可與原本 Controller 能做的事情一樣:
namespace RMSApi.Controllers
{
    public class CompanyController : BaseApiController
    {
        // 這裡是空的,真的是這樣
    }
}

步驟是研究與參考 Generic Repository Pattern with Entity Framework and Web API ( Entity Framework 和 Web API 的通用 Repository 模式 ),目前唯一的限制就是你的資料庫 Key 值名稱都要一樣,我目前專案已經有了這項衝突,所以這個方法介紹給有緣人,建置過程必須依照以下方式:

1.

在 Model 內新增一個 Interface 資料夾,建立一個 interface 類別 IRepository 繼承 IDisposable,裡面宣告一些常用的方法:
using System;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
namespace RMSApi.Models.Interface
{
    public interface IRepository: IDisposable 
    {
        /* 定義需要實作的函數 */


        IQueryable<T> All<T>(string[] includes = null) where T : class;

        T Get<T>(Expression<Func<T, bool>> expression, string[] includes = null) where T : class;

        T Find<T>(Expression<Func<T, bool>> predicate, string[] includes = null) where T : class;

        IQueryable<T> Filter<T>(Expression<Func<T, bool>> predicate, string[] includes = null) where T : class;

        IQueryable<T> Filter<T>(Expression<Func<T, bool>> filter, out int total, int index = 0, int size = 50, string[] includes = null) where T : class;

        bool Contains<T>(Expression<Func<T, bool>> predicate) where T : class;

        T Create<T>(T t) where T : class;

        int Delete<T>(T t) where T : class;

        int Delete<T>(Expression<Func<T, bool>> predicate) where T : class;

        int Update<T>(T t) where T : class;

        void SaveChanges();

        void ExecuteProcedure(String procedureCommand, params SqlParameter[] sqlParams);

    }
}

2.

接著建立每個 Model 內一定會出現的資料欄位的 Interface,視習慣建立兩個或者統整到同一個檔案中,像我這裡是 CreateOn 和 UpdateOn。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace RMSApi.Models.Interface
{
    public interface ICreateOn
    {
        DateTime CreateOn { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace RMSApi.Models.Interface
{
    public interface IUpdateOn
    {
        DateTime UpdateOn { get; set; }
    }
}

再建立一個 IIdentifier,這就是你每個 Model 的 Key 值,需要一模一樣。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace RMSApi.Models.Interface
{
    public interface IIdentifier
    {
        Guid ID { get; set; }
    }
}


3.

在 Model 內新增一個 Repository 資料夾,建立一個類別 Repository 繼承 IRepository,裡面宣告一些常用的方法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.Data;
using System.Data.SqlClient;
using RMSApi.Models.Interface;
using RMSApi.Models;
using System.Linq.Expressions;
using System.Web.Http;

namespace RMSApi.Models.Repository
{
    public class Repository : IRepository
    {
        private DefaultConnection db;

        public Repository()
        {
            db = new DefaultConnection();

            // db.Configuration.ProxyCreationEnabled = false;

            // db.Configuration.LazyLoadingEnabled = false;
        }

        public IQueryable<T> All<T>(string[] includes = null) where T : class
        {
            if (includes != null && includes.Count() > 0)
            {
                var query = db.Set<T>().Include(includes.First());
                foreach (var include in includes.Skip(1))
                    query = query.Include(include);
                return query.AsQueryable();
            }

            return db.Set<T>().AsQueryable();
        }

        public T Get<T>(Expression<Func<T, bool>> expression, string[] includes = null) where T : class
        {
            return All<T>(includes).FirstOrDefault(expression);
        }

        public virtual T Find<T>(Expression<Func<T, bool>> predicate, string[] includes = null) where T : class
        {
            if (includes != null && includes.Count() > 0)
            {
                var query = db.Set<T>().Include(includes.First());
                foreach (var include in includes.Skip(1))
                    query = query.Include(include);
                return query.FirstOrDefault<T>(predicate);
            }

            return db.Set<T>().FirstOrDefault<T>(predicate);
        }

        public virtual IQueryable<T> Filter<T>(Expression<Func<T, bool>> predicate, string[] includes = null) where T : class
        {
            if (includes != null && includes.Count() > 0)
            {
                var query = db.Set<T>().Include(includes.First());
                foreach (var include in includes.Skip(1))
                    query = query.Include(include);
                return query.Where<T>(predicate).AsQueryable<T>();
            }

            return db.Set<T>().Where<T>(predicate).AsQueryable<T>();
        }

        public virtual IQueryable<T> Filter<T>(Expression<Func<T, bool>> predicate, out int total, int index = 0, int size = 50, string[] includes = null) where T : class
        {
            int skipCount = index * size;
            IQueryable<T> _resetSet;

            if (includes != null && includes.Count() > 0)
            {
                var query = db.Set<T>().Include(includes.First());
                foreach (var include in includes.Skip(1))
                    query = query.Include(include);
                _resetSet = predicate != null ? query.Where<T>(predicate).AsQueryable() : query.AsQueryable();
            }
            else
            {
                _resetSet = predicate != null ? db.Set<T>().Where<T>(predicate).AsQueryable() : db.Set<T>().AsQueryable();
            }

            _resetSet = skipCount == 0 ? _resetSet.Take(size) : _resetSet.Skip(skipCount).Take(size);
            total = _resetSet.Count();
            return _resetSet.AsQueryable();
        }

        public virtual T Create<T>(T TObject) where T : class
        {
            if (TObject is ICreateOn)
            {
                (TObject as ICreateOn).CreateOn = DateTime.Now;
            }

            if (TObject is IUpdateOn)
            {
                (TObject as IUpdateOn).UpdateOn = DateTime.Now;
            }

            var newEntry = db.Set<T>().Add(TObject);
            db.SaveChanges();
            return newEntry;
        }

        public virtual int Delete<T>(T TObject) where T : class
        {
            db.Set<T>().Remove(TObject);
            return db.SaveChanges();
        }

        public virtual int Update<T>(T TObject) where T : class
        {
            if (TObject is IUpdateOn)
            {
                (TObject as IUpdateOn).UpdateOn = DateTime.UtcNow;
            }

            var entry = db.Entry(TObject);
            db.Set<T>().Attach(TObject);
            entry.State = EntityState.Modified;
            return db.SaveChanges();
        }

        public virtual int Delete<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            var objects = Filter<T>(predicate);
            foreach (var obj in objects)
                db.Set<T>().Remove(obj);
            return db.SaveChanges();
        }

        public bool Contains<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            return db.Set<T>().Count<T>(predicate) > 0;
        }

        public virtual void ExecuteProcedure(String procedureCommand, params SqlParameter[] sqlParams)
        {
            db.Database.ExecuteSqlCommand(procedureCommand, sqlParams);

        }

        public virtual void SaveChanges()
        {
            db.SaveChanges();
        }

        public void Dispose()
        {
            if (db != null)
                db.Dispose();
        }
    }
}


4.

最後建立一個類別繼承 ApiController 實作 IIdentifier,且實作在 Api 中會使用的方法 ( CRUD )。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

using RMSApi.Models;
using RMSApi.Filters;
using RMSApi.Models.Repository;
using RMSApi.Models.Interface;
using System.Data;

namespace RMSApi.Controllers
{
    public class BaseApiController<T> : ApiController where T : class, IIdentifier
    {

        protected DefaultConnection db = new DefaultConnection();

        protected IRepository DataStore { get; set; }
        protected string[] Includes { get; set; }

        public BaseApiController()
        {
            this.DataStore = new Repository();
        }

        public virtual IEnumerable<T> Get()
        {
            return DataStore.All<T>(Includes);
        }

        public virtual T Get(Guid id)
        {
            return DataStore.Find<T>(t => t.ID == id, Includes);
        }

        public virtual void Post([FromBody]T value)
        {
            try
            {
                DataStore.Update<T>(value);
            }
            catch (OptimisticConcurrencyException ex)
            {
                throw ex;
            }
        }

        public virtual void Put([FromBody]T value)
        {
            DataStore.Create<T>(value);
        }

        public virtual void Delete(Guid id)
        {
            DataStore.Delete<T>(t => t.ID == id);
        }

        public virtual void Delete([FromBody]T value)
        {
            Delete(value.ID);
        }

        protected IEnumerable GetModelErrors()
        {
            return this.ModelState.SelectMany(x => x.Value.Errors.Select(error => error.ErrorMessage));
        }
    }
}

5.

最後在每一個 Controller 中都繼承 BaseApiController 並且帶入類別即可。

namespace RMSApi.Controllers
{
    public class CompanyController : BaseApiController
    {
        // 這裡是空的,真的是這樣
    }
}

這就是完整的把 Controller 的共同部分,也就是資料處理的部分抽離出來,讓你在 Controller 看到的程式碼更簡潔 ( 其實完全沒有了! )。如果你就在這 Controller 有獨特取得的資料方式,那就是在各隻 Controller 加上就好,比較直觀亦較好維護。




沒有留言 :

張貼留言

Related Posts Plugin for WordPress, Blogger...