ASP.NET Core Mvc中空返回值的处理方式
原文地址:https://www.strathweb.com/2018/10/convert-null-valued-results-to-404-in-asp-net-core-mvc/
作者: Filip W.
译者: Lamond Lu
.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
ASP.NET Core Mvc中空返回值的处理方式的更多相关文章
- ASP.NET Core MVC中的IActionFilter.OnActionExecuted方法执行时,Controller中Action返回的对象是否已经输出到Http Response中
我们在ASP.NET Core MVC项目中有如下HomeController: using Microsoft.AspNetCore.Mvc; namespace AspNetCoreActionF ...
- ASP.NET Core MVC/WebAPi 模型绑定探索
前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用 ...
- ASP.NET Core 中文文档 第二章 指南(2)用 Visual Studio 和 ASP.NET Core MVC 创建首个 Web API
原文:Building Your First Web API with ASP.NET Core MVC and Visual Studio 作者:Mike Wasson 和 Rick Anderso ...
- ASP.NET Core 中文文档 第四章 MVC(01)ASP.NET Core MVC 概览
原文:Overview of ASP.NET Core MVC 作者:Steve Smith 翻译:张海龙(jiechen) 校对:高嵩 ASP.NET Core MVC 是使用模型-视图-控制器(M ...
- 创建ASP.NET Core MVC应用程序(4)-添加CRUD动作方法和视图
创建ASP.NET Core MVC应用程序(4)-添加CRUD动作方法和视图 创建CRUD动作方法及视图 参照VS自带的基架(Scaffold)系统-MVC Controller with view ...
- 创建ASP.NET Core MVC应用程序(1)-添加Controller和View
创建ASP.NET Core MVC应用程序(1)-添加Controller和View 参考文档:Getting started with ASP.NET Core MVC and Visual St ...
- 【翻译】在Visual Studio中使用Asp.Net Core MVC创建你的第一个Web API应用(一)
HTTP is not just for serving up web pages. It's also a powerful platform for building APIs that expo ...
- asp.net core mvc实现伪静态功能
在大型网站系统中,为了提高系统访问性能,往往会把一些不经常变得内容发布成静态页,比如商城的产品详情页,新闻详情页,这些信息一旦发布后,变化的频率不会很高,如果还采用动态输出的方式进行处理的话,肯定会给 ...
- 基于Asp.Net Core Mvc和EntityFramework Core 的实战入门教程系列-4
来个目录吧: 第一章-入门 第二章- Entity Framework Core Nuget包管理 第三章-创建.修改.删除.查询 第四章-排序.过滤.分页.分组 第五章-迁移,EF Core 的co ...
随机推荐
- 《JAVA程序设计》结对编程联系_四则运算(第一周:阶段总结)
结对对象与其博客链接 20175312陶光远:https://www.cnblogs.com/20175312-tgy/p/10630566.html 需求分析 (一)功能需求 1.自动生成题目(本周 ...
- SQL数据库的操作,表的操作
数据库定义语言(DDL):用于对数据库及数据库中的各种对象进行创建,删除,修改等操作 (1)create:用于创建数据库或数据库对象 (2)alter:用于对数据库或数据库对象进行修改 (3)drop ...
- Python序列化proto中repeated修饰的数据
一.repeated修饰复合数据结构,即message时 1.使用message的add方法初始化新实例 2.分别对新实例中的每个元素赋值:或使用CopyFrom(a)拷贝a中的元素值 message ...
- 《SpringMVC从入门到放肆》十四、SpringMVC分组数据校验
上一篇我们学习了数据校验,但是在实际项目中,还是有些不够灵活,今天我们就来继续学习一种更灵活的数据校验方法——分组数据校验. 一.什么是分组校验 校验规则是定义在实体中的,而同一个实体可以被多个Con ...
- Mac软件安装提示程序已损坏解决方案
---恢复内容开始--- 最近下载好的Mac软件安装时,系统跳出该程序已损坏: 嗯……估计是因为下载了破解版,被系统屏蔽,重设一下安全设置就好: 很兴奋的打开系统偏好设置->安全性与隐私: 然而 ...
- 我的第一次做app的细节
第一次做一个app 发现 需要和前端沟通好而且 还要注意细节 效果图细节不要忘记 尽量多穿数据不要少传数据 而且 对接 注意细节
- windows10下Kafka环境搭建
内容小白,包含JDK+Zookeeper+Kafka三部分.JDK:1) 安装包:Java SE Development Kit 9.0.1 下载地址:http://www.oracle ...
- Vue v-for循环添加表单v-model不重复变动的注意事项!
正常添加,v-model,修改所有循环会得到一样的结果! 定义一个users[]的数组,不要定义全局的user,而是要在Vue里面的方法里定义局部的变量添加.
- Unbuntu 16.04 英文环境安装中文输入法
ubuntu 16.04 使用的是ibus输入系统,没有预装中文输入法,你要自己安装一下.以中文拼音输入法为例:1.sudo apt install ibus-pinyin2.sudo apt ins ...
- 长沙学院APP
一.开发背景 作为一名长大学子,我认为我们学校没有一个自己专属的手机APP是一件遗憾的事情,虽然大部分的211,985高校也没有一个自己专属的APP,所以,要是我们学校能开发一个出来,那逼格肯定就不一 ...