In my first asp.net mvc application I'm handling errors with try-catch blocks and returning specific messages to the user as Httpstatuscode.
In every crud operation there is same code block.
I tried using exceptionhandler attribute but I couldn't manage to return status code or custom message with it.
Is there any way to replace these try catch blocks on every function and return a message to user?
This is what I tried :
public class ExceptionHandlerFilterAttribute : FilterAttribute, IExceptionFilter
{
private ILogger _logger;
public void OnException(ExceptionContext filterContext)
{
_logger = new NLogLogger();
if (!filterContext.ExceptionHandled)
{
var controller = filterContext.RouteData.Values["controller"].ToString();
var action = filterContext.RouteData.Values["action"].ToString();
var message = filterContext.Exception;
_logger.Log(Business.Enums.LogLevel.Error, string.Concat("/",controller,"/",action), message);
filterContext.ExceptionHandled = true;
filterContext.Result = new ViewResult()
{
ViewName = "Error"
};
}
}
}
This is an example method :
public HttpStatusCodeResult Create(Product product)
{
if (!ModelState.IsValid) return new HttpStatusCodeResult(HttpStatusCode.BadGateway);
try
{
_productService.Create(product);
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
catch (Exception) { return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); }
}
I would like to replace repetitive try-catch blocks for a better code.
You can wrap your methods with something like this:
/// <summary>
/// Tries the specified action.
/// </summary>
/// <param name="action">The action.</param>
public static HttpStatusCodeResult Try(Action action, ModelState model)
{
if (!model.IsValid) return new HttpStatusCodeResult(HttpStatusCode.BadGateway);
try
{
action();
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
catch (Exception) { return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); }
}
And you can use your Try:
public HttpStatusCodeResult Create(Product product)
{
return Try(()=> {
_productService.Create(product);
}, ModelState);
}
Here an wrapper example in github
And the call of that try
Try to set HttpStatusCodeResult for Result property of filterContext:
filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
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);
}
}
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();
}
}
It's been awhile since I have been playing with DbContext. I have been using a Serivce / Repository / UnitOfWork pattern for a number of years now, which has enabled me to use entity framework without worrying about how to add / update and delete.
Now I have decided that for some projects using that design pattern is not suitable (mainly when working with lookup tables). Because of this I have gone back to basics.
Using the DbContext has been find for getting information, but now I have actually started creating my methods for inserting / updating data I seem to have found an issue.
I am fairly certain that the issue is something simple, but I need some help :)
Here is my method:
[HttpPost]
[Route("")]
public IHttpActionResult Import(IList<CollectionBindingModel> models)
{
// If our ModelState is invalid, return a bad request
if (!ModelState.IsValid)
return BadRequest(ModelState);
try
{
// Loop through our models
foreach (var model in models)
{
// Create our collection
var collection = new Collection()
{
CenterId = model.CenterId,
Reference = model.Reference,
CustomerReference = model.CustomerReference,
CustomerName = model.CustomerName,
CustomerBusinessName = model.CustomerBusinessName,
SupplierName = model.SupplierName,
CollectionCode = model.CollectionCode,
Status = model.Status,
CollectionDate = model.CollectionDate
};
// Add to our database context
this.DbContext.Collections.Add(collection);
}
// Save our changes
this.DbContext.SaveChanges();
}
catch (Exception ex)
{
}
// Return Ok
return Ok();
}
I have merely added the try / catch block for testing purposes.
When I call this method from my interface, it returns OK and throws no errors, but if I check the database I see there have been no inserts.
Can anyone tell me why?
Update 1
So, digging a little deeper. I have a base controller which looks like this:
/// <summary>
/// Handles the creation of universal properties and methods
/// </summary>
public class BaseController : ApiController
{
// Create our public properties
protected DatabaseContext DbContext { get { return new DatabaseContext(); } }
protected UserService UserService { get { return Request.GetOwinContext().GetUserManager<UserService>(); } }
protected RoleService RoleService { get { return Request.GetOwinContext().GetUserManager<RoleService>(); } }
protected ModelFactory ModelFactory { get { return new ModelFactory(this.Request, this.UserService); } }
/// <summary>
/// Used to return the correct error from an Identity Result
/// </summary>
/// <param name="result">The Identity Result to process</param>
/// <returns></returns>
protected IHttpActionResult GetErrorResult(IdentityResult result)
{
// If there is no result, return an internal server error
if (result == null)
return InternalServerError();
// If we have an error
if (!result.Succeeded)
{
// If we have some errors
if (result.Errors != null)
{
// For each error, add to the ModelState
foreach (var error in result.Errors)
ModelState.AddModelError("", error);
}
// If our ModelState is valid
if (ModelState.IsValid)
{
// No ModelState errors are available to send, so just return an empty BadRequest.
return BadRequest();
}
// Return a BadRequest with our ModelState
return BadRequest(ModelState);
}
// Return null if no errors are found
return null;
}
}
Which is where the DbContext is pulled from.
If I change my controller method to this:
[HttpPost]
[Route("")]
public IHttpActionResult Import(IList<CollectionBindingModel> models)
{
// If our ModelState is invalid, return a bad request
if (!ModelState.IsValid)
return BadRequest(ModelState);
try
{
using (var context = new DatabaseContext())
{
// Loop through our models
foreach (var model in models)
{
// Create our collection
var collection = new Collection()
{
CenterId = model.CenterId,
Reference = model.Reference,
CustomerReference = model.CustomerReference,
CustomerName = model.CustomerName,
CustomerBusinessName = model.CustomerBusinessName,
SupplierName = model.SupplierName,
CollectionCode = model.CollectionCode,
Status = model.Status,
CollectionDate = model.CollectionDate
};
// Add to our database context
context.Collections.Add(collection);
}
// Save our changes
context.SaveChanges();
}
}
catch (Exception ex)
{
return InternalServerError(ex);
}
// Return Ok
return Ok();
}
then everything saves OK.
You are newing up a new DbContect everytime you access your DbContext property.
Every time you access this.DbContext you get a new instance of the DbContext class because of the way you have set up your property...
protected DatabaseContext DbContext { get { return new DatabaseContext(); } }
So this line gets a new db context and adds the collection object... on the next itteration of this loop you will get ANOTHER new instance of DbContext and add the next one... you are NOT adding your collection classes to the same object instance...
// Add to our database context
this.DbContext.Collections.Add(collection);
And so does this...
// Save our changes
this.DbContext.SaveChanges();
That means you are basically calling save changes on a brand new instance of DbContext that has absolutely no changes. You should always add and attach your entities to the same instance of DbContext that you then call save changes on.
You have fixed the issue in your new code because you create a new DbContext instance in the constructor of your base class and then use that same instance throughout...
// Create our public properties
protected DatabaseContext DbContext { get; private set; }
protected UserService UserService { get { return Request.GetOwinContext().GetUserManager<UserService>(); } }
protected RoleService RoleService { get { return Request.GetOwinContext().GetUserManager<RoleService>(); } }
protected ModelFactory ModelFactory { get { return new ModelFactory(this.Request, this.UserService); } }
public BaseController()
{
this.DbContext = new DatabaseContext();
}
This would also work, when you access the property getter it will return the same instance if it has been initialised... or initialise it first and then return it...
private DatabaseContext _dbContext;
protected DatabaseContext DbContext
{
get { return this._dbContext ?? (this._dbContext = new DatabaseContext()); }
}
Ok, I have fixed this but I have no idea why.
If I change my BaseController to this:
/// <summary>
/// Handles the creation of universal properties and methods
/// </summary>
public class BaseController : ApiController
{
// Create our public properties
protected DatabaseContext DbContext { get; private set; }
protected UserService UserService { get { return Request.GetOwinContext().GetUserManager<UserService>(); } }
protected RoleService RoleService { get { return Request.GetOwinContext().GetUserManager<RoleService>(); } }
protected ModelFactory ModelFactory { get { return new ModelFactory(this.Request, this.UserService); } }
public BaseController()
{
this.DbContext = new DatabaseContext();
}
/// <summary>
/// Used to return the correct error from an Identity Result
/// </summary>
/// <param name="result">The Identity Result to process</param>
/// <returns></returns>
protected IHttpActionResult GetErrorResult(IdentityResult result)
{
// If there is no result, return an internal server error
if (result == null)
return InternalServerError();
// If we have an error
if (!result.Succeeded)
{
// If we have some errors
if (result.Errors != null)
{
// For each error, add to the ModelState
foreach (var error in result.Errors)
ModelState.AddModelError("", error);
}
// If our ModelState is valid
if (ModelState.IsValid)
{
// No ModelState errors are available to send, so just return an empty BadRequest.
return BadRequest();
}
// Return a BadRequest with our ModelState
return BadRequest(ModelState);
}
// Return null if no errors are found
return null;
}
}
then everything works.
Note, that the only thing I have changed is moving the setting of the dbcontext to the constructor.
I suspect that it is because the way it was before, created a new database context every time someone requested it. This time it sets it on creation of the base controller.
I need to implement a ActionFilterAttribute [POST] ActionResult() in the controller. The problem is that I try to “redirect” to a page if validation failed... But it does not work. Validation runs, but then returns to the ActionResult() next line and finally when the view is returned, only then “redirected” to the page listed in the validation. Ultimately what I need is to stop the ActionResult() statements and “redirect” to the page listed in the validation. I tried OnActionExecuting() and OnActionExecuted() but does not work any
I need to...
filterContext.HttpContext.Response.Redirect (loginUrl, true);
Run away, “redirecting” the page indicated
My code:
[HelperSomeValidations("")]
[HttpPost]
public ActionResult Create(Pais pais)
{
try
{
PaisBLL.saveNew(pais);
}
catch (Exception ex)
{
ViewBag.error = ex;
return View(“Error”);
}
return RedirectToAction(“Index”);
}
public class HelperSomeValidations : ActionFilterAttribute
{
public HelperSomeValidations(String permiso)
{
this.permiso = permiso;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var user = filterContext.HttpContext.Session["coco"];
if (user == null) //validates if the user just login
{
//send them off to the login page
var url = new UrlHelper(filterContext.RequestContext);
var loginUrl = url.Content(“~/Usuario/Login”);
filterContext.HttpContext.Response.Redirect(loginUrl, true);
}
else
{
if (permission != “”)
{
//does some validations with “permission”
}
}
}
}
Thks!
I know this doesn't solve the problem you have posted but I feel it's a better solution. I would personally use an AuthoriseAttribute here instead as this is what it's designed todo.
public class Authorise : AuthorizeAttribute
{
private readonly string _permissionSystemName;
public Authorise()
{
}
public Authorise(string permissionSystemName)
{
_permissionSystemName = permissionSystemName;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//DO some logic and return True or False based on whether they are allowed or not.
return false;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new
{
area = filterContext.HttpContext.Request.RequestContext.RouteData.Values["area"],
controller = "Generic",
action = "PermissionDenied"
})
);
}
}
Usage would be along the lines of:
[Authorise("SomePermissionName")]
public class MyController : Controller
{
}
Instead of calling filterContext.HttpContext.Response.Redirect(loginUrl, true), you need to set the filterContext.Result to a RedirectResult.
I am handling error in Base controller. I need to display the error stored in tempdata, Exception type in a razor view. How can I do that?
Base Controller code
protected override void OnException(ExceptionContext filterContext)
{
// if (filterContext.ExceptionHandled)
// return;
//Let the request know what went wrong
filterContext.Controller.TempData["Exception"] = filterContext.Exception.Message;
//redirect to error handler
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(
new { controller = "Error", action = "Index" }));
// Stop any other exception handlers from running
filterContext.ExceptionHandled = true;
// CLear out anything already in the response
filterContext.HttpContext.Response.Clear();
}
Razor View Code
<div>
This is the error Description
#Html.Raw(Html.Encode(TempData["Exception"]))
</div>
Try to make common exception attribute handling and register it as global filters. Like,
Common Exception Handling attribute :
/// <summary>
/// This action filter will handle the errors which has http response code 500.
/// As Ajax is not handling this error.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class HandleErrorAttribute : FilterAttribute, IExceptionFilter
{
private Type exceptionType = typeof(Exception);
private const string DefaultView = "Error";
private const string DefaultAjaxView = "_Error";
public Type ExceptionType
{
get
{
return this.exceptionType;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
this.exceptionType = value;
}
}
public string View { get; set; }
public string Master { get; set; }
public void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!filterContext.IsChildAction && (!filterContext.ExceptionHandled && filterContext.HttpContext.IsCustomErrorEnabled))
{
Exception innerException = filterContext.Exception;
// adding the internal server error (500 status http code)
if ((new HttpException(null, innerException).GetHttpCode() == 500) && this.ExceptionType.IsInstanceOfType(innerException))
{
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
// checking for Ajax request
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var result = new PartialViewResult
{
ViewName = string.IsNullOrEmpty(this.View) ? DefaultAjaxView : this.View,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData
};
filterContext.Result = result;
}
else
{
var result = this.CreateActionResult(filterContext, model);
filterContext.Result = result;
}
filterContext.ExceptionHandled = true;
}
}
}
private ActionResult CreateActionResult(ExceptionContext filterContext, HandleErrorInfo model)
{
var result = new ViewResult
{
ViewName = string.IsNullOrEmpty(this.View) ? DefaultView : this.View,
MasterName = this.Master,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData,
};
result.TempData["Exception"] = filterContext.Exception;
return result;
}
}
And Error/_Error view
#model HandleErrorInfo
<div>
This is the error Description
#TempData["Exception"]
</div>
I agree that you should never expose an exception to your view but if you really need to, try using a custom attribute.
public class CustomExceptionAttribute : System.Web.Mvc.HandleErrorAttribute
{
public override void OnException(System.Web.Mvc.ExceptionContext filterContext)
{
if (!filterContext.ExceptionHandled)
{
filterContext.Controller.TempData.Add("Exception", filterContext.Exception);
filterContext.ExceptionHandled = true;
}
}
}
public class MyController : System.Web.Mvc.Controller
{
[CustomException]
public ActionResult Test()
{
throw new InvalidOperationException();
}
}
If you override the OnException method in the base controller, then every action will get an Exception object placed in temp data. This maybe the desired behavior but with an attribute you can selectively enable this feature.
I would strongly suggest not to show any detailed exception information in any public facing application as this could end up as a security issue. However, if this is an intranet application with controlled access or if you REALLY want to show the exception details, create a DisplayTemplate and use it as follows:
<div>
Exception Details
#Html.Display(TempData["Exception"])
</div>