关于.net-core:盘点C-90中好用的特性

顶级语句将类和类外面Main函数省略,只留下外围的逻辑代码就是顶级语句! 1.顶级语句1 await System.Threading.Tasks.Task.Delay(1000);System.Console.WriteLine("Hi!");return 0;static class $Program{ static async Task<int> $Main(string[] args) { await System.Threading.Tasks.Task.Delay(1000); System.Console.WriteLine("Hi!"); return 0; }}1.顶级语句2 System.Console.WriteLine("Hi!");return 2;static class $Program{ static int $Main(string[] args) { System.Console.WriteLine("Hi!"); return 2; }}Initstruct Point{ public int X { get; } public int Y { get; } public Point(int x, int y) { this.X = x; this.Y = y; }}init通过容许调用方在结构操作过程中扭转成员,拜访器使不可变对象更具灵活性。 这意味着对象的不可变属性能够参加对象初始值设定项,因而不再须要类型中的所有构造函数样板。 Point类型当初只是: struct Point{ public int X { get; init; } public int Y { get; init; }}而后,使用者能够应用对象初始值设定项来创建对象。 ...

April 22, 2023 · 2 min · jiezi

关于.net-core:Net-Core-开发电商后端API-从0到精通吃透RESTful编程猿自学it-java-python-go-c

download:.Net Core 开发电商后端API 从0到精通吃透RESTful编程猿自学it java python go c自学it666 java python go c教你如何用Java获取IP归属。解释次要步骤: 从Java获取申请IP解决Nginx转发问题通过IP地址获取属性 获取IP地址首先,应用基于Spring Boot的我的项目,在控制器中增加HttpServletRequest申请参数:@RestController公共类IpController {@GetMapping("/ip-address ")公共字符串ipAddress(HttpServletRequest申请){//接管申请}}复制代码通过HttpServletRequest获取IP地址:string IP = request . get header(" x-forward-for ");if (ip == null || ip.length() == 0 ||“未知”。equalsIgnoreCase(ip)) {ip = request.getHeader("代理-客户端-IP ");}if (ip == null || ip.length() == 0 ||“未知”。equalsIgnoreCase(ip)) {IP = request . get header(" WL-代理-客户端-IP ");}if (ip == null || ip.length() == 0 ||“未知”。equalsIgnoreCase(ip)) {IP = request . get header(" HTTP CLIENT IP ");}if (ip == null || ip.length() == 0 ||“未知”。equalsIgnoreCase(ip)) {IP = request . get header(" HTTP X FORWARDED _ FOR ");}if (ip == null || ip.length() == 0 ||“未知”。equalsIgnoreCase(ip)) {IP = request . getremoteaddr();}回归IP;复制代码调用以获取本地环境中的IP,0:0:0:0:0:0:0:1或LAN IP。 ...

September 12, 2022 · 2 min · jiezi

关于.net-core:Net-Core-开发电商后端API-从0到精通吃透RESTful

download:.Net Core 开发电商后端API 从0到精通吃透RESTful一份简洁、纯正的Web前端性能优化清单。每个优化点都蕴含有概念、实操和参考资料。面试、实战两相宜。这是一个大工程。在正式开始之前,先对立下语言,廓清每一部分的目标和要求,避免跑偏。 概念:把官话翻译成能看懂、能记住的人话,原则上易读性 > 专业性 实操:本人操作一遍,不做云玩家;记录外围实现,不便CV 参考资料:信息起源选用一手材料,以便保障信息的完整性、准确性和时效性。除非看一手的了解不了…… 一、网络层面 DNS预解析概念DNS-prefetch 是一种 DNS 预解析技术。它会在申请跨域资源之前,事后解析并进行DNS缓存,以缩小真正申请时DNS解析导致的申请提早。对于关上蕴含有许多第三方连贯的网站,成果显著。实操增加ref属性为“dns-prefetch”的link标签。个别放在在html的head中。<link rel="dns-prefetch" href="//xxx.download.com">复制代码href的值就是要预解析的域名,对应前面要加载的资源或用户有可能关上链接的域名。备注同理,也有“ TCP/IP预连贯”,叫preconnect。参考资料中有残缺的形容。利用浏览器缓存概念浏览器缓存是浏览器寄存在本地磁盘或者内存中的申请后果的备份。当有雷同申请进来时,间接响应本地备份,而无需每次都从原始服务器获取。这样不仅晋升了客户端的响应效率,同时还能缓解服务器的拜访压力。其间,约定何时、如何应用缓存的规定,被称为缓存策略。分为强缓存和协商缓存。整个缓存执行的过程大抵如下:①. 申请发动,浏览器判断本地缓存,如果有且未到期,则命中强缓存。浏览器响应本地备份,状态码为200。控制台Network中size那一项显示disk cache;②. 如果没有缓存或者缓存已过期,则申请原始服务器询问文件是否有变动。服务器依据申请头中的相干字段,判断指标文件新鲜度;③. 如果指标文件没变更,则命中协商缓存,服务器设置新的过期工夫,浏览器响应本地备份,状态码为304;④. 如果指标文件有变动,则服务器响应新文件,状态码为200。浏览器更新本地备份。上述过程有几个关键点如何判断缓存是否过期?浏览器读取缓存的申请后果中响应头的Expires 和Cache-Control,与以后工夫进行比拟。其中,Expires是HTTP 1.0的字段,值是一个是相对工夫。Expires: Tue, 18 Jan 2022 09:53:23 GMT复制代码比拟相对工夫,有一个弊病,它依赖计算机时钟被正确设置。为了解决这个问题,HTTP1.1 新增了Cache-Control字段,它的值是一个是绝对工夫。Cache-Control: max-age=60 //单位是秒复制代码 如何判断文件是否变动?首先能够通过比拟 最初批改工夫。// 缓存后果的 响应头Last-Modified: Mon, 10 Jan 2022 09:06:14 GMT// 新申请的 申请头If-Modified-Since: Mon, 10 Jan 2022 09:06:14 GMT复制代码浏览器取出缓存后果中Last-Modified的值,通过If-Modified-Since上送到服务端。与服务器中指标文件的最初批改工夫做比拟。再者能够通过比拟 Etag。 Etag实体标签是附加到文档上的任意标签(援用字符串)。它们可能蕴含了文档的序列号或版本名,或者是文档内容的校验和及其他指纹信息。当发布者对文档进行批改时,会批改文档的实体标签来阐明这是个新的版本。 从响应头的ETag取值,通过申请头的If-None-Match上送,与服务器指标文件的Etag标签比对。// 缓存的 响应头ETag: "61dbf706-142"// 上送的 申请头If-None-Match: "61dbf706-142"复制代码和下面一样,新增的字段也是为了解决前一种计划的某些缺点: 有些文档可能会被周期性地重写(比方,从一个后盾过程中写入),但理论蕴含的数据经常是一样的。只管内容没有变动,但批改日期会发生变化。有些文档可能被批改了,但所做批改并不重要,不须要让世界范畴内的缓存都重装数据(比方对拼写或正文的批改)。有些服务器无奈精确地断定其页面的最初批改日期。有些服务器提供的文档会在亚秒间隙发生变化(比方,实时监视器),对这些服务器来说,以一秒为粒度的批改日期可能就不够用了。 如果两个版本的字段同时存在,怎么办?出于浏览器兼容方面的思考 ,个别两组字段会被同时应用。他们没有优先级一说,取并集。同时呈现时,只有当两个条件都满足,才会命中相应缓存。 实操缓存是web服务器和浏览器的外围能力,支流的web服务框架 nginx、koa-static等都内置有上述缓存策略的实现。开箱即用,无需额定编程或配置。以Nginx举例。强缓存的配置字段是expires,它承受一个数字,单位是秒。server { listen 8080;location / { root /Users/zhp/demo/cache-koa/static; index index.html;# 留神try_files会导致缓存配置不失效 # try_files $uri $uri/ /index.html; expires 60;}}复制代码理论工作中的确配置一下就好了,但这体现不出什么知识点。为了加深印象,我这用koa简陋的模仿了一下,算是对下面那些知识点的验证。上面是一个极简的动态资源服务,不带缓存的。app.use(async (ctx) => { // 1.依据拜访门路读取指定文件 const content = fs.readFileSync(./static${ctx.path}, "utf-8"); // 2.设置响应 ...

March 22, 2022 · 2 min · jiezi

关于.net-core:LeaRun-Net-CoreJava工作流引擎分离式前端升级Vue

流程作为企业管理系统的外围,在企业的倒退过程中具备重要的作用。通常来说,一个残缺的流程大抵包含有工作流引擎、工作流设计器、流程操作、客户界面、流程监控、表单设计器、表单集成以及与应用程序集成等。 工作流引擎 工作流引擎是流程的外围,次要提供对工作流定义的解析以及流程流转的反对。 工作流定义文件形容了业务的交互逻辑,工作流引擎通过解析此工作流定义文件依照业务的交互逻辑进行业务的流转,工作流引擎通常通过参考某种模型来进行设计,通过调度算法来进行流程的流转(流程的启动、终止、挂起、复原等),通过各种环节调度算法来实现对于环节的流转(环节的合并、分叉、抉择、条件性的抉择等)。 工作流设计器 可视化工作流设计器比拟直观,用户能够通过利落点拽等可视化操作来绘制流程,在流程中的线、节点进行权限和逻辑的配置,在肯定水平上打消开发过程中出错的可能性。 流程操作 流程操作是波及到流程的各种细节,如启动流程、终止流程、挂起流程、直流、分流(单人办理)、并流(多人同时办理)、联审等,象这些流程操作都是可间接基于引擎所提供的环节调度算法来间接反对的,而在理论的需要中,通常须要自在的对于流程进行干预,如取回、回退、跳转、追加、传阅,而这些流程操作对于工作流引擎来说是不合理的,因而必须独自的去实现。 工作流客户界面 客户界面程序是工作流零碎的可视化表现形式,通常应用Web形式进行展示(该页面应用vue),通过提供待办列表、已办列表、执行流程操作、查看流程历史信息等来展示工作流零碎的性能。 流程监控 流程监控通过提供图形化的形式来对流程执行过程进行监控,包含流程运行情况,每个环节所消耗的工夫等等,而通过这些可相应的进行流程的优化,以进步工作效率。 表单设计器 表单设计器为可视化的表单设计工具,用户可通过拖放的形式来绘制业务所需的表单,并可相应的进行表单数据的绑定。 与表单、应用程序的集成 通常,业务流转须要通过表单来表白理论的业务,因而须要与表单进行集成来实现业务意义,与表单的集成通常包含表单数据的主动获取、存储、批改,表单域的权限管制、流程相干数据的保护以及流程环节表单的绑定。 最初,通过与应用程序的集成,来欠缺工作流管理系统的业务意义,次要波及到的是与权限零碎以及组织机构的集成。 流程环节须要相应的绑定不同的执行角色,而流程操作通常须要与权限零碎、组织机构进行关联。 更多功能请搜寻“力软”进行体验。

June 1, 2021 · 1 min · jiezi

关于.net-core:AntDesign-Pro-NET-Core-实现基于JWT的登录认证

很多同学说AgileConfig的UI切实是太丑了。我想想也是的,原本这个我的项目是我本人应用的,一开始甚至连UI都没有,全靠手动在数据库里批改数据。起初加上了UI也是应用了老掉牙的bootstrap3做为根底款式。前台框架也是应用了angularjs,同样是老掉牙的货色。过年期间终于下决心翻新AgileConfig的前端UI。最初抉择的前端UI框架为AntDesign Pro + React。至于为啥选Ant-Design Pro是因为他难看,而且风行,抉择React是因为VUE跟Angular我都略知一二,罗唆趁此机会学一学React为何物,为何这么风行。 登录的认证计划为JWT,其实自己对JWT不太感冒(请看这里《咱们真的须要jwt吗?》),无奈大家都喜爱,那我也只能随大流。 其实基于ant-design pro的界面我曾经翻的差不多了,因为它反对mock数据,所以我一行后盾代码都没批改,曾经把界面快写完了。从当初开始要真正的跟后端代码进行联调了。那么咱们先从登录开始吧。先看看后端asp.net core方面会如何进行批改。 批改ASP.NET Core后端代码 "JwtSetting": { "SecurityKey": "xxxxxxxxxxxx", // 密钥 "Issuer": "agileconfig.admin", // 颁发者 "Audience": "agileconfig.admin", // 接收者 "ExpireSeconds": 20 // 过期工夫 s }在appsettings.json文件增加jwt相干配置。 public class JwtSetting { static JwtSetting() { Instance = new JwtSetting(); Instance.Audience = Global.Config["JwtSetting:Audience"]; Instance.SecurityKey = Global.Config["JwtSetting:SecurityKey"]; Instance.Issuer = Global.Config["JwtSetting:Issuer"]; Instance.ExpireSeconds = int.Parse(Global.Config["JwtSetting:ExpireSeconds"]); } public string SecurityKey { get; set; } public string Issuer { get; set; } public string Audience { get; set; } public int ExpireSeconds { get; set; } public static JwtSetting Instance { get; } }定义一个JwtSetting类,用来读取配置。 ...

March 5, 2021 · 3 min · jiezi

关于.net-core:Prism-框架项目应用篇-导航

在Prism中,应用Navigation来进行页面之间的跳转通常须要一下几步: 创立新页面,并且实现INavigationAware接口应用IRegionManager注册页面应用NavigationParameters封装页面跳转的参数应用IRegionManager.RequestNavigate()跳转到指标页面INavigationAware 接口INavigationAware 有三个接口,IsNavigationTarget、OnNavigatedFrom、OnNavigatedTo 在页面导航的时候会登程,能够按需当时接口办法,代码如下所示: Prism基类页面 示例代码如下所示 public class RegionViewModelBase : ViewModelBase, INavigationAware, IConfirmNavigationRequest { protected IRegionManager RegionManager { get; private set; } public RegionViewModelBase(IRegionManager regionManager) { RegionManager = regionManager; } public virtual void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback) { continuationCallback(true); } public virtual bool IsNavigationTarget(NavigationContext navigationContext) { return true; } public virtual void OnNavigatedFrom(NavigationContext navigationContext) { } public virtual void OnNavigatedTo(NavigationContext navigationContext) { } }被导航的页面能够继承自 <font color=Red> RegionViewModelBase</font>,如下代码展现重写OnNavigatedTo 办法,在OnNavigatedTo中登程相干事件办法。 ...

January 12, 2021 · 1 min · jiezi

关于.net-core:因为它在-4-偏移位置处包含一个对象字段该字段已由一个非对象字段不正确地对齐或重叠

TypeLoadException: 未能从程序集“ECS.GUI.Define, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null”中加载类型“ECS.GUI.Define.ArmgAimPos”,因为它在 4 偏移地位处蕴含一个对象字段,该字段已由一个非对象字段不正确地对齐或重叠异样呈现的关键字是:在4偏移地位处蕴含一个对象字段通过查找对象,该对象为共用体 // StructLayout使设计者能够管制类或构造的数据字段的物理布局 // Explicit与FieldOffset一起能够管制每个数据成员的准确地位 [StructLayout(LayoutKind.Explicit)] public struct ArmgAimPos { [FieldOffset(0)] public BayType type; [FieldOffset(4)] public string ContainerID; [FieldOffset(8)] public string JobID; 应用FieldOffset属性实现共用体与强制类型转换应用字段开始的构造中的偏移量初始化 FieldOffsetAttribute 类的新实例。 c# public FieldOffsetAttribute (int offset);参数 offset Int32 从构造开始处到字段开始处的偏移量(以字节为单位)。 可示意的概念为:原理实际上跟C++一样,将一个int[] 型的变量指向与byte[]型变量雷同的内存区域,跟C++中将dat指向char数组的首地址是一样的。同样,这样的构造体性能与C++的共用体是一样的,即一个构造能够作为多种数据类型,而具体是什么类型视状况而定。 批改异样办法:如提醒题目所示:4偏移地位,曾经有一个非对象字段了,所以,将偏移量向后挪动即可解决 // StructLayout使设计者能够管制类或构造的数据字段的物理布局 // Explicit与FieldOffset一起能够管制每个数据成员的准确地位 [StructLayout(LayoutKind.Explicit)] public struct ArmgAimPos { [FieldOffset(0)] public BayType type; [FieldOffset(8)] public string ContainerID; [FieldOffset(16)] public string JobID; [ FieldOffset(<font color=red> 8</font>)] public string ContainerID; 偏移量批改成了8 ,问题修复 ...

January 11, 2021 · 1 min · jiezi

关于.net-core:An-error-occurred-using-the-connection-to-database-on-server

EF Core Migration update-database的时候 An error occurred using the connection to database '' on server '10.28.253.2'问题:在做EF Core Migration的时候,Update-dataBase指令总是报错报错内容为“An error occurred using the connection to database '' on server '10.28.253.2'” 在做数据迁徙的时候,依照惯例操作 Add-Migration init(执行此命令我的项目生成一个目录(Migration))Update-Database init应用的数据库连贯字符串之前是能够查问胜利的,字符串内容如下 "ConnectionStrings": { "Default": "Data Source = 10.28.253.2;Initial Catalog = SignalR;User Id = sa;Password =Ecsgui123;"可是明天拷贝过去之后,死活执行报错,报错内容如下图所示: 本人找谬误的时候,还找错了谬误语句,用了绿色图标中的“Access denied for user”,这句是执行谬误最初一行的提示信息,呈现了一个程序中都不成呈现的“10.28.160.250”这个地址,找的一头乱码 认真查看错误码,发现一开始的报错内容IP是正确的,然而提醒连贯不上,红色框框内容,于是乎将连贯字符串中减少了“Integrated Security=False;”内容,问题到此解决。 "ConnectionStrings": "Default": "Data Source = 10.28.253.2;Initial Catalog = SignalR;User Id = sa;Integrated Security=False;Password =Ecsgui123;"

December 24, 2020 · 1 min · jiezi

关于.net-core:耦合以及相关解耦的思想

耦合是如何产生的?一个类,往往须要跟其余对象打交道,既包含获知其余对象的状态,也包含依赖其余对象的行为,而一旦这样的事件产生时,咱们便称该类依赖于另一个对象。只有两个对象之间存在一方依赖另一方的关系,那么咱们就称这两个对象之间存在耦合。 解除依赖的思维是如何产生的?(1)原始社会里,没有社会分工。须要斧子的人(调用者)仅仅能本人去磨一把斧子(被调用者)。相应的情景为:软件程序里的调用者本人创立被调用者。(2)进入工业社会,工厂呈现。斧子不再由普通人结束,而在工厂里被生产进去,此时须要斧子的人(调用者)找到工厂,购买斧子,毋庸关怀斧子的制作过程。相应软件程序的简略工厂的设计模式。 (3)进入“按需分配”社会,须要斧子的人不须要找到工厂,坐在家里收回一个简略指令:须要斧子。斧子就天然出现在他背后。依赖注入。 从(1)、(2)能够看出,不同点是解放了本人工作,不须要去磨这个斧子了,然而相同点就是这个主导者,还仍然是本人。就说你本人磨斧子还是本人去工厂里去取。这个次要调用者当事人还是本人。 从(2)(3)能够看出,相同点,咱们不须要去磨这个斧子了。不同点,就是主导者变了,斧子曾经不须要咱们去取了,而是咱们想要什么就去要什么。就是控制权曾经扭转了,他曾经不是咱们本人去管制的了。 解耦即让每一个局部都互相独立,一个局部的扭转不会影响其余局部。 我的项目代码松耦合状态 常见的解耦形式DI(Dependency Injection),对依赖的解耦。aop(Aspect Oriented Programming),对关注切面关注点业务逻辑的解耦。Dto(Data Transfer Object),对模型的解耦。ddd畛域驱动设计,对畛域业务模块的解耦。相干基本概念类和对象类:类是一个动态的概念,类自身不携带任何数据。当没有为类创立任何对象时,类自身不存在于内存空间中。对象new实例化的过程:申明援用;应用new关键字创立类的对象并对其初始化;(分配内存空间)将援用指向类的对象。 管制反转 IoC(Inversion of Control)举例场景:比方下面的例子(3)(工厂伐木),从本人须要去工厂买个斧子,而后咱们拿着斧子去上山伐木,变成甲方会给咱们提供斧子,咱们只须要在家里筹备好去就行了,这个时候,咱们对斧头这个控制权,曾经从咱们本人轻轻转化到调用者甲方身上了。这就是管制反转的思维。 比方你把程序里的一个写死的变量改成从配置文件里读取也是一种管制反转(由程序控制反转为由框架管制),你把这个配置改成用户UI界面的一个输出文本框由用户输出也是一种管制反转(由框架管制反转为由用户本人管制)。这些都用了管制反转思维。 什么是管制反转IoC(Inversion of Control)?是一种设计准则,最早由Martin Fowler提出,因为其实践提出工夫和成熟工夫绝对较晚,所以并没有被蕴含在GoF的《设计模式》中。对依赖的管制,从本人,转到本人的调用者上。 依赖注入 DI(Dependency Injection)依赖注入实现了new(做了new实例化这个动作),不是为了省一个new! 依赖注入实现IoC的其中一种设计办法。依赖的对象注入到调用者,你不应该本人创立它,而是应该通过构造函数,由你的调用者给你——容器。 举例场景——自动售货机容器是一个自动售货机,组件是放在外面的在售商品,服务是商品的发售名称。 把商品(我的项目里的具体对象)放入自动售货机(容器)上架的过程叫注册; 注册的时候会给商品贴上标签,标注该商品的名称,这个名称就叫服务; 咱们还能够标注这个商品的适用人群和过期工夫等(生命周期作用域); 把这个包装后的商品放入自动售货机后,它就变成了在售商品(组件)。 当有顾客须要某个商品时,他只有对着售货机报一个商品名(服务名),自动售货机找到对应商品,抛出给客户,这个抛给你的过程,就叫做注入你; 而且这个售货机比拟智能,抛出前还能够先判断商品是不是过期了,该不该抛给你。 依赖注入的步骤注册,创立映射关系蕴含三种生命周期:AddTransient,每次注入或申请时都会创立转瞬即逝的服务;AddScoped,是按范畴创立的,在Web应用程序中,每个Web申请都会创立一个新的独立服务范畴;其中AddTransient和AddScoped每次注册的服务都会在被应用一次之后,被GC销毁掉。AddSingleton,每个DI容器创立一个单例服务,这通常意味着它们在每个应用程序只创立一次,而后用于整个应用程序生命周期,会始终存在内存里。 public void ConfigureServices(IServiceCollection services) { // 注册 services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); }创立,new实例化 public class ValuesController : ControllerBase { public IHttpContextAccessor HttpContextAccessor { get; set; } // 创立,这个时候去new实例 public ValuesController(IHttpContextAccessor accessor) { _accessor = accessor; }}调用/应用 public ActionResult<IEnumerable<string>> Get() { //调用 var name = _accessor.HttpContext.User.Identity.Name; }援用/参考:https://www.bilibili.com/vide... ...

November 26, 2020 · 1 min · jiezi

关于.net-core:NET-Core-3时代已经到来了你还不会创建NET-Core-3应用

点击获取工具>>本指南将介绍如何创立.NET Core 3应用程序、如何将其配置为应用DevExpress WPF控件,以及如何向应用程序自身增加DevExpress控件。(在本教程中,咱们将向您展现如何将Spreadsheet控件增加到.NET Core 3我的项目中) 先决条件.NET Core 3 SDKVisual Studio 2019 version 16.3 (装置.NET桌面开发工作负载)分步教程关上Visual Studio 2019 v16.3,并创立一个新的WPF应用程序(.NET Core)。 增加DevExpress NuGet软件包。在此文章中,咱们形容如何通过DevExpress NuGet Feed获取DevExpress WPF控件。入门指南形容如何从本地源中将DevExpress WPF控件用于.NET Core。 跳转到Tools | NuGet Package Manager | Manage NuGet Packages for Solution 关上"Settings"... ...并增加具备以下属性的新NuGet feed: Name: DevExpress Source: https://nuget.devexpress.com/{your feed authorization key}/api 抉择您刚刚增加的DevExpress软件包源。 在"Browse"标签中,搜寻'WindowsDesktop.Wpf',并将以下两个软件包装置到以后我的项目中。 DevExpress.WindowsDesktop.WpfDevExpress.WindowsDesktop.Wpf.Themes.Office2019Colorful浏览并承受许可协定。 构建解决方案,这将迫使我的项目解决所有NuGet包依赖项。Visual Studio会将可用的控件加载到其工具箱中。 利用应用程序主题在应用程序启动时,将ApplicationThemeHelper.ApplicationThemeName 属性设置为所需的主题名称。 `public partial class App : Application {protected override void OnStartup(StartupEventArgs e) {DevExpress.Xpf.Core.ApplicationThemeHelper.ApplicationThemeName = DevExpress.Xpf.Core.Theme.Office2019ColorfulName;base.OnStartup(e);}}` ...

October 26, 2020 · 1 min · jiezi

关于.net-core:Net-Core高并发业务加个锁

高并发业务,应防止因为并发产生的一些异样问题,须要加把锁,利用Redis的锁 10秒超时,抛出超时异样 using (var redisLocker = new RedisLocker(_appApiRedisConfig, $"SaveStudyStatus:{accountId}", 10000, 10000)) { if (!redisLocker.Success) { throw new TimeoutException(); } }

September 27, 2020 · 1 min · jiezi

CNET基于Topshelf创建Windows服务的守护程序作为服务启动的客户端桌面程序不显示UI界面的问题分析和解决方案

本文首发于:码友网--一个专注.NET/.NET Core开发的编程爱好者社区。 文章目录C#/.NET基于Topshelf创建Windows服务的系列文章目录: C#/.NET基于Topshelf创建Windows服务程序及服务的安装和卸载 (1)在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务) (2)C#/.NET基于Topshelf创建Windows服务的守护程序作为服务启动的客户端桌面程序不显示UI界面的问题分析和解决方案 (3)前言在上一篇文章《在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)》的最后,我给大家抛出了一个遗留的问题--在将TopshelfDemoService程序作为Windows服务安装的情况下,由它守护并启动的客户端程序是没有UI界面的。到这里,我们得分析为什么会出现这个问题,为什么在桌面应用程序模式下可以显示UI界面,而在服务模式下没有UI界面? 分析问题(Session 0 隔离)通过查阅资料,这是由于Session 0 隔离作用的结果。那么什么又是Session 0 隔离呢? 在Windows XP、Windows Server 2003 或早期Windows 系统时代,当第一个用户登录系统后服务和应用程序是在同一个Session 中运行的。这就是Session 0 如下图所示: 但是这种运行方式提高了系统安全风险,因为服务是通过提升了用户权限运行的,而应用程序往往是那些不具备管理员身份的普通用户运行的,其中的危险显而易见。 从Vista 开始Session 0 中只包含系统服务,其他应用程序则通过分离的Session 运行,将服务与应用程序隔离提高系统的安全性。如下图所示: 这样使得Session 0 与其他Session 之间无法进行交互,不能通过服务向桌面用户弹出信息窗口、UI 窗口等信息。这也就是为什么刚才我说那个图已经不能通过当前桌面进行截图了。 潜在的问题解决方案在了解了Session 0 隔离之后,给出一些有关创建服务程序以及由服务托管的驱动程序的建议: 1、与应用程序通信时,使用RPC、命名管道等C/S模式代替窗口消息2、如果服务程序需要UI与用户交互的话,有两种方式:①用WTSSendMessage来创建一个消息框与用户交互②使用一个代理(agent)来完成跟用户的交互,服务程序通过CreateProcessAsUser创建代理。 并用RPC或者命名管道等方式跟代理通信,从而完成复杂的界面交互。3、应该在用户的Session中查询显示属性,如果在Session 0中做这件事,将会得到不正确的结果。4、明确地使用Local或者Global为命名对象命名,Local/为Session/<n>/BaseNamedObject/,Global/为BaseNamedObject/5、将程序放在实际环境中测试是最好的方法,如果条件不允许,可以在XP的FUS下测试。在XP的FUS下能工作的服务程序将很可能可以在新版系统中工作,注意XP的FUS下的测试不能检测到在Session 0下跟视频驱动有关的问题 本文我们的服务程序将通过CreateProcessAsUser创建代理来实现Session 0隔离的穿透。 在项目[TopshelfDemoService]中创建一个静态扩展帮助类ProcessExtensions.cs,代码如下: using System;using System.Runtime.InteropServices;namespace TopshelfDemoService{ /// <summary> /// 进程静态扩展类 /// </summary> public static class ProcessExtensions { #region Win32 Constants private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400; private const int CREATE_NO_WINDOW = 0x08000000; private const int CREATE_NEW_CONSOLE = 0x00000010; private const uint INVALID_SESSION_ID = 0xFFFFFFFF; private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; #endregion #region DllImports [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] private static extern bool CreateProcessAsUser( IntPtr hToken, String lpApplicationName, String lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandle, uint dwCreationFlags, IntPtr lpEnvironment, String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")] private static extern bool DuplicateTokenEx( IntPtr ExistingTokenHandle, uint dwDesiredAccess, IntPtr lpThreadAttributes, int TokenType, int ImpersonationLevel, ref IntPtr DuplicateTokenHandle); [DllImport("userenv.dll", SetLastError = true)] private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit); [DllImport("userenv.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr hSnapshot); [DllImport("kernel32.dll")] private static extern uint WTSGetActiveConsoleSessionId(); [DllImport("Wtsapi32.dll")] private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken); [DllImport("wtsapi32.dll", SetLastError = true)] private static extern int WTSEnumerateSessions( IntPtr hServer, int Reserved, int Version, ref IntPtr ppSessionInfo, ref int pCount); #endregion #region Win32 Structs private enum SW { SW_HIDE = 0, SW_SHOWNORMAL = 1, SW_NORMAL = 1, SW_SHOWMINIMIZED = 2, SW_SHOWMAXIMIZED = 3, SW_MAXIMIZE = 3, SW_SHOWNOACTIVATE = 4, SW_SHOW = 5, SW_MINIMIZE = 6, SW_SHOWMINNOACTIVE = 7, SW_SHOWNA = 8, SW_RESTORE = 9, SW_SHOWDEFAULT = 10, SW_MAX = 10 } private enum WTS_CONNECTSTATE_CLASS { WTSActive, WTSConnected, WTSConnectQuery, WTSShadow, WTSDisconnected, WTSIdle, WTSListen, WTSReset, WTSDown, WTSInit } [StructLayout(LayoutKind.Sequential)] private struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId; } private enum SECURITY_IMPERSONATION_LEVEL { SecurityAnonymous = 0, SecurityIdentification = 1, SecurityImpersonation = 2, SecurityDelegation = 3, } [StructLayout(LayoutKind.Sequential)] private struct STARTUPINFO { public int cb; public String lpReserved; public String lpDesktop; public String lpTitle; public uint dwX; public uint dwY; public uint dwXSize; public uint dwYSize; public uint dwXCountChars; public uint dwYCountChars; public uint dwFillAttribute; public uint dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } private enum TOKEN_TYPE { TokenPrimary = 1, TokenImpersonation = 2 } [StructLayout(LayoutKind.Sequential)] private struct WTS_SESSION_INFO { public readonly UInt32 SessionID; [MarshalAs(UnmanagedType.LPStr)] public readonly String pWinStationName; public readonly WTS_CONNECTSTATE_CLASS State; } #endregion // Gets the user token from the currently active session private static bool GetSessionUserToken(ref IntPtr phUserToken) { var bResult = false; var hImpersonationToken = IntPtr.Zero; var activeSessionId = INVALID_SESSION_ID; var pSessionInfo = IntPtr.Zero; var sessionCount = 0; // Get a handle to the user access token for the current active session. if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0) { var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO)); var current = pSessionInfo; for (var i = 0; i < sessionCount; i++) { var si = (WTS_SESSION_INFO)Marshal.PtrToStructure(current, typeof(WTS_SESSION_INFO)); current += arrayElementSize; if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive) { activeSessionId = si.SessionID; } } } // If enumerating did not work, fall back to the old method if (activeSessionId == INVALID_SESSION_ID) { activeSessionId = WTSGetActiveConsoleSessionId(); } if (WTSQueryUserToken(activeSessionId, ref hImpersonationToken) != 0) { // Convert the impersonation token to a primary token bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary, ref phUserToken); CloseHandle(hImpersonationToken); } return bResult; } public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = null, string workDir = null, bool visible = true) { var hUserToken = IntPtr.Zero; var startInfo = new STARTUPINFO(); var procInfo = new PROCESS_INFORMATION(); var pEnv = IntPtr.Zero; int iResultOfCreateProcessAsUser; startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO)); try { if (!GetSessionUserToken(ref hUserToken)) { throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed."); } uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE); startInfo.lpDesktop = "winsta0\\default"; if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false)) { throw new Exception("StartProcessAsCurrentUser: CreateEnvironmentBlock failed."); } if (!CreateProcessAsUser(hUserToken, appPath, // Application Name cmdLine, // Command Line IntPtr.Zero, IntPtr.Zero, false, dwCreationFlags, pEnv, workDir, // Working directory ref startInfo, out procInfo)) { iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed. Error Code -" + iResultOfCreateProcessAsUser); } iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); } finally { CloseHandle(hUserToken); if (pEnv != IntPtr.Zero) { DestroyEnvironmentBlock(pEnv); } CloseHandle(procInfo.hThread); CloseHandle(procInfo.hProcess); } return true; } }}修改ProcessHelper.cs为如下代码: ...

May 24, 2019 · 4 min · jiezi

dotnetwarp-NSSM-部署-net-core-项目到-windows-服务

如果你想将 .net core 项目以服务的形式部署到 windows 系统,希望本篇文章能够让你少走弯路dotnet-warp 安装使用dotnet-warp 是一个全局的.NET Core 工具,允许将.NET Core 项目打包为单个可执行文件 项目地址:https://github.com/Hubert-Ryb... 安装:dotnet tool install --global dotnet-warp 使用:在项目输出目录执行 dotnet-warp 即可将应用打包成一个exe文件 (支持:win-x64,linux-x64,osx-x64) NSSM 安装使用NSSM 是一个服务封装程序,它可以方便的将 Exe 程序封装成 windows 服务运行下载:nssm-2.24使用:下载后将对应版本拷贝到目标机器,然后命令行执行:nssm install 即可唤出可视化界面 选择应用路径,有参数的添加参数,指定服务名称,指定执行用户,然后安装即可安装成功别忘记启动:nssm start 服务名称执行/运行 nssm 接口查看所有命令,根据所需执行对应的命令即可 简单的给 asp .net core项目传入监听端口public static IWebHostBuilder CreateWebHostBuilder(string[] args){ List<string> urls = new List<string>(); urls.Add("http://*:5454");//默认监听 // --urls http://*:6060,https://*:2333 var urlIndex = args.ToList().IndexOf("--urls"); if (urlIndex > -1 && args.Length > urlIndex + 1 && !string.IsNullOrEmpty(args[urlIndex + 1])) { urls.AddRange(args[urlIndex + 1].Split(',')); } return WebHost.CreateDefaultBuilder(args) .UseUrls(urls.ToArray()) .UseStartup<Startup>();}注意事项wwwroot 文件夹需要手动拷贝发布到 Windows7 时遇到的错误 ****/hostfxr.dll 找不到,下载对应系统版本的 Windows6.1-KB2533623 补丁后重启即可

April 29, 2019 · 1 min · jiezi

在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)

本文首发于:码友网–一个专注.NET/.NET Core开发的编程爱好者社区。文章目录C#/.NET基于Topshelf创建Windows服务的系列文章目录:C#/.NET基于Topshelf创建Windows服务程序及服务的安装和卸载 (1)在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务) (2)C#/.NET基于Topshelf创建Windows服务的守护程序作为服务启动的客户端桌面程序不显示UI界面的问题分析和解决方案 (3)前言在上一篇文章《C#/.NET基于Topshelf创建Windows服务程序及服务的安装和卸载》中,我们了解发C#/.NET创建基于Topshelf Windows服务程序的大致流程,参数配置以及服务的安装和卸载。同时,我们也使用一个简单的定时任务演示了Topshelf服务的执行情况。今天我将继续为大家分享关于Topshelf主题的技术文章。本文主要演示在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)。创建一个演示应用程序首先,打开之前我们创建的[TopshelfDemoService.sln]解决方案。在这个解决方案中再创建一个名为TopshelfDemo.Client的客户端控制台应用程序,这个客户端程序即是我们需要使用[TopshelfDemoService]守护的。只是为了演示,所以客户端并没有实际意义的逻辑和功能,在Program.cs文件中,添加如下示例代码:using System;namespace TopshelfDemo.Client{ class Program { static void Main(string[] args) { Console.WriteLine(“这是一个由[码友网]创建的ERP系统示例程序,目前正在运行…”); Console.WriteLine(“技术支持:码友网(https://codedefautl.com) by Rector”); Console.ReadLine(); } }}仅此而已。编写好后,生成或者运行一下这个项目。你会看到一个控制台应用程序界面,如:实现守护程序功能再回到项目[TopshelfDemoService]中,打开类文件HealthMonitorService.cs,其中的定时功能演示的是一个检查某系统健康状况的任务,现在我们把定时任务功能改为守护某个或者某些应用程序。这里为了演示方便,没有重新创建服务类,在实际项目中,你也可以根据自己的情况创建不同的服务类。修改其中代码为如下所示:using System;using System.Collections.Generic;using System.Timers;namespace TopshelfDemoService{ internal class HealthMonitorService { /// <summary> /// 检测周期计时器 /// </summary> private readonly Timer _timer; /// <summary> /// 检测周期(秒) /// </summary> private int _monitorInterval = 10; /// <summary> /// 要守护的应用程序列表 /// </summary> private List<DaemonApplicationInfo> _daemonApps { get; set; } public HealthMonitorService() { // 初始化要守护的应用程序列表 // 实际项目中,你可以将这里的初始化参数放到配置文件/数据库/缓存中(怎么方便怎么来) _daemonApps = new List<DaemonApplicationInfo> { new DaemonApplicationInfo { ProcessName =“TopshelfDemo.Client”, // 请根据你的情况填写 AppDisplayName =“TopshelfDemo Client”, // 请根据你的情况填写 AppFilePath =@“D:\Projects\github\TopshelfDemoService\TopshelfDemo.Client\bin\Debug\TopshelfDemo.Client.exe” // 这里的路径请根据你的实际情况填写 } }; _timer = new Timer(_monitorInterval*1000) { AutoReset = true }; _timer.Elapsed += (sender, eventArgs) => Monitor(); } /// <summary> /// 守护应用程序的方法 /// </summary> private void Monitor() { foreach (var app in _daemonApps) { // 判断当前进程是存已启动 if (ProcessorHelper.IsProcessExists(app.ProcessName)) { Console.WriteLine(“Application[{0}] already exists.”, app.ProcessName); return; } try { // 当前主机进程列表中没有需要守护的进程名称,则启动这个进程对应的应用程序 ProcessorHelper.RunProcess(app.AppFilePath, app.Args); } catch (Exception ex) { Console.WriteLine(“Start application failed:{0}”, ex); } } } public void Start() { _timer.Start(); } public void Stop() { _timer.Stop(); } }}新建类DaemonApplicationInfo.cs和ProcessorHelper.cs,编写如下代码。DaemonApplicationInfo.cs(需守护的应用程序实体类):namespace TopshelfDemoService{ /// <summary> /// 需守护的应用程序实体 /// </summary> public class DaemonApplicationInfo { /// <summary> /// 进程中显示的名称 /// </summary> public string ProcessName { get; set; } /// <summary> /// 应用程序安装路径 /// </summary> public string AppFilePath { get; set; } /// <summary> /// 应用程序的名称 /// </summary> public string AppDisplayName { get; set; } /// <summary> /// 参数 /// </summary> public string Args { get; set; } }}ProcessorHelper.cs(进程处理帮助类):using System.Collections.Generic;using System.Diagnostics;using System.Linq;namespace TopshelfDemoService{ /// <summary> /// 进程处理帮助类 /// </summary> internal class ProcessorHelper { /// <summary> /// 获取当前计算机所有的进程列表(集合) /// </summary> /// <returns></returns> public static List<Process> GetProcessList() { return GetProcesses().ToList(); } /// <summary> /// 获取当前计算机所有的进程列表(数组) /// </summary> /// <returns></returns> public static Process[] GetProcesses() { var processList = Process.GetProcesses(); return processList; } /// <summary> /// 判断指定的进程是否存在 /// </summary> /// <param name=“processName”></param> /// <returns></returns> public static bool IsProcessExists(string processName) { return Process.GetProcessesByName(processName).Length > 0; } /// <summary> /// 启动一个指定路径的应用程序 /// </summary> /// <param name=“applicationPath”></param> /// <param name=“args”></param> public static void RunProcess(string applicationPath, string args = “”) { try { var psi = new ProcessStartInfo { FileName = applicationPath, WindowStyle = ProcessWindowStyle.Normal, Arguments = args }; Process.Start(psi); } catch{} } }}完成以上编码后,我们将项目程序[TopshelfDemo.Client]和[TopshelfDemoService]先都关闭掉(如果已运行),接着运行项目[TopshelfDemoService],下面就是见证奇迹的时刻啦:可以看到,守护程序[TopshelfDemoService]自动启动了客户端程序[TopshelfDemo.Client.exe],并且只会启动一个客户端实例程序。当我们把客户端关闭后,下次守护程序检测的时候客户端程序又会被重启。遗留问题如果你正高高兴兴地将TopshelfDemoService作为Windows服务安装,那么你可能会遇到这个问题,即守护进程正常运行,客户端程序也能正常地被守护并且启动,在Windows的"任务管理器"中也可以找到客户端的进程,但却看不到客户端程序的UI界面。这是怎么回事呢???是不是哪里出错了呢???应该如何解决呢???预知后事如何请听下回分解(未完待续)…好了,今天的在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)的分享就到这里。我是Rector,希望本文对C#/.NET开发的你有所帮助。源代码下载本示例代码托管地址可以在原出处找到:示例代码下载地址 ...

April 17, 2019 · 2 min · jiezi

ASP.NET Core 入门教程 10、ASP.NET Core 日志记录(NLog)入门

一、前言1、本教程主要内容ASP.NET Core + 内置日志组件记录控制台日志ASP.NET Core + NLog 按天记录本地日志ASP.NET Core + NLog 将日志按自定义LoggerName分类记录到不同目录ASP.NET Core + NLog 按文件大小归档记录本地日志NLog配置文件常用配置项说明2、本教程环境信息软件/环境说明操作系统Windows 10SDK2.1.401ASP.NET Core2.1.3IDEVisual Studio Code 1.33.0浏览器Chrome 73VS Code插件版本说明C#1.18.0提供C#智能感知, .NET Core 调试、编译等vscdoe-solution-explorer0.3.1提供解决方案视图XML Tools2.4.0提供XML高亮,格式化、XML树等本篇代码以下代码进行调整:https://github.com/ken-io/asp…二、ASP.NET Core 内置日志组件使用ASP.NET Core内置日志组件,可以将日志输出在控制台1、应用程序启动时配置日志修改Program.cs,在WebHostBuilder构建时配置日志//需要引入的命名空间using Microsoft.Extensions.Logging;public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .ConfigureLogging(logging => { logging.ClearProviders(); logging.SetMinimumLevel(LogLevel.Info); logging.AddConsole(); });配置项说明ClearProviders()清除日志提供程序,通常在引入第三方日志组件时使用SetMinimumLevel(LogLevel.Information)设置日志级别为InformationAddConsole()添加日志提供程序->控制台这里设置项,主要是SetMinimumLevel(LogLevel.Information),我们把日志级别设置为Information,可以减少很多控制台日志输出2、在控制中记录日志修改HomeController.cs,通过构造函数注入ILogger//需要引入的命名空间using Microsoft.Extensions.Logging;private readonly ILogger<HomeController> _logger;public HomeController(ILogger<HomeController> logger){ this._logger = logger;}public IActionResult Index(){ _logger.LogInformation("——\r\nindex:hello world\r\n——"); return Content(“Hello World ! -ken.io”);}public IActionResult CheckPhone(string phone){ _logger.LogInformation($"——\r\ncheck phone:{phone}\r\n——"); var result = true; var message = “pass”; if (string.IsNullOrWhiteSpace(phone)) { result = false; message = “phone number is empty”; _logger.LogError($"——\r\ncheck phone:{message}\r\n——"); } else if (phone.Length != 11) { result = false; message = “wrong phone number length”; _logger.LogWarning($"——\r\ncheck phone:{message}\r\n——"); } return Json(new { Result = result, Phone = phone, Message = message });}这里日志内容中包含的\r\n转义符在控制台输出时/写入文件时表示换行,这里加入\r\n和—主要是为了日志输出时,方便快速找到我们主动记录的日志。3、验证启动应用,访问 localhost:5001,会看到控制台输出了Hello World访问 localhost:5001/home/checkphone,将会看到Error日志访问 localhost:5001/home/checkphone?phone=000,将会看到警告日志访问 localhost:5001/home/checkphone?phone=16666666666,就只会看到Info级别日志了三、ASP.NET Core + NLog 记录本地日志1、安装NLog Package在控制台使用命令安装NLog包://进入项目目录cd Ken.Tutorial.Web//使用命令安装nlogdotnet add package NLog.Web.AspNetCore –version 4.8.1这里我安装的指定版本:4.8.1,如果不指定版本号,默认安装最新版本。2、引入NLog修改Program.cs,在WebHostBuilder构建时配置日志//需要引入的命名空间using NLog.Web;public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .ConfigureLogging(logging => { logging.ClearProviders(); logging.SetMinimumLevel(LogLevel.Info); logging.AddConsole(); }).UseNLog();//UseNLog3、配置NLog在项目根目录Ken.Tutorial.Web新建NLog配置文件nlog.config<?xml version=“1.0” encoding=“utf-8” ?><nlog xmlns=“http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" autoReload=“true”> <targets> <target name=“defaultlog” xsi:type=“File” keepFileOpen=“false” encoding=“utf-8” fileName="${basedir}/logs/${level}/${shortdate}.log” layout="${longdate}|${level:uppercase=true}|${logger}|${message}” /> </targets> <rules> <logger name="" minlevel=“trace” writeTo=“defaultlog” /> </rules></nlog>NLog配置文件一般主要包含两个部分,<targets>节点配置日志写入目标,<rules>节点配置匹配路由到<targets>的规则。主要配置项ken.io的说明${basedir}/logs/${level}/${shortdate}.log表示在程序运行目录,分日志级别按天写入日志文件${longdate}&#124;${level:uppercase=true}|${logger}&#124;${message}日志内容格式:时间+日志级别+LoggerName+日志内容<logger name="" minlevel=“trace” writeTo=“defaultlog” />支持将任意级别、任意LoggerName的日志写入target:defaultlog为了能让程序运行时能够读取到配置文件,需要修改Ken.Tutorial.Web.csproj文件,在生成运行文件到bin文件夹时,也把nlog.config复制过去。在工具栏切换到资源管理器视图,双击文件即可修改:<ItemGroup> <Content Update=“nlog.config” CopyToOutputDirectory=“PreserveNewest” /></ItemGroup>4、NLog测试启动应用,分别访问以下链接:localhost:5001/home/checkphonelocalhost:5001/home/checkphone?phone=000localhost:5001/home/checkphone?phone=16666666666然后在资源管理器视图下,查看bin/Debug目录,会看到日志已经按照配置文件的格式生成了对应的日志目录及文件。同时也会发现,nlog.config按照之前的配置也被复制到了程序运行目录。Info级别日志Warn级别日志Error级别日志这里Info日志是比较多的,我们可以通过配置rules,只输出程序本身主动记录的日志。<logger name=“Ken.Tutorial.*” minlevel=“trace” writeTo=“defaultlog” />四、NLog使用进阶1、通过自定义LoggerName归纳日志增加NLog配置,根据LoggerName创建目录<!–按照LoggerName分类–><target name=“customlog” xsi:type=“File” keepFileOpen=“false” encoding=“utf-8” fileName="${basedir}/logs/${logger}/${shortdate}-${level}.log" layout="${longdate}|${level:uppercase=true}|${logger}|${message}" /><!–记录LoggerName以log结尾的日志–><logger name="*log" minlevel=“trace” writeTo=“customlog” />在HomeController中添加测试归纳日志的Actionpublic IActionResult TestLog(){ var logger = NLog.LogManager.GetLogger(“testlog”); logger.Trace(“这是Trace日志”); logger.Debug(“这是Debug日志”); logger.Info(“这是Info日志”); logger.Warn(“这是警告日志”); logger.Error(“这是错误日志”); return Content(“ok”);}这里直接通过NLog.LogManager.GetLogger创建Logger,并没有使用内置日志Microsoft.Extensions.Logging.ILogger的实例,所以可以自定义LoggerName,另外,我们在应用启动时配置的最低日志级别等也不会对这种方式生效,是可以输出Trace、Debug级别的日志的。启动应用,访问:localhost:5001/home/testlog,然后就可以在资源管理器界面看到在logs文件夹下按照LoggerName生成的日志目录,并按照日志级别生成了不同的日志文件:2、按照日志文件大小归档日志增加NLog配置,按照日志文件大小归档<!–按照日志文件大小归档–><target name=“archivelog” xsi:type=“File” keepFileOpen=“false” encoding=“utf-8” fileName="${basedir}/logs/${logger}/${shortdate}-current.log" archiveFileName="${basedir}/logs/${logger}/${shortdate}.{####}.log" archiveAboveSize=“1024000” archiveNumbering=“Sequence” maxArchiveFiles=“100” layout="${longdate}|${level:uppercase=true}|${logger}|${message}" /> 这里配置当日志文件大小在超过约1mb的时候归档(这里偷懒用1000b代替1kb),最多归档100个日志文件,当归档日志文件超过100个时会把最早归档的日志删除。定义专属规则:<logger name=“logmany” minlevel=“trace” writeTo=“archivelog” />在HomeController中添加测试归档日志的Actionpublic IActionResult TestLogMany(){ var logger = NLog.LogManager.GetLogger(“logmany”); for (int i = 0; i <= 30000; i++) { logger.Info(“ASP.NET Core入门教程,这里是日志内容,测试NLog的日志归档功能,ken的杂谈(https://ken.io)”); } return Content(“ok”);}这里为了方便测试,直接用for循环,连续写入日志30000次启动应用,访问:localhost:5001/home/testlogmany,然后就可以在资源管理器界面看到在logs/logmany文件夹下按照文件大小归档的日志五、备注1、NLog常用配置项说明target节点/属性ken.io的说明target日志写入目标,可以配置写入类型、写入模板、文件名等nameTargetName,需要唯一,在rules节点引用xsi:type目标类型,支持文件(File)、数据库(Database)、邮件(Mail)keepFileOpen保持文件打开,不用每次写入日志时都打开、关闭文件,因为可以提高性能,默认值为:falseencoding文件编码,此处配置为:utf-8fileName日志文件名,包含日志完整路径和文件名,支持模板语法/变量archiveFileName归档日志文件名,包含日志完整路径和文件名,支持模板语法/变量archiveNumbering归档序号方式maxArchiveFiles最大归档日志文件数layout日志内容模板,内置了一些语法/变量模板语法/变量ken.io的说明${basedir}程序当前运行目录${level}日志级别${shortdate}当前日期,例如:2019-04-05${longdate}当前时间,精确到毫秒,例如:2019-04-05 14:10:22.4372${uppercase:${level}}把内容格式化成大写${logger}LoggerName,日志记录器名称,通常是Logger初始化所在类完整名称,例如:Ken.Tutorial.Web.Controllers.HomeController${machinename}机器名${message}日志内容rules节点/属性ken.io的说明logger日志记录器路由规则配置nameLoggerName匹配,支持完整匹配和模糊匹配,例如:mylog、Ken.*minlevel最低日志级别maxlevel最高日志级别level限制单一的日志级别levels指定一个或多个日志级别,用,间隔writeTo指定一个或多个target,用,间隔final在匹配到该规则之后不再匹配后续规则,默认值:falseenabled是否启用该规则,默认值:true2、附录本文代码示例https://github.com/ken-io/asp…本文参考https://docs.microsoft.com/zh…https://github.com/NLog/NLog....https://github.com/nlog/NLog/…本文后首发于我的独立博客:https://ken.io/note/asp.net-c… ...

April 8, 2019 · 2 min · jiezi

ASP.NET Core 入门教程 9、ASP.NET Core 中间件(Middleware)入门

一、前言1、本教程主要内容ASP.NET Core 中间件介绍通过自定义 ASP.NET Core 中间件实现请求验签2、本教程环境信息软件/环境说明操作系统Windows 10SDK2.1.401ASP.NET Core2.1.3MySQL8.0.xIDEVisual Studio Code 1.32.3浏览器Chrome 70VS Code插件版本说明C#1.17.1提供C#智能感知, .NET Core 调试、编译等vscdoe-solution-explorer0.3.1提供解决方案视图本篇代码以下代码进行调整:https://github.com/ken-io/asp…3、前置知识可能需要的前置知识C# 委托(Delegate)http://www.runoob.com/csharp/…C# 扩展方法https://docs.microsoft.com/zh…二、ASP.NET Core 中间件介绍1、ASP.NET Core 中间件基本说明当 ASP.NET Core MVC应用从Kestrel接收到请求,会建立HttpContext并交由Application来处理请求。在Application中会有一个处理该请求的通道,这就是ASP.NET Core 管道,通常称之为:请求处理管道在这个管道中,有一系列有序处理请求的组件,就是中间件(Middleware)。图中蓝色的部分可以认为是系统内置比较靠前的中间件或者我们自定义的中间件,MVC是一个特殊的中间件且通常放在最后,所以这里单独画出来对于MVC中间件,如果请求的URL与路由匹配,那么后面的中间件均不会生效。所以MVC通常放在最后。ASP.NET Core中会内置一些中间件,例如:身份验证、静态文件处理、MVC等。每个中间件在接受到请求后都可以选择是交由下一个中间件处理还是直接返回结果。例如:身份验证中间件验证未通过会直接引导到登陆页静态文件中间件判断为静态文件就会直接返回静态文件内容所以,中间件可以理解为请求处理管道中的请求处理器。我们也可以通过自定义中间件注册到管道中来干预请求。2、ASP.NET Core 中间件基础使用在程序中,中间件是基于委托来构建的。在应用启动时通过IApplicationBuilder注册到通道中。具体见启动类Startup.cs:public void Configure(IApplicationBuilder app, IHostingEnvironment env){ if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(routes => { //配置默认路由 routes.MapRoute( name: “Default”, template: “{controller}/{action}”, defaults: new { controller = “Home”, action = “Index” } ); });}UseDeveloperExceptionPage、UseMvc都是接口IApplicationBuilder的扩展方法。三、使用 ASP.NET Core 中间件实现请求验签如果你开发的API是为手机App服务的,那么你的API是一定要暴露给公网的,如果有人拿到API地址进行非法请求,获取用户信息或者是篡改数据,用户隐私、数据就会受到损害。这是很不安全的,我们可以让客户端请求的时候必须携带签名,在服务器端鉴权(验证签名)通过了再放行,这样就安全很多了。1、创建验签中间件在项目Ken.Tutorial.Web创建目录Middlewares,然后创建类:TokenCheckMiddleware.csusing System;using System.Security.Cryptography;using System.Text;using System.Threading.Tasks;using Microsoft.AspNetCore.Http;namespace Ken.Tutorial.Web.Middlewares{ public class TokenCheckMiddleware { private readonly RequestDelegate _next; public TokenCheckMiddleware(RequestDelegate requestDelegate) { this._next = requestDelegate; } public Task Invoke(HttpContext context) { //先从Url取token,如果取不到就从Form表单中取token var token = context.Request.Query[“token”].ToString() ?? context.Request.Form[“token”].ToString(); if (string.IsNullOrWhiteSpace(token)) { //如果没有获取到token信息,那么久返回token missing return context.Response.WriteAsync(“token missing”); } //获取前1分钟和当前的分钟 var minute0 = DateTime.Now.AddMinutes(-1).ToString(“yyyy-MM-dd HH:mm”); var minute = DateTime.Now.ToString(“yyyy-MM-dd HH:mm”); //当token和前一分钟或当前分钟任一时间字符串的MD5哈希一致,就认为是合法请求 if (token == MD5Hash(minute) || token == MD5Hash(minute0)) { return _next.Invoke(context); } //如果token未验证通过返回token error return context.Response.WriteAsync(“token error”); } public string MD5Hash(string value) { using (var md5 = MD5.Create()) { var result = md5.ComputeHash(Encoding.ASCII.GetBytes(value)); var strResult = BitConverter.ToString(result); return strResult.Replace("-", “”); } } }}由于是侧重自定义中间件,所有验签的逻辑就写的非常简单,如果实际项目使用,可以按照自己需求调整2、创建扩展方法在Middlewares目录下新建类:MiddlewareExtension.csusing Microsoft.AspNetCore.Builder;namespace Ken.Tutorial.Web.Middlewares{ public static class MiddlewareExtension { public static IApplicationBuilder UseTokenCheck(this IApplicationBuilder builder) { return builder.UseMiddleware<TokenCheckMiddleware>(); } }}这里我们通过扩展方法,将TokenCheckMiddleware挂在接口IApplicationBuilder上3、中间件注册/引用在启动类Startup.cs的Configure方法中注册/引用中间件public void Configure(IApplicationBuilder app, IHostingEnvironment env){ //省略部分代码 app.UseTokenCheck(); app.UseMvc(routes => { //省略路由配置代码 });}如果你觉得扩展方法有点多余,也可以直接使用UseMiddleware方法注册public void Configure(IApplicationBuilder app, IHostingEnvironment env){ //省略部分代码 app.UseMiddleware<TokenCheckMiddleware>(); app.UseMvc(routes => { //省略路由配置代码 });} 这里要注意的是,如果你是一个MVC应用,请一定要把MVC这个中间件作为最后一个注册。因为中间件是按照注册顺序被调用的。如果放在MVC之后,请求的URL也有对应路由适配,那么整个请求已经被MVC接管。后面的中间件就不会被调用了。4、验签中间件测试启动应用,然后验证不同情况下的访问结果URLResponselocalhost:5001token missinglocalhost:5001?token=testtoken errorlocalhost:5001?token=3D76FEA1D0ADD0C7639B73023436C6EAHello World ! -ken.io为了方便测试,MD5哈希的值我们可以在线生成:ttp://tool.chinaz.com/tools/md5.aspx把当前分钟,例如:2019-03-27 23:23 通过MD5在线生成那就是3D76FEA1D0ADD0C7639B73023436C6EA四、备注本文代码示例https://github.com/ken-io/asp…本文参考https://docs.microsoft.com/zh…延伸阅读https://www.cnblogs.com/artec…本文首发于我的独立博客:https://ken.io/note/asp.net-c… ...

April 1, 2019 · 1 min · jiezi

.NET环境大规模使用OpenTracing

作者:Austin ParkerOpenTracing的最大优势之一是围绕它构建的社区,涵盖各种语言和技术。考虑到这一点,我很高兴今天在OpenTracing博客上发表一篇由Aaron Stannard撰写的客座文章。Aaron Stannard是Petabridge的创始人兼首席执行官,Petabridge是一家帮助.NET公司构建大规模分布式系统的创业公司。他也是Akka.NET项目的联合创始人。你可以在Twitter上找到他,网址是https://twitter.com/Aarononth…在过去的五年里,我一直担任Akka.NET开源项目的维护者和联合创始人之一,该项目是最初在Scala开发,极受欢迎的Akka项目的C#和F#移植。我最初开始这个项目,是因为.NET生态系统缺乏用于构建实时大型应用程序类型的工具和框架,就像那时我在MarkedUp开发的那种类型,MarkedUp是我运行的营销自动化和分析的初创公司。在关闭MarkedUp后,我继续创建了Petabridge,这是一家致力于在.NET中支持和开发Akka.NET,和其他分布式系统技术的开源公司。我很高兴地报告说,现在.NET社区有一个更强大的开源生态系统,并且有更多的工具选择,可用于构建我在2013-14年工作的.NET中的大规模应用程序类型。随着.NET Core的出现,整个.NET生态系统正在发生巨大变化,.NET Core是一种高性能,轻量级和100%跨平台的.NET运行时(runtime)的新实现。这为.NET开发者开辟了一个新的可能性,而这之前根本就没有。使用Akka.NET和Actor模型的大规模.NETAkka和Akka.NET,如果你还没有听说过,是在通用虚拟机(分别是JVM和CLR)之上构建的actor模型的实现。演员(actor)模型是一个可追溯到早期20世纪70年代的旧概念,但近年来它重新焕发活力,因为它提供了一种易于在大型数据中心或公共云环境中分发,可理解的计算模型。你问,“可理解的计算模型”做什么?具体来说,actor模型为需要构建可扩展实时系统的开发者,找到了一个家,例如:多人视频游戏;分析(Analytics);营销自动化;医疗/医疗物联网;物流、交通和运输;能源;金融;和实时交易处理(ACH、支付处理器等)所有这些应用程序的共同点是,它们履行了对客户和利益相关者的义务,他们必须能够以一致的快速(实时)方式完成工作,而不管系统的总量(可扩展)。为了使这些应用程序满足这两个目标,它们必须是有状态的,这意味着真实的来源来自应用程序内存,而不是外部数据库。为了使有状态应用既具有容错性,和高可用性,它们也必须分散(decentralized),状态不能集中在一个区域,否则系统容易受到单点瓶颈和单点故障限制的影响。这是actor模型允许开发者做的事情:构建高度分散、容错、有状态的应用程序,其中每个工作(actor)单元都是自包含的私有状态,不能直接从外部修改。修改actor的状态的唯一方法,是通过向该actor发送一条消息,该actor最终将处理该消息,从而可能导致更新actor的状态。在.NET中,Akka.NET是构建这些类型应用程序的主要actor模型实现,它被数百家公司使用,包括戴尔、美国银行、波音、S&P Global、Becton Dickinson、美国能源部,Zynga等等。然而,演员模型为试图大规模采用它的软件团队提出了一些重大挑战,其中最痛苦的一个是大规模诊断和调试编程错误和网络相关问题。这就是OpenTracing和分布式跟踪的登场时间。使用OpenTracing以低成本了解复杂性Akka.NET和大规模分布式演员的问题在于,在任何特定时间,你的系统每秒都可以进行数千万次交互,看起来与此太相似:Akka.NET ActorSystem中的每个actor通常都有一些少量的自包含状态,一些消息处理代码执行其实际工作,以及一些对它经常与之通信的其他actor的引用。演员通过来回传递消息来相互通信。默认情况下,在actor模型中传递的消息100%是异步的,actors一直按照它们被发送的顺序处理消息,但是一个actor可能必须处理来自许多其他actor的消息。Actor可以跨进程和网络边界透明地相互通信,因此,发送到一个进程内的单个actor的消息可能最终传播到多个进程。其中存在的问题是:这种位置透明性,使得演员如此擅长以可扩展的方式分配工作,这可能会使他们在生产中出现问题时进行调试时非常令人沮丧:知道出现问题的地点和时间变成一个非凡问题,尤其是当你有数百万次这样的操作一直在发生时。这是我们发现OpenTracing特别有用的地方。Akka.NET应用程序不作为单线程,单体进程存在,它们是高度并发且通常是分布式的进程。因此.NET中常见的传统跟踪工具,如Intellitrace,通常无法帮助我们回答系统内部“出了什么问题?”。我们需要的是分布式跟踪工具,它们可以从多个进程收集上下文,将它们关联在一起,并从分布式系统的角度讲述完整的故事。我们需要能够回答诸如“akka.tcp://ClusterSys@10.11.22.248:1100/user/actorA/child2收到msg1后,发送给akka.tcp://ClusterSys@10.11.22.249:1100/user/processB/child1的是什么?”,只有在这两个进程上运行的分布式跟踪工具,才能有效地回答这个问题,而这正是我们在Petabridge上使用OpenTracing的原因。OpenTracing实施和优势Petabridge专业支持大规模采用Akka.NET的用户,这意味着我们必须提供各种工具来帮助他们的生活更轻松。这就是为什么我们开始创建Phobos,这是Akka.NET的监控和跟踪解决方案。我们希望通过开发某种分布式跟踪实现,帮助我们的用户解决这个Akka.NET可观察性问题,这些实现可以轻松地包含在他们的应用程序代码。但我们遇到了一个小问题:我们的客户无法接受单一供应商的解决方案作应用程序性能监视,他们肯定不会接受只适用于Akka.NET,而不适用于其他重要的.NET技术,如ASP.NET Core和SignalR。OpenTracing为我们优雅而简单地解决了这个问题:通过瞄准OpenTracing标准,而不是任何单一的销售解决方案,如Zipkin或Jaeger,我们可以为我们的客户打开门口,让他们选择他们想要的任何跟踪解决方案。我们也知道,我们很可能会为.NET用户创建一些兼容OpenTracing的驱动程序,他们希望能够使用我们和其他依赖该标准的产品。因此,我们针对优秀的OpenTracing C#库构建了Phobos的跟踪功能,并设计了Zipkin和Jaeger等工具基于OpenTracing绑定的第一方集成。这大大降低了我们的开发成本,增加了用户享受的选择自由。每次演员发送或接收消息时,我们都会创建一个新的Span,并将跟踪标识符传播到我们在演员之间传递的每条消息中,包括通过网络传递。我们能够构建所有这些,因此它在幕后工作,而不需要太多的手动仪器(instrumentation)。可以肯定的是,OpenTracing允许我们使用Jaeger制作像这样的可理解的图形:在这种情况下,我们正在建模一个“扇出”(“fan out”)调用,其中一个节点通过网络向许多其他节点发出呼叫,使用传统工具难以捕获的东西,因为它涉及多个节点上的大量并发处理和每个人之间的异步沟通。但是使用OpenTracing的标准,我们很容易使用像Jaeger这样的工具来实现这一点,Jaeger在C#中有一个很好的OpenTracing兼容驱动程序。在.NET中创建OpenTracing驱动程序一旦Phobos完全支持OpenTracing,作为我们最终用户的集成点,我们就知道任何拥有内部或第三方跟踪解决方案,但本身不支持OpenTracing的Akka.NET用户最终都可以找到一种方法使用OpenTracing库来将事情联系在一起。但是,我们决定加倍努力,采用一些已经在.NET社区中流行的现有工具,或者通过为这些产品推出第一方OpenTracing驱动程序和适配器来降低进入门槛。我们建造的第一个是Petabridge.Tracing.Zipkin,一个用于Zipkin的高性能OpenTracing兼容驱动程序;我们想在内部使用Zipkin,并希望原生支持像Kafka的传送选项。在许多.NET用户的要求下,我们构建的第二个也是更有趣的是Microsoft Application Insights OpenTracing适配器,用于我们的Akka.NET跟踪产品。对Azure上运行的用户,我们希望能够支持Application Insights作为的跟踪目标,但是没有用于将Application Insights插入OpenTracing的内置解决方案。因此,我们遵循了Microsoft团队编写的标准文档,该文档允许我们在OpenTracing的词典之上映射Application Insights常规,并且能够创建一个开源软件包Petabridge.Tracing.ApplicationInsights,它弥合了这两者之间的差距技术,使Application Insights在大型Akka.NET应用程序中完美可行。我们在发布软件包之后发现,即便是微软本身也在使用OpenTracing和我们的Application Insights驱动程序来内部测试他们自己的一些云应用程序。对于整个.NET生态系统中的每个人来说,这是一件好事:随着OpenTracing继续获得牵引力,它将有助于推动其作为行业标准实践的使用。随着我们继续推动大规模.NET系统的规模和速度的界限,像我们这样的组织将继续投资OpenTracing等技术,以及其有前途的监控对手OpenMetrics,以限制运行这些系统的运营和管理成本。到目前为止,OpenTracing已经为我们的公司和整个Akka.NET项目带来了惊人的表现,我们期待在未来看到更多。-Aaron Stannard,Petabridge首席执行官Akka.NET项目联合创始人2019年KubeCon + CloudNativeCon中国论坛提案征集(CFP)现已开放KubeCon + CloudNativeCon 论坛让用户、开发人员、从业人员汇聚一堂,面对面进行交流合作。与会人员有 Kubernetes、Prometheus 及其他云原生计算基金会 (CNCF) 主办项目的领导,和我们一同探讨云原生生态系统发展方向。2019年中国开源峰会提案征集(CFP)现已开放在中国开源峰会上,与会者将共同合作及共享信息,了解最新和最有趣的开源技术,包括 Linux、容器、云技术、网络、微服务等;并获得如何在开源社区中导向和引领的信息。大会日期:提案征集截止日期:太平洋标准时间 2 月 15 日,星期五,晚上 11:59提案征集通知日期:2019 年 4 月 1 日会议日程通告日期:2019 年 4 月 3 日幻灯片提交截止日期:6 月 17 日,星期一会议活动举办日期:2019 年 6 月 24 至 26 日2019年KubeCon + CloudNativeCon + Open Source Summit China赞助方案出炉啦

January 22, 2019 · 1 min · jiezi

ASP.NET Core 入门教程 8、ASP.NET Core + Entity Framework Core 数据访问入门

一、前言1、本教程主要内容ASP.NET Core MVC 集成 EF Core 介绍&操作步骤ASP.NET Core MVC 使用 EF Core + Linq to Entity 访问MySQL数据库ASP.NET Core MVC 使用 EF Core + 原生SQL访问MySql数据库EF Core + MySQL数据库插入数据后获取自增列的值Entity Framework Core 简称为 EF Core2、本教程环境信息软件/环境说明操作系统Windows 10SDK2.1.401ASP.NET Core2.1.3MySQL8.0.xIDEVisual Studio Code 1.30浏览器Chrome 70VS Code插件版本说明C#1.17.1提供C#智能感知, .NET Core 调试、编译等vscdoe-solution-explorer0.3.1提供解决方案视图本篇代码以下代码进行调整:https://github.com/ken-io/asp…3、准备工作安装MySQL数据库:https://dev.mysql.com/downloa…Windows环境下载安装包后双击安装,一直下一步即可。MacOS环境参考:https://ken.io/note/macos-mys…CentOS环境参考:https://ken.io/note/centos-my…4、前置知识控制反转(IOC)原则与依赖注入(DI)ASP.NET Core 默认集成了DI。所有官方模块的引入都要使用DI的方式引入。https://baike.baidu.com/item/IOCLinq使用教程https://docs.microsoft.com/zh…二、EF Core + MySQL 前置准备EF Core 全称:Entity Framework Core,为微软为 .NET Core平台开发的ORM框架。对应是 .NET Framework平台的 Entity Framework(EF),无论是EF还是EF Core都可以说是 .NET 平台开发效率最高的ORM框架。1、引入 EF Core + MySQL ProviderEF Core已经集成在 ASP.NET Core 中,但默认并不支持MySQL,如果需要连接MySQL,需要添加MySQL相关的Provider,这里我选择的是:Pomelo.EntityFrameworkCore.MySql任意命令行操作即可,我用的是VS Code自带的命令行//进入项目根目录: Ken.Tutorial.Webcd Ken.Tutorial.Web//添加Packagedotnet add package Pomelo.EntityFrameworkCore.MySql这里我添加的 MySql.Data.EntityFrameworkCore 版本是 8.0.13,如果你想跟我使用一样的版本,可以使用以下命令:dotnet add package Pomelo.EntityFrameworkCore.MySql –version 2.1.42、创建MySQL库表创建数据库CREATE DATABASE ken_tutorial;创建表USE ken_tutorial;DROP TABLE IF EXISTS user;CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(255) DEFAULT NULL, age int(11) DEFAULT NULL, hobby varchar(500) DEFAULT NULL, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8;3、配置链接字符串在项目根目录Ken.Tutorial.Web中创建配置文件appsettings.json,并写入以下配置{ “ConnectionStrings”: { “testdb”: “server=localhost;database=Ken.Tutorial;uid=root;pwd=root;” }}4、实体&DbContext准备创建user表对应实体在项目根目录Ken.Tutorial.Web中创建目录Models,并在其中创建类:UserEntity.csusing System;using System.ComponentModel.DataAnnotations;using System.ComponentModel.DataAnnotations.Schema;namespace Ken.Tutorial.Web.Models{ [Table(“user”)] [Serializable] public class UserEntity { [Key]//主键 [DatabaseGenerated(DatabaseGeneratedOption.Identity)]//自增列 [Column(“id”)] public int Id { get; set; } [Column(“name”)] public string Name { get; set; } [Column(“age”)] public int Age { get; set; } [Column(“hobby”)] public string Hobby { get; set; } }}创建DBContext在项目根目录Ken.Tutorial.Web中创建目录Repositories,并在其中创建类:TutorialDbContext.csusing System;using Microsoft.EntityFrameworkCore;using Microsoft.Extensions.Configuration;namespace Ken.Tutorial.Web.Repositories{ public class TutorialDbContext : DbContext { private IConfiguration Configuration { get; } public TutorialDbContext(IConfiguration configuration) { this.Configuration = configuration; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseMySql(Configuration.GetConnectionString(“testdb”)); } ppublic DbSet<UserEntity> Users { get; set; } }}TutorialDbContext 继承了 DbContext,然后再Context初始化时配置MySQL链接。由于 ASP.NET Core 默认使用了DI组件,所以我们取配置文件,就需要在构造函数中获取 IConfiguration 注入的实例。在Startup.cs配置注入,以便数据访问类可以访问DI方式获取TutorialDbContextpublic void ConfigureServices(IServiceCollection services){ //引入MVC模块 services.AddMvc(); //配置DbContext注入 services.AddTransient<TutorialDbContext>();}三、EF Core + Linq to entity 访问数据库1、创建Linq To Entity 数据访问类using System;using System.Collections.Generic;using System.Linq;using Ken.Tutorial.Web.Models;namespace Ken.Tutorial.Web.Repositories{ public class TutorialRepository { private TutorialDbContext DbContext { get; } public TutorialRepository(TutorialDbContext dbContext) { //在构造函数中注入DbContext this.DbContext = dbContext; } //添加 public int Add(UserEntity user) { using (DbContext) { //由于我们在UserEntity.Id配置了自增列的Attribute,EF执行完成后会自动把自增列的值赋值给user.Id DbContext.Users.Add(user); return DbContext.SaveChanges(); } } //删除 public int Delete(int id) { using (DbContext) { var userFromContext = DbContext.Users.FirstOrDefault(u => u.Id == id); DbContext.Users.Remove(userFromContext); return DbContext.SaveChanges(); } } //更新 public int Update(UserEntity user) { using (DbContext) { var userFromContext = DbContext.Users.FirstOrDefault(u => u.Id == user.Id); userFromContext.Name = user.Name; userFromContext.Age = user.Age; userFromContext.Hobby = user.Hobby; return DbContext.SaveChanges(); } } //查询 public UserEntity QueryById(int id) { using (DbContext) { return DbContext.Users.FirstOrDefault(u => u.Id == id); } } //查询集合 public List<UserEntity> QueryByAge(int age) { using (DbContext) { return DbContext.Users.Where(u => u.Age == age).ToList(); } } //查看指定列 public List<string> QueryNameByAge(int age) { using (DbContext) { return DbContext.Users.Where(u => u.Age == age).Select(u => u.Name).ToList(); } } //分页查询 public List<UserEntity> QueryUserPaging(int pageSize, int page) { using (DbContext) { return DbContext.Users.Skip(pageSize * (page - 1)).Take(pageSize).ToList(); } } //事务:将年龄<0的用户修改年龄为0 public int FixAge() { using (DbContext) { using (var transaction = DbContext.Database.BeginTransaction()) { try { var userListFromContext = DbContext.Users.Where(u => u.Age < 0); foreach (UserEntity u in userListFromContext) { u.Age = 0; } var count = DbContext.SaveChanges(); transaction.Commit(); return count; } catch { transaction.Rollback(); return 0; } } } } }}2、创建测试API在Controllers文件夹下创建EfCoreController.cs作为访问入口using System;using Ken.Tutorial.Web.Models;using Ken.Tutorial.Web.Repositories;using Microsoft.AspNetCore.Mvc;namespace Ken.Tutorial.Web.Controllers{ public class EfCoreController : Controller { public TutorialRepository Repository { get; } public EfCoreController(TutorialRepository repository) { this.Repository = repository; } public IActionResult Add(UserEntity user) { var message = Repository.Add(user) > 0 ? “success” : “failed”; return Json(new { Message = message, User = user }); } public IActionResult Delete(int id) { var message = Repository.Delete(id) > 0 ? “success” : “failed”; return Json(new { Message = message }); } public IActionResult Update(UserEntity user) { var message = Repository.Update(user) > 0 ? “success” : “failed”; return Json(new { Message = message, User = user }); } public IActionResult QueryById(int id) { var user = Repository.QueryById(id); return Json(new { User = user }); } public IActionResult QueryByAge(int age) { var users = Repository.QueryByAge(age); return Json(new { Users = users }); } public IActionResult QueryNameByAge(int age) { var users = Repository.QueryNameByAge(age); return Json(new { Users = users }); } public IActionResult QueryUserPaging(int pageSize, int page) { var users = Repository.QueryUserPaging(pageSize, page); return Json(new { Users = users }); } public IActionResult FixAge() { var count = Repository.FixAge(); return Json(new { FixCount = count }); } }}3、启动测试在Startup.cs中配置Repository注入public void ConfigureServices(IServiceCollection services){ //其他代码省略 //配置Repository注入 services.AddTransient<TutorialRepository>();}启动项目并测试API示例添加用户/efcore/add?name=ken&age=18&hobby=coding删除用户/efcore/delete?id=0更新用户/efcore/update?id=1&name=ken&age=666&hobby=codingOrGaming查询单个用户/efcore/querybyid?id=0查询多个用户/efcore/querybyage?age=18查询多个用户名/efcore/querynamebyage?age=18分页查询用户/efcore/queryuserpaging?pagesize=3&page=1修复异常年龄/efcore/fixage四、EF Core + 原生SQL 访问数据库1、创建EF Core + 原生SQL 数据访问类using System;using System.Collections.Generic;using System.Data;using System.Data.SqlClient;using System.Linq;using Ken.Tutorial.Web.Models;using Microsoft.EntityFrameworkCore;using MySql.Data.MySqlClient;namespace Ken.Tutorial.Web.Repositories{ public class TutorialWithSqlRepository { private TutorialDbContext DbContext { get; } public TutorialWithSqlRepository(TutorialDbContext dbContext) { //在构造函数中注入DbContext this.DbContext = dbContext; } //添加 public int Add(UserEntity user) { using (var connection = DbContext.Database.GetDbConnection()) { connection.Open(); var command = connection.CreateCommand() as MySqlCommand; command.CommandText = “INSERT INTO user (name,age,hobby) VALUES(@name,@age,@hobby)”; command.Parameters.Add(new MySqlParameter() { ParameterName = “@name”, DbType = DbType.String, Value = user.Name }); command.Parameters.Add(new MySqlParameter() { ParameterName = “@age”, DbType = DbType.Int32, Value = user.Age }); command.Parameters.Add(new MySqlParameter() { ParameterName = “@hobby”, DbType = DbType.String, Value = user.Hobby }); var count = command.ExecuteNonQuery(); //获取插入时产生的自增列Id并赋值给user.Id使用 user.Id = (int)command.LastInsertedId; return count; } } //删除 public int Delete(int id) { using (DbContext) { return DbContext.Database.ExecuteSqlCommand( “DELETE FROM user WHERE id={0}”, id); } } //更新 public int Update(UserEntity user) { using (DbContext) { return DbContext.Database.ExecuteSqlCommand( “UPDATE user SET name={0}, age={1}, hobby={2} WHERE id={3}”, user.Name, user.Age, user.Hobby, user.Id); } } //查询 public UserEntity QueryById(int id) { using (DbContext) { return DbContext.Users.FromSql(“SELECT id,name,age,hobby FROM user WHERE id={0}”, id).FirstOrDefault(); } } //查询集合 public List<UserEntity> QueryByAge(int age) { using (DbContext) { return DbContext.Users.FromSql(“SELECT id,name,age,hobby FROM user WHERE age={0}”, age).ToList(); } } //查看指定列 public List<string> QueryNameByAge(int age) { using (DbContext) { return DbContext.Users.FromSql(“SELECT id,name FROM user WHERE age={0}”, age).Select(u => u.Name).ToList(); } } //分页查询 public List<UserEntity> QueryUserPaging(int pageSize, int page) { using (DbContext) { return DbContext.Users.FromSql(“SELECT id,name,age,hobby FROM user LIMIT {0},{1}”, pageSize * (page - 1), pageSize).ToList(); } } //事务:将年龄<0的用户修改年龄为0 public int FixAge() { using (DbContext) { using (var connection = DbContext.Database.GetDbConnection()) { //打开连接 connection.Open(); //开启事务 using (var transaction = connection.BeginTransaction()) { try { //获取命令对象 var command = connection.CreateCommand(); command.Transaction = transaction; command.CommandText = “UPDATE user SET age=@age WHERE age<@age”; command.Parameters.Add(new MySqlParameter() { ParameterName = “@age”, DbType = DbType.Int32, Value = 0 }); var count = command.ExecuteNonQuery(); transaction.Commit(); return count; } catch (Exception ex) { connection.Close(); transaction.Rollback(); return 0; } } } } } }}2、创建测试API在Controllers文件夹下创建EfCoreWithSqlController.cs作为访问入口using System;using Ken.Tutorial.Web.Models;using Ken.Tutorial.Web.Repositories;using Microsoft.AspNetCore.Mvc;namespace Ken.Tutorial.Web.Controllers{ public class EfCoreWithSqlController : Controller { public TutorialWithSqlRepository Repository { get; } public EfCoreWithSqlController(TutorialWithSqlRepository repository) { this.Repository = repository; } public IActionResult Add(UserEntity user) { var message = Repository.Add(user) > 0 ? “success” : “failed”; return Json(new { Message = message, User = user }); } public IActionResult Delete(int id) { var message = Repository.Delete(id) > 0 ? “success” : “failed”; return Json(new { Message = message }); } public IActionResult Update(UserEntity user) { var message = Repository.Update(user) > 0 ? “success” : “failed”; return Json(new { Message = message, User = user }); } public IActionResult QueryById(int id) { var user = Repository.QueryById(id); return Json(new { User = user }); } public IActionResult QueryByAge(int age) { var users = Repository.QueryByAge(age); return Json(new { Users = users }); } public IActionResult QueryNameByAge(int age) { var users = Repository.QueryNameByAge(age); return Json(new { Users = users }); } public IActionResult QueryUserPaging(int pageSize, int page) { var users = Repository.QueryUserPaging(pageSize, page); return Json(new { Users = users }); } public IActionResult FixAge() { var count = Repository.FixAge(); return Json(new { FixCount = count }); } }}3、启动测试在Startup.cs中配置Repository注入public void ConfigureServices(IServiceCollection services){ //其他代码省略 //配置Repository注入 services.AddTransient<TutorialWithSqlRepository>();}启动项目并测试API示例添加用户/efcorewithsql/add?name=ken&age=18&hobby=coding删除用户/efcorewithsql/delete?id=0更新用户/efcorewithsql/update?id=1&name=ken&age=666&hobby=codingOrGaming查询单个用户/efcorewithsql/querybyid?id=0查询多个用户/efcorewithsql/querybyage?age=18查询多个用户名/efcorewithsql/querynamebyage?age=18分页查询用户/efcorewithsql/queryuserpaging?pagesize=3&page=1修复异常年龄/efcorewithsql/fixage五、备注1、附录本文代码示例https://github.com/ken-io/asp…本文参考https://docs.microsoft.com/zh… https://www.learnentityframew… https://mysql-net.github.io/M…本文首发于我的独立博客:https://ken.io/note/asp.net-c… ...

December 29, 2018 · 6 min · jiezi

ASP.NET Core 入门教程 7、ASP.NET Core MVC 分部视图入门

一、前言1、本教程主要内容ASP.NET Core MVC (Razor)分部视图简介ASP.NET Core MVC (Razor)分部视图基础教程ASP.NET Core MVC (Razor)强类型分部视图教程2、本教程环境信息软件/环境说明操作系统Windows 10SDK2.1.401ASP.NET Core2.1.3IDEVisual Studio Code 1.30浏览器Chrome 70本篇代码以下代码进行调整:https://github.com/ken-io/asp…3、准备工作VS Code 本身不提供 ASP.NET Core MVC 视图引擎(Razor)的智能感知。 幸运的是,VS Code C#扩展 从 1.17.0 版本开始支持Razor视图引擎的智能感知。所以,我们要将VS Code C#扩展升级到最新版本。另外,要特意说明的是,在VS Code 1.30版本,解决方案(Solution)视图的视图入口改到了侧边工具栏二、ASP.NET Core MVC (Razor)分部视图简介1、Razor分部视图概述在Razor视图引擎中,我们可以定义.cshtml文件作为“视图”来渲染需要呈现给用户的内容。对于所有页面共用的部分,我们可以定义母版页(Layout)让视图继承共用的部分。当有些公共的部分我们只在某些页面用到,不需要每个页面都用到。或者这个公共的内容需要作为模板使用多次,母版页就不适合承担这样的作用。这时候我们可以使用分部视图来实现。2、Razor分部视图定义与引用Razor分部视图定义视图与分部视图在定义上并没有本质的不同,均是创建.cshtml文件作为视图使用,只是在渲染的时候作为分部视图来渲染/加载。在之前提到过,通常公共的Razor视图文件名都以_开头并放在/Views/Shared文件夹中,分部视图也不例外。例如:/Views/Shared/_PartialViewTest.cshtml 如果分部视图只在某个控制器返回的视图中引用,也可以创建在该控制器对应的视图目录。例如:/Views/Home/_PartialViewTest.cshtmlRazor分部视图引用//同步引用@Html.Partial("_PartialViewTest")//异步引用(官方推荐)@await Html.PartialAsync("_PartialViewTest")微软官方更推荐使用异步加载的方式,因为同步加载可能会出现程序死锁的情况如果没有使用异步方式,会收到编译器警告:warning MVC1000: Use of IHtmlHelper.Partial may result in application deadlocks. Consider using <partial> Tag Helper or IHtmlHelper.PartialAsync.如果你非常在意性能,也可以使用 Html.RenderPartialAsync 呈现分部视图。 这种方式会直接呈现分部视图的内容,而不会组装成 IHtmlContent 对象放回。@{ await Html.RenderPartialAsync("_PartialViewTest");}由于 Html.RenderPartialAsync并不会返回任何内容,所以需要在Razor语句块中调用Razor分部视图查找顺序同视图相同:Views/[ControllerName]/[PartialViewName].cshtmlViews/Shared/[PartialViewName].cshtml当然,你也可以直接指定完整路径,例如:@await Html.PartialAsync("/Views/Home/_PartialViewTest.cshtml")三、 Razor分部视图基础使用1、定义分部视图在/Views/Shared目录下创建视图 ‘_DateTimeInfo.cshtml’当前时间:@DateTime.Now.ToString(“yyyy-MM-dd HH:mm:ss:fff”) 当前星期:@DateTime.Now.DayOfWeek2、创建视图并引用分部视图在/Views目录下创建目录Partial,并在/Views/Partial 目录下创建文件 Demo.cshtml@{ ViewBag.Title = “PartialView Demo”;}<h3>@ViewBag.Title</h3><p>PartialView Demo by ken.io</p>@Html.Partial("_DateTimeInfo")<hr/>@await Html.PartialAsync("_DateTimeInfo")3、创建控制器在 /Controllers 目录下创建PartialController.cs并创建对应Actionusing System;using Microsoft.AspNetCore.Mvc;namespace Ken.Tutorial.Web.Controllers{ public class PartialController : Controller { public IActionResult Demo() { return View(); } }}4、访问测试启动项目,访问 /partial/demo ,将会看到四、带参数的Razor分部视图1、视图对象准备在项目根目录中创建模型目录Models,并在下面创建对象NoteViewModel.csusing System;namespace Ken.Tutorial.Web.Models{ public class NoteViewModel { public string Title { get; set; } public DateTime PublishTime { get; set; } public string Body { get; set; } }}2、定义分部视图在/Views/Shared目录下创建视图 ‘_NoteInfo.cshtml’@model Ken.Tutorial.Web.Models.NoteViewModel;<h3>@Model.Title</h3><span>@Model.PublishTime.ToString(“yyyy-MM-dd”)</span><p>@Model.Body</p>实际上就是创建强类型分部视图:-D3、创建视图并引用分部视图在/Views/Partial 目录下创建文件 DemoWithParams.cshtml@using Ken.Tutorial.Web.Models;@{ ViewBag.Title = “PartialView With Params Demo”;}<h3>@ViewBag.Title</h3><p>PartialView With Params Demo by ken.io</p>@await Html.PartialAsync("_NoteInfo", new NoteViewModel() { Title = “这是一个分部视图测试笔记”, PublishTime = DateTime.Now, Body = “这是笔记的内容” })4、在控制器中编写对应Action在控制器 PartialController.cs 中增加以下 Action:public IActionResult DemoWithParams(){ return View();}5、访问测试启动项目,访问 /partial/demowithparams ,将会看到如果是文章列表页,用起来会显得更方便。五、备注1、附录本文代码示例https://github.com/ken-io/asp…本文参考https://docs.microsoft.com/zh…本文首发于我的独立博客:https://ken.io/note/asp.net-c… ...

December 21, 2018 · 1 min · jiezi

开源干货!.NET Core + Vue.js通用动态权限(RBAC)管理系统框架[DncZeus]开源

DncZeus前言关于 DncZeusDncZeus = Dnc + Zeus"Dnc"–.Net Core 的缩写;“Zeus”–中文译为宙斯,是古希腊神话中的众神之王,奥林匹斯十二主神之首,统治宇宙万物的至高无上的主神(在古希腊神话中主神专指宙斯),人们常用“众神和人类的父亲”、“神王”来称呼他,是希腊神话诸神中最伟大的神。DncZeus的愿景就是做一个.NET Core 领域的简易精致的通用后台权限管理模板系统基础框架,努力向.NET Core 领域的"宙斯"看齐。项目简介DncZeus是一个基于 ASP.NET Core 2 + Vue.js 的前后端分离的通用后台管理系统框架。后端使用.NET Core 2 + Entity Framework Core 构建,UI 则是目前流行的基于 Vue.js 的 iView。项目实现了前后端的动态权限管理和控制以及基于 JWT 的用户令牌认证机制,让前后端的交互更流畅。DncZeus并不是一个完整的业务系统,但她提供完成业务系统的绝大多数开发场景,让每一位.NET 开发者都能基于DncZeus快速开发出交互、体验以及功能具佳的.NET Core 单页应用程序(SPA)。支持DncZeus(求Start :))如果你觉得DncZeus对你或者他人有用,请为DncZeus点个赞,求扩散,让更多人获得帮助!!!在线体验(Demo)超级管理员:administrator 管理员:admin密码:111111地址:https://dnczeus.codedefault.com由于是个人项目,资金有限,体验服是低配,请大家爱惜,轻戳,不胜感激!!!适合人群由于 DncZeus 考虑到初级.NET 开发者都可以使用,所以后端项目未涉及过多架构和封装(代码逻辑一目了然),但为了你更好地熟悉和运用 DncZeus,你需要了解:ASP.NET CoreVue.jsiViewASP.NET Core 的知识能确保你可以看懂和了解后端是如何实现和工作的,而 Vue.js 框架则是前端实现的基石,当然 iView 这个基于 Vue.js 的 UI 框架也是必须要了解的,因为 DncZeus 正是基于 [iview-admin]1来实现的前端 UI 交互。如果你对这两个方面的知识还不熟悉,建议你可以先学习一些理论再来运用 DncZeus 这个框架。关于 ASP.NET Core 和 Vue.js 的入门请参考:ASP.NET Core 官方文档Vue.js 官方文档环境和工具Node.js(同时安装 npm 前端包管理工具)Visual Studio 2017(15.8.8 或者以上版本)VS Code 或者其他前端开发工具git 管理工具SQL Server CE 或者 SQL Server Express 或者 SQL Server 2014 +技术实现ASP.NET Core 2(.NET Core 2.1.502)ASP.NET WebApi CoreJWT 令牌认证AutoMapperEntity Framework Core 2.0.NET Core 依赖注入Swagger UIVue.js(ES6 语法)iView(基于 Vue.js 的 UI 框架)下载项目使用Git工具下载首先请确保你本地开发环境已安装了git管理工具,然后在需要存放本项目的目录打开git命令行工具Git Bash Here,在命令行中输入如下命令:git clone https://github.com/lampo1024/DncZeus.git以上命令就把DncZeus的远程代码拉取到你的本地开发机上。手动下载如果你不愿意使用git管理工具下载DncZeus的远程代码,你也可以在github托管地址手动下载,打开地址https://github.com/lampo1024/…,找到页面中的按钮"Clone or download",如下图示:在弹出的对话框中点击按钮"Download ZIP"即可开始下载DncZeus的源代码,如下图:安装依赖前端项目在将DncZeus的源代码下载到本地之后,如果你使用的git管理工具,可以不用退出当前的git管理工具,输入如下命令:cd DncZeus/DncZeus.App进入到DncZeus的前端项目目录DncZeus.App。在命令行中输入如下命令进行前端依赖包的还原操作:npm install或者npm i后端项目在Visual Studio中打开解决方案[DncZeus.sln]。首先根据自己的开发环境(SQL Server数据库类型,本示例默认是SQL Server Localdb)修改配置文件appsettings.json中的数据库连接字符串,示例默认连接字符串为:“ConnectionStrings”: { “DefaultConnection”: “Server=(localdb)\mssqllocaldb;Database=DncZeus;Trusted_Connection=True;MultipleActiveResultSets=true” }再打开包管理控制台(Package Manager Console),执行如下命令生成数据库表结构:Update-Database -verbose最后,打开项目根目录中的脚本文件夹[Scripts],执行脚本文件[Init_data.sql]以初始化系统数据。恭喜你,到这里所有的准备工作就完成了。赶紧体验DncZeus框架吧!!!运行使用Visual Studio开发工具打开DncZeus根目录中的VS解决方案文件DncZeus.sln,设置DncZeus.Api项目为默认启动项并运行此项目。这时在浏览器中打开地址:http://localhost:54321/swagger ,便可以查看到DncZeus已经实现的后端API接口服务了。在命令行中进入到DncZeus的前端项目目录[DncZeus.App],运行如下命令以启动前端项目服务:npm run dev成功运行后会自动在浏览器中打开地址: http://localhost:9000使用和授权DncZeus项目是一个开源项目,你可以直接基于本项目进行扩展或者二次开发,也可以修改其中的代码。但请保留原文件中的版权信息,尊重本人的劳动成果,违者必究,谢谢合作。问题与反馈遇到问题怎么办?直接提交issueQQ群:483350228码友网 ...

December 21, 2018 · 1 min · jiezi