Need Help making dotnet controllers DRY - c#

Need some help making my controllers DRY using the ASP.NET core
So, basically, I have an issue keeping my controllers non-fat and DRY.
Basically, let's say I have 4 controllers : controllerA, controllerB ... controllerD - the general structure of the code for each controller looks like this (pseudocode)
class controllerA
{
IActionResult Get(string ids)
{
//do validation on ids - if invalid return 404
// IEnumerable<obj> results= aservice.GetResource(ids)
-------------------------------------------------------------------------
//Logging of results
//logic which looks at results and determines whether to return a 404 or a 200
//return results
}
}
The logic after the dotted line is essentially repeated in 4 controllers - how can I make the controllers more DRY.
I have read about different solutions including using a DI service, a helper class, extension methods for the Controller type or an abstract class that these controllers can inherit from that extends the .NET controller class.
I am writing production code so would like to seek out a clean solution which is also best practice!
Thanks!

I would recommend to use MediatR nuget.
You can implement IPipelineBehavior<,> for each required step, in your example it will be:
Validation of ids
Logging
Your code will look like:
public async Task<IActionResult> Get(CustomCommand request)
{
var result = await _mediator.Send(request);
// return handled result
}
If you define command as parameter of action it will reduce code more, something like this
public async Task<IActionResult> Get(string ids)
{
// under the hood it will execute (depends on implementation):
// 1. validation of ids for the command
// 2. execute main logic which implemented in IRequestHandler<>
// 3. logging
// 4. return result
var result = await _mediator.Send(new CustomCommand(ids));
// return handled result
}
About results handling, there are two common ways functional programming or using exceptions.
Using exceptions
Create customer exceptions for your service, for example, NotFoundException, BadRequestException (better to call it something meaningful like ValidationException)
Throw them in cases where you need to stop execution and return unsuccessful result (for example, validation was failed)
Add global exception filter to asp.net which will handle this exceptions depends on it's type and it might use some custom data from it
Functional programming
Instead of simply returning response you will return Result object within the response, simple implemented of Result<T>:
public class Result<TValue>
{
public TValue Value { get; set; }
public bool IsSuccess { get; set; }
// you can add also something like 'ErrorCode' to specify how to handle
// failed result, for instance if ErrorCode is 400 (it can be readable
// string as well) you will return BadRequest()
}
Finally you can define in base controller class method to execute the logic and handle results
//... inside your base controller
public async Task<ActionResult<TResponse>> ExecuteAsync<TResponse>(IRequest<Result<TResponse>> request)
{
Result<TResponse> result = await Mediator.Send(request);
if(result.IsSuccess) return Ok(result.Value);
return result.ErrorCode switch
{
"some_error_code" => Conflict(result.ErrorMessage),
"other_code" => NotFound(),
_ => BadRequest(result.ErrorMessage)
};
}
//...
so the action code will be:
public Task<IActionResult> Get(CustomCommand request) => ExecuteAsync(request);

Related

ASP.NET when to use IActionResult and when to use IResult?

I am trying to understand what the pros and cons are of IActionResult and IResult as return types and when to use the approopriate one. From what i've gathered IActionResult is just like IResult but with more options on how to handle the result?
IActionResult :: Defines a contract that represents the result of an action method.ASP.NET Core 7
IActionResult allows you to provide some more operations based on your actions like redirecting, changing the response's format etc.
Use IActionResult on the side of your web application - MVC, since it gives you more approaches to handle requests.
IResult :: Defines a contract that represents the result of an HTTP endpoint. ASP.NET Core 7
ASP.NET Core is a new static Results utility class to produce common HTTP responses as IResults. IResult is a new return type that got introduced with Minimal APIs.
IActionResult: Defines a contract that represents the result of an action method.
Example:
public IActionResult OkResult()
{
return Ok();
}
or
public IActionResult NoContentResult()
{
return NoContent();
}
IResult: Defines a contract that represents the result of an HTTP endpoint.
Example:
class CusomtHTMLResult : IResult
{
private readonly string _htmlContent;
public CusomtHTMLResult(string htmlContent)
{
_htmlContent = htmlContent;
}
public async Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_htmlContent);
await httpContext.Response.WriteAsync(_htmlContent);
}
}
(Line: 1) The 'CustomHTMLResult' implementing the
'Microsoft.AspNetCore.Http.IResult'.
(Line: 3-7) Injecting the HTML result.
(Line: 8) The 'ExecuteAsync' method gets automatically executed on
initializing 'CustomHTMLResult' object.
(Line: 10-12) Updating the 'HttpContext' object with our HTML
response, like defining 'ContentType', 'ContentLength'.
static class CustomResultExtensions
{
public static IResult HtmlResponse(this IResultExtensions extensions, string html)
{
return new CusomtHTMLResult(html);
}
}
The 'CustomResultExtions' is a static class. where we can define the extension method of our custom response.

Can C# use attribute to get return data from methods inside controllers and services?

Currently using asp core to build a web service system
I hope to obtain the return data of the service method in a specific controller through attribute
The following are examples
[HttpPost, Route("list")]
[CustomAttribute]
public IActionResult GetList([FromBody] NewsClassDto request)
{
var data = newsClassService.GetList(model);
return OkResponse(data);
}
NewsClassService Examples
public NewsClassDto GetList(NewsClassDto dto)
{
var daoClassData = _newsClassDao.GetList(dto);
var daoData = _newsDataDao.GetList(dto);
/** logical processing **/
return daoClassData;
}
I want to record through
[CustomAttribute]
newsClassService.GetList(model);
data returns content and
_newsClassDao.GetList(dto);
_newsDataDao.GetList(dto);
daoClassData returns content and daoData returns content , but I don't know how to implement this in Attribute
Yes, this is very common. What you need to do is create an action filter attribute. You can read about filters in general here.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace aspnet6test;
public class CustomFilterAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext context)
{
if (context.Result is OkObjectResult okResult)
{
// Here, okResult.Value will contain the result data. You'll have to
// explore with a series of IF statements whether or not is the data you're
// looking for. Example:
if (okResult.Value is List<SomeDto> listOfDtos)
{
// So it is a list of wanted SomeDto objects. Do your magic.
}
}
}
}
The above example runs the code after the result has been returned by the controller action, which I believe satisfies the requirements in your question.
It would be used like this:
[HttpPost("list")] // <-- The HTTP verp attributes are also Route attributes
[CustomFilter]
public IActionResult GetList([FromBody] NewsClassDto request)
{
var data = newsClassService.GetList(model);
return Ok(data); // <-- I believe this is the correct way.
}
The filter may accept properties and even constructor values, but that's another topic.

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 correctly implement Result object program flow with MediatR?

Instead of using exceptions for program flow, I'm attempting to use a custom Result object based on the ideas discussed here in MediatR. I have a very simple example here....
public class Result
{
public HttpStatusCode StatusCode { get; set; }
public string Error { get; set; }
public Result(HttpStatusCode statusCode)
{
StatusCode = statusCode;
}
public Result(HttpStatusCode statusCode, string error)
{
StatusCode = statusCode;
Error = error;
}
}
public class Result<TContent> : Result
{
public TContent Content { get; set; }
public Result(TContent content) : base(HttpStatusCode.OK)
{
Content = content;
}
}
The idea being that any failure will use the non-generic version, and success will use the generic version.
I'm having trouble with the following design issues...
If a response can potentially be generic or non-generic, what do I specify as the return type for the controller method?
For example...
[HttpGet("{id}")]
public async Task<ActionResult<Result<string>>> Get(Guid id)
{
return await _mediator.Send(new GetBlobLink.Query() { TestCaseId = id });
}
This won't work if I'm just returning a validation failure of type Result
How do I constrain a mediatr pipeline behaviour to potentially handle a generic or non-generic Result response?
For example, if the result is a success, I want to return just the Content from the generic version of Result. If it's a failure, I want to return the result object.
This is what I've come up with to start but it feels extremely 'smelly'
public class RestErrorBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public IHttpContextAccessor _httpContextAccessor { get; set; }
public RestErrorBehaviour(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var response = await next();
if (response.GetType().GetGenericTypeDefinition() == typeof(Result<>))
{
_httpContextAccessor.HttpContext.Response.StatusCode = 200;
//How do I return whatever the value of Content on the response is here?
}
if (response is Result)
{
_httpContextAccessor.HttpContext.Response.StatusCode = 400;
return response;
}
return response;
}
}
Serializing might be a challenge. What if there is a use case for needing the full Result<> object returned for a successful request - I don't necessarily always want to display "error": null.
Am I going down a rabbit hole? Is there a better way to do this?
The reasons for attempting something like this
Skinny controllers
Nicely formatted json responses to API requests
Avoid exception control flow for validation (And therefore avoiding needing to use .net core middleware to handle and format the request exceptions).
Many thanks,
Am I going down a rabbit hole? Is there a better way to do this?
Yes and yes.
There are several issues here. Some are technical difficulties, others stem from taking the wrong approach. I want to address those issues individually before solving them, so the solution (and the reasoning behind it) makes more sense.
1. Type abuse
You're abusing your Result type (and its generic variant) by trying to use it (the type itself) as data:
if (response.GetType().GetGenericTypeDefinition() == typeof(Result<>))
This is not how a result object is supposed to be treated. The result object contains the information inside of it. It should not be divined based on its type.
I'll get back to this point, but first I want to address your direct question. Both issues will be resolved further down this answer.
2. Empty results
Note: while not every default value is null (e.g. structs, primitives), for the rest of this answer I'm going to refer to it as a null value to keep things simple.
You're struggling with a problem in trying to accommodate both the base and generic Result types, which you've noticed is not easy to accomplish. It's important to realize that this problem is one of your own making: you're struggling to manage the two separate types that you intentionally created.
When you've downcast your object to a base type, and then think to yourself "boy I would really want to know what the derived type was and what it contains", then you're dealing with polymorphism abuse. The rule of thumb here is that when you need to know/access the derived type, then you shouldn't have downcast it into its base type.
The first thing to address when dealing with a problem of your own making, is to re-evaluate whether you should've been doing this (the thing that causes the issue) in the first place.
The only real reason to separate Result and Result<T> is so you can avoid having a Result<T> with an uninitialized T value (usually null). So let's re-evaluate that decision: should we have avoided null values here to begin with?
My answer is no. null is a meaningful value here, it shows the absence of a value. Therefore we shouldn't avoid it, and therefore our basis for separating the Result and Result<T> types becomes (pun intended) null and void.
The end conclusion here is that you can safely remove Result and refactor any code that uses it to actually use a Result<T> with an uninitialized T, but I'll get to the concrete solution further on.
3. Setting the response value
_httpContextAccessor.HttpContext.Response.StatusCode = 200;
//How do I return whatever the value of Content on the response is here?
It's unclear to me why you are trying to set the value of the response in your Mediatr pipeline behavior. The value that is returned is decided by the controller action. This is already in your code:
[HttpGet("{id}")]
public async Task<ActionResult<Result<string>>> Get(Guid id)
{
return await _mediator.Send(new GetBlobLink.Query() { TestCaseId = id });
}
This is where both the type of the returned value and the returned value itself are being set.
The Mediatr pipeline is part of your business logic, it is not part of your REST API. I think you've muddied that line by (a) putting the HTTP status code in your Mediatr result object and (b) having your Mediatr pipeline set the HTTP response itself.
Instead, it's your controller which should decide what the HTTP response object is. When using result classes, the usual approach is something along the lines of:
public async Task<IActionResult> Get(Guid id)
{
var result = await GetResult(id);
if(result.IsSuccess)
return Ok(result.Value);
else
// return a bad result
}
Note that I didn't define what that bad result should be. The correct way to handle a bad result is contextual. It may be a general server error, a permissions issue, not finding the referenced resource, ... It generally requires you to understand the reason for the bad result, which requires parsing the result object.
This is too contextual to define reusably - which is exactly why your controller action should be the one deciding the correct "bad" response.
Also note that I used IActionResult. This allows you to return any result you want, which is often beneficial as it allows you to return a different "good" and "bad" result, e.g.:
if(result.IsSuccess)
return Ok(result.Value); // type: ActionResult<Foo>
else
return BadRequest(result.Errors); // type: ActionResult<string[]>
My proposed solution
Remove Result entirely, instantiate bad results as Result<T> with an uninitialized T.
Don't use HTTP status codes here. Your result class should only contain business logic, not presentation logic.
Give your result class a boolean property to indicate success, instead of trying to divine it via the base/generic result type.
Unrelated to your posted question, but good tip: allow for multiple error messages to be returned. It can be contextually relevant to do so.
In order to enforce that a bad result does not contain a value, and a good result must contain a value, hide the constructor and rely on more descriptive static methods
public class Result<T>
{
public bool IsSuccess { get; private set; }
public T Value { get; private set; }
public string[] Errors { get; private set; }
private Result() { }
public static Result<T> Success(T value) => new Result<T>() { IsSuccess = true, Value = value };
public static Result<T> Failure(params string[] errors) => new Result<T>() { IsSuccess = false, Errors = errors };
}
Let your Mediatr requests return their specific Result<MyFoo> type, even when there is no actual value to return
// inside your Mediatr request
return Result<BlobLink>.Success(myBlobLink);
return Result<BlobLink>.Failure("This is an error message");
Remove the Mediatr pipeline behavior that tries to set the response. It doesn't belong there.
Let the controller action decide what to return, based on the result it receives.
Use IActionResult, to allow your code to return anything it wants to - since you specifically want to return different things based on your received result.
[HttpGet("{id}")]
public async Task<IActionResult> Get(Guid id)
{
Result<BlobLink> result = await _mediator.Send(new GetBlobLink.Query() { TestCaseId = id });
if(result.IsSuccess && result.Value != null)
return Ok(result.Value);
else if(result.IsSuccess && result.Value == null)
return NotFound();
else
return BadRequest(result.Errors);
}
This is just a basic example of how you can have multiple http response statuses based on the returned object. Depending on the specific controller action, the relevant return statements may change.
Note that this example takes any failed result as a bad request and not an internal server error. I generally set up my REST API to catch all unhandled exceptions using exception filters, which end up returning internal server errors (unless there is a more pertinent http status code for a certain exception type).
How (and if) to distinguish internal server errors from bad request errors isn't the focus of your question. I showed you one way of doing it for the sake of having a concrete example, others approaches still exist.
Your concerns
Skinny controllers
Skinny controllers are nice, but there are reasonable lines to draw here. The code you wrote to accommodate even skinnier controllers introduced more problems than having a slightly less skinny controller would've been.
If you wanted to, you could avoid the if success check in every controller action by passing it into a generalized ActionResult HandleResult<T>(Result<T> result) method which does it for you (e.g. in a base MyController : ApiController class from which all your api controllers derive); but beware that it may become very difficult for your reusable code to return contextually relevant error statuses.
This is the tradeoff: reusability is great but it comes at the cost of making it harder to handle specific cases and customized responses.
Nicely formatted json responses to API requests
Your ActionResult and its contents will always be serialized, so that's not really an issue to worry about.
Avoid exception control flow for validation (And therefore avoiding needing to use .net core middleware to handle and format the request exceptions).
You don't need to throw exceptions here, your result object specifically exists to return bad results without having to resort to exceptions. Simply check the success status of the result object and return the appropriate HTTP response that corresponds to the status of the result object.
That being said, exceptions are always possible. It's nigh impossible to guarantee that no exception will ever be thrown. Avoiding exceptions where possible is good, but don't skip out on actually handling any exception that may be thrown - even if you don't expect any.

How to move validation handling from a controller action to a decorator

Maintenance Edit
After using this approach for a while I found myself only adding the exact same boilerplate code in every controller so I decided to do some reflection magic. In the meantime I ditched using MVC for my views - Razor is just so tedious and ugly - so I basically use my handlers as a JSON backend. The approach I currently use is to decorate my queries/commands with a Route attribute that is located in some common assembly like this:
[Route("items/add", RouteMethod.Post)]
public class AddItemCommand { public Guid Id { get; set; } }
[Route("items", RouteMethod.Get)]
public class GetItemsQuery : IQuery<GetItemsResponse> { }
// The response inherits from a base type that handles
// validation messages and the like
public class GetItemsResponse : ServiceResponse { }
I then implemented an MVC host that extracts the annotated commands/queries and generates the controllers and handlers for me at startup time. With this my application logic is finally free of MVC cruft. The query responses are also automatically populated with validation messages. My MVC applications now all look like this:
+ MvcApp
+- Global.asax
+- Global.asax.cs - Startup the host and done
+- Web.config
After realizing I really don't use MVC outside the host - and constantly having issues with the bazillion dependencies the framework has - I implemented another host based on NServiceKit. Nothing had to be changed in my application logic and the dependencies are down to System.Web, NServiceKit and NServiceKit.Text that takes good care of the model binding. I know it's a very similar approach to how NServiceKit/ServiceStack does their stuff but I'm now totally decoupled from the web framework in use so in case a better one comes along I just implement another host and that's it.
The situation
I'm currently working on an ASP.NET MVC site that's implementing the businesslogic-view separation via the IQueryHandler and ICommandHandler abstractions (using the almighty SimpleInjector for dependency injection).
The Problem
I've got to attach some custom validation logic to a QueryHandler via a decorator and that's working pretty well in and of itself. The problem is that in the event of validation errors I want to be able to show the same view that the action would have returned but with information on the validation error of course. Here is a sample for my case:
public class HomeController : Controller
{
private readonly IQueryHandler<SomeQuery, SomeTransport> queryHandler;
public ActionResult Index()
{
try
{
var dto = this.queryHandler.Handle(new SomeQuery { /* ... */ });
// Doing something awesome with the data ...
return this.View(new HomeViewModel());
}
catch (ValidationException exception)
{
this.ModelState.AddModelErrors(exception);
return this.View(new HomeViewModel());
}
}
}
In this scenario I have some business logic that's handled by the queryHandler that is decorated with a ValidationQueryHandlerDecorator that throws ValidationExceptions when it is appropriate.
What I want it to do
What I want is something along the lines of:
public class HomeController : Controller
{
private readonly IQueryHandler<SomeQuery, SomeTransport> queryHandler;
public ActionResult Index()
{
var dto = this.queryHandler.Handle(new SomeQuery { /* ... */ });
// Doing something awesome with the data ...
// There is a catch-all in place for unexpected exceptions but
// for ValidationExceptions I want to do essentially the same
// view instantiation but with the model errors attached
return this.View(new HomeViewModel());
}
}
I've been thinking about a special ValidationErrorHandlerAttribute but then I'm losing the context and I can't really return the proper view. The same goes with the approach where I just wrap the IQueryHandler<,> with a decorator... I've seen some strange pieces of code that did some string sniffing on the route and then instantiating a new controller and viewmodel via Activator.CreateInstance - that doesn't seem like a good idea.
So I'm wondering whether there is a nice way to do this ... maybe I just don't see the wood from the trees. Thanks!
I don't think there's a way to make the action method oblivious to this, since the action method is in control of the returned view model, and in case of a validation exception you need to return a view model with all the actual data (to prevent the user from losing his changes). What you might be able to do however to make this more convenient is add an extension method for executing queries in an action:
public ActionResult Index()
{
var result = this.queryHandler.ValidatedHandle(this.ModelState, new SomeQuery { });
if (result.IsValid) {
return this.View(new HomeViewModel(result.Data));
}
else
{
return this.View(new HomeViewModel());
}
}
The ValidatedHandle extension method could look like this:
public static ValidatedResult<TResult> ValidatedHandle<TQuery, TResult>(
this IQueryHandler<TQuery, TResult> handler,
TQuery query, ModelStateDictionary modelState)
{
try
{
return new ValidatedResult<TResult>.CreateValid(handler.Handle(query));
}
catch (ValidationException ex)
{
modelState.AddModelErrors(ex);
return ValidatedResult<TResult>.Invalid;
}
}
Do note that you should only catch such validation exception if the validation is on data that the user has entered. If you send a query with parameters that are set programmatically, a validation exception simply means a programming error and you should blog up, log the exception and show a friendly error page to the user.

Categories

Resources