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
Методи "нормально" повертають 200 як код статусу. Заздалегідь визначені методи охоплюють усі поширені випадки. Щоб повернути 201 (+ заголовок з новим розташуванням ресурсу), ви використовуєте CreatedAtRouteметод тощо
Tseng

Відповіді:


191

Найбільш основна версія, що відповідає "a", 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 Documentation: Форматування даних відповідей


Додаткові речі

Проблема, з якою я стикаюся досить часто, полягає в тому, що мені хотілося більш детального контролю над моїм 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)
}

Ви помітите, що я також включив спосіб додавання власних власних форматів вводу / виводу, якщо ви, можливо, захочете відповісти на інший формат серіалізації (протобуф, ощадливість тощо).

Частка коду вище, здебільшого, є дублікатом AddMvc()методу. Однак ми реалізовуємо кожну службу "за замовчуванням" самостійно, визначаючи кожну службу, замість того, щоб відправлятись із попередньо відправленою з шаблоном. Я додав посилання на сховище до блоку коду, або ви можете перевірити його AddMvc() з репозиторію GitHub. .

Зауважте, що є кілька посібників, які намагатимуться вирішити це шляхом "скасування" за замовчуванням, а не просто в першу чергу не реалізуючи це ... Якщо ви враховуєте, що ми зараз працюємо з Open Source, це зайва робота , поганий код і відверто давня звичка, яка незабаром зникне.


Крок 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] задає формати відповідей для конкретної дії (або контролера). Як і більшість фільтрів, це може бути застосовано до дії, контролера чи глобальної області.

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

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

Шлях 2 Мій кращий метод полягає в тому, щоб WebAPI відповідав на всі запити у запитаному форматі. Однак у випадку, якщо він не приймає запитуваний формат, тоді повертаємось до типового (наприклад, JSON)

По-перше, вам потрібно буде зареєструвати це у своїх параметрах (нам потрібно переробити поведінку за замовчуванням, як зазначалося раніше)

options.RespectBrowserAcceptHeader = true; // false by default

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

Додаткову інформацію можна знайти в цій статті .NET Blog Blog and Tools


Thanx стільки за зусилля , які ви поклали в. Ваш відповідь надихнув мене здійснити IActionResultз return Ok(new {response = "123"});Ура!
Роско

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

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

У налаштуваннях: RespectBrowserAcceptHeader = true; Ви не пояснюєте, чому це робите, і це, як правило, непотрібно і неправильно. Браузери запитують html, а значить, вони жодним чином не повинні впливати на вибір форматера (це, на жаль, відбувається через запрошення XML). Коротше кажучи, я б
утримався

@YishaiGalatzer Основною темою моєї відповіді було висвітлити, як розвантажити середнє програмне забезпечення за замовчуванням між клієнтом та логікою API. На мою думку, RespectBrowserAcceptHeaderце вирішальне значення при впровадженні використання альтернативного серіалізатора чи частіше, коли ви хочете переконатися, що ваші клієнти не надсилають деформації запитів. Отже, я підкреслив, "Якщо ваш проект вимагає повного контролю та ви хочете чітко визначити свою послугу", і зазначив, що підкреслена блокова цитата є також і вище цього твердження.
Svek

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()
amedina

1
@amedina: Моє погано, просто видаліть Json(...)і передайте рядок на StatusCode
Tseng

Коли ви говорите «Добре (результат)» - що таке результат? Це рядок формату JSON чи це об'єкт C # (який автоматично перетворюється на рядок JSON?)?
змінна

@variable: Завжди POCO / клас / об'єкт. Якщо ви хочете повернути рядок, вам замість цього потрібно скористатись "Вміст"
Tseng

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
Це хороша відповідь, оскільки він зосереджується на питанні та коротко пояснює деякі практичні аспекти.
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, як це: повернути новий JsonResult (новий {}) {StatusCode = StatusCodes.Status404NotFound};
Брайан Бедард

2
Це має бути прийнятою відповіддю. Хоча існують способи універсальної настройки json, іноді доводиться працювати із застарілими кінцевими точками, і налаштування можуть бути різними. Поки ми не можемо перестати підтримувати деякі застарілі кінцеві точки, це остаточний спосіб повного контролю
pqsk

Microsoft.AspNetCore.Mvc.JsonResult - це цілком кваліфіковане ім'я. Ні FQN, ні відповіді на "використання" не викликають мене. :) Асамблея Microsoft.AspNetCore.Mvc.Core, версія = 3.1.0.0, культура = нейтральна, PublicKeyToken = adb9793829ddae60 // C: \ програмні файли \ dotnet \ packs \ Microsoft.AspNetCore.App.Ref \ 3.1.0 \ ref \ netcoreapp3.1 \ Microsoft.AspNetCore.Mvc.Core.dll
granadaCoder

1
Це працювало для мене, коли у мене був сильний тип ("ITem результат = новий елемент" у цьому прикладі ... Елемент - це відомий тип під час виконання). Дивіться мою відповідь (на це запитання), коли тип ~ не ~ відомий. (У мене був json в db..і тип 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 використовуйте 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 !!!"));
    }

Енум - чудова ідея !.
Бхімбім

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.