C
C#6mo ago
qqdev

ASP.NET: Elegant controller <-> service interaction

Hey! I am looking for an elegant controller <-> service interaction. Example (pseudocode):
[ApiController]
[Route("[controller]")]
public class CarController(ICarService carService) : ControllerBase
{
public async Task<IActionResult> UpdateCar(CarDto carDto)
{
var (car, statusCode) = await carService.UpdateCar(carDto);

if (statusCode != HttpStatusCode.OK)
{
return StatusCode((int)statusCode);
}

return Ok(car);
}
}
[ApiController]
[Route("[controller]")]
public class CarController(ICarService carService) : ControllerBase
{
public async Task<IActionResult> UpdateCar(CarDto carDto)
{
var (car, statusCode) = await carService.UpdateCar(carDto);

if (statusCode != HttpStatusCode.OK)
{
return StatusCode((int)statusCode);
}

return Ok(car);
}
}
My issue with this code: The service layer is returning an HTTP status code although the car service shouldn't have to do anything with HTTP stuff. I thought about adding a dedicated enum which would kinda imitate some of the HTTP status codes (OK, NotFound, Conflict). Other ideas: - Throw custom exceptions (don't like this one, exceptions are ugly) Do you have a more convenient solution for this issue? How do you deal with this kind of stuff? Thanks in advance! Related: - https://stackoverflow.com/questions/76990166/what-should-the-service-layer-return-to-the-controller-when-validating-data-in-s - https://www.reddit.com/r/dotnet/comments/yyix35/mvc_how_do_you_return_failures_from_services/
7 Replies
Keswiik
Keswiik6mo ago
At work we'd normally use exceptions for this type of flow control, but my day job is java and our services aren't implemented within the API itself (all messaging through a broker such as rabbitmq). I don't see any harm returning an internal enum that you can map to HTTP statuses.
Pobiega
Pobiega6mo ago
If the errors are meaningfully different, I would probably go with a result type (there is a tag with a bunch of result type libs) Might also want to look at something like Immediate.Handlers
kurumi
kurumi6mo ago
I usually return Result<T>, so I can set it success or failer with error message
qqdev
qqdevOP6mo ago
Thanks! I will probably return a tuple because it’s not always an "either a or b" scenario. Example: Was the entity created or updated? I could also return a Result<(Entity, CreatedOrUpdatedEnum> in that case I guess
Pobiega
Pobiega6mo ago
might be a good usecase for OneOf where you could return either a EntityUpdatedResult, EntityCreatedResult or one of a list of errors a bit unergonomic to deal with in C# imho, since there just isnt language support for DUs
qqdev
qqdevOP6mo ago
Yeah that’s what’s holding me kinda back Rust, for example, is build around Results/Options and it’s so ergonomic
Pobiega
Pobiega6mo ago
yeah Result<T> can be fairly ergonomic, with as singular return and if you dont need exhaustive error matching but full blown OneOf is nuts
Want results from more Discord servers?
Add your server