代码已上传Github+Gitee,文末有地址
番外:时间真快,今天终于到了系统打包的日子,虽然项目还是有很多问题,虽然后边还有很多的内容要说要学,但是想着初级基本的.Net Core 用到的基本至少就这么多了(接口文档,项目框架,持久化ORM,依赖注入,AOP,分布式缓存,CORS跨域等等),中高级的,比如在Linux高级发布,Nginx代理,微服务,Dockers等等,这个在以后的更新中会慢慢提到,不然的话,Vue就一直说不到了 [哭笑哈哈],其实我还有很多要总结的,比如 Power BI系列(没用过的点击看看),比如C#7.0系列等文章,都在慢慢酝酿中,希望能坚持下来,不过这两个系列目前还不会写到,如果有需要用或学微软Power BI(https://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; }
想了想这才是一个方法,一般的系统都会有少则几十,多则上百个这样的方法,这还不算,大家肯定遇到过一个情况,如果有一天要在页面多显示一个字段,噗!不是吧,首先要存在数据库,然后在该实体类就应该多一个,然后再在每一个赋值的地方增加一个,而且也没有更好的办法不是,一不小心就少了一个,然后被产品测试说咱们不细心,心塞哟,别慌!神器来了,一招搞定。
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、在Blog.Core.Services项目中引用Nuget包,AutoMapper 和 AutoMapper.Extensions.Microsoft.DependencyInjection
AutoMapper.Extensions.Microsoft.DependencyInjection,这个是用来配合依赖注入的,看名字也能看的出来吧,大家回忆下,整个项目中,都是使用的依赖注入,所以尽量不要用new 来实例化,导致层耦合。
4、基于上边原理,在接口层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>();
5、修改上边服务层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、一定要在服务器中安装.Net Core SDK (已安装则跳过):
地址:https://www.microsoft.com/net/download
在CMD命令窗口下,输入 dotnet 查看
3、安装WindowsHosting(已安装则跳过)
IIS安装服务器上安装DotNetCore.X.X.X-WindowsHosting安装成功后重启IIS服务器。根据版本选择下载
下载地址:https://www.microsoft.com/net/download/windows
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项目中,所以这时候我们无需设置应用程序池来托管我们的代码,它只负责转发请求而已)
6、如果需要读写根目录权限,要更改应用池 ApplicationPoolIdentity
7、如果没有出现正常的页面,你可以打开错误日志
在发布的时候,会有一个web.config出现,通过修改web.config 启用错误日志查看详细错误信息
将stdoutLogEnabled的修改为 true,并在应用程序根目录添加 logs 文件夹
一定要手动添加logs文件,不然会不出现
8、只要本地能通过,常见的错误就是生成的文件不全导致的,大家可以自行看看,如果有问题,也可以大家一起解决
9、在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
https://github.com/anjoy8/Blog.Core
https://gitee.com/laozhangIsPhi/Blog.Core