Returning Service result to Controller (Minimal API)

/// <summary>
/// Deletes a reservation by its ID.
/// </summary>
/// <param name="reservationId">ID of the reservation to delete.</param>
public async Task Delete(int reservationId)
{
try
{
var reservation = await _context.Reservations
.FirstOrDefaultAsync(r => r.ReservationId == reservationId);

if (reservation is not null)
{
_context.Reservations.Remove(reservation);
await _context.SaveChangesAsync();
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
/// <summary>
/// Deletes a reservation by its ID.
/// </summary>
/// <param name="reservationId">ID of the reservation to delete.</param>
public async Task Delete(int reservationId)
{
try
{
var reservation = await _context.Reservations
.FirstOrDefaultAsync(r => r.ReservationId == reservationId);

if (reservation is not null)
{
_context.Reservations.Remove(reservation);
await _context.SaveChangesAsync();
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
I have a method in my service. If my deletion fails, how do I let the controller know it failed? Endpoint for reference:
/// <summary>
/// Deletes a reservation by ID.
/// </summary>
/// <param name="reservationId">ID of the reservation to delete.</param>
/// <returns>
/// 200 OK status code to confirm that the reservation has been deleted.
/// </returns>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> DeleteReservation([FromQuery] int reservationId)
{
var existingReservation = await reservationStorage.Read(reservationId);

if (existingReservation == null)
{
return NotFound();
}

await reservationStorage.Delete(reservationId);
return Ok();
}
/// <summary>
/// Deletes a reservation by ID.
/// </summary>
/// <param name="reservationId">ID of the reservation to delete.</param>
/// <returns>
/// 200 OK status code to confirm that the reservation has been deleted.
/// </returns>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> DeleteReservation([FromQuery] int reservationId)
{
var existingReservation = await reservationStorage.Read(reservationId);

if (existingReservation == null)
{
return NotFound();
}

await reservationStorage.Delete(reservationId);
return Ok();
}
31 Replies
packetphysicist
I was thinking to have the return type be Task<bool> but I am just wondering if there is a different way to handle it.
Pobiega
Pobiega3w ago
Exceptions would be one way, but its frowned upon by many. Personally, I'd use a proper result type.
Kringe
Kringe3w ago
why is exceptions frowned upon?
Pobiega
Pobiega3w ago
ie, your Delete method would return a Task<Result>, where Result has a bool and optionally an error message. Because exceptions should be for exceptional things, not control flow or error messages
Kringe
Kringe3w ago
if a deletion fails is that not an exception
Pobiega
Pobiega3w ago
a lot of us here on the discord like result types, so theres a pretty large collection of libraries that you could use: $resultlibs
MODiX
MODiX3w ago
see $resultlib
Pobiega
Pobiega3w ago
$resultlib
Pobiega
Pobiega3w ago
if it fails, sure. but a non-existing ID isnt really an exceptional failure
packetphysicist
Yeah but what if database has exceptions and stuff
Pobiega
Pobiega3w ago
then exception is fine if the DB connection fails, thats 100% an exception but is me passing a non-existing ID exceptional? I'm not sure it is
Kringe
Kringe3w ago
Okay ty I see
Pobiega
Pobiega3w ago
again, you could solve this with exceptions and try/catch but I among many others prefer result types for sitatuations like this
packetphysicist
Could you maybe provide a code snippet? Because what I basically want is that if there is an exception, my controller knows my delete didn't delete because of that exception. and return the proper code.
Pobiega
Pobiega3w ago
I can do you one better. I made a youtube video on this topic only a few weeks ago. https://www.youtube.com/watch?v=gHZNQe9zh6s
packetphysicist
omg youre a youtuber
Pobiega
Pobiega3w ago
I mean, uploading a few videos is a pretty low bar...
packetphysicist
Do you think this would properly handle my case?
Pobiega
Pobiega3w ago
if you dont wanna sit through the theory, https://github.com/jscarle/LightResults is pretty nice
return Result.Ok();
//or
return Result.Fail("Operation failed!");

//in your controller
var result = await Service.Delete(x);
if (result.IsFailed)
{
return ... // your http error code stuff here
}

return Ok();
return Result.Ok();
//or
return Result.Fail("Operation failed!");

//in your controller
var result = await Service.Delete(x);
if (result.IsFailed)
{
return ... // your http error code stuff here
}

return Ok();
packetphysicist
kk, I will check it out.
Pobiega
Pobiega3w ago
I personally tend to use Remora.Results, but its a bit too strict for some people the authors of most of those libs hang around here, or have done in the past but even without a library its a pretty simple concept, as my video demonstrates yes.
packetphysicist
Why Remora? Any reason?
Pobiega
Pobiega3w ago
because it forces errors to be objects you absolutely dont have to use remora just necause its my personal preference :p I also tend to use quite a bit of other Remora libraries in my recent projects, and they all use that internally so it just makes sense there
packetphysicist
Aren't they already objects..? Exception is an object, no? Or am I completely misunderstanding
Pobiega
Pobiega3w ago
No you're fine. Exceptions are objects. But in cases where you don't have an exception, but still want information about the error
packetphysicist
If there's no exception there's no error, right? Or are exceptions only handled errors?
Pobiega
Pobiega3w ago
exceptions are almost always errors, but there are exceptions to most rules. There are also errors that are not exceptions I mean "error" here as anything that can cause a method to fail that might be "the user is stupid and sent a string instead of an int" or "no hit for given id 5 in database" these may or may not be exceptions, but they are certainly errors
MODiX
MODiX3w ago
Pobiega
was it... this?
[HttpGet]
public async Task<ActionResult<TodoListDto>> Get(int id)
{
var query = new GetTodoListQuery()
{
Id = id
};

var result = await _mediator.Send(query);
if (!result.IsSuccess)
{
return result.Error switch
{
NotFoundError notFoundError => NotFound(notFoundError.Message),
ValidationError validationError => BadRequest(validationError.ValidationFailures),
_ => Problem(detail: result.Error.Message, title: "Internal Server Error", statusCode: 500)
};
}

return _mapper.Map<TodoListDto>(result.Entity);
}
[HttpGet]
public async Task<ActionResult<TodoListDto>> Get(int id)
{
var query = new GetTodoListQuery()
{
Id = id
};

var result = await _mediator.Send(query);
if (!result.IsSuccess)
{
return result.Error switch
{
NotFoundError notFoundError => NotFound(notFoundError.Message),
ValidationError validationError => BadRequest(validationError.ValidationFailures),
_ => Problem(detail: result.Error.Message, title: "Internal Server Error", statusCode: 500)
};
}

return _mapper.Map<TodoListDto>(result.Entity);
}
Quoted by
<@105026391237480448> from #help-0 (click here)
React with ❌ to remove this embed.
Pobiega
Pobiega3w ago
here is an example NotFoundError => ok send the user a 404 not found ValidationError => send the user a 400 bad request some other error? => send the user a 500 Internal server error no error? send the user a 200 ok with the mapped result
packetphysicist
Mhmmmmmm Okay I think I'm getting the gist of it
Want results from more Discord servers?
Add your server