ฉันจะส่งคืน NotFound () IHttpActionResult พร้อมข้อความแสดงข้อผิดพลาดหรือข้อยกเว้นได้อย่างไร


99

ฉันกำลังส่งคืน NotFound IHttpActionResultเมื่อไม่พบบางสิ่งในการดำเนินการ WebApi GET ของฉัน พร้อมกับการตอบกลับนี้ฉันต้องการส่งข้อความที่กำหนดเองและ / หรือข้อความยกเว้น (ถ้ามี) ปัจจุบันApiController's NotFound()วิธีการไม่ให้เกินที่จะผ่านข้อความ

มีวิธีใดบ้างในการทำเช่นนี้? หรือฉันจะต้องเขียนเองIHttpActionResult?


คุณต้องการส่งคืนข้อความเดียวกันสำหรับผลลัพธ์ที่ไม่พบทั้งหมดหรือไม่
Nikolai Samteladze

@NikolaiSamteladze ไม่อาจเป็นข้อความที่แตกต่างกันขึ้นอยู่กับสถานการณ์
Ajay Jadhav

คำตอบ:


84

คุณต้องเขียนผลการดำเนินการของคุณเองหากคุณต้องการปรับแต่งรูปร่างข้อความตอบกลับ

เราต้องการจัดเตรียมรูปร่างข้อความตอบกลับที่พบบ่อยที่สุดสำหรับสิ่งต่างๆเช่น 404 ว่างเปล่าธรรมดา แต่เราต้องการให้ผลลัพธ์เหล่านี้เรียบง่ายที่สุด ข้อดีหลักอย่างหนึ่งของการใช้ผลการดำเนินการคือทำให้วิธีการดำเนินการของคุณง่ายขึ้นมากในการทดสอบหน่วย ยิ่งเราใส่คุณสมบัติมากขึ้นในผลลัพธ์ของการดำเนินการก็ยิ่งต้องพิจารณาสิ่งที่ต้องพิจารณามากขึ้นเพื่อให้แน่ใจว่าวิธีการดำเนินการนั้นเป็นไปตามที่คุณคาดหวัง

ฉันมักต้องการความสามารถในการจัดเตรียมข้อความที่กำหนดเองเช่นกันดังนั้นอย่าลังเลที่จะบันทึกข้อบกพร่องเพื่อให้เราพิจารณาสนับสนุนผลการดำเนินการนั้นในการเปิดตัวในอนาคต: https://aspnetwebstack.codeplex.com/workitem/list/advanced

สิ่งที่ดีอย่างหนึ่งเกี่ยวกับผลลัพธ์ของการกระทำคือคุณสามารถเขียนของคุณเองได้ค่อนข้างง่ายหากคุณต้องการทำสิ่งที่แตกต่างออกไปเล็กน้อย ต่อไปนี้เป็นวิธีที่คุณสามารถทำได้ในกรณีของคุณ (สมมติว่าคุณต้องการข้อความแสดงข้อผิดพลาดเป็นข้อความ / ธรรมดาหากคุณต้องการ JSON คุณจะต้องทำสิ่งที่แตกต่างออกไปเล็กน้อยกับเนื้อหา):

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

จากนั้นในวิธีการดำเนินการของคุณคุณสามารถทำสิ่งนี้:

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

หากคุณใช้คลาสพื้นฐานของคอนโทรลเลอร์ที่กำหนดเอง (แทนที่จะสืบทอดโดยตรงจาก ApiController) คุณสามารถกำจัด "this" ได้ ส่วนหนึ่ง (ซึ่งน่าเสียดายที่จำเป็นเมื่อเรียกวิธีการขยาย):

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' ที่คล้ายกันทุกประการ แต่ไม่เฉพาะเจาะจงสำหรับผลลัพธ์ 'NotFound' สิ่งนี้อาจใช้ได้กับ 'HttpStatusCodes' ทั้งหมด โค้ด CustomActionResult ของฉันมีลักษณะดังนี้ และการกระทำ 'Get ()' ของตัวควบคุมของฉันจะมีลักษณะเช่นนี้: 'IHttpActionResult Get () สาธารณะ {return CustomNotFoundResult ("Meessage to Return"); } 'นอกจากนี้ฉันยังบันทึกข้อบกพร่องบน CodePlex เพื่อพิจารณาสิ่งนี้ในรุ่นอนาคต
Ajay Jadhav

ฉันใช้ ODataControllers และฉันต้องใช้สิ่งนี้ NotFound ("blah");
Jerther

1
โพสต์ที่ดีมาก แต่ฉันแค่อยากจะแนะนำเคล็ดลับการสืบทอด ทีมของฉันตัดสินใจที่จะทำอย่างนั้นเมื่อนานมาแล้วและมันทำให้ชั้นเรียนล้นมากโดยการทำ ฉันเพิ่งปรับโครงสร้างใหม่ทั้งหมดให้เป็นวิธีการขยายและย้ายออกจากห่วงโซ่การสืบทอด ฉันขอแนะนำให้ผู้คนพิจารณาอย่างรอบคอบว่าเมื่อใดควรใช้มรดกเช่นนี้ โดยปกติการจัดองค์ประกอบภาพจะดีกว่ามากเนื่องจากมีการแยกส่วนออกมามาก
julealgon

6
ฟังก์ชั่นนี้ควรจะไม่อยู่ในกรอบ การรวมพารามิเตอร์ "ResponseBody" ที่เป็นทางเลือกไม่ควรส่งผลต่อการทดสอบหน่วย
Theodore Zographos

235

นี่คือหนึ่งซับสำหรับส่งคืน IHttpActionResult NotFound ด้วยข้อความง่ายๆ:

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

24
ผู้คนควรโหวตคำตอบนี้ มันดีและง่าย!
เจส

2
โปรดทราบว่าโซลูชันนี้ไม่ได้ตั้งค่าสถานะส่วนหัว HTTP เป็น "404 Not Found"
Kasper Halvas Jensen

4
@KasperHalvasJensen รหัสสถานะ http จากเซิร์ฟเวอร์คือ 404 คุณต้องการอะไรเพิ่มเติมหรือไม่?
Anthony F

4
@AnthonyF คุณพูดถูก ฉันใช้ Controller.Content (... ) Shoud ใช้ ApiController.Content (... ) - ไม่ดี
Kasper Halvas Jensen

ขอบคุณเพื่อนนี่คือสิ่งที่ฉันกำลังมองหา
Kaptein Babbalas

28

คุณสามารถใช้ได้ResponseMessageResultถ้าคุณต้องการ:

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

ใช่ถ้าคุณต้องการเวอร์ชันที่สั้นกว่านี้ฉันเดาว่าคุณต้องใช้ผลการดำเนินการที่กำหนดเองของคุณ


ฉันใช้วิธีนี้เพราะมันดูเรียบร้อย ฉันเพิ่งกำหนดข้อความที่กำหนดเองที่อื่นและเยื้องรหัสส่งคืน
ozzy432836

ฉันชอบสิ่งนี้ดีกว่าเนื้อหาเพราะมันส่งคืนอ็อบเจ็กต์ที่ฉันสามารถแยกวิเคราะห์ด้วยคุณสมบัติข้อความได้เหมือนกับวิธี BadRequest มาตรฐาน
user1568891

7

คุณสามารถใช้คุณสมบัติ ReasonPhrase ของคลาส HttpResponseMessage

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

ขอบคุณ. ก็น่าจะได้ผล แต่ฉันจะต้องสร้าง HttpResponseException ด้วยตัวเองในทุกการกระทำ เพื่อให้โค้ดน้อยลงฉันกำลังคิดว่าจะใช้คุณสมบัติ WebApi 2 ได้หรือไม่ (เช่นเดียวกับNotFount สำเร็จรูป () , Ok ()สำเร็จรูป) และส่งข้อความ ReasonPhrase ไปให้
Ajay Jadhav

คุณสามารถสร้างวิธีการขยาย NotFound ของคุณเอง (ข้อยกเว้นข้อยกเว้น) ซึ่งจะส่ง HttpResponseException ที่ถูกต้อง
Dmytro Rudenko

@DmytroRudenko: ผลการดำเนินการถูกนำมาใช้เพื่อปรับปรุงความสามารถในการทดสอบ การโยน HttpResponseException ที่นี่คุณจะยอมแพ้ นอกจากนี้เรายังไม่มีข้อยกเว้นใด ๆ แต่ OP กำลังมองหาการส่งข้อความกลับ
Kiran Challa

โอเคถ้าคุณไม่ต้องการใช้ NUint สำหรับการทดสอบคุณสามารถเขียนการใช้งาน NotFoundResult ของคุณเองและเขียน ExecuteAsync ใหม่เพื่อส่งคืนข้อมูลข้อความของคุณ และส่งคืนอินสแตนซ์ของคลาสนี้อันเป็นผลมาจากการเรียกใช้การดำเนินการของคุณ
Dmytro Rudenko

1
โปรดทราบว่าตอนนี้คุณสามารถส่งรหัสสถานะได้โดยตรงเช่น HttpResponseException (HttpStatusCode.NotFound)
Mark Sowul

3

คุณสามารถสร้างผลลัพธ์เนื้อหาที่ต่อรองแบบกำหนดเองได้ตามที่ d3m3t3er แนะนำ อย่างไรก็ตามฉันจะได้รับมรดกจาก นอกจากนี้หากคุณต้องการเพียงแค่ส่งคืน NotFound คุณไม่จำเป็นต้องเริ่มต้นสถานะ http จากตัวสร้าง

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

ฉันแก้ไขได้โดยเพียงแค่หาOkNegotiatedContentResultและแทนที่รหัส HTTP ในข้อความตอบกลับที่เป็นผลลัพธ์ คลาสนี้อนุญาตให้คุณส่งคืนเนื้อหาด้วยรหัสตอบกลับ HTTP

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

ฉันต้องการสร้างIHttpActionResultอินสแตนซ์ในเนื้อหาของIExceptionHandlerคลาสเพื่อตั้งค่าExceptionHandlerContext.Resultคุณสมบัติ อย่างไรก็ตามฉันต้องการตั้งค่าแบบกำหนดเองด้วยReasonPhraseด้วย

ฉันพบว่าResponseMessageResultสามารถห่อไฟล์HttpResponseMessage (ซึ่งทำให้สามารถตั้งค่า ReasonPhrase ได้อย่างง่ายดาย)

ตัวอย่างเช่น:

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>ตามที่กล่าวไว้และคุณไม่จำเป็นต้องแปลงร่างของคุณcontent(เช่นคุณแค่ต้องการส่งคืนสตริง) คุณก็ไม่จำเป็นต้องแทนที่ExecuteAsyncเมธอด

สิ่งที่คุณต้องทำคือระบุคำจำกัดความประเภทที่เหมาะสมและตัวสร้างที่บอกฐานที่จะส่งคืนรหัสสถานะ HTTP อย่างอื่นก็ใช้งานได้

นี่คือตัวอย่างสำหรับทั้งสองNotFoundและ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) { }
}

จากนั้นคุณสามารถสร้างวิธีการขยายที่สอดคล้องกันสำหรับApiController(หรือทำในคลาสพื้นฐานหากคุณมี):

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

จากนั้นก็ทำงานเหมือนกับวิธีการในตัว คุณสามารถเรียกสิ่งที่มีอยู่NotFound()หรือคุณสามารถเรียกแบบกำหนดเองใหม่NotFound(myErrorMessage)ก็ได้

และแน่นอนคุณสามารถกำจัดประเภทสตริง "ฮาร์ดโค้ด" ในคำจำกัดความประเภทที่กำหนดเองและปล่อยให้เป็นแบบทั่วไปได้หากคุณต้องการ แต่คุณอาจต้องกังวลเกี่ยวกับExecuteAsyncสิ่งต่างๆขึ้นอยู่กับสิ่งที่คุณ<T>เป็นจริง

คุณสามารถดูมากกว่ารหัสที่มาสำหรับการNegotiatedContentResult<T>ที่จะเห็นทั้งหมดมันไม่ มีไม่มากนัก


0

Iknow PO ถามด้วยข้อความ แต่อีกทางเลือกหนึ่งในการส่งคืน 404 คือทำให้เมธอดส่งคืน IHttpActionResult และใช้ฟังก์ชัน StatusCode

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

0

คำตอบที่นี่ไม่มีปัญหาเรื่องนักพัฒนาเล็กน้อย ApiControllerระดับยังคงเปิดเผยNotFound()วิธีการที่นักพัฒนาอาจจะใช้ สิ่งนี้จะทำให้การตอบสนอง 404 บางส่วนมีเนื้อหาของผลลัพธ์ที่ไม่สามารถควบคุมได้

ฉันนำเสนอโค้ดบางส่วนที่นี่ " วิธี ApiController NotFound ที่ดีกว่า " ซึ่งจะให้ที่มีข้อผิดพลาดน้อยกว่าซึ่งไม่ต้องการให้นักพัฒนารู้ว่า "วิธีที่ดีกว่าในการส่ง 404"

  • สร้างคลาสที่สืบทอดจากการApiControllerเรียกApiController
    • ฉันใช้เทคนิคนี้เพื่อป้องกันไม่ให้นักพัฒนาใช้คลาสเดิม
  • แทนที่NotFoundวิธีการของมันเพื่อให้ devs ใช้ api แรกที่มี
  • หากคุณต้องการกีดกันสิ่งนี้ให้ทำเครื่องหมายว่าเป็น [Obsolete("Use overload instead")]
  • เพิ่มพิเศษ protected NotFoundResult NotFound(string message)ที่คุณต้องการให้กำลังใจ
  • ปัญหา: ผลลัพธ์ไม่สนับสนุนการตอบสนองด้วยร่างกาย วิธีการแก้ปัญหา: NegotiatedContentResultมรดกและการใช้งาน ดูเอกสารแนบระดับ NotFoundResult ดีกว่า
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.