ASP.NET Core Mvc中空返回值的处理方法详解

前言

如果你是一个初学者开始学习 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.strathweb.com/2018/10/convert-null-valued-results-to-404-in-asp-net-core-mvc/

作者: Filip W.

译者: Lamond Lu

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • ASP.NET Core2读写InfluxDB时序数据库的方法教程

    前言 在我们很多应用中会遇到有一种基于一系列时间的数据需要处理,通过时间的顺序可以将这些数据点连成线,再通过数据统计后可以做成多纬度的报表,也可通过机器学习来实现数据的预测告警.而时序数据库就是用于存放管理这种有着时间顺序数据的,时序数据库一般都支持时序数据的快速写入.持久化.多纬度的聚合查询等基本功能. InfluxDB简介 InfluxDB是一个基于时间序列数据而开发的高性能数据存储平台,它可以对时序数据进行高吞吐量的摄取.压缩和实时查询.InfluxDB是用Go语言编写的,它会编译成一个没

  • 浅谈从ASP.NET Core2.2到3.0你可能会遇到这些问题

    趁着假期的时间所以想重新学习下微软的官方文档来巩固下基础知识.我们都知道微软目前已经发布了.NET Core3.0的第三个预览版,同时我家里的电脑也安装了vs2019.So,就用vs2019+.NET Core3.0来跟着做一下Contoso University这个WEB应用,但是在基于3.0进行操作的时候遇到了一些问题,所以我就查看了微软的<从 ASP.NET Core 迁移 2.2 到 3.0 预览版 2>这篇文档,就着今天遇到的问题,所以我整理下,希望对大伙有所帮助,当然大伙也可以直接

  • ASP.NET Core使用自定义验证属性控制访问权限详解

    前言 大家都知道在应用中,有时我们需要对访问的客户端进行有效性验证,只有提供有效凭证(AccessToken)的终端应用能访问我们的受控站点(如WebAPI站点),此时我们可以通过验证属性的方法来解决. 本文将详细介绍ASP.NET Core使用自定义验证属性控制访问权限的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧 方法如下 一.public class Startup的配置: //启用跨域访问(不同端口也是跨域) services.AddCors(options

  • ASP.NET Core Web App应用第三方Bootstrap模板的方法教程

    引言 作为后端开发来说,前端表示玩不转,我们一般会选择套用一些开源的Bootstrap 模板主题来进行前端设计.那如何套用呢?今天就简单创建一个ASP.NET Core Web MVC 模板项目为例,来应用第三方Bootstrap Template--Admin LTE. AdminLte Dashboard 1. 创建ASP.NET Core MVC Demo 命令行执行dotnet new mvc -n ApplyBootstrapTemplate,即可创建预置的MVC模板项目.项目结构如下

  • Asp.NET Core 如何调用WebService的方法

    0.使用背景 因为现在的项目都是基于 .NET Core 的,但是某些需要调用第三方的 WebService 服务,故有了此文章.其基本思路是通过微软提供的 Svcutil 工具生成代理类,然后通过 System.ServiceModel 来调用代理类所提供的对象与方法. 1.配置准备 1.1 新建并编辑项目 首先新建一个新的 .NET Core 控制台程序,通过命令行窗口或者 Powershell 执行以下命令新建一个项目. dotnet new console 然后编辑项目文件 WebSer

  • asp.net core项目中如何使用html文件

    前言 大家应该都知道,在asp.net core 项目中,使用html文件一般通过使用中间件来提供服务: 打开 NuGet程序管理控制台 输入install-package Microsoft.aspnetcore.staticfiles 进行添加 ASP.NET Core static files middleware. Includes middleware for serving static files, directory browsing, and default files. 在S

  • ASP.NET Core DI手动获取注入对象的方法

    依赖注入简单介绍: 依赖注入(Dependency injection , DI)是一种实现对象及其合作者或依赖项之间松散耦合的技术.将类用来执行其操作的这些对象以某种方式提供给该类,而不是直接实例化合作者或使用静态引用. ASP.NET Core DI 一般使用构造函数注入获取对象,比如在ConfigureServices配置注入后,通过下面方式获取: private IValueService _valueService; public ValueController(IValueServi

  • Centos7+Docker+Jenkins+ASP.NET Core 2.0自动化发布与部署的实现

    前言 Docker一直很火热,一直想把原本的Jenkins自动部署工具搬到Docker上面,无奈今年一直忙于各种事情,迟迟未实施这个事情,正好迎来了dotnet core 2.0 的正式发布,升级项目的同时,顺便直接将Jenkins搬到Docker上.为什么要写这篇文章呢?因为找过相关的资料,大多数文章都是基于Ubuntu 安装.net core 又或者 GitLab 进行持续集成 自动部署等等等,并未有人尝试过Centos7.3 上部署 Jenkins 并且 构建 ASP.NET CORE 2

  • 详解ASP.NET Core WebApi 返回统一格式参数

    业务场景: 业务需求要求,需要对 WebApi 接口服务统一返回参数,也就是把实际的结果用一定的格式包裹起来,比如下面格式: { "response":{ "code":200, "msg":"Remote service error", "result":"" } } 具体实现: using Microsoft.AspNetCore.Mvc; using Microsoft.AspNe

  • asp.net core集成MongoDB的完整步骤

    一.前言及MongoDB的介绍 最近在整合自己的框架,顺便把MongoDBD的最简单CRUD重构一下作为组件化集成到asp.net core项目中,当然此篇文章中没有讲解mongodb的集群部署,等有机会分享一下. 首先,我们在MongoDB的官方文档中看到,MongoDb的2.4以上的For .Net的驱动是支持.Net Core 2.0的. 针对MongoDB,我想大家应该不陌生,没有用过也有听过. 1.mongodb是什么? MongoDB是一个基于分布式文件存储的数据库,为web应用提供

随机推荐