In ASP.NET MVC 5, I'm trying to extend my site's general Error Handling and I'm having trouble to cover ActionResult and JsonResult in once. I'm aware that JsonResult is divered from ActionResult, but when I return ActionResult I just give MVC a View, and for JsonResult, I handle the result with Javascript.
So the case, where the return value for the Action is an ActionResult, and the Application_Error() returns a JsonResult, the Json with just be printed in the view. What are you supposed to do in that situation? I rather have it to redirect to errorpage or login screen. I don't even know if I'm supossed to craft so javascript that handle the catch of the json to manually redirect or there is some "built-in" MVC mechanisme I should use.
In a simplified example I have the two Action's:
public ActionResult ShowPackages(PackagesViewModel packagesViewModel)
{
var model = PackageBalhelper.GetPackageViewModel(packagesViewModel);
return View(model);
}
[HttpPost]
public JsonResult GetDatatableRows(PackagesViewModel packagesViewModel)
{
var jsonResult = CreateJsonResult(packagesViewModel);
return jsonResult;
}
What I've so far (From MSDN)
protected void Application_Error()
{
HttpContext httpContext = HttpContext.Current;
if (httpContext != null)
{
var requestContext = ((MvcHandler)httpContext.CurrentHandler).RequestContext;
// When the request is ajax the system can automatically handle a mistake with a JSON response. Then overwrites the default response
if (requestContext.HttpContext.Request.IsAjaxRequest())
{
httpContext.Response.Clear();
string controllerName = requestContext.RouteData.GetRequiredString("controller");
IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
IController controller = factory.CreateController(requestContext, controllerName);
ControllerContext controllerContext = new ControllerContext(requestContext, (ControllerBase)controller);
var jsonResult = new JsonResult
{
Data = new
{
success = false,
serverError = "500"
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
jsonResult.ExecuteResult(controllerContext);
httpContext.Response.End();
}
else
{
LogHelper.WriteMessageToLog("In 'Application_Error'. Not an ajax request'.");
//httpContext.Response.Redirect("~/Error");
}
}
}
What are you supposed to do in that situation? I rather have it to
redirect to errorpage or login screen.
You are on the right track. We definitely do not want to return View (redirect to custom error page), if the client expects Json response from server. Otherwise, it will mess up the client's logic.
This answer might not answer your question directly. However, if you see yourself returning JsonResult a lot, you might want to consider using Web API Controller in ASP.NET MVC 5.
Web API 2.1 supports GlobalExceptionHandler in which you can customize the Http response that is sent when an unhanded application expcetion occurs.
In my case, I use Angular with ASP.NET MVC and Web API. So, I have to return unhandled exception in Json format for Ajax requests.
WebApiExceptionHandler.cs
If your application throws custom exception, you can even filter them here, and return appropriate message.
public class WebApiExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
var exception = context.Exception;
var httpException = exception as HttpException;
if (httpException != null)
{
context.Result = new WebApiErrorResult(context.Request,
(HttpStatusCode) httpException.GetHttpCode(), httpException.Message);
return;
}
/*if (exception is MyCustomException)
{
context.Result = new WebApiErrorResult(context.Request,
HttpStatusCode.NotFound, exception.Message);
return;
}*/
context.Result = new WebApiErrorResult(context.Request,
HttpStatusCode.InternalServerError,
"An error occurred while processing your request.");
}
}
WebApiErrorResult.cs
public class WebApiErrorResult : IHttpActionResult
{
private readonly string _errorMessage;
private readonly HttpRequestMessage _requestMessage;
private readonly HttpStatusCode _statusCode;
public WebApiErrorResult(
HttpRequestMessage requestMessage,
HttpStatusCode statusCode,
string errorMessage)
{
_requestMessage = requestMessage;
_statusCode = statusCode;
_errorMessage = errorMessage;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_requestMessage.CreateErrorResponse(_statusCode, _errorMessage));
}
}
WebApiConfig.cs
Finally, we register our custom exception handler with framework.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
config.Services.Replace(typeof(IExceptionHandler), new WebApiExceptionHandler());
config.Filters.Add(new AuthorizeAttribute());
}
}
Related
Short story about what I am doing and why. I had been doing view to string conversion in Mvc project, but suddenly all project moved to the REST API. But I had to use razor engine to convert my view with all model data there, so I was trying to use directly from api, but it didn't work for me, so I decided to create a Mvc controller and call it directly from API, what is missing, only ControllerContext, because when I create controller directly, it appears to be null.
Here is my Api controller
public class AttachmentController : ApiController
{
[HttpGet]
public async Task<IHttpActionResult> Get(long id)
{
try
{
var mvcController = new AttachmentMvcController();
var result = await mvcController.Get();
return Ok(result);
}
catch (Exception e)
{
return InternalServerError(e);
}
}
}
and this is my Mvc Controller
public class AttachmentMvcController : Controller
{
public AttachmentMvcController(){ }
public async Task<byte[]> Get()
{
string result;
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// HERE IS MY PROBLEM, I NEED this.ControllerContext, but it is null !!!
if (ControllerContext == null)
{
// create it and do task
var factory = DependencyResolver.Current.GetService<IControllerFactory>() ?? new DefaultControllerFactory();
AttachmentMvcController controller = factory.CreateController(ctx.RequestContext, "AttachmentMvc") as AttachmentMvcController;
RouteData route = new RouteData();
route.Values.Add("action", "ActionThatUsesControllerContext"); // ActionName, but it not required
ControllerContext newContext = new ControllerContext(new HttpContextWrapper(System.Web.HttpContext.Current), route, controller);
controller.ControllerContext = newContext;
result = await controller.ActionThatUsesControllerContext(id);
}
else
{
result = await this.ActionThatUsesControllerContext(id);
}
return result;
}
private async Task<byte[]> ActionThatUsesControllerContext()
{
{....}
// here I am trying to use helper that uses that controller context
string viewAsString = ControllerContext.RenderPartialToString("MyView", requestModel);
{....}
}
}
If anyone has idea how to get that ControllerContext or any other ways to get my razor engine render view inside ApiController, please share.
Let's imagine I have the following action
public ViewResult Products(string color)
{...}
and route which maps url "products" to this action.
According to SEO tips link /products?color=red should return
200 OK
But link /products?someOtherParametr=someValue
404 Not found
So the question is - how to handle unexisting query parameters and return 404 in this case
Considering the accepted answer at What is the proper way to send an HTTP 404 response from an ASP.NET MVC action?, there is a special ActionResult that could fulfill your expectation.
public class HomeController : Controller
{
public ViewResult Products(string color)
{
if (color != "something") // it can be replaced with any guarded clause(s)
return new HttpNotFoundResult("The color does not exist.");
...
}
}
Update:
public class HomeController : Controller
{
public ViewResult Products(string color)
{
if (Request.QueryString.Count != 1 ||
Request.QueryString.GetKey(0) != "color")
return new HttpNotFoundResult("the error message");
...
}
}
Validate this before executing the Action Method. This approach is valid for all Controller of you project or specific Action method.
Controller Action Methods
[attr] // Before executing any Action Method, Action Filter will execute to
//check for Valid Query Strings.
public class ActionResultTypesController : Controller
{
[HttpGet]
public ActionResult Index(int Param = 0)
{
return View();
}
[HttpPost]
public ActionResult Index(MyViewModel obj)
{
return View(obj);
}
}
Action Filter
public class attr : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionParameters.Count == 0 &&
System.Web.HttpContext.Current.Request.QueryString.Count > 0)
{
//When no Action Parameter exists and Query String exists.
}
else
{
// Check the Query String Key name and compare it with the Action
// Parameter name
foreach (var item in System.Web.HttpContext
.Current
.Request.QueryString.Keys)
{
if (!filterContext.ActionParameters.Keys.Contains(item))
{
// When the Query String is not matching with the Action
// Parameter
}
}
}
base.OnActionExecuting(filterContext);
}
}
If you pay attention to the above code, we are checking the Action parameter as shown in the screen show below.
What can we do in case the QueryString passed does not exists in the Action Method Parameter? We can redirect the user to another page as shown in this link.
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{
{"action", "ActionName"},
{"controller", "ControllerName"},
{"area", "Area Name"},
{"Parameter Name","Parameter Value"}
});
Or
We can do like this. The below mentioned code will be written in the OnActionExecuting Method Override
filterContext.Result = new HttpStatusCodeResult(404);
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var actionParameters = filterContext.ActionParameters.Keys;
var queryParameters = filterContext
.RequestContext
.HttpContext
.Request
.QueryString
.Keys;
// if we pass to action any query string parameter which doesn't
// exists in action we should return 404 status code
if(queryParameters.Cast<object>().Any(queryParameter
=> !actionParameters.Contains(queryParameter)))
filterContext.Result = new HttpStatusCodeResult(404);
}
Actually, if you don't want to write this is in every controller, you should override DefaultControllerFactory
This works in Asp.Net Core.
In Controller ActionMethods use below one liner code:
return StatusCode(404, "Not a valid request.");
In OnActionExecuting method, set StatusCode to context.Result as in below sample:
public override void OnActionExecuting(ActionExecutingContext context)
{
string color = HttpContext.Request?.Query["color"].ToString();
if (string.IsNullOrWhiteSpace(color))
{
// invalid or no color param, return 404 status code
context.Result = StatusCode(200, "Not a valid request.");
}
else
{
// write logic for any common functionality
}
base.OnActionExecuting(context);
}
In my MVC application, the controllers are being created using spring IOC as controller factory. If the user is requesting for a wrong controller by editing the url in the browser I am displaying in the browser 'the resource is not exist' message. Instead I want to direct him to the login page of the application.
public class ControllerFactory : IControllerFactory
{
private static readonly ILog log =
LogManager.GetLogger(typeof(ControllerFactory));
public IController CreateController(RequestContext requestContext, string controllerName)
{
log.Debug("ControllerFactory.CreateController :controllerName =" + controllerName);
controllerName = controllerName.ToLower();
IApplicationContext ctx = ContextRegistry.GetContext();
Controller ControllerObj = null;
if(ctx.ContainsLocalObject(controllerName))
{
ControllerObj = (Controller)ctx[controllerName];
log.Debug("Controller Object is created :" + controllerName);
}
else
{
//Showing error message
requestContext.HttpContext.Response.Write(String.Format("<br/><b><valign=\"center\"><Font Size=\"6\" Font Color=\"Red\"> The Resource {0} is not available</b>", controllerName));
// **Insteadd of showing the above message I want to direct user to the login page.**
// **"Account/Login"**
log.Error("there is no controller defintion with " + controllerName);
requestContext.HttpContext.Response.End();
}
return ControllerObj;
}
public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
{
return SessionStateBehavior.Default;
}
public void ReleaseController(IController controller)
{
IDisposable disposable = controller as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
}
How can I redirect user to login page("/Account/Login") instead of showing error message?
Did you try requestContext.HttpContext.Response.Redirect(url) ?
I suppose UrlHelper will also have hardcoded controller and action names, e.g.
UrlHelper url = new UrlHelper(Request.RequestContext);
var result = url.Action("Login", "Account");
But with T4MVC(http://t4mvc.codeplex.com/) you can do this:
var result = url.Action(MVC.Account.Login());
How can i prevent a partial view from being loaded by typing http://mydomain.com/site/edit/1 Is there any way of doing this?
/Martin
If you load your partials through Ajax then you can check if the request HTTP header HTTP_X_REQUESTED_WITH is present and its value is equals to XMLHttpRequest.
When a request is made through the browser that header is not present
Here is a very simple implementation of an Action Filter attribute that does the job for you
public class CheckAjaxRequestAttribute : ActionFilterAttribute
{
private const string AJAX_HEADER = "X-Requested-With";
public override void OnActionExecuting( ActionExecutingContext filterContext ) {
bool isAjaxRequest = filterContext.HttpContext.Request.Headers[AJAX_HEADER] != null;
if ( !isAjaxRequest ) {
filterContext.Result = new ViewResult { ViewName = "Unauthorized" };
}
}
}
You can use it to decorate any action where you want to check if the request is an ajax request
[HttpGet]
[CheckAjaxRequest]
public virtual ActionResult ListCustomers() {
}
I believe the [ChildActionOnly] attribute is what you're looking for.
[ChildActionOnly]
public ActionResult Edit( int? id )
{
var item = _service.GetItem(id ?? 0);
return PartialView( new EditModel(item) )
}
Phil Haack has an article using it here
protected internal RedirectToRouteResult RedirectToAction(
string actionName,
string controllerName);
means I cannot do
public static ActionResult RedirectToErrorAction(this Controller controller)
{
// redirect to custom error page
return controller.RedirectToAction("error", "support");
}
Any ideas?
You might want to consider returning a custom, shared error view instead, but if you really need to do this, you might want to consider using reflection to invoke the internal method. The former could be implemented in a base controller that becomes the foundation for all your controllers.
First example:
public class BaseController : Controller
{
private ActionResult CreateErrorResult( string message )
{
ViewData["message"] = message;
...
return new View( "CustomError" );
}
}
Second example (if getting the internal attribute via reflection works):
public static ActionResult RedirectToErrorAction(this Controller controller)
{
MethodInfo info = typeof(Controller).GetMethod( "RedirectToAction",
BindingFlags.NonPublic|BindingFlags.Instance,
null,
new Type[] { typeof(string), typeof(string) },
null );
return info.Invoke( controller, new object[] { "error", "support" } )
as ActionResult;
}