首页 文章 精选 留言 我的

精选列表

搜索[插件规范],共10000篇文章
优秀的个人博客,低调大师

.NET 分布式事件总线规范 Jaina v3.1.0 发布

本期亮点 本期带来了很多开发者期待的功能。 亮点一:支持通配符、正则表达式事件消息 // 支持正则表达式匹配 [EventSubscribe("(^1[3456789][0-9]{9}$)|((^[0-9]{3,4}\\-[0-9]{3,8}$)|(^[0-9]{3,8}$)|(^\\([0-9]{3,4}\\)[0-9]{3,8}$)|(^0{0,1}13[0-9]{9}$))")] public async Task RegexHandler(EventHandlerExecutingContext context) { var eventId = context.Source.EventId; await Task.CompletedTask; } 测试: await _eventPublisher.PublishAsync("13800138000"); await _eventPublisher.PublishAsync("13434563233"); 亮点二:支持局部重试策略,全局自定义策略 // 支持多种异常重试配置 [EventSubscribe("test:error", NumRetries = 3)] [EventSubscribe("test:error", NumRetries = 3, RetryTimeout = 1000)] // 重试间隔时间 [EventSubscribe("test:error", NumRetries = 3, ExceptionTypes = new[] { typeof(ArgumentException) })] // 特定类型异常才重试 public async Task ExceptionHandler(EventHandlerExecutingContext context) { var eventId = context.Source.EventId; await Task.CompletedTask; } // 全局自定义策略 public class RetryEventHandlerExecutor : IEventHandlerExecutor { public async Task ExecuteAsync(EventHandlerExecutingContext context, Func<EventHandlerExecutingContext, Task> handler) { // 如果执行失败,每隔 1s 重试,最多三次 await Retry(async () => { await handler(context); }, 3, 1000); } } 亮点三:支持运行时订阅消息或删除消息 public class TestEventBusController : Controller { private readonly IEventPublisher _eventPublisher; private readonly IEventBusFactory _eventBusFactory; public TestEventBusController(IEventPublisher eventPublisher, IEventBusFactory eventBusFactory) { _eventPublisher = eventPublisher; _eventBusFactory = eventBusFactory; } // 运行时动态添加一个订阅器 public async Task AddSubscriber() { await _eventBusFactory.AddSubscriber("xxx", async (c) => { Console.WriteLine("我是动态的"); await Task.CompletedTask; }); } // 运行时动态删除一个订阅器 public async Task RemoveDynamic(string eventId) { await _eventBusFactory.RemoveSubscriber(eventId); } } 亮点四:支持多种丰富注册处理程序方式 // 注册 EventBus 服务 services.AddEventBus(builder => { // 注册 ToDo 事件订阅者 builder.AddSubscriber<ToDoEventSubscriber>(); // 通过类型注册 builder.AddSubscriber(typeof(ToDoEventSubscriber)); // 批量注册事件订阅者 builder.AddSubscribers(ass1, ass2, ....); }); Jaina .NET 事件总线,简化项目、类库、线程、服务等之间的通信,代码更少,质量更好。‎ 源码解析 特性 简化组件之间通信 支持事件监视器 支持动作执行器 支持自定义消息存储组件 支持自定义策略执行 支持单消费、多消费消息 支持消息幂等性处理 高内聚,低耦合,使代码更简单 非常快速,每秒可处理30000 +消息 很小,仅10KB 无第三方依赖 可在Windows/Linux/MacOS守护进程部署 支持分布式、集群 高质量代码和良好单元测试 安装 Package Manager Install-Package Jaina .NET CLI dotnet add package Jaina 快速入门 我们在主页上有不少例子,这是让您入门的第一个: 定义事件订阅者ToDoEventSubscriber: // 实现 IEventSubscriber 接口 public class ToDoEventSubscriber : IEventSubscriber { private readonly ILogger<ToDoEventSubscriber> _logger; public ToDoEventSubscriber(ILogger<ToDoEventSubscriber> logger) { _logger = logger; } [EventSubscribe("ToDo:Create")] // 支持多个 [EventSubscribe(YourEnum.Message)] // 支持枚举 public async Task CreateToDo(EventHandlerExecutingContext context) { var todo = context.Source; _logger.LogInformation("创建一个 ToDo:{Name}", todo.Payload); await Task.CompletedTask; } // 支持枚举类型 [EventSubscribe(YourEnum.Some)] public async Task EnumHandler(EventHandlerExecutingContext context) { var eventEnum = context.Source.EventId.ParseToEnum(); // 将事件 Id 转换成枚举对象 await Task.CompletedTask; } // 支持正则表达式匹配 [EventSubscribe("(^1[3456789][0-9]{9}$)|((^[0-9]{3,4}\\-[0-9]{3,8}$)|(^[0-9]{3,8}$)|(^\\([0-9]{3,4}\\)[0-9]{3,8}$)|(^0{0,1}13[0-9]{9}$))")] public async Task RegexHandler(EventHandlerExecutingContext context) { var eventId = context.Source.EventId; await Task.CompletedTask; } // 支持多种异常重试配置 [EventSubscribe("test:error", NumRetries = 3)] [EventSubscribe("test:error", NumRetries = 3, RetryTimeout = 1000)] // 重试间隔时间 [EventSubscribe("test:error", NumRetries = 3, ExceptionTypes = new[] { typeof(ArgumentException) })] // 特定类型异常才重试 public async Task ExceptionHandler(EventHandlerExecutingContext context) { var eventId = context.Source.EventId; await Task.CompletedTask; } } 创建控制器ToDoController,依赖注入IEventPublisher服务: public class ToDoController : ControllerBase { // 依赖注入事件发布者 IEventPublisher private readonly IEventPublisher _eventPublisher; public ToDoController(IEventPublisher eventPublisher) { _eventPublisher = eventPublisher; } // 发布 ToDo:Create 消息 public async Task CreateDoTo(string name) { await _eventPublisher.PublishAsync(new ChannelEventSource("ToDo:Create", name)); // 简化版本 await _eventPublisher.PublishAsync("ToDo:Create", name); } } 在Startup.cs注册EventBus服务: // 注册 EventBus 服务 services.AddEventBus(builder => { // 注册 ToDo 事件订阅者 builder.AddSubscriber<ToDoEventSubscriber>(); // 通过类型注册 builder.AddSubscriber(typeof(ToDoEventSubscriber)); // 批量注册事件订阅者 builder.AddSubscribers(ass1, ass2, ....); }); 运行项目: info: Jaina.Samples.ToDoEventSubscriber[0] 创建一个 ToDo:Jaina 更多文档 文档 您可以在主页找到 Jaina 文档。 贡献 该存储库的主要目的是继续发展 Jaina 核心,使其更快、更易于使用。Jaina 的开发在Gitee上公开进行,我们感谢社区贡献错误修复和改进。 许可证 Jaina 采用MulanPSL-2.0开源许可证。 Copyright (c) 2020-2021 百小僧, Baiqian Co.,Ltd. Jaina is licensed under Mulan PSL v2. You can use this software according to the terms andconditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: https://gitee.com/dotnetchina/Jaina/blob/master/LICENSE THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUTWARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details.

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

用ASP.NET Core 2.0 建立规范的 REST API -- 预备知识

什么是REST REST 是 Representational State Transfer 的缩写. 它是一种架构的风格, 这种风格基于一套预定义的规则, 这些规则描述了网络资源是如何定义和寻址的. 一个实现了REST这些规则的服务就叫做RESTful的服务. 最早是由Roy Fielding提出的. RPC 风格 /getUsers /getUser?id=1 /createUser /deleteUser?id=4 /updateUser?name=dave 上面这些节点是针对User的CRUD操作. 这种样式风格的web服务更倾向于叫做RPC风格的服务. 在RPC的世界里, 节点仅仅就是可以在远程被触发的函数, 而在REST的世界里, 节点就是实体, 也叫做资源. REST的原则/约束 REST有6大原则/约束, 每一个原则都是对API有正面或负面影响的设计决定. RESTful API 最关心的有这几方面: 性能, 可扩展性, 简洁性, 互操作性, 通讯可见性, 组件便携性和可靠性. 这些方面被封装在REST的6个原则里, 它们是: 1. 客服端-服务端约束: 客户端和服务端是分离的, 它们可以独自的进化. 2. 无状态: 客户端和服务段的通信必须是无状态的, 状态应包含在请求里的. 也就是说请求里要包含服务端需要的所有的信息, 以便服务端可以理解请求并可以创造上下文. 3. 分层系统: 就像其它的软件架构一样, REST也需要分层结构, 但是不允许某层直接访问不相邻的层. 4. 统一接口: 这里分为4点, 他们是: 资源标识符(URI), 资源的操作(也就是方法Method, HTTP动词), 自描述的响应(可以认为是媒体类型Media-Type), 以及状态管理(超媒体作为应用状态的引擎 HATEOAS, Hypermedia as the Engine of Application State). 5. 缓存: 缓存约束派生于无状态约束, 它要求从服务端返回的响应必须明确表明是可缓存的还是不可缓存的. 6. 按需编码: 这允许客户端可以从服务端访问特定的资源而无须知晓如何处理它们. 服务端可以扩展或自定义客户端的功能. 只有满足了这6个原则的系统才可以真正称得上是RESTful的, 其实大部分系统的RESTful API并不是RESTful的, 但这样并不代表这些API就不好, 利弊需要开发人员去衡量. Richardson 成熟度模型 Richardson 成熟度模型代表着你的API是否足够成熟, 分为4个级别, 0代表最差, 3代表最好. 0级, Plain Old XML沼泽: 这里HTTP协议只是被用来进行远程交互, 协议的其余部分都用错了, 都是RPC风格的实现(例如SOAP, 尤其是使用WCF的时候). 例如: POST (查询数据信息) http://host/myapi POST (创建数据) http://host/myapi 1级, 资源: 这级里, 每个资源都映射到一个URI上了, 但是HTTP方法并没有正确的使用, 结果的复杂度不算太高. 例如这两个查询: POST http://host/api/authors POST http://host/api/authors/{id} 2级, 动词: 正确使用了HTTP动词, 状态码也正确的使用了, 同时也去掉了不必要的变种. 例如: GET http://host/api/authors 200 Ok (authors) POST (author representation) http://host/api/authors 201 Created (author) 3级, 超媒体: API支持超媒体作为应用状态的引擎HATEOAS, Hypermedia as the Engine of Application State, 引入了可发现性. 例如: GET http://host/api/authors 200 Ok (返回了authors 和 驱动应用程序的超链接) 介绍ASP.NET Core 略. 但是, 你需要知道以下概念: .NET Core, .NET Standard. 还需要会使用下列工具: .NET Core CLI, Visual Studio 2017/Visual Studio Code/Visual Studio for Mac ASP.NET Core 支持创建Web API, 但并不是直接支持RESTful的 Web API. ASP.NET Core的基本知识 这部分还是需要简单的介绍下, 如果已经会了, 请略过本文其余部分. 创建ASP.NET Core项目 打开VS2017, 选择ASP.NET Core Web Application项目模板, 写好名字, OK. 选择空模板, OK: 项目建立好了, 结果如下: 然后我们看一下项目文件, 右键编辑MyRestful.Api: 这里, SDK属性表示了我们使用的是哪个SDK, 而目标框架是.NET Core 2.0. (提示: 如果需要指向多个目标框架的话可以使用TargetFrameworks元素, 注意多了个s) 看一下Program.cs: Main方法是程序的入口. 而Web的宿主是通过BuildWebHost函数来实例化的, 它调用了WebHost.CreateDefaultBuilder方法, 很明显这是一个建造者模式, 它最终会构建出一个web宿主. 调用WebHost.CreateDefaultBuilder会返回一个IWebHostBuilder, 它允许我们进行一些配置动作. 程序启动 UseStartup方法会注册一个类, 这个类负责配置整个程序的启动过程. 这里默认用的是Startup类. Startup类有两个方法 ConfigureServices (这个可以没有) 和 Configure (这个必须有): 在Configure方法里, 配置应该遵循Add/Use的风格样式, 首先定义需要什么, 然后定义如何使用它. 而在ConfigureServices方法里, 所有程序级的依赖项都可以在这里注册到默认的IoC容器里, 把它们添加到IServiceCollection即可. Configure方法才是真正负责配置HTTP请求管道的方法, 并且运行时也需要它. IApplicationBuilder的扩展方法Run会传递一个RequestDelegate, 其内部功能就是回写Hello World. ASP.NET Core还允许我们按约定为指定环境建立单独的启动配置. 启动类可以通过这个函数定义UseStartup(startupAssemblyName: xxx); 运行时会在这个指定的组件查找叫做Startup, Startup[环境名]的类, 其中[环境名]就是ASPNETCORE_ENVIRONMENT这个环境变量的值. 如果能找到指定环境的类, 那么它将覆盖默认的启动类. 例如 环境变量值如果是Developmen的话, 那么运行时就会尝试寻找Startup和StartupDevelopment类, 该约定在启动类里面的方法名上也有效, 环境特定的启动类里的两个方法分别是 Configure[环境名]和Configure[环境名]Services. 除了之前讲的Run方法外, IApplicationBuilder还有一个Use扩展方法. Use扩展方法接受RequestDelegate作为参数来提供HttpContext, 同时接受也为下一层准备的RequestDelegate参数. 需要注意的是, Run方法和Use方法定义的顺序非常重要, 运行时将会精确的按照创建的顺序来执行. 服务器 ASP.NET Core 服务器的作用是响应客户端发过来的请求, 这些请求会作为HttpContext传递进来. ASP.NET Core 内置两种服务器: Kestrel, 它是跨平台的服务器, 基于Libuv. HTTP.sys, 它是仅限Windows系统的服务器, 基于HTTP.sys内核驱动. 下面就是从客户端发请求到应用程序的流图: 其中Kestrel可以作为一个独立进程自行托管, 也可以在IIS里. 但是还是建议使用IIS或Nginx等作为反向代理服务器. 在构建API或微服务时, 这些服务器可以作为网关使用, 因为它们会限制对外暴露的东西也可以更好的与现有系统集成, 所以它们会提供额外的防御层, 使用反向代理服务器(IIS)之后的流图如下: 让web宿主工作于IIS之后需要使用IWebHostBuilder的UseIISIntegration这个扩展方法. 除了内置的两种服务器, 您还可以使用自定义的服务器, 使用IWebHostBuilder的UserServer扩展方法, 它接受一个实现了IServer接口的实例, 您的自定义服务器需要实现该接口. 这里就不讲了. 中间件 在应用程序请求管道内装配的组件就是中间件, 它们负责处理通过管道的请求和响应. 在HTTP请求管道的上下文里, 中间件可以叫做请求委托, 它们是由Run, Map 和 Use 扩展方法共同组建而成的. 每个中间件可以在它被调用之前和之后执行可选的逻辑, 同时也可以决定该请求是否可以被送到管道的下一个中间件那里. 请求在中间件里的流图如下: 看一下这个例子: 如果我在浏览器地址输入http://localhost:5000/return, 那么结果就是Returned! 如果输入 http://localhost:5000/end, 那么是The End. 如果输入http://localhost:5000/xxx?value=1234, 结果是 the number is 1234 如果输入http://localhost:5000/xxx?value=abcde, 结果是Hello, the value is abcde! 注意: 应用程序管道里的请求委托(中间件)定义的顺序是非常重要的, 请求的时候按定义的顺序执行, 而响应的顺序正好相反. 中间件最好不要像上面一样写在Startup类里, 每个中间件应该放在单独的类里. 我把上例中检查是否为数字的中间件写在一个单独的类里: 这种中间件没有实现特定的接口或者继承特定类, 它更像是Duck Typing (你走起路来像个鸭子, 叫起来像个鸭子, 那么你就是个鸭子). 然后在Startup的Configure方法里调用app.UseMiddleware<NumberMiddleware>()即可: 路由 在ASP.NET Core里,使用路由中间件RouterMiddleware来处理路由. 想要使用路由, 同样也是遵循 Add/Use 这个模式. 首先在ConfigureServices方法里添加(Add): 然后在Configure方法里使用(Use): UseRouter这个扩展方法可以接受IRouter或者Action<IRouterBuilder>作为参数. 例如: 当发送http://localhost:5000/ GET请求的时候, 返回 Default route. 当 GET http://localhost:5000/user/dave的时候, 返回 Hi dave 当 POSThttp://localhost:5000/user/dave的时候, 返回 Hi, posted name is dave 其中{name}, 是名为name的参数. 如果写成"user/{name}/{age:number}", 那么age这个参数的必须可以被解析为数值型. 而"user/{name}/{gender?}", 这里的gender参数可以没有. Controller HTTP请求通过管道最终到达Action并返回的流图如下: 默认情况下Controller放在ASP.NET Core项目的Controllers目录下。 在ASP.NET Core项目里可以通过多种方式来创建Controller,当然最建议的方式还是通过继承AspNetCore.Mvc.Controller这个抽象类来建立Controller。 例如: 上例中类名可以不是以Controller结尾。 还有其它的方式创建Controller,按约定类名以Controller结尾的POCO类也会被认为是Controller,例如: 针对POCO类, 即使名称不是以Controller结尾,仍然可以把它作为Controller,这就需要在类上面添加 [Controller] 这个属性: 如果某个类的名字以Controller结尾, 但是你不想把它当作Controller,那么就应该为该类标注 [NonController] 这个属性: 实际上, 看源码就可以知道 Controller 继承于 ControllerBase: 而ControllerBase上面标注着 [Controller] 属性。 Action 在Controller里面,可以使用public修饰符来定义Action,通常会带有参数,可以返回任何类型,但是大多数情况下应该返回IActionResult。Action的方法名要么是以HTTP的动词开头,要么是使用HTTP动词属性标签,包括:[HttpGet], [HttpPut], [HttpPost], [HttpDelete], [HttpHead], [HttpOptions], [HttpPatch]. 例如: 其中某个方法名如果恰好是以HTTP的动词开头,那么可以通过标注 [NonAction] 属性来表示这个方法不是Action。 通过继承Controller基类的方法来创建Controller还是有很多好处的,因为它提供了很多帮助方法,例如:Ok, NotFound, BadRequest等,它们分别对应HTTP的状态码 200, 404, 400;此外还有Redirect,LocalRedirect,RedirectToRoute,Json,File,Content等方法。 为MVC定义路由有两种方式:使用IRouteBuilder或者使用基于属性标签的路由。针对Rest,最好还是使用基于属性标签的方式。 路由属性标签可以标注在Controller或者Action方法上,例如: Controller类上标注的路由“api/[controller]”,其中[controller] 就代表该类的名字去掉结尾Controller的部分,也就是“api/person”。 在Controller上使用[Route]属性就定义了该Controller下所有Action的路由基地址,每个Action可以包含一个或者多个相对的路由模板(地址),这些路由模板可以在[Http...]中定义。但是如果使用 ~ 这个符号的话,该Action的地址将会是绝对路由地址,也就是覆盖了Controller定义的基路由。 实体绑定 传入的请求会映射到Action方法的参数,可以实原始数据类型也可以是复杂的类型例如Dto(data transfer object)或ViewModel。这个把Http请求绑定到参数的过程叫做实体绑定。 例如: 其中id参数是定义在路由里的,而name参数在路由里没有,但是仍然可以从查询参数中把name参数映射出来。 注意路由参数和查询参数的区别,下面这个URL里val1和val2是查询参数,它们是在url的后边使用?和&分隔: /product?val1=2&val2=10 而针对上面的Action,下面这个URL的路由参数id就是123: /api/first/123 针对下面这个POST Action: 我们可以通过几种方式为其传递类型为Person的参数。 可以使用查询参数:/api/people?id=1&name=Dave 如果POST Json数据: 那么在Action里面得到的参数person的属性值都是null。这是因为这样的原始数据是包含在请求的Body里面,为了解决这个问题,你需要告诉Action从哪里获取参数,针对这个例子就应该使用 [FromBody] 属性标签: 如果提交的是表单数据,那么就应该使用[FromForm]: 其它的出处还有 [FromHeader], [FromRoute], [FromServices]等。 再看一个FromHeader的例子: 如果使用复杂类型Person来获取person参数好像不行,只能使用原始类型的吧? 实体验证 ASP.NET Core内置的实体验证是通过验证属性标签来实现的,大多数情况下这样会很方便。 例如: 其中Display不是验证标签,但是通过它可以自定义属性的显式名称,在其它错误信息里可以使用{0}来引用该名称。 判断实体参数是否符合要求,可以检查ModelState.IsValid属性,这个属性也是由ControllerBase提供的,例如: 发送一个请求: 这是个不合理的参数,返回的是400 BadRequest,带着验证结果: 尽管大多数情况西,验证属性标签都满足要求,但是有时候还是需要进行一些灵活的验证,你可以使用像FluentValidation这样的第三方库,也可以使用内置的方式来实现自定义验证。 ASP.NET Core内置支持两种方式来进行自定义验证:通过继承ValidationAttribute来创建自定义验证属性标签,或者让实体实现IValidatebleObject接口。 使用自定义验证属性标签: 把该标签放到name属性上 使用刚才的请求,其结果是: 另一种方式,在Person类实现IValidatableObject接口 但是我使用这种方法并不好用,不知道我哪里用错了! 过滤器 和中间件一样,ASP.NET Core MVC的过滤器也可以在请求管道的特定阶段的之前或之后执行某些代码。过滤器还可以有子管道,子管道里面包含着其它过滤器。 过滤器和中间件的区别:中间件是应用程序级别的,它可以处理每个发送过来的请求;而过滤器是针对MVC的,它只会处理发往MVC的请求。 ASP.NET Core MVC的过滤器分为5类: 授权过滤器,它是第一个运行的,它的作用就是判断HTTP Context中的用户是否拥有当前请求的权限,如果用户没有权限,那么它就会“短路”管道。 资源过滤器,在授权过滤器后运行,在管道其它动作之前,和管道动作都结束后运行。它可以实现缓存或由于性能原因执行短路操作。它在实体绑定之前运行,所以它也可以对影响实体绑定。 Action过滤器,它在Action方法调用之前和之后立即执行,它可以操作传进Action的参数和返回的结果。 异常过滤器,针对在写入响应Body之前发生的未处理的异常,它可以应用全局的策略, 结果过滤器,它可以在每个Action结果执行之前和之后运行代码,但也只是在Action方法无错误的成功完成后才可以执行。 下图标明了这些过滤器在管道中是如何交互的: 过滤器可以作为属性标签使用,或者也可以在Startup类里面进行全局注册。 例子: using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Filters; namespace MyRestful.Api.Filters { public class DefaultNameFilter: IActionFilter, IAsyncActionFilter { public void OnActionExecuting(ActionExecutingContext context) { context.ActionDescriptor.RouteValues["name"] = "Anonymous"; } public void OnActionExecuted(ActionExecutedContext context) { context.HttpContext.Response.Headers["X-Name"] = context.ActionDescriptor.RouteValues["name"]; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { OnActionExecuting(context); var result = await next(); OnActionExecuted(result); } } } 全局注册,在Startup里: public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => { options.Filters.Add<DefaultNameFilter>(); }); } 或者自定义一个属性标签,内部的代码是一样的: using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Filters; namespace MyRestful.Api.Filters { public class DefaultUserNameFilterAttribute: Attribute, IActionFilter, IAsyncActionFilter { public void OnActionExecuting(ActionExecutingContext context) { context.ActionDescriptor.RouteValues["name"] = "Anonymous"; } public void OnActionExecuted(ActionExecutedContext context) { context.HttpContext.Response.Headers["X-Name"] = context.ActionDescriptor.RouteValues["name"]; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { OnActionExecuting(context); var result = await next(); OnActionExecuted(result); } } } 然后把该标签用在Action方法上即可: [DefaultUserNameFilter] [HttpGet("first/{id}")] public IActionResult FindFirstPerson(int id, string name) { return null; } 格式化响应结果 Action的结果最好使用IActionResult, 但也可以使用其他类型,例如IEnumerable<T>等。强制结果输出为特定的类型可以通过调用特定的方法来实现,例如JsonResponse就是输出JSON,ContentResponse就是输出文本。另外也可以使用[Produces(xxx)] 这个过滤器,它可以应用于全局,controller或者Action。 在REST服务里,有个词叫内容协商,它表示客户端通过Accept Header里的media-type来指定所需的结果格式。 ASP.NET Core MVC 默认实现并使用JSON格式化,但也支持其它格式,这需要在startup里面注册。 客户端浏览器可能在请求的Accept Headers里提供了多种的格式,但是ASP.NET Core MVC 默认是忽略浏览器的Accept Header的,并使用标准的输出格式。但是修改MvcOptions的RespectBrowserAcceptHeader值为true,可以改变这个行为: ASP.NET Core还提供了 XML 格式,可以在MvcOptions里面添加: 今天先写到这,还没有切入正题。下面是我的关于ASP.NET Core Web API相关技术的公众号--草根专栏:

资源下载

更多资源
Mario

Mario

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

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

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

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册