IHTTPActionResult custom response and unit testing with WebApi 2 - c#

I have created a class which is as follows
public class Response<T> : IHttpActionResult where T : class
{
private readonly T _body;
....
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage msg = new HttpResponseMessage();
switch (_httpStatus)
{
case HttpResponseStatus.Ok:
{
msg = _request.CreateResponse(HttpStatusCode.OK, _body);
break;
}
case HttpResponseStatus.BadRequest:
{
msg = _request.CreateResponse(HttpStatusCode.BadRequest, _body);
break;
}
...
return Task.FromResult(msg);
}
}
This is my base class for returning IHTTPActionResult in my web api calls.
It works well for what i require it for.
I now have some unit tests setup using MSTest.
// Act
IHttpActionResult result = controller.Step1();
// Assert
Assert.IsNotNull(result, "Is null when it shouldnt be");
Assert.IsInstanceOfType(result, typeof(Response<BodyContent<LogingActivityResult>>), "Is not expected IsInstanceOfType");
Both of these Assertions work fine.
However i now want to actually get # the data held within my response and assert that they are ok i.e.: correct values, counts, etc but am having no luck with this.
I have tried ALL the various System.Web.Http.Results types such as
var contentResult = result as OkNegotiatedContentResult<Response<BodyContent<LogingActivityResult>>>;
or
var contentResult = result as FormattedContentResult<Response<BodyContent<LogingActivityResult>>>;
but both of these are null when i hover over contentResult variable.
Can anyone lead me in the right direction?
Thanks

I managed to solve this.. the property _body in my above class was set to PRIVATE, as a result I could not access this in my unit test class. Setting it to PUBLIC solved it and was able to access the data being returned in the response.

Related

How to handle API responses globally in ASP.NET MVC controller?

I have a couple of controller methods that call APIs and I handle the responses using a switch statement like the one below.
Question:
Is there a way to refactor my code to handle the responses from one place instead of having pretty much the same switch statement in every request?
NOTE: I have a BearerTokenHandler class file. Could I handle the responses there?
var apiResponse = _httpClient.PostAsync()
switch (apiResponse.StatusCode)
{
case HttpStatusCode.OK:
//Parse response
break:
case HttpStatusCode.BadRequest:
//Parse response
break:
case HttpStatusCode.NotFound:
//Parse response
break
case HttpStatusCode.Unauthorized:
return RedirectToAction("AccessDenied");
default:
// ViewData["Error"]
break:
}
There are a lot of ways you can refactor your code. Here I just point out the two most common ways.
Make an HttpClient service and put all the work (POST, GET) relate to HttpClient there
Have a BaseController and generic methods. Then inherit all common works there.
It's better to combine two ways, which means always having a BaseController and made some services to handle related things.
Here is the example code:
public class BaseController : Controller
{
// Put all the cross cutting-concern things here
// Like: Object Mapper, Logger, Cache Manager, etc...
// So that you can use them without re-initialize
}
Make the interface IHttpClientService and its implementation HttpClientService. If you're using the ASP.NET Core then use its default Dependency Injection, otherwise if you using the old ASP.NET MVC, read this Dependency Injection
public interface IHttpClientService
{
Task<IActionResult> PostAsync();
}
public class HttpClientService : IHttpClientService
{
public async Task<object> PostAsync()
{
var _httpClient = new HttpClient();
var apiResponse = _httpClient.PostAsync();
switch (apiResponse.StatusCode)
{
case HttpStatusCode.OK:
//Parse response
break;
case HttpStatusCode.BadRequest:
//Parse response
break;
case HttpStatusCode.NotFound:
//Parse response
break;
case HttpStatusCode.Unauthorized:
return RedirectToAction("AccessDenied");
default:
// ViewData["Error"]
break;
}
}
}
Then you can implement your controller
public class YourController : BaseController
{
private readonly IHttpClientService _httpClientService;
public YourController(IHttpClientService httpClientService)
{
_httpClientService = httpClientService;
}
public async Task<IActionResult> YourMethod()
{
var result = await _httpClientService.PostAsync();
// You can use the logger from the BaseController to do something here
return result;
}
}
You could have a BaseContoller class that accepts a generic model to post and then each of your post methods can just call this instead.

Unit Testing a Controller method returns null

I'm trying to learn Unit testing in .NET 6 by testing a controller function GetProduct. The problem is I get null returned in the variable var product = await _productController.GetProduct(productId);. As you can see in the picture below, the Result is ok but the Value, where the ServiceResponse<Product> was suppose to be is null.
Here is the controller function:
public class ProductController : ControllerBase
{
private readonly IProductService _productService;
public ProductController(IProductService productService)
{
_productService = productService;
}
[HttpGet("{productId}")]
public async Task<ActionResult<ServiceResponse<Product>>> GetProduct(int productId)
{
var result = await _productService.GetProductAsync(productId);
return Ok(result);
}
}
Here is the Test:
public class ProductControllerTest
{
private readonly ProductController _productController;
private readonly Mock<IProductService> _productService = new Mock<IProductService>();
public ProductControllerTest()
{
_productController = new ProductController(_productService.Object);
}
[Test]
public async Task GetProducts_ReturnsProduct_IfProductExists()
{
//Arange
var productId = 1;
var prodData = new Product
{
Id = productId,
Title = "null"
};
var prductResponse = new ServiceResponse<Product>
{
Data = prodData,
Success = true ,
Message = ""
};
_productService.Setup(x => x.GetProductAsync(productId)).ReturnsAsync(prductResponse);
//Act
var product = await _productController.GetProduct(productId);
//Assert
Assert.That(product?.Value?.Data?.Id, Is.EqualTo(productId));
}
}
This behavior is observed due to overloaded operators on ActionResult<T> class.
Since the method, OK(value) returns an instance of OKObjectResult and the return type of controller method is of type ActionResult<ServiceResponse<Product>> the returned OKObjectResult instance is wrapped in an instance of ActionResult<T> and is exposed by the Result property. Hence typecasting the Result property (as shown by #BennyM) to OKObjectResult works.
Please note that the assertion would have succeeded had the controller method returned the ServiceResponse<Product> directly without modifying the return type on controller's method.
While this explains the behavior, I personally feel there is a better way to test controllers. This MSDN Article explains about integration testing. One can effectively unit test all the dimensions of the controllers - authentication, validation, (de)serialization, etc - by mocking the immediate dependencies of the respective controllers.
Since you are returning with an Ok call in your controller you can add a cast to the unit test.
var result = (await _productController.GetProduct(productId)).Result as OkObjectResult;
Assert.IsNotNull(result);
var returnedServiceResponse = result.Value as ServiceResponse<Product>;
Assert.That(returnedServiceResponse ?.Data?.Id, Is.EqualTo(productId));
Also you don't have to use Actionresult. You can also just return your service response
[HttpGet("{productId}")]
public async Task<ServiceResponse<Product>> GetProduct(int productId)
{
var result = await _productService.GetProductAsync(productId);
return result;
}
This will also make the test a bit easier as no need to use the OkObjectResult.

Return String or 404 exception in asp .net web api core 3 [duplicate]

ASP.NET Core API controllers typically return explicit types (and do so by default if you create a new project), something like:
[Route("api/[controller]")]
public class ThingsController : Controller
{
// GET api/things
[HttpGet]
public async Task<IEnumerable<Thing>> GetAsync()
{
//...
}
// GET api/things/5
[HttpGet("{id}")]
public async Task<Thing> GetAsync(int id)
{
Thing thingFromDB = await GetThingFromDBAsync();
if(thingFromDB == null)
return null; // This returns HTTP 204
// Process thingFromDB, blah blah blah
return thing;
}
// POST api/things
[HttpPost]
public void Post([FromBody]Thing thing)
{
//..
}
//... and so on...
}
The problem is that return null; - it returns an HTTP 204: success, no content.
This is then regarded by a lot of client side Javascript components as success, so there's code like:
const response = await fetch('.../api/things/5', {method: 'GET' ...});
if(response.ok)
return await response.json(); // Error, no content!
A search online (such as this question and this answer) points to helpful return NotFound(); extension methods for the controller, but all these return IActionResult, which isn't compatible with my Task<Thing> return type. That design pattern looks like this:
// GET api/things/5
[HttpGet("{id}")]
public async Task<IActionResult> GetAsync(int id)
{
var thingFromDB = await GetThingFromDBAsync();
if (thingFromDB == null)
return NotFound();
// Process thingFromDB, blah blah blah
return Ok(thing);
}
That works, but to use it the return type of GetAsync must be changed to Task<IActionResult> - the explicit typing is lost, and either all the return types on the controller have to change (i.e. not use explicit typing at all) or there will be a mix where some actions deal with explicit types while others. In addition unit tests now need to make assumptions about the serialisation and explicitly deserialise the content of the IActionResult where before they had a concrete type.
There are loads of ways around this, but it appears to be a confusing mishmash that could easily be designed out, so the real question is: what is the correct way intended by the ASP.NET Core designers?
It seems that the possible options are:
Have a weird (messy to test) mix of explicit types and IActionResult depending on expected type.
Forget about explicit types, they're not really supported by Core MVC, always use IActionResult (in which case why are they present at all?)
Write an implementation of HttpResponseException and use it like ArgumentOutOfRangeException (see this answer for an implementation). However, that does require using exceptions for program flow, which is generally a bad idea and also deprecated by the MVC Core team.
Write an implementation of HttpNoContentOutputFormatter that returns 404 for GET requests.
Something else I'm missing in how Core MVC is supposed to work?
Or is there a reason why 204 is correct and 404 wrong for a failed GET request?
These all involve compromises and refactoring that lose something or add what seems to be unnecessary complexity at odds with the design of MVC Core. Which compromise is the correct one and why?
This is addressed in ASP.NET Core 2.1 with ActionResult<T>:
public ActionResult<Thing> Get(int id) {
Thing thing = GetThingFromDB();
if (thing == null)
return NotFound();
return thing;
}
Or even:
public ActionResult<Thing> Get(int id) =>
GetThingFromDB() ?? NotFound();
I'll update this answer with more detail once I've implemented it.
Original Answer
In ASP.NET Web API 5 there was an HttpResponseException (as pointed out by Hackerman) but it's been removed from Core and there's no middleware to handle it.
I think this change is due to .NET Core - where ASP.NET tries to do everything out of the box, ASP.NET Core only does what you specifically tell it to (which is a big part of why it's so much quicker and portable).
I can't find a an existing library that does this, so I've written it myself. First we need a custom exception to check for:
public class StatusCodeException : Exception
{
public StatusCodeException(HttpStatusCode statusCode)
{
StatusCode = statusCode;
}
public HttpStatusCode StatusCode { get; set; }
}
Then we need a RequestDelegate handler that checks for the new exception and converts it to the HTTP response status code:
public class StatusCodeExceptionHandler
{
private readonly RequestDelegate request;
public StatusCodeExceptionHandler(RequestDelegate pipeline)
{
this.request = pipeline;
}
public Task Invoke(HttpContext context) => this.InvokeAsync(context); // Stops VS from nagging about async method without ...Async suffix.
async Task InvokeAsync(HttpContext context)
{
try
{
await this.request(context);
}
catch (StatusCodeException exception)
{
context.Response.StatusCode = (int)exception.StatusCode;
context.Response.Headers.Clear();
}
}
}
Then we register this middleware in our Startup.Configure:
public class Startup
{
...
public void Configure(IApplicationBuilder app)
{
...
app.UseMiddleware<StatusCodeExceptionHandler>();
Finally actions can throw the HTTP status code exception, while still returning an explicit type that can easily be unit tested without conversion from IActionResult:
public Thing Get(int id) {
Thing thing = GetThingFromDB();
if (thing == null)
throw new StatusCodeException(HttpStatusCode.NotFound);
return thing;
}
This keeps the explicit types for the return values and allows easy distinction between successful empty results (return null;) and an error because something can't be found (I think of it like throwing an ArgumentOutOfRangeException).
While this is a solution to the problem it still doesn't really answer my question - the designers of the Web API build support for explicit types with the expectation that they would be used, added specific handling for return null; so that it would produce a 204 rather than a 200, and then didn't add any way to deal with 404? It seems like a lot of work to add something so basic.
You can actually use IActionResult or Task<IActionResult> instead of Thing or Task<Thing> or even Task<IEnumerable<Thing>>. If you have an API that returns JSON then you can simply do the following:
[Route("api/[controller]")]
public class ThingsController : Controller
{
// GET api/things
[HttpGet]
public async Task<IActionResult> GetAsync()
{
}
// GET api/things/5
[HttpGet("{id}")]
public async Task<IActionResult> GetAsync(int id)
{
var thingFromDB = await GetThingFromDBAsync();
if (thingFromDB == null)
return NotFound();
// Process thingFromDB, blah blah blah
return Ok(thing); // This will be JSON by default
}
// POST api/things
[HttpPost]
public void Post([FromBody] Thing thing)
{
}
}
Update
It seems as though the concern is that being explicit in the return of an API is somehow helpful, while it is possible to be explicit it is in fact not very useful. If you're writing unit tests that exercise the request / response pipeline you are typically going to verify the raw return (which would most likely be JSON, i.e.; a string in C#). You could simply take the returned string and convert it back to the strongly typed equivalent for comparisons using Assert.
This seems to be the only shortcoming with using IActionResult or Task<IActionResult>. If you really, really want to be explicit and still want to set the status code there are several ways to do this - but it is frowned upon as the framework already has a built-in mechanism for this, i.e.; using the IActionResult returning method wrappers in the Controller class. You could write some custom middleware to handle this however you'd like, however.
Finally, I would like to point out that if an API call returns null according to W3 a status code of 204 is actually accurate. Why on earth would you want a 404?
204
The server has fulfilled the request but does not need to return an
entity-body, and might want to return updated metainformation. The
response MAY include new or updated metainformation in the form of
entity-headers, which if present SHOULD be associated with the
requested variant.
If the client is a user agent, it SHOULD NOT change its document view
from that which caused the request to be sent. This response is
primarily intended to allow input for actions to take place without
causing a change to the user agent's active document view, although
any new or updated metainformation SHOULD be applied to the document
currently in the user agent's active view.
The 204 response MUST NOT include a message-body, and thus is always
terminated by the first empty line after the header fields.
I think the first sentence of the second paragraph says it best, "If the client is a user agent, it SHOULD NOT change its document view from that which caused the request to be sent". This is the case with an API. As compared to a 404:
The server has not found anything matching the Request-URI. No
indication is given of whether the condition is temporary or
permanent. The 410 (Gone) status code SHOULD be used if the server
knows, through some internally configurable mechanism, that an old
resource is permanently unavailable and has no forwarding address.
This status code is commonly used when the server does not wish to
reveal exactly why the request has been refused, or when no other
response is applicable.
The primary difference being one is more applicable for an API and the other for the document view, i.e.; the page displayed.
In order to accomplish something like that(still, I think that the best approach should be using IActionResult), you can follow, where you can throw an HttpResponseException if your Thing is null:
// GET api/things/5
[HttpGet("{id}")]
public async Task<Thing> GetAsync(int id)
{
Thing thingFromDB = await GetThingFromDBAsync();
if(thingFromDB == null){
throw new HttpResponseException(HttpStatusCode.NotFound); // This returns HTTP 404
}
// Process thingFromDB, blah blah blah
return thing;
}
From ASP.NET Core 7, a action controller can return a HttpResults type. Then you can :
public async Task<Results<Ok<Product>, NotFound>> GetAsync(int id)
{
Thing thingFromDB = await GetThingFromDBAsync();
if(thingFromDB == null)
return TypedResults.NotFound();
...
return TypedResults.Ok(thingFromDB);
}
I love this syntax, because it's explicitly indicate that return the API. But actually, the openAPI specification generator don't manage this. You can follow the progress from this Github ticket :
TypedResults metadata are not inferred for API Controllers
I too looked high and low for an answer to what to do about strongly typed responses when I wanted to return an 400 response for bad data sent into the request. My project is in ASP.NET Core Web API (.NET5.0). The solution I found was basically set the status code and return default version of the object. Here is your example with the change to set the status code to 404 and return the default object when the db object is null.
[Route("api/[controller]")]
public class ThingsController : Controller
{
// GET api/things
[HttpGet]
public async Task<IEnumerable<Thing>> GetAsync()
{
//...
}
// GET api/things/5
[HttpGet("{id}")]
public async Task<Thing> GetAsync(int id)
{
Thing thingFromDB = await GetThingFromDBAsync();
if(thingFromDB == null)
{
this.Response.StatusCode = Microsoft.AspNetCore.Http.StatusCodes.Status404NotFound;
return default(Thing);
}
// Process thingFromDB, blah blah blah
return thing;
}
// POST api/things
[HttpPost]
public void Post([FromBody]Thing thing)
{
//..
}
//... and so on...
}
ASP.NET Core 3.1 introduced filter.
Filters in ASP.NET Core allow code to run before or after specific stages in the request processing pipeline.
You can define a result filter that transform null ok result to not found result :
public class NullAsNotFoundResultFilter : IResultFilter
{
public void OnResultExecuted(ResultExecutedContext context)
{ }
public void OnResultExecuting(ResultExecutingContext context)
{
if(context.Result is ObjectResult result && result.Value == null)
{
context.Result = new NotFoundResult();
}
}
}
Finally, you need to add the filter in the MVC pipeline :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(o => o.Filters.Add<NullAsNotFoundResultFilter>());
Had another problem with same behavior - all methods return 404. The problem was in missing code block
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});

How to write unit test case for BadRequest?

I want to write Unit test cases for following code
HomeController.cs
[HttpPost]
[ActionName("CreateDemo")]
public async Task<IHttpActionResult> CreateDemo([FromBody] MyRequest request)
{
if (request == null)
{
return BadRequest("request can not be null");
}
if (request.MyID == Guid.Empty)
{
return BadRequest("MyID must be provided");
}
}
I tried like following which is not correct way i guess so
[TestMethod]
public async Task NullCheck()
{
try
{
var controller = new HomeController();
var resposne = await controller.CreateDemo(null);
Assert.AreEqual(); // not sure what to put here
}
catch (HttpResponseException ex) //catch is not hit
{
Assert.IsTrue(
ex.Message.Contains("request can not be null"));
}
}
Each unit test shall test one requirement or concern. Your method implements two requirements:
1) If request is null, return BadRequestErrorMessageResult object with predefined error message.
2) If request's MyID property is empty GUID, return BadRequestErrorMessageResult object with another predefined error message.
This means we should have two unit tests:
[Test]
public async Task CreateDemo_returns_BadRequestErrorMessageResult_when_request_is_null()
{
// Arrange
var controller = new HomeController();
// Act
var response = await controller.CreateDemo(null);
// Assert
Assert.IsInstanceOf<BadRequestErrorMessageResult>(response);
Assert.AreEqual("request can not be null", response.Message);
}
[Test]
public async Task CreateDemo_returns_BadRequestErrorMessageResult_when_request_ID_is_empty_GUID()
{
// Arrange
var controller = new HomeController();
var request = new MyRequest(Guid.Empty);
// Act
var response = await controller.CreateDemo(request);
// Assert
Assert.IsInstanceOf<BadRequestErrorMessageResult>(response);
Assert.AreEqual("MyID must be provided", response.Message);
}
You can go even further and split each of these tests into two where one would test that return object is of the expected type and another that validates that returned object state is as expected (e.g. Message string is as expected). This way you would have a single assert per test.
Side notes:
You tagged this question with nunit tag so I provided the code which uses that framework. In your example though, you use [TestMethod] attribute which comes from Microsoft unit testing framework. If you want to use that framework you'd have to make some changes e.g. replace Assert.IsInstanceOf with Assert.IsInstanceOfType.
I assumed that GUID is passed to MyRequest via its constructor which assigns it to MyID.
I am not coming from web world but I found that BadRequest method has an overload which returns BadRequestErrorMessageResult if string is passed as its argument.

How do I return NotFound() IHttpActionResult with an error message or exception?

I am returning a NotFound IHttpActionResult, when something is not found in my WebApi GET action. Along with this response, I want to send a custom message and/or the exception message (if any). The current ApiController's NotFound() method does not provide an overload to pass a message.
Is there any way of doing this? or I will have to write my own custom IHttpActionResult?
Here's a one-liner for returning a IHttpActionResult NotFound with a simple message:
return Content(HttpStatusCode.NotFound, "Foo does not exist.");
You'd need to write your own action result if you want to customize the response message shape.
We wanted to provide the most common response message shapes out of the box for things like simple empty 404s, but we also wanted to keep these results as simple as possible; one of the main advantages of using action results is that it makes your action method much easier to unit test. The more properties we put on action results, the more things your unit test needs to consider to make sure the action method is doing what you'd expect.
I often want the ability to provide a custom message as well, so feel free to log a bug for us to consider supporting that action result in a future release:
https://aspnetwebstack.codeplex.com/workitem/list/advanced
One nice thing about action results, though, is that you can always write your own fairly easily if you want to do something slightly different. Here's how you might do it in your case (assuming you want the error message in text/plain; if you want JSON, you'd do something slightly different with the content):
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);
}
}
Then, in your action method, you can just do something like this:
public class TestController : ApiController
{
public IHttpActionResult Get()
{
return this.NotFound("These are not the droids you're looking for.");
}
}
If you used a custom controller base class (instead of directly inheriting from ApiController), you could also eliminate the "this." part (which is unfortunately required when calling an extension method):
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.");
}
}
You could use ResponseMessageResult if you like:
var myCustomMessage = "your custom message which would be sent as a content-negotiated response";
return ResponseMessage(
Request.CreateResponse(
HttpStatusCode.NotFound,
myCustomMessage
)
);
yeah, if you need much shorter versions, then I guess you need to implement your custom action result.
You may use ReasonPhrase property of HttpResponseMessage class
catch (Exception exception)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
{
ReasonPhrase = exception.Message
});
}
You can create a custom negotiated content result as d3m3t3er suggested. However I would inherit from. Also, if you need it only for returning NotFound, you don't need to initialize the http status from constructor.
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);
}
}
one line code in asp.net core:
Return StatusCode(404, "Not a valid request.");
I solved it by simply deriving from OkNegotiatedContentResult and overriding the HTTP code in the resulting response message. This class allows you to return the content body with any HTTP response code.
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);
}
}
I was needing to create an IHttpActionResult instance in the body of an IExceptionHandler class, in order to set the ExceptionHandlerContext.Result property. However I also wanted to set a custom ReasonPhrase.
I found that a ResponseMessageResult could wrap a HttpResponseMessage (which allows ReasonPhrase to be set easily).
For Example:
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" });
}
}
}
If you inherit from the base NegotitatedContentResult<T>, as mentioned, and you don't need to transform your content (e.g. you just want to return a string), then you don't need to override the ExecuteAsync method.
All you need to do is provide an appropriate type definition and a constructor that tells the base which HTTP Status Code to return. Everything else just works.
Here are examples for both NotFound and 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) { }
}
And then you can create corresponding extension methods for ApiController (or do it in a base class if you have one):
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);
}
And then they work just like the built-in methods. You can either call the existing NotFound() or you can call your new custom NotFound(myErrorMessage).
And of course, you can get rid of the "hard-coded" string types in the custom type definitions and leave it generic if you want, but then you may have to worry about the ExecuteAsync stuff, depending on what your <T> actually is.
You can look over the source code for NegotiatedContentResult<T> to see all it does. There isn't much to it.
Iknow PO asked with a message text, but another option to just return a 404 is making the method return a IHttpActionResult and use the StatusCode function
public async Task<IHttpActionResult> Get([FromUri]string id)
{
var item = await _service.GetItem(id);
if(item == null)
{
StatusCode(HttpStatusCode.NotFound);
}
return Ok(item);
}
Answers here are missing a little developer story problem. The ApiController class is still exposing a NotFound() method that developers may use. This would cause some 404 response to contain a uncontrolled result body.
I present here a few parts of code "better ApiController NotFound method" that will provide a less error-prone method that does not require developers to know "the better way of sending a 404".
create a class inheriting from ApiController called ApiController
I use this technique to prevent developers from using the original class
override its NotFound method to let devs use the first available api
if you want to discourage this, mark this as [Obsolete("Use overload instead")]
add an extra protected NotFoundResult NotFound(string message) that you want to encourage
problem: the result does not support responding with a body. solution: inherit and use NegotiatedContentResult. see attached better NotFoundResult class.
Another nice possibility is to use a different built-in result type: NotFoundObjectResult(message).
Needed to return the error message for 404 Not Found and I am using Dot Net 6.0.
This is the code
Problem(statusCode: 404, detail: "Put your detailed error message here");
Where Problem is a method present in ControllerBase class.

Categories

Resources