NotFound () IHttpActionResult'u bir hata iletisi veya özel durumla nasıl döndürürüm?


99

Bir NOTFOUND dönen IHttpActionResultbir şey benim WebAPI GET eylem bulunmazsa zaman. Bu yanıtın yanı sıra özel bir mesaj ve / veya istisna mesajı (varsa) göndermek istiyorum. Mevcut ApiControllersitesindeki NotFound()yöntem Mesaj geçmek için bir aşırı sağlamaz.

Bunu yapmanın bir yolu var mı? yoksa kendi geleneğimi IHttpActionResultmi yazmam gerekecek ?


Tüm Bulunamadı sonuçları için aynı mesajı döndürmek ister misiniz?
Nikolai Samteladze

@NikolaiSamteladze Hayır, duruma göre farklı bir mesaj olabilir.
Ajay Jadhav

Yanıtlar:


84

Yanıt mesajı şeklini özelleştirmek istiyorsanız, kendi eylem sonucunuzu yazmanız gerekir.

Basit boş 404'ler gibi şeyler için kutudan çıkan en yaygın yanıt mesajı şekillerini sağlamak istedik, ancak bu sonuçları olabildiğince basit tutmak istedik; Eylem sonuçlarını kullanmanın temel avantajlarından biri, eylem yönteminizin birim testini çok daha kolay hale getirmesidir. Eylem sonuçlarına ne kadar çok özellik koyarsak, eylem yönteminin beklediğiniz şeyi yaptığından emin olmak için birim testinizin o kadar çok şeyi dikkate alması gerekir.

Sık sık özel bir mesaj sağlama yeteneğini de istiyorum, bu nedenle bu eylemin gelecekteki bir sürümle sonuçlanmasını desteklemeyi düşünmemiz için bir hata kaydetmekten çekinmeyin: https://aspnetwebstack.codeplex.com/workitem/list/advanced

Bununla birlikte, eylem sonuçlarıyla ilgili güzel bir şey, biraz farklı bir şey yapmak istiyorsanız, her zaman oldukça kolay bir şekilde kendi yazınızı yazabilmenizdir. Sizin durumunuzda bunu nasıl yapabileceğiniz aşağıda açıklanmıştır (hata mesajını metin / düz olarak istediğinizi varsayarak; JSON istiyorsanız, içerikle biraz farklı bir şey yaparsınız):

public class NotFoundTextPlainActionResult : IHttpActionResult
{
    public NotFoundTextPlainActionResult(string message, HttpRequestMessage request)
    {
        if (message == null)
        {
            throw new ArgumentNullException("message");
        }

        if (request == null)
        {
            throw new ArgumentNullException("request");
        }

        Message = message;
        Request = request;
    }

    public string Message { get; private set; }

    public HttpRequestMessage Request { get; private set; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute());
    }

    public HttpResponseMessage Execute()
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.NotFound);
        response.Content = new StringContent(Message); // Put the message in the response body (text/plain content).
        response.RequestMessage = Request;
        return response;
    }
}

public static class ApiControllerExtensions
{
    public static NotFoundTextPlainActionResult NotFound(this ApiController controller, string message)
    {
        return new NotFoundTextPlainActionResult(message, controller.Request);
    }
}

Ardından, eylem yönteminizde şöyle bir şey yapabilirsiniz:

public class TestController : ApiController
{
    public IHttpActionResult Get()
    {
        return this.NotFound("These are not the droids you're looking for.");
    }
}

Özel bir denetleyici temel sınıfı kullandıysanız (doğrudan ApiController'den miras almak yerine), "bunu" da ortadan kaldırabilirsiniz. bölüm (maalesef bir uzantı yöntemi çağrılırken gereklidir):

public class CustomApiController : ApiController
{
    protected NotFoundTextPlainActionResult NotFound(string message)
    {
        return new NotFoundTextPlainActionResult(message, Request);
    }
}

public class TestController : CustomApiController
{
    public IHttpActionResult Get()
    {
        return NotFound("These are not the droids you're looking for.");
    }
}

1
'IHttpActionResult' için tamamen benzer bir uygulama yazdım, ancak 'NotFound' sonucuna özel değil. Bu muhtemelen Tüm 'HttpStatusCodes' için çalışacaktır. Benim CustomActionResult kodu gibi görünür bu Ve Denetleyici en 'Get ()' böyle eylem görünüyor: 'kamu IHttpActionResult alın () {return CustomNotFoundResult ( "Meessage Dön"); } 'Ayrıca, gelecekteki sürümde bunu dikkate aldığım için CodePlex'e bir hata kaydettim .
Ajay Jadhav

ODataControllers kullanıyorum ve this.NotFound ("blah") kullanmak zorunda kaldım;
Jerther

1
Çok güzel bir gönderi, ancak miras ipucuna karşı tavsiye etmek istiyorum. Ekibim uzun zaman önce tam olarak bunu yapmaya karar verdi ve bunu yaparak dersleri çok şişirdi. Kısa bir süre önce hepsini genişletme yöntemlerine dönüştürdüm ve miras zincirinden uzaklaştım. İnsanların böyle mirası ne zaman kullanmaları gerektiğini dikkatlice düşünmelerini şiddetle tavsiye ederim. Kompozisyon genellikle çok daha iyidir, çünkü çok daha fazla ayrıştırılmıştır.
julealgon

6
Bu işlevin kullanıma hazır olması gerekirdi. İsteğe bağlı bir "ResponseBody" parametresinin dahil edilmesi, birim testlerini etkilememelidir.
Theodore Zographos

235

İşte basit bir mesajla bir IHttpActionResult NotFound döndürmek için tek satırlık bir kılavuz:

return Content(HttpStatusCode.NotFound, "Foo does not exist.");

24
İnsanlar bu cevabı oylamalıdır. Güzel ve kolay!
Jess

2
Bu çözümün HTTP başlık durumunu "404 Bulunamadı" olarak ayarlamadığını unutmayın.
Kasper Halvas Jensen

4
@KasperHalvasJensen Sunucudan gelen http durum kodu 404, daha fazlasına ihtiyacınız var mı?
Anthony F

4
@AnthonyF Haklısın. Controller.Content (...) kullanıyordum. Shoud ApiController.Content'i kullandı. (...) - Benim hatam.
Kasper Halvas Jensen

Teşekkürler dostum, tam olarak aradığım
buydu

28

İsterseniz kullanabilirsiniz ResponseMessageResult:

var myCustomMessage = "your custom message which would be sent as a content-negotiated response"; 
return ResponseMessage(
    Request.CreateResponse(
        HttpStatusCode.NotFound, 
        myCustomMessage
    )
);

evet, çok daha kısa sürümlere ihtiyacınız varsa, o zaman sanırım özel eylem sonucunuzu uygulamanız gerekir.


Güzel göründüğü için bu yöntemi uyguladım. Özel mesajı başka bir yerde tanımladım ve dönüş kodunu girintili yaptım.
ozzy432836

Bunu İçerikten daha çok seviyorum çünkü aslında standart BadRequest yöntemi gibi bir Message özelliği ile ayrıştırabileceğim bir nesne döndürüyor.
user1568891

7

HttpResponseMessage sınıfının ReasonPhrase özelliğini kullanabilirsiniz

catch (Exception exception)
{
  throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
  {
    ReasonPhrase = exception.Message
  });
}

Teşekkürler. Peki .. bu işe yaramalı, ancak o zaman her eylemde HttpResponseException'ı kendim oluşturmam gerekecek. Kodu daha az tutmak için, herhangi bir WebApi 2 özelliğini (hazır NotFount () , Ok () yöntemleri gibi) kullanabilir miyim ve ona ReasonPhrase mesajını iletebilir miyim diye düşünüyordum.
Ajay Jadhav

Doğru HttpResponseException
Dmytro Rudenko

@DmytroRudenko: Test edilebilirliği artırmak için eylem sonuçları tanıtıldı. Buraya HttpResponseException atarak bundan ödün vermiş olursunuz. Ayrıca burada herhangi bir istisnamız yok, ancak OP bir mesaj göndermek istiyor.
Kiran Challa

Tamam, test için NUint kullanmak istemiyorsanız, kendi NotFoundResult uygulamanızı yazabilir ve mesaj verilerinizi döndürmek için ExecuteAsync'i yeniden yazabilirsiniz. Ve eylem çağrınızın bir sonucu olarak bu sınıfın örneğini döndür.
Dmytro Rudenko

1
Artık durum kodunu doğrudan iletebileceğinizi unutmayın, örneğin HttpResponseException (HttpStatusCode.NotFound)
Mark Sowul

3

D3m3t3er'ın önerdiği şekilde özel bir müzakere edilmiş içerik sonucu oluşturabilirsiniz. Ancak ben miras alırdım. Ayrıca, yalnızca NotFound'u döndürmek için ihtiyacınız varsa, yapıcıdan http durumunu başlatmanız gerekmez.

public class NotFoundNegotiatedContentResult<T> : NegotiatedContentResult<T>
{
    public NotFoundNegotiatedContentResult(T content, ApiController controller)
        : base(HttpStatusCode.NotFound, content, controller)
    {
    }

    public override Task<HttpResponseMessage> ExecuteAsync(
        CancellationToken cancellationToken)
    {
        return base.ExecuteAsync(cancellationToken).ContinueWith(
            task => task.Result, cancellationToken);
    }
}

2

OkNegotiatedContentResultOrtaya çıkan yanıt mesajındaki HTTP kodunu basitçe türetip geçersiz kılarak çözdüm. Bu sınıf, içerik gövdesini herhangi bir HTTP yanıt koduyla döndürmenize olanak tanır.

public class CustomNegotiatedContentResult<T> : OkNegotiatedContentResult<T>
{
    public HttpStatusCode HttpStatusCode;

    public CustomNegotiatedContentResult(
        HttpStatusCode httpStatusCode, T content, ApiController controller)
        : base(content, controller)
    {
        HttpStatusCode = httpStatusCode;
    }

    public override Task<HttpResponseMessage> ExecuteAsync(
        CancellationToken cancellationToken)
    {
        return base.ExecuteAsync(cancellationToken).ContinueWith(
            task => { 
                // override OK HTTP status code with our own
                task.Result.StatusCode = HttpStatusCode;
                return task.Result;
            },
            cancellationToken);
    }
}

2

Özelliği ayarlamak için IHttpActionResultbir IExceptionHandlersınıfın gövdesinde bir örnek oluşturmam gerekiyordu ExceptionHandlerContext.Result. Ancak ben de bir gelenek ayarlamak istedim ReasonPhrase.

Bir can ResponseMessageResultsarma olduğunu buldum HttpResponseMessage(ReasonPhrase'in kolayca ayarlanmasına izin verir).

Örneğin:

public class MyExceptionHandler : ExceptionHandler
{
    public override void Handle(ExceptionHandlerContext context)
    {
        var ex = context.Exception as IRecordNotFoundException;
        if (ex != null)
        {
            context.Result = new ResponseMessageResult(new HttpResponseMessage(HttpStatusCode.NotFound) { ReasonPhrase = $"{ex.EntityName} not found" });
        }
    }
}

1

NegotitatedContentResult<T>Belirtildiği gibi tabandan miras alırsanız ve sizin dönüşümü yapmanız gerekmiyorsa content(örneğin sadece bir dizge döndürmek istiyorsanız), o zaman ExecuteAsyncyöntemi geçersiz kılmanıza gerek yoktur .

Tüm yapmanız gereken uygun bir tür tanımı ve hangi HTTP Durum Kodunun döndürüleceğini tabana söyleyen bir kurucu sağlamaktır. Diğer her şey çalışıyor.

İşte hem NotFoundve hem için örnekler InternalServerError:

public class NotFoundNegotiatedContentResult : NegotiatedContentResult<string>
{
    public NotFoundNegotiatedContentResult(string content, ApiController controller)
        : base(HttpStatusCode.NotFound, content, controller) { }
}

public class InternalServerErrorNegotiatedContentResult : NegotiatedContentResult<string>
{
    public InternalServerErrorNegotiatedContentResult(string content, ApiController controller)
        : base(HttpStatusCode.InternalServerError, content, controller) { }
}

Ve sonra ilgili uzatma yöntemlerini oluşturabilirsiniz ApiController(veya varsa, bir temel sınıfta yapabilirsiniz):

public static NotFoundNegotiatedContentResult NotFound(this ApiController controller, string message)
{
    return new NotFoundNegotiatedContentResult(message, controller);
}

public static InternalServerErrorNegotiatedContentResult InternalServerError(this ApiController controller, string message)
{
    return new InternalServerErrorNegotiatedContentResult(message, controller);
}

Ve sonra tıpkı yerleşik yöntemler gibi çalışırlar. Ya var olanı arayabilir ya NotFound()da yeni geleneğinizi arayabilirsiniz NotFound(myErrorMessage).

Ve tabii ki, özel tip tanımlarındaki "sabit kodlanmış" dizgi türlerinden kurtulabilir ve isterseniz onu genel olarak bırakabilirsiniz, ancak daha sonra gerçekte ne olduğuna bağlı olarak şeyler için endişelenmeniz gerekebilir .ExecuteAsync<T>

Tüm yaptıklarını görmek için kaynak koduna bakabilirsiniz NegotiatedContentResult<T>. Fazla bir şey yok.


0

PO'nun bir mesaj metni ile sorulduğunu biliyorum, ancak yalnızca bir 404 döndürmek için başka bir seçenek, yöntemin bir IHttpActionResult döndürmesini sağlamak ve StatusCode işlevini kullanmaktır.

    public async Task<IHttpActionResult> Get([FromUri]string id)
    {
       var item = await _service.GetItem(id);
       if(item == null)
       {
           StatusCode(HttpStatusCode.NotFound);
       }
       return Ok(item);
    }

0

Buradaki yanıtlarda küçük bir geliştirici hikayesi sorunu eksik. ApiControllerSınıf hala tutulmuşsa NotFound()geliştiriciler kullanabileceğini yöntemi. Bu, bazı 404 yanıtlarının kontrolsüz bir sonuç gövdesi içermesine neden olur.

Burada , geliştiricilerin "404 göndermenin daha iyi yolunu" bilmesini gerektirmeyen, daha az hataya açık bir yöntem sağlayacak olan " daha iyi ApiController NotFound yöntemi " kodunun birkaç parçasını sunuyorum.

  • çağrılanlardan devralanApiController bir sınıf yaratApiController
    • Bu tekniği geliştiricilerin orijinal sınıfı kullanmasını önlemek için kullanıyorum
  • NotFoundgeliştiricilerin ilk kullanılabilir API'yi kullanmasına izin vermek için yöntemini geçersiz kılın
  • bunu caydırmak istiyorsanız, bunu olarak işaretleyin [Obsolete("Use overload instead")]
  • protected NotFoundResult NotFound(string message)teşvik etmek istediğiniz bir ekstra ekleyin
  • sorun: sonuç bir vücutla yanıt vermeyi desteklemiyor. çözüm: devral ve kullan NegotiatedContentResult. ekli bkz iyi NotFoundResult sınıfı .
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.