通過很多日夜的趕工,Chloe 的官網於上週正式上線。上篇博客中LZ說過要將官網以及後臺源碼都會開放出來,爲了儘快兌現我說過的話,趁週末,我稍微整理了一下項目的源碼,就今兒毫無保留的開放給你們,但願能對你們有幫助,獻醜了。css
項目不大,官網就是幾個展現頁面。後臺借鑑了 NFine 的設計,直接套用了 NFine 的前端模版以及他的一些權限設計。因爲我的開發習慣和所用技術與 NFine 的有些不一樣,好比,NFine 前端數據展現用 jqgrid,而我比較傾向於使用 knockoutjs,同時,後端我也根據本身的一些思想搭建了一個與 NFine 徹底不一樣的架構,因此,基本重寫了 NFine 的前端開發模式和後端架構。目前能夠在4個數據庫間無縫切換(SqlServer、MySql、Oracle和SQLite),主要得益於強大的 Chloe^_^。html
後臺:前端
在線體驗地址
官網: http://www.52chloe.com/
後臺:http://www.52chloe.com:82/vue
前端主要技術
jQuery:聞名中外的前端類庫
knockout.js:MVVM 框架,相似 AngularJS 和 vue.JS
bootstrap:家喻戶曉的css
editormd:一個免費開源的 markdown 編輯器,主要用於文檔編輯,很好用,推薦
layer:一款近年來備受青睞的web彈層組件。這個仍是 NFine 原本就有的,不錯的一個組件,我也直接用上了
jquery-plugin-validation:前端數據庫驗證插件,也是 NFine 原本就有的,很不錯,我也保留了下來jquery
後端是用asp.net mvc 5,Newtonsoft.Json,Validator,數據庫訪問毫無疑問是用 Chloe.ORM。git
開發人員都喜歡給本身的框架安個名字,我也不例外,我這個將就叫作Ace吧(海賊王裏的艾斯)。咱們先看下整個項目架構: 程序員
很常規的分層,簡單介紹下各層:
Ace:項目架構基礎層,有點相似abp裏面的abp.dll,裏面包含了一些基礎接口的定義,如應用服務接口,以及不少重用性高的代碼。同時,我在這個文件夾下建了Ace.Web和Ace.Web.Mvc兩個dll,分別是對asp.net和asp.net mvc的一些公共擴展和通用的方法。這一層裏的東西,基本都是重用度極高的代碼,並且是比較方便移植的。
Application:應(業)用(務)服(邏)務(輯)層。
Data:數據層。包含實體類和ORM操做有關的基礎類。
Web:所謂的展現層。github
整個框架並無使用repository,緣由有三:
* 沒理解錯的話repository是DDD領域驅動設計裏的概念,我這個並非DDD,我不會爲了封裝而封裝。 web
* 不是DDD也能夠用repository啊!沒錯,不是DDD也能夠有repository。repository設計能夠隱藏數據的來源細節,但若是用repository包裝了一遍數據庫訪問組件(ORM),極可能由於一些所謂的設計「規範」會限制ORM框架的發揮,這不是我想要的結果。好比強大的EF,自己對鏈接查詢以及各類功能支持是很好,但通過倉儲包裝後,可能變得不夠靈活了,不知道您是否疑惑包裝後該怎麼多表鏈接查詢,怎麼調用存儲過程,我想實現xxx怎麼寫好?我我的以爲,若是一個設計給開發帶來了必定的困擾,就應該考慮設計是否應該存在或合理不合理了。引入一個框架,咱們就應該要把它應有的功能發揮的淋漓盡致,否則不如不用。 ajax
* repository能夠更加方便的切換數據庫訪問框架啊!這個確實有必定道理,想得很前瞻。然而,我不予考慮。由於得不償失的行爲。爲何這樣說?若是項目真的須要換ORM,無非兩個緣由:1.所用的ORM數據庫類型支持的少,而項目要換數據庫類型,ORM卻不支持新數據庫了;2.所用ORM技術太老,就是想換個其餘的ORM;3.所用ORM出現了性能瓶頸,拖程序後腿了。若是由於第一個緣由而換數據庫,那麼更多的應該檢討項目之初技術選型是否正確,由於Chloe已經友好支持支持了多數據庫,因此這個緣由在目前咱們的項目裏不會存在了。第2、三個緣由分析:一個項目若是真的須要更換技術,確定得對代碼重構,試想一下,一個項目從開始到完成整個開發週期長仍是重構的時間長?前面也說過,對ORM封裝了一遍,多多少少會限制了ORM的發揮,開發的時候多多少少不順,若是爲了短期(相對開發週期)重構的溫馨而讓整個開發過程飽受各類不爽、不順折磨,我真的不樂意。再一個,項目一跑起來到項目被淘汰,極可能壓根就不會更換ORM,因此,我以爲從方便切換ORM的角度考慮,使用倉儲是得不償失的行爲。
以上只是我的對倉儲的一些理解,並不徹底正確,若有不一樣見解,還望各位留言探討。
層層之間須要解耦,引用了什麼IOC框架嗎?No!IOC很「高大上」,實質就是一個超級大工廠,特點功能就是依賴注入,說白了就是支持有參構造函數建立對象和屬性賦值。然而,我我的不大喜歡用注入,所以,我以爲不必引入IOC框架了。因此我本身建了個超級工廠,充當了IOC容器的角色。
大概實現以下:
public class AppServiceFactory : IAppServiceFactory { List<IAppService> _managedServices = new List<IAppService>(); static readonly Type[] InjectConstructorParamTypes = new Type[] { typeof(IAppServiceFactoryProvider) }; static readonly object[] EmptyObjects = new object[0]; static List<Type> _serviceTypes = new List<Type>(); public AppServiceFactory() : this(null) { } public AppServiceFactory(IAceSession session) { this.Session = session; } public IAceSession Session { get; set; } public void AttachService(IAppService service) { this._managedServices.Add(service); } public void DetachService(IAppService service) { this._managedServices.Remove(service); } public T CreateService<T>(bool managed = true) where T : IAppService { Type t = typeof(T); ConstructorInfo c = null; bool injectAppServiceFactoryProvider = false; /* List 的查找效率和反射建立對象的性能稍微差點,若有必要,須要優化 */ _serviceTypes.Find(a => { if (a.IsAbstract == true) return false; if (t.IsAssignableFrom(a) == false) return false; c = a.GetConstructor(Type.EmptyTypes); injectAppServiceFactoryProvider = false; if (c == null) { c = a.GetConstructor(InjectConstructorParamTypes); injectAppServiceFactoryProvider = true; } if (c == null) { return false; } return true; }); if (c == null) throw new Exception("Can not find the service implementation."); T service = default(T); if (injectAppServiceFactoryProvider == false) { service = (T)c.Invoke(EmptyObjects); IAppServiceFactoryProvider factoryProvider = service as IAppServiceFactoryProvider; if (factoryProvider != null) { factoryProvider.ServiceFactory = this; } } else { service = (T)c.Invoke(new Object[1] { this }); } IAceSessionAppService sessionService = service as IAceSessionAppService; if (sessionService != null) sessionService.AceSession = this.Session; if (managed == true) this.AttachService(service); return service; throw new NotImplementedException(); } public void Dispose() { foreach (var service in this._managedServices) { if (service != null) service.Dispose(); } } public static T CreateAppService<T>() where T : IAppService { using (AppServiceFactory factory = new AppServiceFactory()) { return factory.CreateService<T>(); } } public static void Register<T>() { Register(typeof(T)); } public static void Register(Type t) { if (t == null || t.IsAbstract || typeof(IAppService).IsAssignableFrom(t) == false) throw new ArgumentException(); lock (_serviceTypes) { _serviceTypes.Add(t); } } public static void RegisterServices() { Assembly assembly = Assembly.GetExecutingAssembly(); RegisterServicesFromAssembly(assembly); } public static void RegisterServicesFromAssembly(Assembly assembly) { var typesToRegister = assembly.GetTypes().Where(a => { var b = a.IsAbstract == false && a.IsClass && typeof(IAppService).IsAssignableFrom(a) && a.GetConstructor(Type.EmptyTypes) != null; return b; }); foreach (var type in typesToRegister) { Register(type); } } }
程序啓動的時候,在啓動事件(Application_Start)裏會將全部應用服務的實現類給註冊進去。這個工廠實現了 IDisposable 方法,由於有些應用服務用完是須要銷燬的,因此,這個工廠建立出對象的同時,還承擔銷燬應用服務對象的職責。
對象到對象的映射框架呢?也No,DTO和實體間轉換目前我是傻呼呼的一個一個屬性賦值搞的,但之後有可能會引入。
當今流行的repository、IOC和OOM等技術都不用,你們會不會以爲LZ很奇葩或者out了?嘿嘿,個人理念是能減小依賴就儘可能減小依賴。減小學習成本同時也能夠更好的移植或掌控項目。
對於一個開發框架而言,最基本的就是規範和避免重複編碼。雖然個人這個框架簡單,但不乏實用技巧點。
ajax請求:對於系統後臺,ajax交互是很頻繁的事。對於ajax請求的返回數據我制定了基本的狀態碼和格式
public enum ResultStatus { OK = 100, Failed = 101, NotLogin = 102, Unauthorized = 103, }
public class Result { ResultStatus _status = ResultStatus.OK; public Result() { } public Result(ResultStatus status) { this._status = status; } public Result(ResultStatus status, string msg) { this.Status = status; this.Msg = msg; } public ResultStatus Status { get { return this._status; } set { this._status = value; } } public object Data { get; set; } public string Msg { get; set; } }
序列化成json大概是這樣子:{"Data":{},"Status":100,"Msg":null}
同時,爲了不頻繁new這個Result類,我在web層的 BaseController 中增長了不少有關方法:
protected ContentResult JsonContent(object obj) { string json = JsonHelper.Serialize(obj); return base.Content(json); } protected ContentResult SuccessData(object data = null) { Result<object> result = Result.CreateResult<object>(ResultStatus.OK, data); return this.JsonContent(result); } protected ContentResult SuccessMsg(string msg = null) { Result result = new Result(ResultStatus.OK, msg); return this.JsonContent(result); } protected ContentResult AddSuccessData(object data, string msg = "添加成功") { Result<object> result = Result.CreateResult<object>(ResultStatus.OK, data); result.Msg = msg; return this.JsonContent(result); } protected ContentResult AddSuccessMsg(string msg = "添加成功") { return this.SuccessMsg(msg); } protected ContentResult UpdateSuccessMsg(string msg = "更新成功") { return this.SuccessMsg(msg); } protected ContentResult DeleteSuccessMsg(string msg = "刪除成功") { return this.SuccessMsg(msg); } protected ContentResult FailedMsg(string msg = null) { Result retResult = new Result(ResultStatus.Failed, msg); return this.JsonContent(retResult); }
這樣,咱們在Action中能夠直接這樣用:
[HttpGet] public ActionResult GetModels(Pagination pagination, string keyword) { PagedData<Sys_User> pagedData = this.CreateService<IUserAppService>().GetPageData(pagination, keyword); return this.SuccessData(pagedData); } [HttpPost] public ActionResult Add(AddUserInput input) { this.CreateService<IUserAppService>().AddUser(input); return this.AddSuccessMsg(); } [HttpPost] public ActionResult Update(UpdateUserInput input) { this.CreateService<IUserAppService>().UpdateUser(input); return this.UpdateSuccessMsg(); } [HttpPost] public ActionResult Delete(string id) { this.CreateService<IUserAppService>().DeleteAccount(id); return this.DeleteSuccessMsg(); } [HttpPost] public ActionResult RevisePassword(string userId, string newPassword) { if (userId.IsNullOrEmpty()) return this.FailedMsg("userId 不能爲空"); this.CreateService<IUserAppService>().RevisePassword(userId, newPassword); return this.SuccessMsg("重置密碼成功"); }
經過vs強大的智能提示,直接 this. 就能夠出來,省了很多事。
儘可能"少的使用using":
在.net的規範中,對於任何實現了 IDisposable 接口的類,用完咱們都應將其銷燬掉。在項目開發中,若是充斥着太多的 using 寫法,我是很是煩的,我想你們也是一樣的感覺,好比 DbContext。但如何避免使用 using,但又保證對象最終又被銷燬呢?若是你們用了一些IOC框架,估計不須要太多的關心對象的銷燬問題,由於IOC容器幫你們作了這些事。我不用IOC,那該咋辦呢?其實不難,就拿 DbContext 舉例。在我這個架構中,應用服務層是直接操做 DbContext 的,我建了個應用服務基類(AppServiceBase),DbContext 的建立和銷燬交給 AppServiceBase 就好了。所以,這又引伸了一個規範,每一個應用服務必須實現 IDisposable 接口(一個類內部若是有 IDisposable 的對象,我以爲該類也應該實現 IDisposable 接口)!
public abstract class AppServiceBase : IAppServiceFactoryProvider, IDisposable { IDbContext _dbContext; protected AppServiceBase() : this(null) { } protected AppServiceBase(IAppServiceFactory serviceFactory) { this.ServiceFactory = serviceFactory; } public IAppServiceFactory ServiceFactory { get; set; } public IDbContext DbContext { get { if (this._dbContext == null) this._dbContext = DbContextFactory.CreateContext(); return this._dbContext; } set { this._dbContext = value; } } public void Dispose() { if (this._dbContext != null) { this._dbContext.Dispose(); } this.Dispose(true); } protected virtual void Dispose(bool disposing) { } }
而後子類應用服務就能夠直接 this.DbContext 這樣毫無顧忌的使用 DbContext 了,不再用關心 DbContext 是如何建立和被銷燬了。技巧雖小,卻給我開發便利了許多。
全部應用服務是由前面提到的超級工廠建立的,因此,個人超級工廠也須要實現 IDisposable 接口,目的就是爲了掌管其建立出的應用服務對象。那麼問題來了,LZ你使用的超級工廠對象的時候還不是得要銷燬你的超級工廠,不用 using 怎麼作?哈哈,這個問題用一樣的基類方式就能夠解決。
不知道你們是否留意MVC的 Controller 這個類也實現了 IDisposable 接口,它提供了一個 Dispose(bool disposing) 方法的重載!對於這個重載方法,咱們好好利用它就行,嘿嘿~
咱們建的 Controller 都會繼承於咱們自定義的 BaseController 中,BaseController 則重寫了 Dispose(bool disposing) 方法,用於銷燬一些咱們自定義的 IDisposable 對象:
public abstract class BaseController : Controller { static readonly Type TypeOfCurrent = typeof(BaseController); static readonly Type TypeOfDisposableAttribute = typeof(DisposableAttribute); protected override void Dispose(bool disposing) { base.Dispose(disposing); this.DisposeMembers(); } [Disposable] AppServiceFactory _appServicesFactory; IAppServiceFactory AppServicesFactory { get { if (this._appServicesFactory == null) this._appServicesFactory = new AppServiceFactory(this.CurrentSession); return this._appServicesFactory; } } protected T CreateService<T>(bool managed = true) where T : IAppService { return this.AppServicesFactory.CreateService<T>(managed); } /// <summary> /// 掃描對象內全部帶有 DisposableAttribute 標記並實現了 IDisposable 接口的屬性和字段,執行其 Dispose() 方法 /// </summary> void DisposeMembers() { Type type = this.GetType(); List<PropertyInfo> properties = new List<PropertyInfo>(); List<FieldInfo> fields = new List<FieldInfo>(); Type searchType = type; while (true) { properties.AddRange(searchType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly).Where(a => { if (typeof(IDisposable).IsAssignableFrom(a.PropertyType)) { return a.CustomAttributes.Any(c => c.AttributeType == TypeOfDisposableAttribute); } return false; })); fields.AddRange(searchType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly).Where(a => { if (typeof(IDisposable).IsAssignableFrom(a.FieldType)) { return a.CustomAttributes.Any(c => c.AttributeType == TypeOfDisposableAttribute); } return false; })); if (searchType == TypeOfCurrent) break; else searchType = searchType.BaseType; } foreach (var pro in properties) { IDisposable val = pro.GetValue(this) as IDisposable; if (val != null) val.Dispose(); } foreach (var field in fields) { IDisposable val = field.GetValue(this) as IDisposable; if (val != null) val.Dispose(); } } }
注意看,上面的 BaseController 有一個 CreateService 方法,用於建立應用服務對象。而後呢,子類的操做方法中,咱們就能夠毫無顧忌的調用 this.CreateService 方法建立相關的應用服務了:
[HttpPost] public ActionResult Add(AddUserInput input) { this.CreateService<IUserAppService>().AddUser(input); return this.AddSuccessMsg(); }
若是您從github下載了源碼就會發現,源碼中using關鍵字少之又少~
管理每一個View對應的js文件:
對於後臺開發,基本上每一個頁面都須要js支持,但咱們又不想js代碼和html放在一個窗口裏開發,因此你們都習慣建一個js文件,而後在頁面中引用就好了。在 webform 時代,咱們都習慣將頁面和其對應的js文件放在同一個文件夾中,方便!!但在mvc默認設置中,views文件夾下是不容許放js文件的,這可咋辦呢??雖然有設置可讓views文件夾裏的js文件能夠被訪問,但,我不想打破mvc的默認規則!所以,我用了個投機取巧的辦法,就是利用mvc的部分視圖功能。我把js代碼寫在部分視圖中,而後頁面中直接以@Html.Partial("Index-js")的方式引用,以下:
這樣一個頁面就能和它對應的js文件成雙對的在同一個文件夾中了,實現了js代碼和 html 代碼能夠分開!但有一點要注意,最終的頁面輸出到瀏覽器的時候 html 和 js 代碼是混在一塊兒的!那麼問題來了,你這樣幹會影響頁面加載速度,而且沒能充分利用瀏覽器對靜態資源的緩存啊~在一些技術羣裏,我跟他們說出個人這個設想和作法時,有很多人提出了這樣的質疑~其實,對於一個後臺頁面而言,這點影響不足爲懼,並無想象中那麼大。這實際上是個智仁問題,我也很少解釋了,利弊本身權衡就好。沒有絕對好的設計,只要實用和適合本身就夠了!~
每一個人都夢想有個屬於本身的開發框架,我也同樣。目前的Ace框架雖然簡單,在大牛面前不值一提,但仍是但願能對一些人有幫助。
官網使用的是 SQLite 數據庫,GitHub 項目中已經有了相應的db文件,只要你本地裝了asp.net環境,下載便可運行。考慮到不少同窗對 SQLite 不熟悉,以及你們裝的是 SqlServer、MySql 或 Oracle,我也給你們準備好了全部的相關dll以及相應的數據庫腳本,只要建個數據庫,而後運行腳本就能夠建立相關的表了。框架已經支持4種數據庫之間隨意切換,因此,你們在web.config裏設置下數據庫類型和數據庫鏈接字符串就能夠運行了(web.config都有修改說明)。試問,世上這麼熱心的程序員,除了博主還有誰?若是這都換不來您的一個推薦,博主我真的無語問蒼天,是時候考慮關閉博客了555~
技術教程或心得我倒不是很擅長寫,我只想把平常開發的一些乾貨分享給你們,您的推薦是我分享的最大動力。同時,若是您對 Chloe 這個項目感興趣,敬請在 Github 關注或收藏(star)一下,以便能及時收到更新通知。也歡迎廣大C#同胞入羣交流,暢談.NET復興大計。最後,感謝你們閱讀至此!同時也很是感謝 NFine 做者給我們提供了一個那麼好的一個開發框架!
Chloe 官網及後臺源碼地址:https://github.com/shuxinqin/Ace