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


153

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

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

这是一个4.6 MVC应用程序,但现在随着.NET核心我似乎没有这个IHttpActionResult我有ActionResult和使用这样的:

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

但是服务器的响应很奇怪,如下图所示:

在此处输入图片说明

我只希望Web API控制器像在Web API 2中一样返回带有HTTP状态代码的JSON。


1
“ ok”方法返回200作为状态代码。预定义的方法涵盖了所有常见情况。要返回201(带有新资源位置的页眉),请使用CreatedAtRoute方法等
。– Tseng

Answers:


191

响应的最基本的版本JsonResult是:

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

但是,这将无法解决您的问题,因为您无法明确处理自己的响应代码。

控制状态结果的方法是,您需要返回a 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序列化对象作为响应,您应该首先确保已将AddMvc()服务包含在ConfigureServices通常在中找到的方法中Startup.cs

重要的是要注意,AddMvc()它将自动包括JSON的输入/输出格式化程序以及对其他请求类型的响应。

如果您的项目需要完全控制,并且您想严格定义服务,例如您的WebAPI如何处理各种请求类型(包括但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()方法的重复。但是,我们通过定义每个服务来自己实现每个“默认”服务,而不是使用带有模板的预装服务。我已经在代码块中添加了存储库链接,或者您可以AddMvc() 从GitHub存储库中签出

请注意,有一些指南将尝试通过“撤消”默认值来解决此问题,而不是仅仅不首先实现它。如果您考虑到我们现在正在使用开放源代码,那么这是多余的工作,错误的代码以及坦率的旧习惯,这种习惯很快就会消失。


步骤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("application/json")]
public class AuthorsController

[Produces]过滤器将迫使内的所有动作 AuthorsController返回JSON格式的响应,即使其他格式化被配置为应用程序和客户端提供了一个Accept头,要求不同,可用的格式。

方法2 我的首选方法是WebAPI以请求的格式响应所有请求。但是,如果它不接受请求的格式,则回退到默认格式(即JSON)

首先,您需要在选项中进行注册(如前所述,我们需要重做默认行为)

options.RespectBrowserAcceptHeader = true; // false by default

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

有关更多信息,请参见此.NET Web开发和工具博客条目。


非常感谢您付出的努力。您的回答激励着我IActionResultreturn Ok(new {response = "123"});Cheers 上实施!
Rossco '17

1
@Rossco没问题。希望其余的代码将在您开发项目时帮助您。
Svek '17

1
为了扩展此主题,我在此处创建了一个附加的更完整的WebAPI实施指南:stackoverflow.com/q/42365275/3645638
Svek

设置时:RespectBrowserAcceptHeader = true; 您没有在解释为什么要这样做,通常这样做是不必要和错误的。浏览器要求使用html,因此它们无论如何都不应该影响格式化程序的选择(不幸的是,chrome通过要求XML来实现)。简而言之,这是我会避免的事情,您指定的后备选项已经是默认行为
Yishai Galatzer

@YishaiGalatzer我的答案的这一部分的主要主题是强调如何减轻客户端和API逻辑之间的默认中间件负担。我认为,RespectBrowserAcceptHeader在实现使用替代串行器或更常见的情况下(当您要确保客户未发送格式错误的请求时)至关重要。因此,我强调“如果您的项目需要完全控制,并且您想严格定义服务”,并且还要注意该语句上方突出显示的块引用。
Svek

57

您已经为大多数常见状态代码预定义了方法。

  • Ok(result)200响应返回
  • CreatedAtRoute返回201+新资源URL
  • NotFound 退货 404
  • BadRequest退货400

请参阅BaseController.csController.cs有关所有方法的列表。

但是,如果您真的坚持可以使用它StatusCode来设置自定义代码,但实际上不应该这样做,因为它会使代码的可读性降低,并且您必须重复代码来设置标头(例如CreatedAtRoute)。

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

1
这使我对下面的答复有深刻的了解。谢谢
Oge Nwike '19

这段代码不适用于ASP.NET Core 2.2。我已经尝试过了,它序列化到JSONActionResult由创建Json()方法。它不直接包含“ 123”字符串。
amedina '19

1
@amedina:我的坏人,只需删除Json(...)并将字符串传递给 StatusCode
Tseng

当您说“确定(结果)”时-结果是什么?是JSON格式的字符串还是C#对象(会自动转换为JSON字符串?)?
可变

@variable:始终为POCO /类/对象。如果要返回字符串,则需要使用“ Content”(内容)
Tseng

42

使用ASP.NET Core 2.0,从中返回对象Web API(与MVC统一并使用相同的基类Controller)的理想方法是

public IActionResult Get()
{
    return new OkObjectResult(new Item { Id = 123, Name = "Hero" });
}

注意

  1. 它返回一个200 OK状态代码(这是一个Ok类型的ObjectResult
  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 Json(object)
  4. 第二种方法通过将Produces()属性(为ResultFilter)与contentType = application/json

官方文档中阅读有关它们的更多信息。在此处了解过滤器

样本中使用的简单模型类

public class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
}

10
这是一个很好的答案,因为它专注于问题并简要说明了一些实用性。
netfed

33

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

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

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

2
我认为这是比从@tseng的答案,因为他的解决方案包括用于复制状态代码等领域
基督教绍尔

2
您可以做的一项改进是使用Microsoft.AspNetCore.Http中定义的StatusCodes,如下所示:return new JsonResult(new {}){StatusCode = StatusCodes.Status404NotFound};
布莱恩·贝达德

2
这应该是公认的答案。尽管有多种方法可以普遍设置json,但有时我们必须使用旧式端点,并且设置可能有所不同。直到我们不再支持某些旧式端点之前,这是拥有完全控制权的最终方法
pqsk

我认为Microsoft.AspNetCore.Mvc.JsonResult是完全限定名称。没有FQN或“正在使用”的答案让我很疯狂。:)程序集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结果=新项目” ...项目在运行时是已知类型),这对我有用。请参阅我的答案(针对此问题)以了解何时未知类型。(我在数据库中有一个json。并且在运行时不知道json类型)。谢谢杰拉尔德。
granadaCoder

15

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

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

要么

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

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

2

我在这里找到了很棒的答案,我也尝试了这个return语句,看到StatusCode(whatever code you wish)了!

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

1
像这个!好建议!
滴答滴答

0

请参考以下代码,您可以使用不同类型的JSON管理多个状态代码

public async Task<HttpResponseMessage> GetAsync()
{
    try
    {
        using (var entities = new DbEntities())
        {
            var resourceModelList = entities.Resources.Select(r=> new ResourceModel{Build Your Resource Model}).ToList();

            if (resourceModelList.Count == 0)
            {
                return this.Request.CreateResponse<string>(HttpStatusCode.NotFound, "No resources found.");
            }

            return this.Request.CreateResponse<List<ResourceModel>>(HttpStatusCode.OK, resourceModelList, "application/json");
        }
    }
    catch (Exception ex)
    {
        return this.Request.CreateResponse<string>(HttpStatusCode.InternalServerError, "Something went wrong.");
    }
}

9
不,这很糟糕。
菲利普·科普利

0

我在Asp Net Core Api应用程序中要做的是创建一个从ObjectResult扩展的类,并提供许多构造函数来自定义内容和状态代码。然后,我的所有Controller动作都会适当地使用其中一个构造函数。您可以在以下位置查看我的实现:https//github.com/melardev/AspNetCoreApiPaginatedCrud

https://github.com/melardev/ApiAspCoreEcommerce

这是类的外观(转到我的仓库以获取完整代码):

public class StatusCodeAndDtoWrapper : ObjectResult
{



    public StatusCodeAndDtoWrapper(AppResponse dto, int statusCode = 200) : base(dto)
    {
        StatusCode = statusCode;
    }

    private StatusCodeAndDtoWrapper(AppResponse dto, int statusCode, string message) : base(dto)
    {
        StatusCode = statusCode;
        if (dto.FullMessages == null)
            dto.FullMessages = new List<string>(1);
        dto.FullMessages.Add(message);
    }

    private StatusCodeAndDtoWrapper(AppResponse dto, int statusCode, ICollection<string> messages) : base(dto)
    {
        StatusCode = statusCode;
        dto.FullMessages = messages;
    }
}

注意您的对象替换了dto的base(dto),您应该一切顺利。


0

我有这个工作。我的大问题是我的json是一个字符串(在我的数据库中……而不是特定的/已知的Type)。

好的,我终于可以使用它了。

////[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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.