Làm cách nào để trả về NotFound () IHttpActionResult với thông báo lỗi hoặc ngoại lệ?


98

Tôi đang trả về NotFound IHttpActionResultkhi không tìm thấy thứ gì đó trong hành động GET trên WebApi của tôi. Cùng với phản hồi này, tôi muốn gửi một tin nhắn tùy chỉnh và / hoặc tin nhắn ngoại lệ (nếu có). Các hiện ApiControllercủa NotFound()phương pháp không cung cấp một quá tải để thông qua một tin nhắn.

Có cách nào để làm điều này? hoặc tôi sẽ phải viết tùy chỉnh của riêng tôi IHttpActionResult?


Bạn có muốn trả lại cùng một thông báo cho tất cả các kết quả Không tìm thấy không?
Nikolai Samteladze

@NikolaiSamteladze Không, nó có thể là một tin nhắn khác tùy theo tình huống.
Ajay Jadhav

Câu trả lời:


84

Bạn cần phải viết kết quả hành động của riêng mình nếu bạn muốn tùy chỉnh hình dạng thông báo phản hồi.

Chúng tôi muốn cung cấp các hình dạng thông báo phản hồi phổ biến nhất cho những thứ như 404 trống đơn giản, nhưng chúng tôi cũng muốn giữ các kết quả này càng đơn giản càng tốt; một trong những lợi thế chính của việc sử dụng kết quả hành động là nó làm cho phương pháp hành động của bạn dễ dàng hơn nhiều để kiểm tra đơn vị. Chúng tôi càng đưa nhiều thuộc tính vào kết quả hành động, thì bài kiểm tra đơn vị của bạn càng cần phải xem xét để đảm bảo rằng phương pháp hành động đang thực hiện những gì bạn mong đợi.

Tôi cũng thường muốn có khả năng cung cấp thông báo tùy chỉnh, vì vậy vui lòng ghi lại lỗi để chúng tôi xem xét hỗ trợ kết quả hành động đó trong bản phát hành trong tương lai: https://aspnetwebstack.codeplex.com/workitem/list/advanced

Tuy nhiên, có một điều thú vị về kết quả hành động là bạn luôn có thể viết cho riêng mình một cách khá dễ dàng nếu bạn muốn làm điều gì đó hơi khác một chút. Đây là cách bạn có thể thực hiện trong trường hợp của mình (giả sử bạn muốn thông báo lỗi ở dạng văn bản / thuần túy; nếu bạn muốn JSON, bạn sẽ làm điều gì đó hơi khác với nội dung):

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

Sau đó, trong phương pháp hành động của bạn, bạn chỉ có thể làm một số việc như sau:

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

Nếu bạn đã sử dụng lớp cơ sở của bộ điều khiển tùy chỉnh (thay vì kế thừa trực tiếp từ ApiController), bạn cũng có thể loại bỏ "this". phần (không may là bắt buộc khi gọi một phương thức mở rộng):

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
Tôi đã viết cách triển khai chính xác tương tự của 'IHttpActionResult', nhưng không cụ thể cho kết quả 'NotFound'. Điều này có thể sẽ hoạt động cho Tất cả 'HttpStatusCodes'. Mã CustomActionResult của tôi trông giống như thế này Và hành động 'Get ()' của bộ điều khiển của tôi trông giống như sau: 'public IHttpActionResult Get () {return CustomNotFoundResult ("Meessage to Return."); } 'Ngoài ra, tôi đã ghi lại một lỗi trên CodePlex để xem xét vấn đề này trong bản phát hành trong tương lai.
Ajay Jadhav

Tôi sử dụng ODataControllers và tôi đã phải sử dụng this.NotFound ("blah");
Jerther

1
Bài đăng rất hay, nhưng tôi chỉ muốn khuyên bạn không nên dùng mẹo kế thừa. Nhóm của tôi đã quyết định thực hiện chính xác điều đó từ rất lâu trước đây, và nó đã khiến các lớp học rất nhiều khi làm được điều đó. Tôi vừa mới cấu trúc lại tất cả thành các phương thức mở rộng và chuyển khỏi chuỗi kế thừa. Tôi thực sự khuyên mọi người nên cân nhắc cẩn thận khi nào họ nên sử dụng tài sản thừa kế như thế này. Thông thường, bố cục tốt hơn rất nhiều, bởi vì nó được tách rời nhiều hơn.
julealgon

6
Chức năng này đáng ra phải có sẵn. Bao gồm một tham số "ResponseBody" tùy chọn sẽ không ảnh hưởng đến các bài kiểm tra đơn vị.
Theodore Zographos

230

Đây là một lớp lót để trả về IHttpActionResult NotFound với một thông báo đơn giản:

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

24
Mọi người nên bình chọn câu trả lời này. Thật tốt và dễ dàng!
Jess

2
Lưu ý rằng giải pháp này không đặt trạng thái tiêu đề HTTP thành "404 Không tìm thấy".
Kasper Halvas Jensen

4
@KasperHalvasJensen Mã trạng thái http từ máy chủ là 404, bạn có cần thêm gì không?
Anthony F

4
@AnthonyF Bạn nói đúng. Tôi đang sử dụng Controller.Content (...). Shoud đã sử dụng ApiController.Content (...) - Thật tệ.
Kasper Halvas Jensen

Nhờ giao phối, đây là chính xác những gì tôi đang tìm kiếm
Kaptein Babbalas

28

Bạn có thể sử dụng ResponseMessageResultnếu bạn thích:

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

vâng, nếu bạn cần các phiên bản ngắn hơn nhiều, thì tôi đoán bạn cần triển khai kết quả hành động tùy chỉnh của mình.


Tôi đã đi với phương pháp này vì nó có vẻ gọn gàng. Tôi vừa xác định thông báo tùy chỉnh ở nơi khác và mã trả lại thụt lề.
ozzy432836

Tôi thích điều này hơn Nội dung vì nó thực sự trả về một đối tượng mà tôi có thể phân tích cú pháp với thuộc tính Message giống như phương thức BadRequest tiêu chuẩn.
user1568891

7

Bạn có thể sử dụng thuộc tính ReasonPhrase của lớp HttpResponseMessage

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

Cảm ơn. Chà .. điều này sẽ hoạt động, nhưng sau đó tôi sẽ phải tự xây dựng HttpResponseException trong mọi hành động. Để giữ cho mã ít hơn, tôi đã suy nghĩ nếu tôi có thể sử dụng bất kỳ tính năng WebApi 2 nào (giống như các phương thức NotFount () , Ok () được tạo sẵn ) và chuyển thông báo ReasonPhrase vào đó.
Ajay Jadhav

Bạn có thể tạo phương pháp của riêng bạn mở rộng notfound (Exception ngoại lệ), mà sẽ ném HttpResponseException đúng
Dmytro Rudenko

@DmytroRudenko: kết quả hành động đã được giới thiệu để cải thiện khả năng kiểm tra. Bằng cách ném HttpResponseException vào đây, bạn sẽ thỏa hiệp với điều đó. Ngoài ra ở đây chúng tôi không có bất kỳ ngoại lệ nào, nhưng OP đang tìm cách gửi lại một tin nhắn.
Kiran Challa

Được rồi, nếu bạn không muốn sử dụng NUint để thử nghiệm, bạn có thể viết phần triển khai NotFoundResult của riêng mình và viết lại ExecuteAsync của nó để trả về dữ liệu thư của bạn. Và trả về thể hiện của lớp này là kết quả của hành động của bạn.
Dmytro Rudenko

1
Lưu ý rằng bây giờ bạn có thể vượt qua các mã trạng thái trực tiếp, ví dụ như HttpResponseException (HttpStatusCode.NotFound)
Đánh dấu Sowul

3

Bạn có thể tạo kết quả nội dung thương lượng tùy chỉnh như d3m3t3er đề xuất. Tuy nhiên tôi sẽ thừa kế từ. Ngoài ra, nếu bạn chỉ cần nó để trả về NotFound, bạn không cần phải khởi tạo trạng thái http từ hàm tạo.

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

Tôi đã giải quyết nó bằng cách đơn giản lấy từ OkNegotiatedContentResultvà ghi đè mã HTTP trong thông báo phản hồi kết quả. Lớp này cho phép bạn trả về nội dung với bất kỳ mã phản hồi HTTP nào.

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

1

Nếu bạn kế thừa từ cơ sở NegotitatedContentResult<T>, như đã đề cập và bạn không cần phải chuyển đổi content(ví dụ: bạn chỉ muốn trả về một chuỗi), thì bạn không cần ghi đè ExecuteAsyncphương thức.

Tất cả những gì bạn cần làm là cung cấp một định nghĩa kiểu thích hợp và một hàm tạo cho cơ sở biết Mã trạng thái HTTP nào sẽ trả về. Mọi thứ khác chỉ hoạt động.

Dưới đây là các ví dụ cho cả hai NotFoundInternalServerError:

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

Và sau đó, bạn có thể tạo các phương thức mở rộng tương ứng cho ApiController(hoặc làm điều đó trong một lớp cơ sở nếu bạn có):

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

Và sau đó chúng hoạt động giống như các phương thức tích hợp sẵn. Bạn có thể gọi hiện tại NotFound()hoặc có thể gọi tùy chỉnh mới của bạnNotFound(myErrorMessage) .

Và tất nhiên, bạn có thể loại bỏ các loại chuỗi "mã hóa cứng" trong định nghĩa loại tùy chỉnh và để nó chung chung nếu bạn muốn, nhưng sau đó bạn có thể phải lo lắng về những ExecuteAsyncthứ này, tùy thuộc vào<T> thực tế .

Bạn có thể xem qua các mã nguồn cho NegotiatedContentResult<T>thấy tất cả nó. Không có gì nhiều cho nó.


1

Tôi cần tạo một IHttpActionResultthể hiện trong phần thân của một IExceptionHandlerlớp, để thiết lập thuộc ExceptionHandlerContext.Resulttính. Tuy nhiên, tôi cũng muốn đặt một tùy chỉnhReasonPhrase .

Tôi thấy rằng a ResponseMessageResultcó thể bọc mộtHttpResponseMessage (cho phép dễ dàng đặt ReasonPhrase).

Ví dụ:

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

0

Tôi biết PO đã hỏi bằng văn bản thông báo, nhưng một tùy chọn khác để chỉ trả về 404 là làm cho phương thức trả về một IHttpActionResult và sử dụng hàm 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

Các câu trả lời ở đây đang thiếu một vấn đề nhỏ về câu chuyện của nhà phát triển. Các ApiControllerlớp vẫn còn phơi bày mộtNotFound() phương pháp mà các nhà phát triển có thể sử dụng. Điều này sẽ khiến một số phản hồi 404 chứa phần thân kết quả không được kiểm soát.

Tôi trình bày ở đây một vài phần của mã " phương pháp ApiController NotFound tốt hơn " sẽ cung cấp một phương pháp ít lỗi hơn mà không yêu cầu các nhà phát triển biết "cách tốt hơn để gửi 404".

  • tạo một lớp kế thừa từApiController được gọiApiController
    • Tôi sử dụng kỹ thuật này để ngăn các nhà phát triển sử dụng lớp gốc
  • ghi đè NotFoundphương pháp của nó để cho phép các nhà phát triển sử dụng api có sẵn đầu tiên
  • nếu bạn muốn ngăn cản điều này, hãy đánh dấu điều này là [Obsolete("Use overload instead")]
  • thêm một phần protected NotFoundResult NotFound(string message) mà bạn muốn khuyến khích
  • vấn đề: kết quả không hỗ trợ phản hồi với một nội dung. giải pháp: kế thừa và sử dụng NegotiatedContentResult. xem lớp NotFoundResult đính kèm tốt hơn .
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.