Using MediatR in Web API - How do you handle different status codes - asp.net-core-webapi

I am using (and learning how to use) MediatR and am trying to use it in my Web API. My question is, assuming that the goal of the 'controller' is to look like the following where you simply send off the message:
[ApiController]
public class List : ControllerBase
{
private readonly IMediator mediator;
public List( IMediator mediator )
{
this.mediator = mediator;
}
[HttpGet( "/calc-engines" )]
public async Task<ActionResult<CalcEngine[]>> HandleAsync(
[FromQuery] Query query,
CancellationToken cancellationToken = default ) => await mediator.Send( query, cancellationToken );
What is the best practice to handle different status codes based on item not found, forbidden, etc.?
Do you have your handler return a complex type with a Payload (the requested item) along with an HttpStatus and then examine the result in the action and return appropriately? Throw exceptions and catch in the action (or a filter) and set status based on exception type?
Update: This is the best I could come up with, but wondering if there is something better.
public class ApiResult<T>
{
private ApiResult( HttpStatusCode failureStatus ) => FailureStatus = failureStatus;
private ApiResult( T payload ) => Payload = payload;
public T Payload { get; }
public HttpStatusCode? FailureStatus { get; }
public bool IsSuccess => FailureStatus == null;
public static ApiResult<T> Fail( HttpStatusCode failureStatus ) => new ApiResult<T>( failureStatus );
public static ApiResult<T> Success( T paylooad ) => new ApiResult<T>( paylooad );
public static implicit operator bool( ApiResult<T> result ) => result.IsSuccess;
}
Then handler looks something like this:
public async Task<ApiResult<SomeType>> Handle( TRequest message, CancellationToken token )
{
if ( badValidation )
{
return ApiResult<SomeType>.Fail( HttpStatusCode.BadRequest );
}
// do work...
return ApiResult<SomeType>.Success( someResult );
}
And action looks something like this:
public async Task<ActionResult<ApiResult<SomeType>>> HandleAsync(
[FromQuery] Query query,
CancellationToken cancellationToken = default )
{
var result = await mediator.Send( query, cancellationToken );
if ( result ) return result;
return StatusCode( (int)result.FailureStatus );
}

In the team I take part, we detach the controller/presentation layer from the Mediatr logic (which will be in our application/business layer).
As you did, we return a specified object result from the Mediatr commands/queries, stating success/unsuccess, holding the result payload or errors. However, we do not return HTTP status codes, since that is responsability of the controller - the controller handles the HTTP requests and responses, so only the controller needs to know the meaning of Ok, Bad Request, etc. In this way, we decouple the presentation and the application layers (we employ a clean architecture scheme).
Thus, to map Meditr results in APIs, we often use two different strategies:
Middlewares to model responses. Example: if an input validation exception is thrown, a middleware catches it and retrieves a Bad Request;
Methods in the controllers to interpret responses. Examples: if a Get endpoint calls a query and the result payload is null, that is mapped to a Not Found; if a failed result is returned by the Mediatr, a 500 is returned.
Pipeline behaviours (reference) may also be useful to you. They are classes that can be registered in the pipelines of commands/queries, executing in the specified order as middlewares. They are very useful, for example, to validate input conditions, handle exceptions, model responses, get metrics, etc. In your example, the following validation
if ( badValidation )
{
return ApiResult<SomeType>.Fail( HttpStatusCode.BadRequest );
}
could go to a validation behaviour, for example, instead of staying inside the Handle. Here is a good tutorial.

public class ErrorHandler:Exception
{
public HttpStatusCode Codigo { get; set; }
public object Error { get; set; }
public ErrorHandler(HttpStatusCode Code, object Err)
{
this.Codigo = Code;
this.Error = Err;
}
}
i work with mediatr and i handle exceptions with this code. usage example bellow
var Comment = await contexto.TComentario.FindAsync(request.CommentId);
if (Comment == null)
throw new ManejadorErr.ErrorHandler(System.Net.HttpStatusCode.NotFound, new { Message = "comment no could be found" });

Related

Is it possible to call synchronous http post request using ASP.net?

I have a scenario where http POST request execute, call another get request and return response of get request. Here is my code
public class EmployeeController : ControllerBase
{
private readonly IBusControl _bus;
public EmployeeController(IBusControl bus)
{
_bus = bus;
}
[HttpPost]
public async Task<IActionResult> Emp(EmployeeModel employee)
{
Uri uri = new Uri("rabbitmq://localhost/ret_eligibility");
var endPoint = await _bus.GetSendEndpoint(uri);
await endPoint.Send(employee);
return Ok("Success");
}
[HttpGet]
[Route("getRetFund")]
public IActionResult fund()
{
Fund fund = RetFundConsumer.fund;
return Ok(fund.retfund);
}
}
I want to call getRetFund request in POST request method, So that when employee data is sent to queue using postman, it call second service which consume message and send response back. This response will be shown then in console.
I also have tried the following POST method.
[HttpPost]
public async Task<IActionResult> Emp(EmployeeModel employee)
{
Uri uri = new Uri("rabbitmq://localhost/ret_eligibility");
var endPoint = await _bus.GetSendEndpoint(uri);
await endPoint.Send(employee);
//return Ok("Success");
Fund fund = RetFundConsumer.fund;
return Ok("your retirement fund is " + fund.retfund);
}
But this throw null Exception as it call second service before executing POST request. The response would be greatly appreciated.
This would never work. You need to spend time about both Web API request handling scope and MassTransit message handling scope.
In short, both Web API and MassTransit message handling is scoped to one message. There is no way you can consume a response message, somehow magically keeping the HTTP session open. The consumer gets disposed when it finishes handling a message.
You can do it, though, but you need to use the MassTransit request/response feature.
public class EmployeeController : ControllerBase
{
private readonly IRequestClient<EmployeeModel> _client;
public EmployeeController(IClientFactory clientFactory)
=> _client = clientFactory.CreateRequestClient<EmployeeModel>(
new Uri("rabbitmq://localhost/ret_eligibility"));
[HttpPost]
public async Task<IActionResult> Emp(EmployeeModel employee)
{
var response = await _client.GetResponse<Fund>(employee);
return Ok("your retirement fund is " + fund.retfund);
}
}
Of course, you need to change your consumer accordingly to send a message back. Check the documentation referenced above for the details.

Asp.net core 2 XUnit -- Unit Test MVC controller that throws exception

I am trying to unit test a controller that returns an IActionResult but can also throw an exception in certain circumstances. The problem I am running into is I'm not sure how to call it as the Assert throws an error where you cannot convert from IActionResult to Action.
How would I go about testing below statement?
Assert.Throws<Exception>(await controller.SendEmail(email)); //how to test this
I looked through the Microsoft testing controller documentation and didn't find something relevant. Most examples I see testing exceptions are for things like accessing repositories or services.
I understand I can return a badrequest or redirect to the page with an error message. But is what I am trying to accomplish possible?
My HomeController Method
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SendEmail(EmailModel emailModel)
{
if(!ModelState.IsValid)
{
return View("Index",emailModel);
}
var response = await _sendEmail.SendEmailMessage(emailModel);
if (response != System.Net.HttpStatusCode.Accepted)
{
throw new Exception("Email failed to send, please try again later");
}
else
{
TempData["message"] = $"Email has been sent";
}
return RedirectToAction("Index");
}
XUnit HomeControllerTest Constructor for arrange
private Mock<ISendEmail> mockSendEmail;
private HomeController controller;
public HomeControllerShould()
{
mockSendEmail = new Mock<ISendEmail>();
mockSendEmail.Setup(x => x.SendEmailMessage(It.IsAny<EmailModel>())).ReturnsAsync(HttpStatusCode.Accepted);
controller = new HomeController(mockSendEmail.Object);
}
XUnit Test for Sending Email
[Fact]
public async Task SendEmailActionThrowsExceptionOnEmailFailure()
{
mockSendEmail.Setup(x => x.SendEmailMessage(It.IsAny<EmailModel>())).ReturnsAsync(HttpStatusCode.InternalServerError);
var email = new EmailModel();
Assert.Throws<Exception>(await controller.SendEmail(email)); //how to test this
}
Assert.Throws requires a function. You could use ThrowsAsync.
[Fact]
public async Task SendEmailActionThrowsExceptionOnEmailFailure()
{
mockSendEmail.Setup(x => x.SendEmailMessage(It.IsAny<EmailModel>()))
.ReturnsAsync(HttpStatusCode.InternalServerError);
var email = new EmailModel();
await Assert.ThrowsAsync<Exception>(() => controller.SendEmail(email));
}
FYI: We don't normally return HttpStatusCode from service layer such as email service, but I'll let you decide.

WebApi and Swagger

I am using asp.net webapi and using swagger to create a RestApi within a WPF app via AutoRest.
I am having problem figuring out how to consume the returned data if there is an error.
My controller is as follows;
// POST: api/Personnel
//[SwaggerResponse(HttpStatusCode.InternalServerError ,Type = typeof(HttpError))]
[SwaggerOperation("AddEditContract")]
[SwaggerResponse(HttpStatusCode.OK, Description = "Add/Edit a Contract", Type =typeof(int))]
public IHttpActionResult Post(ContractDto value)
{
try
{
var _contractsService = new Business.ContractsService();
var contractToSave = _contractsService.GetContractsById(value.CC_Id);
if (contractToSave == null)
{
return NotFound();
}
var ret = _contractsService.SaveContract(value);
if (ret > 0)
{
return Ok(ret);
}
else
{
return BadRequest();
}
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
I happened to have an error appear within the WebApi based on an error with AutoMapper but it was getting swallowed up. It is returning an error message in the response, which is great.
Here is the current AutoRest code for this call.
public static int? AddEditContract(this IBuxtedConAPI operations, ContractDto value)
{
return Task.Factory.StartNew(s => ((IBuxtedConAPI)s).AddEditContractAsync(value), operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult();
}
As you can see its expecting an int. If I uncomment the
//[SwaggerResponse(HttpStatusCode.InternalServerError ,Type = typeof(HttpError))]
The int return type turns to object.
So the real question.
Here is my service call from WPF to the WebApi
public async Task<int> SaveContract(ContractDto entity)
{
using (var db = new BuxtedConAPI())
{
var ret = await db.AddEditContractAsync(entity);
return (int)ret;
}
}
If an object is returned how do I pick up if an error has occurred or if the simple int (with a success) is just returned.
Thanks in advance.
Scott
Can you post the swagger file that you're generating and passing to AutoRest?
The reason return type turns to object (or whatever common base class is shared between all the possible responses), is because AutoRest treats explicitly defined responses as return values. Exceptions are used only for the default response.
We're investigating ways to specify multiple error responses that will generate the appropriate exceptions.

Only allow Nancy to return json or Xml and 406 when accept header is html

I'm writing a Nancy endpoint and I want to do something that I think should be really simple. I want to support returning the content in either json or xml but when html or any other type is requested to return a 406 Not supported. I can easily force either XML or JSON only, and I guess I could do and if (accept is html) return 406 but I would assume that there is some support for this in the content Negotiation support.
Can anybody shed any light?
Implement your own IResponseProcessor, Nancy will pick it up and hook in the engine.
public sealed class NoJsonOrXmlProcessor : IResponseProcessor
{
public ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context)
{
if (requestedMediaRange.Matches("application/json") || requestedMediaRange.Matches("aaplication/xml"))
{
//pass on, so the real processors can handle
return new ProcessorMatch{ModelResult = MatchResult.NoMatch, RequestedContentTypeResult = MatchResult.NoMatch};
}
return new ProcessorMatch{ModelResult = MatchResult.ExactMatch, RequestedContentTypeResult = MatchResult.ExactMatch};
}
public Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context)
{
return new Response{StatusCode = HttpStatusCode.NotAcceptable};
}
public IEnumerable<Tuple<string, MediaRange>> ExtensionMappings { get; private set; }
}
We avoided the use of ResponseProcessor for the whole reason that the request was still being run all the way through our authentication layer, domain layer, etc. We wanted a way to quickly kill the request as soon as possible.
What we ended up doing was performing the check inside our own Boostrapper
public class Boostrapper : DefaultNancyBootstrapper
{
protected override void RequestStartup(TinyIoCContainer requestContainer, IPipelines pipelines, NancyContext context)
{
base.RequestStartup(requestContainer, pipelines, context);
pipelines.BeforeRequest += nancyContext =>
{
RequestHeaders headers = nancyContext.Request.Headers
if (!IsAcceptHeadersAllowed(headers.Accept))
{
return new Response() {StatusCode = HttpStatusCode.NotAcceptable};
}
return null;
}
}
private bool IsAcceptHeadersAllowed(IEnumerable<Tuple<string, decimal>> acceptTypes)
{
return acceptTypes.Any(tuple =>
{
var accept = new MediaRange(tuple.Item1);
return accept.Matches("application/json") || accept.Matches("application/xml");
});
}
}

How to invoke a post when using HubController<T>?

I can't find much documentation on the new HubController<T> so maybe I'm going about this wrong. This is what I have:
public class StatusController : HubController<StatusHub>
{
private string _status = "";
public string Get()
{
return _status;
}
public void Post(string status)
{
_status = status;
// Call StatusChanged on SignalR clients listening to the StatusHub
Clients.All.StatusChanged(status);
}
}
public class StatusHub : Hub { }
This is how I'm attempting to create the hub proxy:
var hubConnection = new HubConnection("http://localhost:51076/");
var statusHubProxy = hubConnection.CreateHubProxy("StatusHub");
statusHubProxy.On<string>("StatusChanged", status => Console.WriteLine("New Status: {0}", status));
await hubConnection.Start();
How do I call the Post method of my controller? This is where I'm getting an exception:
await statusHubProxy.Invoke("Post", "Test Status");
HubController<T> just provides some basic plumbing that gets you access to the resources that are associated with the specific hub type (e.g. Clients) that you want to work with. Calling it has nothing to do with invoking the actual hub itself, so you don't use the hub client API, it's just straight HTTP calls. Without HubController<T> you would have to reach out to SignalR's GlobalHost.Configuration.GetHubContext<T>() yourself to find the IHubContext for your hub type.
So, you can call your StatusController::Post method with any of the standard .NET HTTP APIs: HttpClient, WebClient or HttpWebRequest.

Resources