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();
}
}
Related
I want to call a method of controller A from controller B and get its return value.
How can I do that?
ControllerA:
[HttpGet]
public async Task<ParentModel> GetParentModel(string contractNumberPar)
{
try
{
// (...) - some code
var viewModel = new ParentModel
{
// (...) - some code
};
return viewModel;
}
catch (Exception ex)
{
throw;
}
}
ControllerB:
ParentModel viewModel = RedirectToAction(
"ControllerA",
"GetParentModel",
new
{
contractNumberPar = contractNumber
});
Error message:
Cannot implicitly convert type microsoft.aspnetcore.mvc.redirecttoaction to (....).ParentModel
You'd better create a common service,so that you can reuse it in both A and B controllers,here is a demo:
service:
public class MyService
{
public async Task<ParentModel> GetParentModel(string contractNumberPar)
{
try
{
// (...) - some code
var viewModel = new ParentModel
{
// (...) - some code
};
return viewModel;
}
catch (Exception ex)
{
throw;
}
}
}
register the service(before .net6 in Startup.cs):
services.AddScoped<MyAccountService>();
register the service(.net6,.net7 in Program.cs):
builder.Services.AddScoped<MyService>();
BController:
public class BController : Controller
{
private readonly MyService MyService;
public BController(MyService myService)
{
MyService = myService;
}
public async Task<IActionResult> IndexAsync()
{
string contractNumber="1";
ParentModel viewModel = await MyService.GetParentModel(contractNumber);
return Ok(viewModel);
}
}
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);
}
}
I have the following method:
public IActionResult DoSomeThing()
{
try
{
Some code...
}
catch (Exception)
{
return BadRequest();
}
return Ok();
}
I have another method from which I must capture what the DomeSomething () method returns to me in a variable:
public void OtherMethod()
{
var result = DoSomeThing();
if (result == Here I need to compare with the result, for example if it is a 200 result or Ok, do the action)
{
Do an action...
}
}
I need to extract the status code, for example result == 200 for it to execute an action.
We usually use HttpClient to perform such operations. You can see my example below.
In your Startup, add
services.AddHttpClient();
In your controller:
private readonly IHttpClientFactory _clientFactory;
public HomeController(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public IActionResult DoSomeThing()
{
return Ok();
}
public void OtherMethod()
{
var URL = "https://localhost:xxxx/home/DoSomeThing";
var message = new HttpRequestMessage(HttpMethod.Get, URL);
var client = _clientFactory.CreateClient();
var response = client.Send(message);
if (response.IsSuccessStatusCode)
{
//...
}
else
{
}
}
Test result:
You can see more about HttpClient here.
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 a problem with the FluentSecurity when the ActionNameSelectorAttribute is used on controller's action.
public static void Configure()
{
var applicationConfiguration = DependencyResolver.Current.GetService<IApplicationConfiguration>();
var superUserGroupName = applicationConfiguration.GetSuperUserGroupName();
var userGroupName = applicationConfiguration.GetUserGroupName();
var securityConfiguration = SecurityConfigurator.Configure(configuration =>
{
configuration.GetAuthenticationStatusFrom(() => HttpContext.Current.User.Identity.IsAuthenticated);
configuration.GetRolesFrom(System.Web.Security.Roles.GetRolesForUser);
configuration.ForAllControllers().DenyAnonymousAccess().CachePerHttpRequest();
configuration.ForAllControllers().RequireAnyRole(superUserGroupName).CachePerHttpRequest();
configuration.For<Elmah.Mvc.ElmahController>().RequireAnyRole(userGroupName).CachePerHttpRequest();
configuration.ApplyProfile<ProjectSecurityProfile>();
configuration.ApplyProfile<ProjectsSecurityProfile>();
configuration.ApplyProfile<RewecoSecurityProfile>();
configuration.DefaultPolicyViolationHandlerIs(() => new HttpUnauthorizedPolicyViolationHandler());
});
securityConfiguration.AssertAllActionsAreConfigured();
}
When I run the application under the configuration above with the AssertAllActionsAreConfigured everything seems to be correct, no exceptions. But as soon as I call the action methods in the ActualHoursAssignmentController where the HttpParamAction is used , which is the class which inherits from ActionNameSelectorAttribute I get the exception.
Security has not been configured for controller PDATA.Web.Controllers.ActualHoursAssignmentController, action ActionChoiceByNameAttributeValue Area: (not set) Controller: ActualHoursAssignment Action: ActionChoiceByNameAttributeValue
public class HttpParamActionAttribute : ActionNameSelectorAttribute
{
public static string ActionChoiceByNameAttributeValue
{
get { return "ActionChoiceByNameAttributeValue"; }
}
public override bool IsValidName([NotNull] ControllerContext controllerContext,
[NotNull] string actionName, [NotNull] MethodInfo methodInfo)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (actionName == null)
{
throw new ArgumentNullException("actionName");
}
if (methodInfo == null)
{
throw new ArgumentNullException("methodInfo");
}
if (String.IsNullOrWhiteSpace(actionName))
{
throw new ArgumentException("actionName");
}
if (String.IsNullOrWhiteSpace(methodInfo.Name))
{
throw new ArgumentException("methodInfo.Name");
}
if (actionName.Equals(methodInfo.Name, StringComparison.InvariantCultureIgnoreCase))
return true;
if (!actionName.Equals(ActionChoiceByNameAttributeValue, StringComparison.InvariantCultureIgnoreCase))
return false;
var request = controllerContext.RequestContext.HttpContext.Request;
return request[methodInfo.Name] != null;
}
}
Usage of HttpParamAction attribute in ActualHoursAssignmentController
public class ActualHoursAssignmentController : PdataBaseController
{
[HttpParamAction]
[HttpPost]
public ActionResult UpdateAssignment(ActualHoursAssignmentViewModel vm)
{
}
[HttpParamAction]
[HttpPost]
public ActionResult DeleteAssignment(ActualHoursAssignmentViewModel vm)
{
}
}
UPDATE:
Because I didn't find the solution I temporary eliminate of usage HttpParamActionAttribute. Instead of that I'm using this solution to call multiple buttons in the one Form, but the question persists, maybe it is a bug.
It looks like there is an issue in older versions of FluentSecurity with supporting Controller inheritance, see:
https://github.com/kristofferahl/FluentSecurity/wiki/Securing-controllers#securing-controllers-based-on-inheritance