I am trying to convert my TryCatch exception handling used in multiple places to a generic method.
Aim here is for the custom method to take service method as parameter and return the result as Customer object.
I am using async Task so have written a basic ErrorHandling method as such
public Task<T> ErrorHandlingWrapper<T>(Func<Task<T>> action)
{
try
{
return action();
//return Ok(action());
}
catch (Exception ex)
{
throw new Exception(ex.Message);
//return StatusCode(500, e.Message);
}
}
In the above method I want to replicate what my original async Task does as shown below:
[HttpPost("{custId}")]
[Authorize]
public async Task<IActionResult> GetCustomer(int custId)
{
Models.Customer customer;
try
{
customer = await _service.GetCustomer(custId);
if(customer == null)
{
return BadRequest("Error retrieving customer");
}
}
catch (Exception e)
{
return StatusCode(500, e.Message);
}
return Ok(note);
}
After modifying my original method to use the new error handling method
[HttpPost("{custId}")]
[Authorize]
public async Task<IActionResult> GetCustomer(int custId)
{
return ErrorHandlingWrapper<Models.Customer>(async () =>
await _service.GetCustomer(custId)
);
}
With the above code am getting an exception
Cannot implicitly convert type System.Threading.Tasks.Task To return type IActionResult?
I am not really sure if my approach is correct?
Update
Customer.cs
public class Customer{
public int CustId { get; set; }
public string FirtName { get; set; }
public string LastName { get; set; }
}
The problem is with some wrong return types and the missing use of async/await. I refactored your example to return IActionResult. This should do:
public async Task<IActionResult> ErrorHandlingWrapper<T>(Func<Task<T>> action)
{
try
{
//return action();
return Ok(await action());
}
catch (Exception ex)
{
//throw new Exception(ex.Message);
return StatusCode(500, ex.Message);
}
}
[HttpPost("{custId}")]
[Authorize]
public async Task<IActionResult> GetCustomer(int custId)
{
return await ErrorHandlingWrapper(async () =>
await _service.GetCustomer(custId)
);
}
Related
I have a .NET appplication where there is a controller for receiving user requests, a service Service 1 which calls another service Service 2.
I have some code in the Service 2 where I query the database(DynamoDB) and get a 500 error in response when the user request values are incorrect. I want to handle this such that I catch this error/exception and send back the error message along with a 400 status code from the controller to the user. How should I modify the code to do this?
This is what I have tried. Currently, I'm just printing the error in Service 1 but I need to send it to the controller. Is sending the error message to the controller by throwing exceptions along the way the right way to do it?
The below code is similar to the actual code
Controller:
[HttpGet]
[Authorize(Policy = "Read-Entity")]
[Route("byParams/{param1}/{param2}")]
[Produces(typeof(DynamoResult<EntityResponse>))]
public async Task<IActionResult> ListByParams([FromQuery] DynamoQuery entityQuery)
{
try
{
return await HandleRequest(async () =>
{
return Ok((await _entityStore.ListByParams(entityQuery)));
});
}
catch (Exception e)
{
return BadRequest(e.Message);
}
}
Service 1:
public async Task<DynamoResult<EntityResponse>> ListByParams(DynamoQuery entityQuery)
{
results = new DynamoResult<Entity>();
try {
results = await GetPagedQueryResults(entityQuery);
}
catch (Exception e) {
Console.WriteLine(e);
}
return new DynamoResult<EntityResponse>
{
Data = results.Data.Select(_mapper.Map<EntityResponse>).ToList(),
};
}
Service 2:
private async Task<DynamoResult<TResponse>> GetPagedQueryResults(DynamoQuery query)
{
var results = new List<Document>();
try{
results = await search.GetNextSetAsync();
}
catch(Exception e){
throw new PaginationTokenException(e.Message);
}
return results;
}
[Serializable]
public class PaginationTokenException : Exception
{
public PaginationTokenException() { }
public PaginationTokenException(string message)
: base(message) {
throw new Exception(message);
}
public PaginationTokenException(string message, Exception inner)
: base(message, inner) { }
}
Assuming you want to hide implementation details from the controller (i.e. you don't want the controller to know/care that it's DynamoDB), I would create a custom exception and throw that from Service1.
Service1 would look something like this:
public async Task<DynamoResult<EntityResponse>> ListByParams(DynamoQuery entityQuery)
{
results = new DynamoResult<Entity>();
try {
results = await GetPagedQueryResults(entityQuery);
}
catch (Exception e) {
throw new MyCustomException('My error message', e);
}
return new DynamoResult<EntityResponse>
{
Data = results.Data.Select(_mapper.Map<EntityResponse>).ToList(),
};
}
In the controller you can then capture that exception explicitly:
[HttpGet]
[Authorize(Policy = "Read-Entity")]
[Route("byParams/{param1}/{param2}")]
[Produces(typeof(DynamoResult<EntityResponse>))]
public async Task<IActionResult> ListByParams([FromQuery] DynamoQuery entityQuery)
{
try
{
return await HandleRequest(async () =>
{
return Ok((await _entityStore.ListByParams(entityQuery)));
});
}
catch (MyCustomException e)
{
return BadRequest(e.Message);
}
}
The context: An AspNetCore controller I've been asked to maintain contains methods similar to the following:
// Get api/Foo/ABCXXX/item/12345
[HttpGet("{accountId}/item/{itemNumber}")]
public async Task<ActionResult<ItemViewModel>> GetFoo([FromRoute] string accountId, [FromRoute] int itemNumber)
{
if (string.IsNullOrWhiteSpace(accountId))
{
return BadRequest("accountId must be provided");
}
if (itemNumber < 0)
{
return BadRequest("itemNumber must be positive");
}
if (!await CanAccessAccountAsync(accountId))
{
return Forbid();
}
// Returns null if account or item not found
var result = _fooService.GetItem(accountId, itemNumber);
if (result == null)
{
return NotFound();
}
return result;
}
// GET api/Foo/ABCXXX
[HttpGet("{accountId}")]
public async Task<ActionResult<IEnumerable<ItemViewModel>>> GetFoos([FromRoute] string accountId)
{
if (string.IsNullOrWhiteSpace(accountId))
{
return BadRequest("accountId must be provided");
}
if (!await CanAccessAccountAsync(accountId))
{
return Forbid();
}
// Returns null if account not found
var results = _fooService.GetItems(accountId);
if (results == null)
{
return NotFound();
}
return Ok(results);
}
You may assume that there are more than 2 such methods with very similar parts.
Looking at this code makes me itchy—there appears to be a lot of repetition, but the repeated parts can't be extracted to their own methods because they contain return statements.
To me, it would make sense for these early exits to be exceptions rather than return values. Say, for the sake of argument, that I define an exception to wrap an IActionResult:
internal class ActionResultException : Exception
{
public ActionResultException(IActionResult actionResult)
{
ActionResult = actionResult;
}
public IActionResult ActionResult { get; }
}
Then I can extract some specific validations:
private void CheckAccountId(string accountId)
{
if (string.IsNullOrWhiteSpace(accountId))
{
throw new ActionResultException(BadRequest("accountId must be provided"));
}
}
private async Task CheckAccountIdAccessAsync(string accountId)
{
if (!await CanAccessAccountAsync(accountId))
{
throw new ActionResultException(Forbid());
}
}
private void CheckItemNumber(int itemNumber)
{
if (itemNumber < 0)
{
throw new ActionResultException(BadRequest("itemNumber must be positive"));
}
}
And rewrite the controller to use them:
// Get api/Foo/ABCXXX/item/12345
[HttpGet("{accountId}/item/{itemNumber}")]
public async Task<IActionResult> GetFoo([FromRoute] string accountId, [FromRoute] int itemNumber)
{
try
{
CheckAccountId(accountId);
CheckItemNumber(itemNumber);
await CheckAccountIdAccessAsync(accountId);
// Returns null if account or item not found
var result = _fooService.GetItem(accountId, itemNumber);
if (result == null)
{
return NotFound();
}
return Ok(result);
}
catch (ActionResultException e)
{
return e.ActionResult;
}
}
// GET api/Foo/ABCXXX
[HttpGet("{accountId}")]
public async Task<IActionResult> GetFoos([FromRoute] string accountId)
{
try
{
CheckAccountId(accountId);
await CheckAccountIdAccessAsync(accountId);
// Returns null if account not found
var results = _fooService.GetItems(accountId);
if (results == null)
{
return NotFound();
}
return Ok(results);
}
catch (ActionResultException e)
{
return e.ActionResult;
}
}
To get this to work, I had to wrap the controller bodies in a try to unwrap the action result from the exception.
I also had to revert the return types to IActionResult—there are reasons I may prefer not to do that. The only thing I can think of to address that problem is to go more specific with the exceptions and catches, but this seems only to shift the WET-ness from the validation code to the catch blocks.
// Exceptions
internal class AccessDeniedException : Exception { ... }
internal class BadParameterException : Exception { ... }
// Controller
private void CheckAccountId(string accountId)
{
if (string.IsNullOrWhiteSpace(accountId))
{
throw new BadParameterException("accountId must be provided");
}
}
private async Task CheckAccountIdAccessAsync(string accountId)
{
if (!await CanAccessAccountAsync(accountId))
{
throw new AccessDeniedException();
}
}
private void CheckItemNumber(int itemNumber)
{
if (itemNumber < 0)
{
throw new BadParameterException("itemNumber must be positive");
}
}
// Get api/Foo/ABCXXX/item/12345
[HttpGet("{accountId}/item/{itemNumber}")]
public async Task<IActionResult> GetFoo([FromRoute] string accountId, [FromRoute] int itemNumber)
{
try
{
...
}
catch (AccessDeniedException)
{
return Forbid();
}
catch(BadParameterException e)
{
return BadRequest(e.Message);
}
}
// GET api/Foo/ABCXXX
[HttpGet("{accountId}")]
public async Task<IActionResult> GetFoos([FromRoute] string accountId)
{
try
{
...
}
catch (AccessDeniedException)
{
return Forbid();
}
catch (BadParameterException e)
{
return BadRequest(e.Message);
}
}
There's a few simple things you can do. No need to go overboard at this point.
First and foremost, checking whether accountId is null or whitespace is completely superfluous. It's part of the route; if something isn't stuck in there, you wouldn't get here in the first place.
Second, you can make judicious use of route constraints where appropriate. For example, for your itemNumber being positive:
[HttpGet("{accountId}/item/{itemNumber:min(0)}")]
Though, honestly, I'm not sure something like /XXXX/item/-1 would even work in the first place. Regardless, specifying a min value will cover you.
Third, your CanAccessAccount check should actually be handled via resource-based authorization, built-in to ASP.NET Core.
Long and short, if you use what's already available to you, you actually don't need to do much additional validation, in the first place, negating the need to find some way to "factor it out".
In my project I'm calling a lot of WebApi with Refit (link). Basically, I define the WebApi as an interface. For example:
public interface ICustomer
{
[Get("/v1/customer")]
Task<CustomerResponse> GetDetails([Header("ApiKey")] string apikey,
[Header("Authorization")] string token,
[Header("Referer")] string referer);
}
For each WebApi, I create a client like that:
public async Task<CustomerResponse> GetDetails(string apikey, string token)
{
CustomerResponse rsl = new CustomerResponse();
rsl.Success = false;
var customer = RestService.For<ICustomer>(apiUrl);
try
{
rsl = await customer.GetDetails(apikey, token, apiUrl);
rsl.Success = true;
}
catch (ApiException ax)
{
rsl.ErrorMessage = ax.Message;
}
catch (Exception ex)
{
rsl.ErrorMessage = ex.Message;
}
return rsl;
}
The only difference between clients are the interface (in the above example code ICustomer), the return structure (in the example CustomerResponse derives from BaseResponse), and the function I have to call (in the example GetDetails with params).
I should have a base class to avoid duplicated code.
Thanks in advance.
I like when people gives you a negative feedback without any explanation or a solution. If someone has a similar problem of mine, it can find my generic class to resolve this problem.
public class BaseClient<T> where T : IGeneric
{
public const string apiUrl = "<yoururl>";
public T client;
public BaseClient() : base() {
client = RestService.For<T>(apiUrl);
}
public async Task<TResult> ExecFuncAsync<TResult>(Func<TResult> func)
where TResult : BaseResponse
{
TResult rsl = default(TResult);
T apikey = RestService.For<T>(apiUrl);
try
{
rsl = func.Invoke();
rsl.Success = true;
}
catch (ApiException ax)
{
rsl.ErrorMessage = ax.Message;
}
catch (Exception ex)
{
rsl.ErrorMessage = ex.Message;
}
return rsl;
}
public async Task<List<TResult>> ExecFuncListAsync<TResult>(Func<List<TResult>> func)
{
List<TResult> rsl = default(List<TResult>);
T apikey = RestService.For<T>(apiUrl);
try
{
rsl = func.Invoke();
}
catch (ApiException ax)
{
}
catch (Exception ex)
{
}
return rsl;
}
}
public async Task<ActionResult> Index()
{
var service = new CoreServiceFactory().GetImpersonatingService();
try
{
var data = new Impersonation()
{
ImpersonatingId = "dac733c3-01ad-447b-b0df-3a7c21fef90b",
UserId = "dac733c3-01ad-447b-b0df-3a7c21fef90b"
};
var imp = await service.Add(data);
}catch(Exception ex) { throw ex; }
return View();
}
Above is one of my controllers action method. And this works fine when the insertion is successful. This should fail if the data already exists in database(unique constraints). So when i intentionally try to make it fail(i manually add the same record in the db and then try to add it again via this action method) the action method goes into a loop or something, the exception is never thrown , chrome keeps me showing me the loading icon , looks like it went into some deadlock state. Can someone please help me understand why it goes into deadlock state when exception is thrown and how can i handle it?
Below are the reference methods
service.Add(data)
public async Task<Impersonation> Add(Impersonation t)
{
if (ValidateData(t))
{
using (var uow = GetUnitOfWork())
{
var r = GetRepository(uow);
var item = r.Add(t);
try
{
var ret = await uow.Save();
if (ret > 0)
{
return item;
}
else
{
return null;
}
}
catch (Exception ex)
{
throw ex;
}
}
}
else
{
throw new ValidationException(null, "error");
}
}
uow.Save()
public class BaseUnitOfWork : IUnitOfWork
{
public DbContext _Context { get; private set; }
public BaseUnitOfWork(DbContext context)
{
this._Context = context;
}
public async Task<int> Save()
{
try
{
var ret = await this._Context.SaveChangesAsync();
return ret;
}catch(Exception ex)
{
throw ex;
}
}
}
Here is my suggestion: in uow.Save, log the error in the catch block and return zero (do not throw any exceptions).
public class BaseUnitOfWork : IUnitOfWork
{
public DbContext _Context { get; private set; }
public BaseUnitOfWork(DbContext context)
{
this._Context = context;
}
public async Task<int> Save()
{
try
{
var ret = await this._Context.SaveChangesAsync();
return ret;
}catch(Exception ex)
{
// log the error here
return 0;
}
}
}
I'm not sure if returning the null in the Add service is a good idea or not, you might need to handle that differently too.
I have lots of controllers methods in WebAPI similar to the following:
public IHttpActionResult Delete(int id)
{
var command = new DeleteItemCommand() { Id = id };
try
{
_deleteCommandHandler.Handle(command);
}
catch (CommandHandlerResourceNotFoundException)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
catch(CommandHandlerException)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
// More catches etc...
return Ok();
}
The command handlers (in this instance _deleteCommandHandler) is injected earlier in the execution and the commands may be built in the method or using WebApi's automatic method.
What I would like to do is to encapsulate the try/catch error handling in a private method and end up with a controller similar to:
public IHttpActionResult Delete(int id)
{
var command = new DeleteItemCommand() { Id = id };
return ExecuteCommand(x => _deleteCommandHandler.Handle(command));
}
I'm not sure what the signature of the private ExecuteCommand method should be though.
I think you can Invoke your action in a method like this:
public IHttpActionResult Delete(int id)
{
return ExecuteCommand(() => {
var command = new DeleteItemCommand() { Id = id };
_deleteCommandHandler.Handle(command);
});
}
private IHttpActionResult ExecuteCommand(Action action)
{
try
{
action.Invoke();
//or: action();
}
catch (CommandHandlerResourceNotFoundException)
{
return HttpResponseException(HttpStatusCode.NotFound);
}
catch (CommandHandlerException)
{
return HttpResponseException(HttpStatusCode.InternalServerError);
}
return Ok();
}
A good reference for HttpResponseException.
I would create a custom error handler filter, and handle all possible errors there in a centralized form. That way you can just throw whatever exception from the action methods, and then they will be caught at the filter where you can handle them and change the response accordingly.
public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is NotImplementedException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
}
}
The example is taken from this article where you can find the concept in more detail.
Here's a solution similar to shA.t's answer, but the exceptions are mapped in a dictionary and the try/catch logic is in an extension method:
public class TestController:ApiController
{
public IHttpActionResult Delete(int id)
{
return ExecuteCommand(() => {
var command = new DeleteItemCommand() { Id = id };
_deleteCommandHandler.Handle(command);
});
}
private IHttpActionResult ExecuteCommand(Action action)
{
return action.SafeInvoke();
}
}
public static class ActionExtensions
{
private static readonly Dictionary<Type, HttpStatusCode> _exceptionToStatusCodeLookup = new Dictionary<Type, HttpStatusCode>
{
{typeof(CommandHandlerResourceNotFoundException), HttpStatusCode.NotFound },
{typeof(CommandHandlerException), HttpStatusCode.InternalServerError },
};
public static IHttpActionResult SafeInvoke(this Action action)
{
try
{
action();
}
catch (Exception ex)
{
var statusCode = _exceptionToStatusCodeLookup.ContainsKey(ex.GetType()) ? _exceptionToStatusCodeLookup[ex.GetType()] : HttpStatusCode.InternalServerError;
return new HttpResponseException(statusCode);
}
return new OkResult();
}
}