加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > asp.Net > 正文

ASP.NET Core Mvc中空返回值的处理方式

发布时间:2020-12-16 09:27:19 所属栏目:asp.Net 来源:网络整理
导读:原文: 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在如何返回操作结果方面非常灵活的。 你可以返回一个实
原文: 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返回值是voidTask时,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个特性类NotFoundActionFilterAttributeNotFoundResultFilterAttribute,代码如下:

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();
        }
    }
}

代码解释

  • 这里使用了ActionFilterAttributeResultFilterAttribute,ActionFilterAttribute中的OnActionExecuted方法会在action执行完后触发,?ResultFilterAttributeOnResultExecuting会在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,这说明我们的代码又生效了。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读