NFine的后台源碼


Chloe官網及基於NFine的后台源碼毫無保留開放

 

扯淡

經過不少日夜的趕工,Chloe 的官網於上周正式上線。上篇博客中LZ說過要將官網以及后台源碼都會開放出來,為了盡快兌現我說過的話,趁周末,我稍微整理了一下項目的源碼,就今兒毫無保留的開放給大家,希望能對大家有幫助,獻丑了。

項目不大,官網就是幾個展示頁面。后台借鑒了 NFine 的設計,直接套用了 NFine 的前端模版以及他的一些權限設計。由於個人開發習慣和所用技術與 NFine 的有些不同,比如,NFine 前端數據展示用 jqgrid,而我比較傾向於使用 knockoutjs,同時,后端我也根據自己的一些思想搭建了一個與 NFine 完全不同的架構,所以,基本重寫了 NFine 的前端開發模式和后端架構。目前可以在4個數據庫間無縫切換(SqlServer、MySql、Oracle和SQLite),主要得益於強大的 Chloe^_^。

后台:

houtai

在線體驗地址 
官網: http://www.52chloe.com/   
后台:http://www.52chloe.com:82/

項目介紹

前端主要技術 
jQuery:舉世聞名的前端類庫 
knockout.js:MVVM 框架,類似 AngularJS 和 vue.JS 
bootstrap:家喻戶曉的css 
editormd:一個免費開源的 markdown 編輯器,主要用於文檔編輯,很好用,推薦 
layer:一款近年來備受青睞的web彈層組件。這個還是 NFine 本來就有的,不錯的一個組件,我也直接用上了 
jquery-plugin-validation:前端數據庫驗證插件,也是 NFine 本來就有的,很不錯,我也保留了下來

后端是用asp.net mvc 5,Newtonsoft.Json,Validator,數據庫訪問毫無疑問是用 Chloe.ORM。

開發人員都喜歡給自己的框架安個名字,我也不例外,我這個將就叫做Ace吧(海賊王里的艾斯)。我們先看下整個項目架構:

chloesite

很常規的分層,簡單介紹下各層: 
Ace:項目架構基礎層,有點類似abp里面的abp.dll,里面包含了一些基礎接口的定義,如應用服務接口,以及很多重用性高的代碼。同時,我在這個文件夾下建了Ace.Web和Ace.Web.Mvc兩個dll,分別是對asp.net和asp.net mvc的一些公共擴展和通用的方法。這一層里的東西,基本都是重用度極高的代碼,而且是比較方便移植的。 
Application:應(業)用(務)服(邏)務(輯)層。 
Data:數據層。包含實體類和ORM操作有關的基礎類。 
Web:所謂的展示層。

整個框架並沒有使用repository,原因有三: 
* 沒理解錯的話repository是DDD領域驅動設計里的概念,我這個並不是DDD,我不會為了封裝而封裝。

* 不是DDD也可以用repository啊!沒錯,不是DDD也可以有repository。repository設計可以隱藏數據的來源細節,但如果用repository包裝了一遍數據庫訪問組件(ORM),很可能因為一些所謂的設計“規范”會限制ORM框架的發揮,這不是我想要的結果。比如強大的EF,本身對連接查詢以及各種功能支持是很好,但經過倉儲包裝后,可能變得不夠靈活了,不知道您是否疑惑包裝后該怎么多表連接查詢,怎么調用存儲過程,我想實現xxx怎么寫好?我個人覺得,如果一個設計給開發帶來了一定的困擾,就應該考慮設計是否應該存在或合理不合理了。引入一個框架,我們就應該要把它應有的功能發揮的淋漓盡致,不然不如不用。

* repository可以更加方便的切換數據庫訪問框架啊!這個確實有一定道理,想得很前瞻。然而,我不予考慮。因為得不償失的行為。為什么這樣說?如果項目真的需要換ORM,無非兩個原因:1.所用的ORM數據庫類型支持的少,而項目要換數據庫類型,ORM卻不支持新數據庫了;2.所用ORM技術太老,就是想換個其他的ORM;3.所用ORM出現了性能瓶頸,拖程序后腿了。如果因為第一個原因而換數據庫,那么更多的應該反省項目之初技術選型是否正確,因為Chloe已經友好支持支持了多數據庫,所以這個原因在目前我們的項目里不會存在了。第二、三個原因分析:一個項目如果真的需要更換技術,肯定得對代碼重構,試想一下,一個項目從開始到完成整個開發周期長還是重構的時間長?前面也說過,對ORM封裝了一遍,多多少少會限制了ORM的發揮,開發的時候多多少少不順,如果為了短時間(相對開發周期)重構的舒適而讓整個開發過程飽受各種不爽、不順折磨,我真的不樂意。再一個,項目一跑起來到項目被淘汰,很可能壓根就不會更換ORM,所以,我覺得從方便切換ORM的角度考慮,使用倉儲是得不償失的行為。

以上只是個人對倉儲的一些理解,並不完全正確,如有不同看法,還望各位留言探討。

層層之間需要解耦,引用了什么IOC框架嗎?No!IOC很“高大上”,實質就是一個超級大工廠,特色功能就是依賴注入,說白了就是支持有參構造函數創建對象和屬性賦值。然而,我個人不大喜歡用注入,因此,我覺得沒必要引入IOC框架了。所以我自己建了個超級工廠,充當了IOC容器的角色。 
大概實現如下:

  View Code

 

程序啟動的時候,在啟動事件(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")的方式引用,如下:

view-js

image

這樣一個頁面就能和它對應的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

 
 
標簽:  .NETC#開源


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM