前言
如果你是一个初学者开始学习 ASP.NET 或 ASP.NET MVC, 你可能并不知道什么是. net Framework 和. net ore. 不用担心! 我建议您看下官方文档 https://docs.microsoft.com/zh-cn/aspnet/index , 您可以轻松地看到比较和差异.
.NET Core MVC 在如何返回操作结果方面非常灵活的.
你可以返回一个实现 IActionResult 接口的对象, 比如我们熟知的 ViewResult, FileResult, ContentResult 等.
- [HttpGet]
- public IActionResult SayGood()
- {
- return Content("Good!");
- }
当然你还可以直接返回一个类的实例.
- [HttpGet]
- public string HelloWorld()
- {
- return "Hello World";
- }
在. NET Core 2.1 中, 你还可以返回一个 ActionResult 的泛型对象.
- [HttpGet]
- public ActionResult<IEnumerable<string>> Get()
- {
- return new string[] { "value1", "value2" };
- }
今天的博客中, 我们将一起看一下. NET Core Mvc 是如何返回一个空值对象的, 以及如何改变. NET Core Mvc 针对空值对象结果的默认行为.
下面话不多说了, 来一起看看详细的介绍吧
.NET Core Mvc 针对空值对象的默认处理行为
那么当我们在 Action 中返回 null 时, 结果是什么样的呢?
下面我们新建一个 ASP.NET Core webApi 项目, 并添加一个 BookController, 其代码如下:
- [Route("api/[controller]")]
- [ApiController]
- public class BookController : ControllerBase
- {
- private readonly List<Book> _books = new List<Book> {
- new Book(1, "CLR via C#"),
- new Book(2, ".NET Core Programming")
- };
- [HttpGet("{id}")]
- public IActionResult GetById(int id)
- {
- var item = _books.FirstOrDefault(p => p.BookId == id);
- return Ok(item);
- }
- //[HttpGet("{id}")]
- //public ActionResult<Book> GetById(int id)
- //{
- // var book = _books.FirstOrDefault(p => p.BookId == id);
- // return book;
- //}
- //[HttpGet("{id}")]
- //public Book GetById(int id)
- //{
- // var book = _books.FirstOrDefault(p => p.BookId == id);
- // return book;
- //}
- }
- public class Book
- {
- public Book(int bookId, string bookName)
- {
- BookId = bookId;
- BookName = bookName;
- }
- public int BookId { get; set; }
- public string BookName { get; set; }
- }
在这个 Controller 中, 我们定义了一个图书的集合, 并提供了根据图书 ID 查询图书的三种实现方式.
然后我们启动项目, 并使用 Postman, 并请求 / API/book/3, 结果如下:
你会发现返回的 Status 是 204 NoContent, 而不是我们想象中的 200 OK. 你可修改之前代码的注释, 使用其他 2 种方式, 结果也是一样的.
你可以尝试创建一个普通的 ASP.NET Mvc 项目, 添加相似的代码, 结果如下
返回的结果是 200 OK, 内容是 null
为什么会出现结果呢?
与前辈们 (ASP.NET Mvc, ASP.NET WebApi) 不同, ASP.NET Core Mvc 非常巧妙的处理了 null 值, 在以上的例子中, ASP.NET Core Mvc 会选择一个合适的输出格式化器 (output formatter) 来输出响应内容. 通常这个输出格式化器会是一个 JSON 格式化器或 xml 格式化器.
但是对于 null 值, ASP.NET Core Mvc 使用了一种特殊的格式化器 HttpNoContentOutputFormatter, 它会将 null 值转换成 204 的状态码. 这意味着 null 值不会被序列化成 JSON 或 xml, 这可能不是我们期望的结果, 有时候我们希望返回 200 OK, 响应内容为 null.
Tips: 当 Action 返回值是 void 或 Task 时, ASP.NET Core Mvc 默认也会使用 HttpNoContentOutputFormatter
通过修改配置移除默认的 null 值格式化器
我们可以通过设置 HttpNoContentOutputFormatter 对象的 TreatNullValueAsNoContent 属性为 false, 去除默认的 HttpNoContentOutputFormatter 对 null 值的格式化.
在 Startup.cs 文件的 ConfigureService 方法中, 我们在添加 Mvc 服务的地方, 修改默认的输出格式化器, 代码如下
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddMvc(o =>
- {
- o.OutputFormatters.RemoveType(typeof(HttpNoContentOutputFormatter));
- o.OutputFormatters.Insert(0, new HttpNoContentOutputFormatter
- {
- TreatNullValueAsNoContent = false;
- });
- });
- }
修改之后我们重新运行程序, 并使用 Postman 访问 / API/book/3
结果如下, 返回值 200 OK, 内容为 null, 这说明我们的修改成功了.
使用 404 Not Found 代替 204 No Content
在上面的例子中, 我们禁用了 204 No Content 行为, 响应结果变为了 200 OK, 内容为 null. 但是有时候, 我们期望当找不到任何结果时, 返回 404 Not Found , 那么这时候我们应该修改代码, 进行扩展呢?
在. NET Core Mvc 中我们可以使用自定义过滤器(Custom Filter), 来改变这一行为.
这里我们创建 2 个特性类 NotFoundActionFilterAttribute 和 NotFoundResultFilterAttribute , 代码如下:
- public class NotFoundActionFilterAttribute : ActionFilterAttribute
- {
- public override void OnActionExecuted(ActionExecutedContext context)
- {
- if (context.Result is ObjectResult objectResult && objectResult.Value == null)
- {
- context.Result = new NotFoundResult();
- }
- }
- }
- public class NotFoundResultFilterAttribute : ResultFilterAttribute
- {
- public override void OnResultExecuting(ResultExecutingContext context)
- {
- if (context.Result is ObjectResult objectResult && objectResult.Value == null)
- {
- context.Result = new NotFoundResult();
- }
- }
- }
代码解释
这里使用了 ActionFilterAttribute 和 ResultFilterAttribute,ActionFilterAttribute 中的 OnActionExecuted 方法会在 action 执行完后触发, ResultFilterAttribute 的 OnResultExecuting 会在 action 返回结果前触发.
这 2 个方法都是针对 action 的返回结果进行了替换操作, 如果返回结果的值是 null, 就将其替换成 NotFoundResult
添加完成后, 你可以任选一个类, 将他们添加在
controller 头部
- [Route("api/[controller]")]
- [ApiController]
- [NotFoundResultFilter]
- public class BookController : ControllerBase
- {
- ...
- }
或者 action 头部
- [HttpGet("{id}")]
- [NotFoundResultFilter]
- public IActionResult GetById(int id)
- {
- var item = _books.FirstOrDefault(p => p.BookId == id);
- return Ok(item);
- }
你还可以在添加 Mvc 服务的时候配置他们
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddMvc(o =>
- {
- o.Filters.Add(new NotFoundResultFilterAttribute());
- });
- }
选择一种重新运行项目之后, 效果和通过修改配置移除默认的 null 值格式化器是一样的.
IAlwaysRunResultFilter
以上的几种解决方案看似完美无缺, 但实际上还是存在一点瑕疵. 由于 ASP.NET Core Mvc 中过滤器的短路机制(即在任何一个过滤器中对 Result 赋值都会导致程序跳过管道中剩余的过滤器), 可能现在使用某些第三方组件后, 第三方组件在管道中插入自己的短路过滤器, 从而导致我们的代码失效.
ASP.NET Core Mvc 的过滤器, 可以参见这篇文章
下面我们添加以下的短路过滤器.
- public class ShortCircuitingFilterAttribute : ActionFilterAttribute
- {
- public override void OnActionExecuting(ActionExecutingContext context)
- {
- context.Result = new ObjectResult(null);
- }
- }
然后修改 BookController 中 GetById 的方法
- [HttpGet("{id}")]
- [ShortCircuitingFilter]
- [NotFoundActionFilter]
- public IActionResult GetById(int id)
- {
- var item = _books.FirstOrDefault(p => p.BookId == id);
- return Ok(item);
- }
重新运行程序后, 使用 Postman 访问 / API/book/3, 程序又返回了 204 Not Content, 这说明我们的代码失效了.
这时候, 为了解决这个问题, 我们需要使用. NET Core 2.1 中新引入的接口 IAlwaysRunResultFilter. 实现 IAlwaysRunResultFilter 接口的过滤器总是会执行, 不论前面的过滤器是否触发短路.
这里我们添加一个新的过滤器 NotFoundAlwaysRunFilterAttribute.
- public class NotFoundAlwaysRunFilterAttribute : Attribute, IAlwaysRunResultFilter
- {
- public void OnResultExecuted(ResultExecutedContext context)
- {
- }
- public void OnResultExecuting(ResultExecutingContext context)
- {
- if (context.Result is ObjectResult objectResult && objectResult.Value == null)
- {
- context.Result = new NotFoundResult();
- }
- }
- }
然后我们继续修改 BookController 中的 GetById 方法, 为其添加 NotFoundAlwaysRunFilter 特性
- [HttpGet("{id}")]
- [ShortCircuitingFilter]
- [NotFoundActionFilter]
- [NotFoundAlwaysRunFilter]
- public IActionResult GetById(int id)
- {
- var item = _books.FirstOrDefault(p => p.BookId == id);
- return Ok(item);
- }
重新运行程序后, 使用 Postman 访问 / API/book/3, 程序又成功返回了 404 Not Found, 这说明我们的代码又生效了.
本篇源代码: https://github.com/lamondlu/NullAction (本地下载)
来源: https://www.jb51.net/article/149180.htm