首页 文章 精选 留言 我的

精选列表

搜索[asp.net],共1116篇文章
优秀的个人博客,低调大师

如何在ASP.NET Core中编写高效的控制器

通过遵循最佳实践,可以编写更好的控制器。所谓的“瘦”控制器(指代码更少、职责更少的控制器)更容易阅读和维护。而且,一旦你的控制器很瘦,可能就不需要对它们进行太多测试了。相反,你可以专注于测试业务逻辑和数据访问代码。瘦控制器的另一个优点是,它更容易维护控制器的多个版本。 这篇文章讨论了使控制器变胖的坏习惯,然后探索了使控制器变瘦和易于管理的方法。我列出编写控制器的最佳实践可能并不全面,但我已经讨论了最重要的一些,并在适当的情况下提供了相关的源代码。在接下来的几节中,我们将研究什么是胖控制器,为什么它是一种代码坏味道,瘦控制器是什么,为什么它是有益的,以及如何使控制器瘦、简单、可测试和可管理。 从控制器中删除数据访问代码 在编写控制器时,你应该坚持单一责任原则,这意味着控制器应该有“一个责任”或“有且只有一个原因可以更改”。换句话说,你希望将更改控制器代码的原因减至最少。下面的代码显示了具有数据访问逻辑的典型控制器。 在.NET生态系统中使用特定的技术堆栈会产生一些困惑,因为有很多选择,比如应该使用哪种类型的运行时?在这篇文章中,我们将试图把这些要点都说清楚。 public class AuthorController : Controller{ private AuthorContext dataContext = new AuthorContext(); public ActionResult Index(int authorId) { var authors = dataContext.Authors .OrderByDescending(x=>x.JoiningDate) .Where(x=>x.AuthorId == authorId) .ToList(); return View(authors); }} 在action内部使用数据上下文实例读取数据,违反了单一职责原则,并使你的控制器充斥着不应该出现在那里的代码。在本例中,我们使用一个DataContext(假设我们使用Entity Framework Core)来连接、处理数据库中的数据。 明天如果你决定更改数据访问技术(为了更好的性能或其他原因),你也必须更改你的控制器。例如,如果我想使用Dapper连接到底层数据库该怎么办?更好的方法是使用repository类来封装数据访问逻辑(尽管我不太喜欢repository模式)。让我们用以下代码更新AuthorController。 public class AuthorController : Controller{ private AuthorRepository authorRepository = new AuthorRepository(); public ActionResult Index(int authorId) { var authors = authorRepository.GetAuthor(authorId); return View(authors); }} 控制器现在看起来更瘦了。那么这是编写这个控制器的最佳方法吗?不是。如果你的控制器正在访问数据访问组件,那么它将做太多的事情,因此违反了单一职责原则。控制器不应该有直接访问数据访问组件的数据访问逻辑或代码。下面是AuthorController类的改进版本。 public class AuthorController : Controller{ private AuthorService authorService = new AuthorService(); public ActionResult Index(int authorId) { var authors = authorService.GetAuthor(authorId); return View(authors); }} AuthorService类利用AuthorRepository类执行CRUD操作。 public class AuthorService{ private AuthorRepository authorRepository = new AuthorRepository(); public Author GetAuthor (int authorId) { return authorRepository.GetAuthor(authorId); }} 避免编写样板代码来映射对象 你经常需要映射数据传输对象(DTO)和域对象,反之亦然。请参考下面给出的代码片段,它显示了控制器方法内部的映射逻辑。 public IActionResult GetAuthor(int authorId){ var author = authorService.GetAuthor(authorId); var authorDTO = new AuthorDTO(); authorDTO.AuthorId = author.AuthorId; authorDTO.FirstName = author.FirstName; authorDTO.LastName = author.LastName; authorDTO.JoiningDate = author.JoiningDate;} 你不应该在控制器中编写这样的映射逻辑,因为它会使控制器膨胀并增加额外的责任。如果你要编写映射逻辑,可以利用像AutoMapper这样的对象映射器工具来避免编写大量样板代码。 最后,你应该将映射逻辑移到前面创建的服务类中。注意AutoMapper是如何被用来映射两个不兼容的类型Author和AuthorDTO的。 public class AuthorService{ private AuthorRepository authorRepository = new AuthorRepository(); public AuthorDTO GetAuthor (int authorId) { var author = authorRepository.GetAuthor(authorId); return Mapper.Map<AuthorDTO>(author); }} 避免在控制器中编写业务逻辑代码 不应该在控制器中编写业务逻辑或验证逻辑。控制器应该只接受一个请求,然后跳转下一个action,除此之外没有其他的。所有的业务逻辑代码都应该转移到其他类中(比如我们前面创建的AuthorService类)。有几种方法可以在请求管道中设置验证器,而不要在控制器中编写验证逻辑。这会使你的控制器变得不必要的臃肿,并让它负责它不应该做的任务。 更喜欢依赖注入而不是组合 你应该更喜欢在控制器中使用依赖项注入来管理依赖项。依赖注入是控制反转(IoC)原则的一个子集。它用于通过允许从外部注入的依赖项删除内部依赖项。 通过利用依赖注入,你不必关心对象的实例化、初始化等。你可以有一个返回所需类型实例的工厂,然后可以使用构造函数注入来使用该实例。下面的代码片段说明了如何使用构造函数将IAuthorService类型的实例注入到AuthorController。(假设IAuthorService是AuthorService类扩展的接口。) public class AuthorController : Controller{ private IAuthorService authorService = new AuthorService(); public AuthorController(IAuthorService authorService) { this.authorService = authorService; }} 使用action过滤器来消除重复的代码 可以在asp.netcore中使用action过滤器在请求管道中的特定点执行定制代码。例如,你可以使用action过滤器在操作action方法执行之前和之后执行自定义代码。你可以从控制器的action方法中删除验证逻辑,并将其写入action过滤器中,而不是在控制器中编写验证逻辑。下面的代码片段显示了如何实现这一点。 [ValidateModelState][HttpPost]public ActionResult Create(AuthorRequest request){ AuthorService authorService = new AuthorService(); authorService.Save(request); return RedirectToAction("Home");} 你将多个职责分配给了一个控制器,那么也会有多个原因导致控制器更改。因此,这违反了单一责任原则,该原则规定类应该有且只有一个变更的理由。 本文分享自微信公众号 - 码农译站(gh_c0d62fbb4bda)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

ASP.NET Core微服务之基于Consul实现服务治理(3)

一、示例整体架构 此示例会由一个API Gateway, 一个Consul Client以及三个Consul Server组成,有关Consul的Client和Server这两种模式的Agent的背景知识,请移步我之前的文章加以了解:《.NET Core微服务之基于Consul实现服务治理》。其中,Consul的Client和Server节点共同构成一个Data Center,而API Gateway则从Consul中获取到服务的IP和端口号,并返回给服务消费者。这里的API Gateway是基于Ocelot来实现的,它不是这里的重点,也就不过多说明了,不了解的朋友请移步我的另一篇:《.NET Core微服务之基于Ocelot实现API网关服务》。 二、Consul集群搭建 2.1 Consul镜像拉取 docker pull consul:1.4.4 验证:docker images 2.2 Consul Server实例创建 以下我的实践是在一台机器上(CentOS 7)操作的,因此将三个实例分别使用了不同的端口号(区别于默认端口号8500)。实际环境中,建议多台机器部署。 (1)Consul实例1 docker run -d -p 8510:8500 --restart=always -v /XiLife/consul/data/server1:/consul/data -v /XiLife/consul/conf/server1:/consul/config -e CONSUL_BIND_INTERFACE='eth0' --privileged=true --name=consul_server_1 consul:1.4.4 agent -server -bootstrap-expect=3 -ui -node=consul_server_1 -client='0.0.0.0' -data-dir /consul/data -config-dir /consul/config -datacenter=xdp_dc; (2)Consul实例2 为了让Consul实例2加入集群,首先获取一下Consul实例1的IP地址: JOIN_IP="$(docker inspect -f '{{.NetworkSettings.IPAddress}}' consul_server_1)"; docker run -d -p 8520:8500 --restart=always -v /XiLife/consul/data/server2:/consul/data -v /XiLife/consul/conf/server2:/consul/config -e CONSUL_BIND_INTERFACE='eth0' --privileged=true --name=consul_server_2 consul:1.4.4 agent -server -ui -node=consul_server_2 -client='0.0.0.0' -datacenter=xdp_dc -data-dir /consul/data -config-dir /consul/config -join=$JOIN_IP; (3)Consul实例3 docker run -d -p 8530:8500 --restart=always -v /XiLife/consul/data/server3:/consul/data -v /XiLife/consul/conf/server3:/consul/config -e CONSUL_BIND_INTERFACE='eth0' --privileged=true --name=consul_server_3 consul:1.4.4 agent -server -ui -node=consul_server_3 -client='0.0.0.0' -datacenter=xdp_dc -data-dir /consul/data -config-dir /consul/config -join=$JOIN_IP; 验证1:docker exec consul_server_1 consul operator raft list-peers 验证2:http://192.168.16.170:8500/ 2.3 Consul Client实例创建 (1)准备services.json配置文件,向Consul注册两个同样的Product API服务 { "services": [ { "id": "core.product-/192.168.16.170:8000", "name": "core.product", "tags": [ "xdp-/core.product" ], "address": "192.168.16.170", "port": 8000, "checks": [ { "name": "core.product.check", "http": "http://192.168.16.170:8000/api/health", "interval": "10s", "timeout": "5s" } ] }, { "id": "core.product-/192.168.16.170:8001", "name": "core.product", "tags": [ "xdp-/core.product" ], "address": "192.168.16.170", "port": 8001, "checks": [ { "name": "core.product.check", "http": "http://192.168.16.170:8001/api/health", "interval": "10s", "timeout": "5s" } ] } ] } 有关配置文件的细节,请移步另一篇文章:《.NET Core微服务之基于Consul实现服务治理(续)》 (2)Consul Client实例 docker run -d -p 8550:8500 --restart=always -v /XiLife/consul/conf/client1:/consul/config -e CONSUL_BIND_INTERFACE='eth0' --name=consul_client_1 consul:1.4.4 agent -node=consul_client_1 -join=$JOIN_IP -client='0.0.0.0' -datacenter=xdp_dc -config-dir /consul/config (3)验证 2.4 服务检查监控邮件提箱 (1)为Client添加watches.json { "watches": [ { "type": "checks", "handler_type": "http", "state": "critical", "http_handler_config": { "path": "http://192.168.16.170:6030/api/Notifications/consul", "method": "POST", "timeout": "10s", "header": { "Authorization": [ "token" ] } } } ] } *.这里的api接口http://192.168.16.170:6030/api/Notifications/consul是我的一个通知服务接口,下面是实现的代码 /// <summary> /// 发送Consul服务中心健康检查Email /// </summary> [HttpPost("consul")] public async Task SendConsulHealthCheckEmail() { using (var stream = new MemoryStream()) { HttpContext.Request.Body.CopyTo(stream); var ary = stream.ToArray(); var str = Encoding.UTF8.GetString(ary); dynamic notifications = JsonConvert.DeserializeObject(str); if (notifications == null || notifications.Count == 0) { return; } var title = "XDP服务中心健康检查通知"; var emailBody = new StringBuilder($"<span style='font-weight:bold; color:red;'>{title}</span> : <br/>"); foreach (var notification in notifications) { emailBody.AppendLine($"---------------------------------------------------------<br/>"); emailBody.AppendLine($"<span style='font-weight:bold;'>节点</span>:{notification.Node}<br/>"); emailBody.AppendLine($"<span style='font-weight:bold;'>服务ID</span>:{notification.ServiceID}<br/>"); emailBody.AppendLine($"<span style='font-weight:bold;'>服务名称</span>:{notification.ServiceName}<br/>"); emailBody.AppendLine($"<span style='font-weight:bold;'>检查ID</span>:{notification.CheckID}<br/>"); emailBody.AppendLine($"<span style='font-weight:bold;'>检查名称</span>:{notification.Name}<br/>"); emailBody.AppendLine($"<span style='font-weight:bold;'>检查状态</span>:{notification.Status}<br/>"); emailBody.AppendLine($"<span style='font-weight:bold;'>检查输出</span>:{notification.Output}<br/>"); emailBody.AppendLine($"---------------------------------------------------------<br/>"); } var email = new Email() { Username = _configuration["EmailSettings:Username"], Password = _configuration["EmailSettings:Password"], SmtpServerAddress = _configuration["EmailSettings:SmtpServerAddress"], SmtpPort = Convert.ToInt32(_configuration["EmailSettings:SmtpPort"]), Subject = title, Body = emailBody.ToString(), Recipients = _configuration["EmailSettings:Recipients"] }; email.Send(); } } /// <summary> /// 使用同步发送邮件 /// </summary> public void Send() { using (SmtpClient smtpClient = GetSmtpClient) { using (MailMessage mailMessage = GetClient) { if (smtpClient == null || mailMessage == null) return; Subject = Subject; Body = Body; //EnableSsl = false; smtpClient.Send(mailMessage); //异步发送邮件,如果回调方法中参数不为"true"则表示发送失败 } } } (2)验证 三、Ocelot网关配置 3.1 为Ocelot增加Consul支持 (1)增加Nuget包:Ocelot.Provider.Consul Nuget>>Install-PackageOcelot.Provider.Consul (2)修改StartUp.cs,增加Consul支持 s.AddOcelot() .AddConsul(); 更多内容,请移步:Ocelot官方文档-服务发现 3.2 修改Ocelot配置文件增加Consul配置 "GlobalConfiguration": { "BaseUrl": "http://api.xique.com", "ServiceDiscoveryProvider": { "Host": "192.168.16.170", "Port": 8550, "Type": "Consul" } } *.这里指向的是Consul Client实例的地址 此外,Ocelot默认策略是每次请求都去Consul中获取服务地址列表,如果想要提高性能,也可以使用PollConsul的策略,即Ocelot自己维护一份列表,然后定期从Consul中获取刷新,就不再是每次请求都去Consul中拿一趟了。例如下面的配置,它告诉Ocelot每2秒钟去Consul中拿一次。 "Type": "PollConsul", "PollingInterval": 2000 3.3 Service配置 // -- Service { "UseServiceDiscovery": true, "DownstreamPathTemplate": "/api/{url}", "DownstreamScheme": "http", "ServiceName": "core.product", "LoadBalancerOptions": { "Type": "RoundRobin" }, "UpstreamPathTemplate": "/product/{url}", "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ] } 这里配置了在Consul中配置的服务名(ServiceName),以及告诉Ocelot我们使用轮询策略(RoundRobin)做负载均衡。 3.4 验证 第一次访问: 第二次访问: 四、HA示例整体架构 对于实际应用中,我们往往会考虑单点问题,因此会借助一些负载均衡技术来做高可用的架构,这里给出一个建议的HA示例的整体架构: 对于一个API请求,首先会经历一个Load Balancer才会到达API Gateway,这个Load Balancer可以是基于硬件的F5,也可以是基于软件的Nginx或LVS再搭配Keepalived,一般来说大部分团队都会选择Nginx。然后API Gateway通过部署多个,来解决单点问题,也达到负载均衡的效果。而对于API Gateway和Consul Client之间的连接,我们往往也会增加一个Load Balancer来实现服务发现的高可用,这个Load Balancer也一般会基于Nginx/LVS搭配Keepalived,API Gateway只需要访问一个Virtual IP即可。而在Consul Data Center中,Consul Server会选择3或5个,Consul Client也会部署多个,刚刚提到的Virtual IP则会指向多个Consul Client,从而防止了Consul Client的单点问题。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册