在 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 加上就好,比較直觀亦較好維護。