1. 程式人生 > >從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】框架之十三 || DTOs 物件對映使用,專案部署Windows+Linux完整版

從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】框架之十三 || DTOs 物件對映使用,專案部署Windows+Linux完整版

 程式碼已上傳Github+Gitee,文末有地址

番外:時間真快,今天終於到了系統打包的日子,雖然專案還是有很多問題,雖然後邊還有很多的內容要說要學,但是想著初級基本的.Net Core 用到的基本至少就這麼多了(介面文件,專案框架,持久化ORM,依賴注入,AOP,分散式快取,CORS跨域等等),中高階的,比如在Linux高階釋出,Nginx代理,微服務,Dockers等等,這個在以後的更新中會慢慢提到,不然的話,Vue就一直說不到了 [哭笑哈哈],其實我還有很多要總結的,比如 Power BI系列(沒用過的點選看看),比如C#7.0系列等文章,都在慢慢醞釀中,希望能堅持下來,不過這兩個系列目前還不會寫到,如果有需要用或學微軟

Power BIhttps://docs.microsoft.com/zh-cn/power-bi/sample-customer-profitability)的,可以加QQ群聯絡我,我在微軟專案中已經用到了。還是打算從下週一開始轉戰Vue的文章,當然後端也會一直穿插著,這裡要說下,我們的QQ群已經有一些小夥伴了,每天可以一起交流心得和問題,感覺還是很不錯的,如果你有什麼問題,或者其他技術上的需要討論,咱們的群是可以試試喲,我和其他小夥伴會一直線上給大家解答(咋感覺像一個廣告哈哈,大家隨意哈)。

  正傳:好啦,書接上文,昨天說到了《從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】框架之十二 || 三種跨域方式比較,DTOs(資料傳輸物件)初探

》,因為下午時間的問題,只是講解了四種跨域方法,沒有講解完DTO,其實這個東西很簡單,說白了,就是把兩個實體類進行轉換,不用人工手動去一一賦值,今天呢,就簡單說下常見DTO框架AutoMapper的使用,然後做一個打包處理,釋出到我的windows伺服器裡,今天剛剛買了一個Ubuntu Linux伺服器,因為如果開發.Net Core,一定會接觸到Linux伺服器,等各種,因為它跨域了,就是醬紫。但是還沒有配置好,所以會在下邊留下位置,慢慢補充在Ubuntu部署的講解。

零、今天完成右下角的深藍色部分

一、在專案中使用新增一個案例使用AutoMapper

1、普通的模型對映

在介面 IBlogArticleServices.cs和 類BlogArticleServices.cs中,新增GetBlogDetails()方法,返回型別是BlogViewModels

請看這兩個類

   /// <summary>
    /// 部落格文章實體類
    /// </summary>
    public class BlogArticle
    {
        /// <summary>
        /// 
        /// </summary>
        public int bID { get; set; }
        /// <summary>
        /// 建立人
        /// </summary>
        public string bsubmitter { get; set; }

        /// <summary>
        /// 部落格標題
        /// </summary>
        public string btitle { get; set; }

        /// <summary>
        /// 類別
        /// </summary>
        public string bcategory { get; set; }

        /// <summary>
        /// 內容
        /// </summary>
        public string bcontent { get; set; }

        /// <summary>
        /// 訪問量
        /// </summary>
        public int btraffic { get; set; }

        /// <summary>
        /// 評論數量
        /// </summary>
        public int bcommentNum { get; set; }

        /// <summary> 
        /// 修改時間
        /// </summary>
        public DateTime bUpdateTime { get; set; }

        /// <summary>
        /// 建立時間
        /// </summary>
        public System.DateTime bCreateTime { get; set; }
        /// <summary>
        /// 備註
        /// </summary>
        public string bRemark { get; set; }
    }
-------------------------------------------------
   /// <summary>
    /// 部落格資訊展示類
    /// </summary>
    public class BlogViewModels
    {
        /// <summary>
        /// 
        /// </summary>
        public int bID { get; set; }
        /// <summary>/// 建立人
        /// </summary>
        public string bsubmitter { get; set; }

        /// <summary>/// 部落格標題
        /// </summary>
        public string btitle { get; set; }

        /// <summary>/// 摘要
        /// </summary>
        public string digest { get; set; }

        /// <summary>
        /// 上一篇
        /// </summary>
        public string previous { get; set; }

        /// <summary>
        /// 上一篇id
        /// </summary>
        public int previousID { get; set; }

        /// <summary>
        /// 下一篇
        /// </summary>
        public string next { get; set; }

        /// <summary>
        /// 下一篇id
        /// </summary>
        public int nextID { get; set; }

        /// <summary>/// 類別
        /// </summary>
        public string bcategory { get; set; }

        /// <summary>/// 內容
        /// </summary>
        public string bcontent { get; set; }

        /// <summary>
        /// 訪問量
        /// </summary>
        public int btraffic { get; set; }

        /// <summary>
        /// 評論數量
        /// </summary>
        public int bcommentNum { get; set; }

        /// <summary>/// 修改時間
        /// </summary>
        public DateTime bUpdateTime { get; set; }

        /// <summary>
        /// 建立時間
        /// </summary>
        public System.DateTime bCreateTime { get; set; }
        /// <summary>/// 備註
        /// </summary>
        public string bRemark { get; set; }
    }

兩個實體類欄位還基本可以,不是很多,但是我曾經開發一個旅遊網站的系統,有一個表字段都高達30多個,當然還有更多的,額,如果我們一個個賦值是這樣的

            BlogViewModels models = new BlogViewModels()
            {
                bsubmitter=blogArticle.bsubmitter,
                btitle = blogArticle.btitle,
                bcategory = blogArticle.bcategory,
                bcontent = blogArticle.bcontent,
                btraffic = blogArticle.btraffic,
                bcommentNum = blogArticle.bcommentNum,
                bUpdateTime = blogArticle.bUpdateTime,
                bCreateTime = blogArticle.bCreateTime,
                bRemark = blogArticle.bRemark,
            };    

所以這個方法的全部程式碼是:

介面層也要新增:

   public interface IBlogArticleServices :IBaseServices<BlogArticle>
    {
        Task<List<BlogArticle>> getBlogs();
        Task<BlogViewModels> getBlogDetails(int id);
    }
 /// <summary>
        /// 獲取檢視部落格詳情資訊
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task<BlogViewModels> getBlogDetails(int id)
        {
            var bloglist = await dal.Query(a => a.bID > 0, a => a.bID);
            var idmin = bloglist.FirstOrDefault() != null ? bloglist.FirstOrDefault().bID : 0;
            var idmax = bloglist.LastOrDefault() != null ? bloglist.LastOrDefault().bID : 1;
            var idminshow = id;
            var idmaxshow = id;

            BlogArticle blogArticle = new BlogArticle();

            blogArticle = (await dal.Query(a => a.bID == idminshow)).FirstOrDefault();

            BlogArticle prevblog = new BlogArticle();


            while (idminshow > idmin)
            {
                idminshow--;
                prevblog = (await dal.Query(a => a.bID == idminshow)).FirstOrDefault();
                if (prevblog != null)
                {
                    break;
                }
            }

            BlogArticle nextblog = new BlogArticle();
            while (idmaxshow < idmax)
            {
                idmaxshow++;
                nextblog = (await dal.Query(a => a.bID == idmaxshow)).FirstOrDefault();
                if (nextblog != null)
                {
                    break;
                }
            }


            blogArticle.btraffic += 1;
            await dal.Update(blogArticle, new List<string> { "btraffic" });

            BlogViewModels models = new BlogViewModels()
            {
                bsubmitter=blogArticle.bsubmitter,
                btitle = blogArticle.btitle,
                bcategory = blogArticle.bcategory,
                bcontent = blogArticle.bcontent,
                btraffic = blogArticle.btraffic,
                bcommentNum = blogArticle.bcommentNum,
                bUpdateTime = blogArticle.bUpdateTime,
                bCreateTime = blogArticle.bCreateTime,
                bRemark = blogArticle.bRemark,
            };

            if (nextblog != null)
            {
                models.next = nextblog.btitle;
                models.nextID = nextblog.bID;
            }

            if (prevblog != null)
            {
                models.previous = prevblog.btitle;
                models.previousID = prevblog.bID;
            }
            return models;

        }
View Code

想了想這才是一個方法,一般的系統都會有少則幾十,多則上百個這樣的方法,這還不算,大家肯定遇到過一個情況,如果有一天要在頁面多顯示一個欄位,噗!不是吧,首先要存在資料庫,然後在該實體類就應該多一個,然後再在每一個賦值的地方增加一個,而且也沒有更好的辦法不是,一不小心就少了一個,然後被產品測試說咱們不細心,心塞喲,別慌!神器來了,一招搞定。

2、先來引入DTO講解,以及它的原理

  在學習EF的時候我們知道了ORM(Object Relational Mapping)對映,是一種物件關係的對映,物件-關係對映(ORM)系統一般以中介軟體的形式存在,主要實現程式物件到關係資料庫資料的對映。

而Automapper是一種實體轉換關係的模型,AutoMapper是一個.NET的物件對映工具。主要作用是進行領域物件與模型(DTO)之間的轉換、資料庫查詢結果對映至實體物件。

下邊的是基本原理,大家喵一眼就行:

Ø 什麼是DTO?
  資料傳輸物件(DTO)(DataTransfer Object),是一種設計模式之間傳輸資料的軟體應用系統。資料傳輸目標往往是資料訪問物件從而從資料庫中檢索資料。資料傳輸物件與資料互動物件或資料訪問物件之間的差異是一個以不具有任何行為除了儲存和檢索的資料(訪問和存取器)。

Ø 為什麼用?
  它的目的只是為了對領域物件進行資料封裝,實現層與層之間的資料傳遞。為何不能直接將領域物件用於資料傳遞?因為領域物件更注重領域,而DTO更注重資料。不僅如此,由於“富領域模型”的特點,這樣做會直接將領域物件的行為暴露給表現層。

  需要了解的是,資料傳輸物件DTO本身並不是業務物件。資料傳輸物件是根據UI的需求進行設計的,而不是根據領域物件進行設計的。比如,Customer領域物件可能會包含一些諸如FirstName, LastName, Email, Address等資訊。但如果UI上不打算顯示Address的資訊,那麼CustomerDTO中也無需包含這個 Address的資料”。

Ø 什麼是領域物件?
  領域模型就是面向物件的,面向物件的一個很重要的點就是:“把事情交給最適合的類去做”,即:“你得在一個個領域類之間跳轉,才能找出他們如何互動”。在我們的系統中Model(EF中的實體)就是領域模型物件。領域物件主要是面對業務的,我們是通過業務來定義Model的。

以上的這些大家簡單看看原理即可,意思大家肯定都懂,下邊開始講解如何使用

3、引入 AutoMapper 的相關包

在Blog.Core.Services專案中引用Nuget包,AutoMapper 和 AutoMapper.Extensions.Microsoft.DependencyInjection

AutoMapper.Extensions.Microsoft.DependencyInjection,這個是用來配合依賴注入的,看名字也能看的出來吧,大家回憶下,整個專案中,都是使用的依賴注入,所以儘量不要用new 來例項化,導致層耦合。

4、新增對映檔案 CustomProfile.cs

基於上邊原理,在介面層Blog.Core 中,新增資料夾AutoMapper,然後新增對映配置檔案 CustomProfile.cs,用來匹配所有的對映物件關係

     public class CustomProfile : Profile
    {
        /// <summary>
        /// 配置建構函式,用來建立關係對映
        /// </summary>
        public CustomProfile()
        {
            CreateMap<BlogArticle, BlogViewModels>();
        }
    }

大家看下F12這個CreateMap方法

public IMappingExpression<TSource, TDestination> CreateMap<TSource, TDestination>();

第一個引數是原物件,第二個是目的物件,所以,要想好,是哪個方向轉哪個,當然可以都寫上,比如

CreateMap<BlogArticle, BlogViewModels>();

CreateMap<BlogViewModels, BlogArticle>(); 

更新:2018-10-26

//如果不想一個一個的配置,可以用介面的形式,批量匯入
//這是一個思路,我沒有具體去寫,留個坑吧

//public interface IMapperTo<TDestination>{}
//然後同樣來個Profile集中處理這些interface
/// <summary>
/// 根據IMapperTo<>介面 自動初始化AutoMapper
/// </summary>
public class AutoMapperProfile : Profile
{
    public override string ProfileName
    {
        get
        {
            return "AutoForIMapperTo";
        }
    }

    protected override void Configure()
    {
        base.Configure();

        typeof(SaveBuyerDemandRequest).Assembly.GetTypes()//SaveBuyerDemandRequest是TSource同屬的Assembly底下的任意類,要包含多個Aeembly的話自己擴充套件咯
            .Where(i => i.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IMapperTo<>)))
            .ToList().ForEach(item =>
            {
                item.GetInterfaces()
                    .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IMapperTo<>))
                    .ToList()//這裡可以支援多個IMapperTo
                    .ForEach(i => {
                        var t2 = i.GetGenericArguments()[0];
                        Mapper.CreateMap(item, t2);
                        Mapper.CreateMap(t2, item);
                    });
            });
    }
}

//Class For Example
public class SaveBuyerDemandRequest : IMapperTo<BuyerDemandEntity>
{

}

5、使用AutoMapper實現模型對映,並注入

修改上邊服務層BlogArticleServices.cs 中getBlogDetails 方法中的賦值,改用AutoMapper,並用建構函式注入

最終的程式碼是

     IBlogArticleRepository dal;
        IMapper IMapper;
        public BlogArticleServices(IBlogArticleRepository dal, IMapper IMapper)
        {
            this.dal = dal;
            base.baseDal = dal;
            this.IMapper = IMapper;
        }

        public async Task<BlogViewModels> getBlogDetails(int id)
        {
            var bloglist = await dal.Query(a => a.bID > 0, a => a.bID);
            var idmin = bloglist.FirstOrDefault() != null ? bloglist.FirstOrDefault().bID : 0;
            var idmax = bloglist.LastOrDefault() != null ? bloglist.LastOrDefault().bID : 1;
            var idminshow = id;
            var idmaxshow = id;

            BlogArticle blogArticle = new BlogArticle();

            blogArticle = (await dal.Query(a => a.bID == idminshow)).FirstOrDefault();

            BlogArticle prevblog = new BlogArticle();


            while (idminshow > idmin)
            {
                idminshow--;
                prevblog = (await dal.Query(a => a.bID == idminshow)).FirstOrDefault();
                if (prevblog != null)
                {
                    break;
                }
            }

            BlogArticle nextblog = new BlogArticle();
            while (idmaxshow < idmax)
            {
                idmaxshow++;
                nextblog = (await dal.Query(a => a.bID == idmaxshow)).FirstOrDefault();
                if (nextblog != null)
                {
                    break;
                }
            }


            blogArticle.btraffic += 1;
            await dal.Update(blogArticle, new List<string> { "btraffic" });

            //注意就是這裡
            BlogViewModels models = IMapper.Map<BlogViewModels>(blogArticle);

            if (nextblog != null)
            {
                models.next = nextblog.btitle;
                models.nextID = nextblog.bID;
            }

            if (prevblog != null)
            {
                models.previous = prevblog.btitle;
                models.previousID = prevblog.bID;
            }
            return models;

        }

6、老規矩,還是在Startup中,注入服務

services.AddAutoMapper(typeof(Startup));//這是AutoMapper的2.0新特性

7、修改BlogController.cs中的 Get(int id)方法,執行專案,斷點除錯,發現已經成功了,是不是很方便,你也可以反過來試一試

     [HttpGet("{id}", Name = "Get")]
        public async Task<object> Get(int id)
        {
            var model = await blogArticleServices.getBlogDetails(id);//呼叫該方法
            var data = new { success = true, data = model };
            return data;
        }

8、好啦,DTOs就到這裡了,我們的ConfigureServices也基本告一段落了,主要有這些

二、Blog.Core專案打包釋出在IIS

1、專案打包釋出

在專案Blog.Core中,右鍵,釋出,選擇檔案,相信大家都會,不會的可以聯絡我

注意: 這裡有一個坑,還記得我們用swagger中使用的兩個xml檔案麼,編譯的時候有,但是.net core官方限制了在釋出的時候包含xml檔案,所以我們需要處理下

在專案工程檔案WebApplication1.csproj中,增加

<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>

</PropertyGroup>

----------------------------------------------------------------------

當然我們還可以基於CLI的Publish命令進行釋出,只需切換到Light.API根目錄下,輸入以下命令即可:

dotnet publish --framework netcoreapp1.1 --output "E:\Publish" --configuration Release

framework表示目標框架,output表示要釋出到的目錄資料夾,configuration表示配置檔案,等同於和上面我們通過管理器來發布的操作

具體的大家可以自行實驗

2、在伺服器中安裝 dotnet (已安裝則跳過):

   在CMD命令視窗下,輸入 dotnet 檢視

3、安裝WindowsHosting(已安裝則跳過)

  IIS安裝伺服器上安裝DotNetCore.X.X.X-WindowsHosting安裝成功後重啟IIS伺服器。根據版本選擇下載

4、安裝AspNetCoreModule託管模組(已安裝則跳過),

  下載地址:點選我下載

5、應用池配置為無託管程式碼

(網上解釋:ASP.NET Core不再是由IIS工作程序(w3wp.exe)託管,而是使用自託管Web伺服器(Kestrel)執行,IIS則是作為反向代理的角色轉發請求到Kestrel不同埠的ASP.NET Core程式中,隨後就將接收到的請求推送至中介軟體管道中去,處理完你的請求和相關業務邏輯之後再將HTTP響應資料重新回寫到IIS中,最終轉達到不同的客戶端(瀏覽器,APP,客戶端等)。而配置檔案和過程都會由些許調整,中間最重要的角色便是AspNetCoreModule,它是其中一個的IIS模組,請求進入到IIS之後便立即由它轉發,並迅速重定向到ASP.NET Core專案中,所以這時候我們無需設定應用程式池來託管我們的程式碼,它只負責轉發請求而已)

老張:如果需要讀寫根目錄許可權,要更改應用池 ApplicationPoolIdentity

6、可以開啟錯誤日誌

在釋出的時候,會有一個web.config出現,通過修改web.config 啟用錯誤日誌檢視詳細錯誤資訊

 將stdoutLogEnabled的修改為 true,並在應用程式根目錄新增 logs 資料夾

一定要手動新增logs檔案,不然會不出現

7、只要本地能通過,常見的錯誤就是生成的檔案不全導致的,大家可以自行看看,如果有問題,也可以大家一起解決

 比如錯誤

1、缺少一個補丁

其中一個問題是少一個補丁,發現需要打個補丁(Windows6.1-KB2533623-x64.msu),下載地址:點我點我

8、在IIS中啟動專案,或者直接輸入伺服器IP地址,加埠除錯

注意:這裡有一個小問題,因為釋出以後,預設啟動頁是在開發環境中重定向到了swagger,但是在伺服器部署以後,不能跳轉,大家開啟後會這樣,404找不到,不要怕,

只需要在後邊加上Swagger就行了

三、專案在Liunx Ubuntu中部署(簡單版,慢慢完善)

1、在騰訊雲購買Ubuntu伺服器後,登陸,然後進入命令頁面

2、部署Linux系統中的微軟環境

繼續執行下面的命令

Register the trusted Microsoft signature key:

curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg

繼續

sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg

根據系統版本,執行下面的命令

sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-xenial-prod xenial main" > /etc/apt/sources.list.d/dotnetdev.list'

好了,環境部署完畢,下面我們安裝 SDK

3、部署.Ne Core 環境

sudo apt-get install apt-transport-https
sudo apt-get update
sudo apt-get install dotnet-sdk-2.1.4

 安裝成功後,輸入命令 dotnet 

 

證明安裝成功啦

4、安裝程式碼上傳工具,Fillzila或者winSCP都可以,(我用的是winSCP)

軟體下好開啟後介面是這樣的,我們需要填的就是主機名(你伺服器的公網IP)、使用者名稱(伺服器的使用者名稱)、密碼(你買伺服器時設定的密碼),那個檔案協議就是SFTP,不用改變

5、登陸進去預設是 /Home/ubuntu 資料夾,我們都在這裡操作

 6、下面我們在伺服器新建一個控制檯專案測試一下

dotnet new console -o myApp

然後就在winSCP發現多了一個專案

 7、然後執行我們剛剛建立的專案

cd myApp
dotnet run

程式碼一起正常!

8、把我們的專案釋出上去,注意這裡不是咱們釋出的版本!不是釋出的版本!

因為我們本地釋出的是windows版本的,如果把publish打包版本釋出上去,會報錯,各種錯

所以應該是把整個解決方法提交上去,當然git就別提交了

然後呢,進入到我們要釋出的介面層專案

cd Blog.Core,然後再cd Blog.Core

最後執行 dotnet run 即可

 

四、結語

今天暫時就先寫到這裡,我們學到了如何用AutoMapper來實現DTO資料物件對映,也學會了在windows下的IIS中釋出專案,最後就是Linux系統中,搭建環境和執行.net core 。以後呢我還會講到如何桌面話Linux系統,Nginx代理等等,大家拭目以待吧

五、CODE