ASP.NET Core возвращает JSON с кодом состояния


153

Я ищу правильный способ вернуть JSON с кодом состояния HTTP в моем контроллере .NET Core Web API. Я использую, чтобы использовать это так:

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

Это было в приложении 4.6 MVC, но теперь с .NET Core у меня, кажется, этого IHttpActionResultнет, ActionResultи я использую вот так:

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

Но ответ от сервера странный, как на изображении ниже:

введите описание изображения здесь

Я просто хочу, чтобы контроллер Web API возвращал JSON с кодом состояния HTTP, как я это делал в Web API 2.


1
Методы ok возвращают 200 в качестве кода состояния. Предопределенные методы охватывают все общие случаи. Чтобы вернуть 201 (+ заголовок с новым расположением ресурса), вы используете CreatedAtRouteметод и т. Д.
Tseng

Ответы:


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: Форматирование данных ответов.


Extra Stuff

Проблема, с которой я сталкиваюсь довольно часто, заключается в том, что я хотел более детальный контроль над моим 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-TypeиAccept

Вы должны убедиться, что ваш Content-Typeи Acceptзаголовки в вашем запросе установлены правильно. В вашем случае (JSON) вы захотите настроить его на application/json.

Если вы хотите, чтобы ваш WebAPI отвечал как JSON по умолчанию, независимо от того, что указывает заголовок запроса, вы можете сделать это несколькими способами .

Способ 1 Как показано в статье, которую я рекомендовал ранее ( форматирование данных ответа ), вы можете принудительно установить определенный формат на уровне контроллера / действия. Мне лично этот подход не нравится ... но вот для полноты картины:

Форсирование определенного формата Если вы хотите ограничить форматы ответов для конкретного действия, которое вы можете, вы можете применить фильтр [Производит]. Фильтр [Produces] определяет форматы ответов для определенного действия (или контроллера). Как и большинство фильтров, это может быть применено к действию, контроллеру или глобальной области видимости.

[Produces("application/json")]
public class AuthorsController

[Produces]Фильтр заставит все действия в пределах AuthorsControllerвернуть JSON-форматированные ответы, даже если другие форматтеры были сконфигурированы для приложения , и клиент предоставил Acceptзаголовок запрашивающий другой, доступный формат.

Способ 2 Мой предпочтительный метод заключается в том, чтобы WebAPI отвечал на все запросы в запрошенном формате. Тем не менее, в случае, если он не принимает запрошенный формат, откат к стандартному (т.е. JSON)

Во-первых, вам нужно зарегистрировать это в ваших настройках (нам нужно переработать поведение по умолчанию, как отмечалось ранее)

options.RespectBrowserAcceptHeader = true; // false by default

Наконец, просто переупорядочив список форматеров, которые были определены в компоновщике сервисов, веб-хост по умолчанию будет использовать тот форматтер, который вы размещаете в верхней части списка (то есть позицию 0).

Дополнительную информацию можно найти в этой записи блога .NET Web Development and Tools.


Большое спасибо за усилия, которые вы приложили. Ваш ответ вдохновил меня на реализацию IActionResultс return Ok(new {response = "123"});Приветствиями!
Росско

1
@Rossco Нет проблем. Надеюсь, остальная часть кода поможет вам в развитии вашего проекта.
Свек

1
Чтобы расширить эту тему, я создал дополнительное и более полное руководство по реализации WebAPI здесь: stackoverflow.com/q/42365275/3645638
Svek

При установке: RespectBrowserAcceptHeader = true; Вы не объясняете, почему вы это делаете, и это обычно не нужно и неправильно. Браузеры запрашивают html, и, следовательно, они никоим образом не должны влиять на выбор форматера (что, к сожалению, делает Chrome, запрашивая XML). Короче говоря, это то, что я бы не стал использовать, и указанный вами запасной вариант уже является поведением по умолчанию
Ишай

@YishaiGalatzer Основная тема этой части моего ответа заключалась в том, чтобы осветить, как освободить промежуточное ПО по умолчанию между клиентом и логикой API. По моему мнению,RespectBrowserAcceptHeader это критично при реализации использования альтернативного сериализатора или чаще, когда вы хотите убедиться, что ваши клиенты не отправляют искаженные запросы. Поэтому я подчеркнул «Если ваш проект требует полного контроля, и вы хотите строго определить свой сервис», и обратите внимание на выделенную цитату блока выше этого утверждения.
Свек

57

У вас есть предопределенные методы для наиболее распространенных кодов состояния.

  • Ok(result)возвращается 200с ответом
  • CreatedAtRouteвозвращается 201+ новый ресурс URL
  • NotFound возвращается 404
  • BadRequestвозвращается 400и т. д.

Смотрите BaseController.csи Controller.csсписок всех методов.

Но если вы действительно настаиваете на том, что вы можете использовать StatusCodeдля установки пользовательского кода, но вам это не следует делать, поскольку это делает код менее читабельным, и вам придется повторять код для установки заголовков (например, для CreatedAtRoute).

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

1
это дало мне понимание моего ответа ниже. Спасибо
Oge Nwike

Этот код не является правильным для ASP.NET Core 2.2. Я просто попытался его , и он упорядочивает в созданный методом. Он не включает строку «123» напрямую. JSONActionResultJson()
Амедина

1
@amedina: Мой плохой, просто удали Json(...)и передай строку StatusCode
Ценг

Когда вы говорите «ОК (результат)» - что является результатом? Это строка формата JSON или объект C # (который автоматически преобразуется в строку JSON?)?
переменная

@variable: всегда POCO / класс / объект. Если вы хотите вернуть строку, вам нужно использовать вместо
нее

43

В 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по умолчанию.

Если необходимо отправить с определенным кодом состояния , используйте ObjectResultили StatusCodeвместо. И делает то же самое, и поддерживает согласование контента.

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
Это хороший ответ, потому что он фокусируется на вопросе и кратко объясняет некоторые практические аспекты.
сети

33

Самый простой способ, который я придумал, это:

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

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

2
Я думаю, что это лучше, чем ответ @tseng, потому что его решение включает дублированные поля для кодов состояния и т. Д.
Christian Sauer

2
Одним из улучшений, которое вы можете сделать, является использование StatusCodes, определенных в Microsoft.AspNetCore.Http, например: return new JsonResult (new {}) {StatusCode = StatusCodes.Status404NotFound};
Брайан Бедард

2
Это должен быть принятый ответ. Хотя существуют способы универсальной настройки json, иногда нам приходится работать с устаревшими конечными точками, и настройки могут отличаться. До тех пор, пока мы не прекратим поддерживать некоторые устаревшие конечные точки, это самый
лучший

Я думаю, что 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 result = new Item» ... Item - известный тип во время выполнения)). Смотрите мой ответ (на этот вопрос), когда тип не известен. (У меня был JSON в БД .. и тип JSON не был известен во время выполнения). Спасибо, Джеральд.
ГранадаКодер

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 с помощью enum

     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 !!!"));
    }

Enum - отличная идея!
Бхимбим

2

Потрясающие ответы, которые я нашел здесь, и я также попробовал это заявление о возвращении, 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 и предоставляет множество конструкторов для настройки содержимого и кода состояния. Тогда все мои действия контроллера используют один из конструкторов в качестве подходящего. Вы можете взглянуть на мою реализацию по адресу: 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), которую вы заменяете dto своим объектом, и вы должны быть в порядке.


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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.