ASP.NET Core 返回带有状态码的 JSON

233

我正在寻找在.NET Core Web API控制器中正确返回带有HTTP状态码的JSON的方法。我曾经这样使用:

public IHttpActionResult GetResourceData()
{
    return this.Content(HttpStatusCode.OK, new { response = "Hello"});
}

这是在一个4.6的MVC应用程序中,但现在使用.NET Core时我似乎没有这个IHttpActionResult,我有ActionResult并且像这样使用:

这是在一个4.6的MVC应用程序中,但现在使用.NET Core时我似乎没有这个IHttpActionResult,我有ActionResult并且像这样使用:

public ActionResult IsAuthenticated()
{
    return Ok(Json("123"));
}

但是服务器的响应很奇怪,就像下面的图片:

在此输入图片描述

我只想让 Web API 控制器像在 Web API 2 中那样返回带有 HTTP 状态码的 JSON。


1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Tseng
12个回答

263

响应最基本版本的JsonResult是:

// GET: api/authors
[HttpGet]
public JsonResult Get()
{
    return Json(_authorRepository.List());
}

然而,这并不能解决您的问题,因为您无法显式地处理自己的响应代码。

要控制状态结果,您需要返回一个ActionResult,而这正是您可以利用StatusCodeResult类型的地方。

例如:

// GET: api/authors/search?namelike=foo
[HttpGet("Search")]
public IActionResult Search(string namelike)
{
    var result = _authorRepository.GetByNameSubstring(namelike);
    if (!result.Any())
    {
        return NotFound(namelike);
    }
    return Ok(result);
}

请注意,上述示例均来自Microsoft文档中提供的一份很好的指南:格式化响应数据


额外内容

我经常遇到的问题是,我希望对我的WebAPI进行更细粒度的控制,而不仅仅使用VS中“新项目”模板中的默认配置。

让我们确保您已经掌握了一些基础知识......

第1步:配置服务

为了让ASP.NET Core WebAPI响应一个JSON序列化对象以及完全控制状态码,您应该首先确保在ConfigureServices方法中包含了AddMvc()服务,通常可以在Startup.cs中找到。

需要注意的是,AddMvc()会自动包含用于JSON的输入/输出格式化程序,并响应其他请求类型。

如果您的项目需要完全控制,并且要严格定义您的服务,例如如何处理各种请求类型(包括application/json),并且不响应其他请求类型(例如标准浏览器请求),则可以使用以下代码手动定义:

public void ConfigureServices(IServiceCollection services)
{
    // Build a customized MVC implementation, without using the default AddMvc(), instead use AddMvcCore().
    // https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs

    services
        .AddMvcCore(options =>
        {
            options.RequireHttpsPermanent = true; // does not affect api requests
            options.RespectBrowserAcceptHeader = true; // false by default
            //options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();

            //remove these two below, but added so you know where to place them...
            options.OutputFormatters.Add(new YourCustomOutputFormatter()); 
            options.InputFormatters.Add(new YourCustomInputFormatter());
        })
        //.AddApiExplorer()
        //.AddAuthorization()
        .AddFormatterMappings()
        //.AddCacheTagHelper()
        //.AddDataAnnotations()
        //.AddCors()
        .AddJsonFormatters(); // JSON, or you can build your own custom one (above)
}

你会注意到,我还提供了一种方法让你添加自定义的输入/输出格式化器,以便你可以响应其他序列化格式(protobuf、thrift等)。

上面那段代码大部分是 AddMvc() 方法的副本。然而,我们通过定义每个服务来实现每个“默认”服务,而不是使用模板中预装的服务。我已经在代码块中添加了存储库链接,或者你可以从GitHub存储库中查看AddMvc()

请注意,有些指南会尝试通过“撤消”默认设置来解决此问题,而不是一开始就不实现它... 如果考虑到现在我们正在使用开源软件,这是重复性劳动、糟糕的代码,并且老习惯很快就会消失。


步骤2:创建控制器

我将向您展示一个非常简单直观的控制器,只是为了让您的问题得到解决。

public class FooController
{
    [HttpPost]
    public async Task<IActionResult> Create([FromBody] Object item)
    {
        if (item == null) return BadRequest();

        var newItem = new Object(); // create the object to return
        if (newItem != null) return Ok(newItem);

        else return NotFound();
    }
}

步骤3:检查你的Content-TypeAccept

确保在请求中设置了正确的Content-TypeAccept头部。对于你的情况(JSON),你需要将其设置为application/json

如果你想让你的 WebAPI 以 JSON 格式作为默认响应,不管请求头部指定的格式是什么,你可以用两种方式实现。

方式1 正如我之前推荐的文章所示(格式化响应数据),你可以在 Controller/Action 级别上强制使用特定格式。个人而言,我不喜欢这种方法... 但出于完整性考虑,这里还是介绍一下:

强制使用特定格式 如果你想限制特定操作的响应格式,可以应用 [Produces] 过滤器。[Produces] 过滤器指定了特定操作(或控制器)的响应格式。像大多数过滤器一样,它可以应用于操作、控制器或全局范围。

[Produces("application/json")]
public class AuthorsController
[Produces] 过滤器将强制 AuthorsController 中的所有操作返回 JSON 格式的响应,即使为应用程序和客户端配置了其他格式化程序且客户端提供了请求不同可用格式的 Accept 标头。第二种方法是WebAPI以所请求的格式响应所有请求。但是,如果它不接受请求的格式,则回退到默认格式(即JSON)。首先,您需要在选项中注册它(我们需要重新设计默认行为,如前所述)。
options.RespectBrowserAcceptHeader = true; // false by default

通过简单地重新排列在服务生成器中定义的格式化程序列表,Web主机将默认使用您在列表顶部(即位置0)放置的格式化程序。

有关更多信息,请参阅.NET Web开发和工具博客文章


1
非常感谢您所付出的努力。您的回答激励我使用 return Ok(new {response = "123"}); 实现 IActionResult。干杯! - Rossco
1
@Rossco 没问题。希望代码的其余部分能够在您的项目开发中为您提供指导。 - Svek
2
为了扩展这个主题,我创建了一个更全面的指南来实现WebAPI,链接在这里:https://dev59.com/ElgQ5IYBdhLWcg3wsGCZ - Svek
在设置中:RespectBrowserAcceptHeader = true; 你没有解释为什么要这样做,通常这是不必要和错误的。浏览器请求HTML,因此它们不应以任何方式影响格式化程序的选择(Chrome通过请求XML而不幸地影响了这一点)。简而言之,这是我会关闭的东西,而你指定的回退已经是默认行为。 - Yishai Galatzer
我认为在Net Core中,你没有内置的Json()方法,而是要使用JsonResult - Mayer Spitz
显示剩余5条评论

78

对于大多数常见的状态码,您都可以使用预定义的方法。

  • Ok(result) 返回带响应的200
  • CreatedAtRoute 返回201和新资源URL
  • NotFound 返回404
  • BadRequest 返回400等等

有关所有方法的列表,请参见BaseController.csController.cs

但是,如果您确实坚持要使用StatusCode设置自定义代码,则不建议这样做,因为它会使代码难以阅读,并且您必须重复代码以设置标头(例如CreatedAtRoute)。

public ActionResult IsAuthenticated()
{
    return StatusCode(200, "123");
}

1
这让我对下面的回应有了更深刻的理解。谢谢。 - Chsiom Nwike
这段代码不适用于ASP.NET Core 2.2。我刚试过它,它将Json()方法创建的ActionResult序列化为JSON,但并没有直接包含字符串"123"。 - amedina
1
@amedina:我的错,只需删除 Json(...) 并将字符串传递给 StatusCode - Tseng
当你说“Ok(result)”时,result是什么?它是一个JSON格式的字符串还是一个C#对象(自动转换为JSON字符串)? - variable
@variable:始终是一个POCO/类/对象。如果您想返回一个字符串,您需要使用“Content”而不是。 - Tseng

72
使用 ASP.NET Core 2.0,从 Web API 返回对象的最佳方式(该 API 与 MVC 统一,并使用相同的基类 Controller)是:
public IActionResult Get()
{
    return new OkObjectResult(new Item { Id = 123, Name = "Hero" });
}

请注意:

  1. 它以 200 OK 状态码返回(它是 ObjectResult 类型的 Ok
  2. 它进行内容协商,即根据请求中的 Accept 头返回响应。如果请求中发送了 Accept: application/xml,则返回 XML 格式。如果没有发送任何东西,则默认返回 JSON 格式。

如果需要指定状态码,请使用 ObjectResultStatusCode。两者都可以实现相同的功能,并支持内容协商。

return new ObjectResult(new Item { Id = 123, Name = "Hero" }) { StatusCode = 200 };
return StatusCode( 200, new Item { Id = 123, Name = "Hero" });

甚至可以使用ObjectResult进行更细粒度的控制:

 Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection myContentTypes = new Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection { System.Net.Mime.MediaTypeNames.Application.Json };
 String hardCodedJson = "{\"Id\":\"123\",\"DateOfRegistration\":\"2012-10-21T00:00:00+05:30\",\"Status\":0}";
 return new ObjectResult(hardCodedJson) { StatusCode = 200, ContentTypes = myContentTypes };

如果您想要特别以JSON的格式返回,有几种方法。
//GET http://example.com/api/test/asjson
[HttpGet("AsJson")]
public JsonResult GetAsJson()
{
    return Json(new Item { Id = 123, Name = "Hero" });
}

//GET http://example.com/api/test/withproduces
[HttpGet("WithProduces")]
[Produces("application/json")]
public Item GetWithProduces()
{
    return new Item { Id = 123, Name = "Hero" };
}

请注意:
1. 两种方法都以不同的方式强制使用 JSON。 2. 两种方法都忽略内容协商。 3. 第一种方法使用特定的序列化程序 Json(object) 强制执行 JSON。 4. 第二种方法使用具有 contentType = application/jsonProduces() 属性(它是一个 ResultFilter)来执行相同操作。
请在官方文档中了解更多信息。在这里学习有关筛选器的知识。
这些示例中使用的是简单的模型类。
public class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
}

10
这是一个很好的回答,因为它聚焦于问题,并简要解释了一些实际情况。 - netfed
如何使用Post方法进行路由,兄弟? - toha
我是指这段代码:[HttpPost("AsJson")],我的兄弟,谢谢。 - toha
1
“硬编码的JSON”示例对我无效。它将字符串解析为JSON,并将带有双引号(“)的字符串与转义的JSON字符一起返回给我。因此,我使用了ContentResult而不是ObjectResult,如下所示: return new ContentResult() { Content = hardCodedJson, StatusCode = (int)HttpStatusCode.OK, ContentType = "application/json" }; - Shane
1
[Produces("application/json")] 可以在 .NET 6 中与 Azure Functions V4 一起使用。 - user160357
1
这是最佳答案,因为它不假设 OP 需要返回 JSON 数据或 需要自定义 HTTP 代码。他提供了所有默认和自定义选项。 - AbeyMarquez

43

我想到的最简单的方法是:

var result = new Item { Id = 123, Name = "Hero" };

return new JsonResult(result)
{
    StatusCode = StatusCodes.Status201Created // Status code here 
};

3
我认为这比@tseng的答案更好,因为他的解决方案包括重复的状态码字段等内容。 - Christian Sauer
2
你可以进行的一项改进是使用 Microsoft.AspNetCore.Http 中定义的 StatusCodes,像这样:return new JsonResult(new { }) { StatusCode = StatusCodes.Status404NotFound }; - Bryan Bedard
3
这应该是被接受的答案。虽然有通用设置JSON的方法,但有时我们必须使用旧的端点并且设置可能不同。在我们能够停止支持某些旧的端点之前,这是完全掌控的最终方式。 - pqsk
1
Microsoft.AspNetCore.Mvc.JsonResult 是我认为的完全限定名称。没有 FQN 或“using”答案会让我疯掉。 :) 程序集 Microsoft.AspNetCore.Mvc.Core,版本=3.1.0.0,文化=中性,PublicKeyToken=adb9793829ddae60 // C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\3.1.0\ref\netcoreapp3.1\Microsoft.AspNetCore.Mvc.Core.dll - granadaCoder
1
当我有一个强类型时(例如在此示例中,“ITem result = new Item”...Item是运行时已知的类型),这对我很有效。请参见我的答案(针对此问题),当类型是未知的时候。(我在数据库中有json,而且在运行时不知道json的类型)。谢谢Gerald。 - granadaCoder

21

这是我最简单的解决方案:

public IActionResult InfoTag()
{
    return Ok(new {name = "Fabio", age = 42, gender = "M"});
}
或者
public IActionResult InfoTag()
{
    return Json(new {name = "Fabio", age = 42, gender = "M"});
}

5

我在这里找到了很棒的答案,我也尝试了这个返回语句,看到StatusCode(任意你想要的代码)它起作用了!!!

return Ok(new {
                    Token = new JwtSecurityTokenHandler().WriteToken(token),
                    Expiration = token.ValidTo,
                    username = user.FullName,
                    StatusCode = StatusCode(200)
                });

1
像这样的!好建议! - ticky

4

使用枚举而不是404/201状态码

     public async Task<IActionResult> Login(string email, string password)
    {
        if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(password))
        { 
            return StatusCode((int)HttpStatusCode.BadRequest, Json("email or password is null")); 
        }

        var user = await _userManager.FindByEmailAsync(email);
        if (user == null)
        {
            return StatusCode((int)HttpStatusCode.BadRequest, Json("Invalid Login and/or password"));

        }
        var passwordSignInResult = await _signInManager.PasswordSignInAsync(user, password, isPersistent: true, lockoutOnFailure: false);
        if (!passwordSignInResult.Succeeded)
        {
            return StatusCode((int)HttpStatusCode.BadRequest, Json("Invalid Login and/or password"));
        }
        return StatusCode((int)HttpStatusCode.OK, Json("Sucess !!!"));
    }

枚举是一个伟大的想法! - Bhimbim

1
在ASP.NET Core Web API中,控制器操作的返回类型 2020年2月3日
阅读时间6分钟 +2
作者:Scott Addie 链接
同步操作
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById(int id)
{
    if (!_repository.TryGetProduct(id, out var product))
    {
        return NotFound();
    }

    return product;
}

异步操作

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    await _repository.AddProductAsync(product);

    return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}

0
我找到的最干净的解决方案是在Startup.cs的ConfigureServices方法中设置以下内容(在我的情况下,我希望剥离TZ信息。我始终希望看到用户看到的日期时间)。
   services.AddControllers()
                .AddNewtonsoftJson(o =>
                {
                    o.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Unspecified;
                });

DateTimeZoneHandling选项是Utc,Unspecified,Local或RoundtripKind。

我仍然希望找到一种方法,以便能够按每个调用的基础请求它。

例如:

  static readonly JsonMediaTypeFormatter _jsonFormatter = new JsonMediaTypeFormatter();
 _jsonFormatter.SerializerSettings = new JsonSerializerSettings()
                {DateTimeZoneHandling = DateTimeZoneHandling.Unspecified};

return Ok("Hello World", _jsonFormatter );

我正在从ASP.NET转换,那里我使用了以下辅助方法

public static ActionResult<T> Ok<T>(T result, HttpContext context)
    {
        var responseMessage = context.GetHttpRequestMessage().CreateResponse(HttpStatusCode.OK, result, _jsonFormatter);
        return new ResponseMessageResult(responseMessage);
    }

0

我解决了这个问题。我的主要问题是我的 JSON 是一个字符串(在我的数据库中...而不是一个特定/已知的类型)。

好的,最终我解决了这个问题。

////[Route("api/[controller]")]
////[ApiController]
////public class MyController: Microsoft.AspNetCore.Mvc.ControllerBase
////{
                    //// public IActionResult MyMethod(string myParam) {

                    string hardCodedJson = "{}";
                    int hardCodedStatusCode = 200;

                    Newtonsoft.Json.Linq.JObject job = Newtonsoft.Json.Linq.JObject.Parse(hardCodedJson);
                    /* "this" comes from your class being a subclass of Microsoft.AspNetCore.Mvc.ControllerBase */
                    Microsoft.AspNetCore.Mvc.ContentResult contRes = this.Content(job.ToString());
                    contRes.StatusCode = hardCodedStatusCode;

                    return contRes;

                    //// } ////end MyMethod
              //// } ////end class

我恰好在使用asp.net core 3.1

#region Assembly Microsoft.AspNetCore.Mvc.Core, Version=3.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
//C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\3.1.0\ref\netcoreapp3.1\Microsoft.AspNetCore.Mvc.Core.dll

我从这里得到了提示 :: https://www.jianshu.com/p/7b3e92c42b61

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接