How do I handle a HttpRequestValidationException and return a meaningful error using AddModelError? - model-view-controller

I would like to handle HttpRequestValidationExceptions (e.g. when html is inserted into form fields) and return the user back to the page after submission with a meaningful error to highlight the problem.
For example, on a login page, if the user types some invalid characters into the username field, then I would like to catch the HttpRequestValidationException and return the user to the login page with the username field highlighted.
I can catch the error in a class inheriting from HandleErrorAttribute using the OnException method.
Is there a way from here (or any other way) that I can get the Controller *ModelState* to add the error?
Note: I do not want to turn validation off or redirect to an error page.
e.g.:
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception is HttpRequestValidationException)
{
ModelStateDictionary ModelState = <...>
ModelState.AddModelError("Error", "Invalid chars");
filterContext.HttpContext.Response
filterContext.ExceptionHandled = true;
HttpContextBase HttpContext = filterContext.HttpContext;
HttpContext.Response.Redirect(HttpContext.Request.Path);
return;
}
}
Thanks in advance for any help you can give me!

Instead of capturing and handling the HttpRequestValidationException, you could decorate your model's properties with the [AllowHtml] data annotation and your own custom data annotation, which contains the validation rules you require. See this answer for a good example.
Your model's properties may look like this:
[AllowHtml]
[DisallowHtml]
public string SomeProperty{ get; set; }
Which looks a bit silly, but so far it's the cleanest solution I've encountered.

Related

How do I return a template(thymeleaf) in spring boot and resolve it to a particular endpoint

Short: I want to use Thymeleaf template index.html but have the url point to thanks.html.
In depth: I am trying to have a form submission take my user to a page http://localhost:8080/thanks.html. I dont want the action of the form to be thanks.html for a few different reasons but I have greatly simplified the logic below. When all of the validation of the form are passed, I want to pass in a variable to indicate which layout to use. I have that working by using a model variable called contentPage. The problem is that if i have "return "thanks.html";" in the indexSubmit Method I get an error from thymeleaf saying template not found. If I change that to "return "index.html"; everything works but the url is http://localhost:8080/ instead of http://localhost:8080/thanks.html.
#PostMapping("/")
public String indexSubmit(Model model) {
model.asMap().clear();
model.addAttribute("contentPage","layout/thanks.html");
return "thanks.html";
}
#GetMapping("/thanks.html")
public String thanks(Model model) {
model.addAttribute("contentPage","layout/thanks.html");
return "index.html";
}
I fond an answer on my own:
return "redirect:thanks.html";
Thanks,
Brian

ModelState serialization

I am trying to implement custom validation in Web API. The code in Galloway's video on the subject seems to have changed. I did download the code and the way the create action filter is like this:
public class ValidationActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest,
actionContext.ModelState);
}
}
}
When I post to my api with bad data, this is what is returned:
{"Message":"The request is invalid.","ModelState":{"user":["First name cannot start with A"]}}
Notice how ModelState is not showing individual field causing the error (like user.FirstName).
When I run their application, ModelState does have the field info (comment.Author):
{"Message":"The request is invalid.","ModelState":{"comment.Author":["Author is too long! This was validated on the server."]}}
I have the same action filter and very similar post web api. Why is my error not showing the field level detail?
You probably did set your IncludeErrorDetailPolicy to be 'never' or did set the customErrors in Web.config file to be something else. See this post for details: http://www.jefclaes.be/2012/08/aspnet-web-api-error-detail-policy-now.html
Apparently this was a known issue and has been fixed recently:
http://aspnetwebstack.codeplex.com/workitem/362

Displaying custom message in login page when redirected by `AuthorizeAttribute`

I have written a custom AuthorizeAttribute to display message in the login page. Here is the code.
public class MyAuthorizeAttribute : AuthorizeAttribute
{
public string Message { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
httpContext.Items["LoginFailMessage"] = Message;
}
return authorized;
}
}
In my action I will do
[MyAuthorize(Message = "Please login to continue")]
public ActionResult Detail()
Now, I cannot access the item HttpContext.Current.Items["LoginFailMessage"] in my view. I realize that the problem is, the item exists only for one redirection call but authorization failure is causing more than one redirection.
So, is there a way I can solve the issue? From exactly where should I pass the message?
Edit
What I am trying to do is, suppose, an Anonymous user is allowed to see a short description of something.
With the description, there is a edit and a detail link. Both edit and detail requires the user to login. So, the user will be redirected to login page if clicks either.
If user clicks edit I will display a message Please login to edit and if clicks detail may be please login to see detail in the login page.
It would be much easier for you to use the ModelState to do this, for example:
[HttpPost]
public ActionResult Login(LoginViewModel model) {
// .. check model state is valid
if (AuthorizationService.Authorize(model.Username, model.Password)) {
// .. user is authenticated
}
else {
ModelState.AddModelError("", "Login failed.");
return View(model);
}
}
Then you can use the ValidationSummary() to show the error on the login page, or, pass the name of the username or password model property in the AddModelError call to use a ValidationMessage().

MVC [Autorize] plus Roles from string

Hey I got some idea but problem is i can't make this work.
in MVC we can use [Authorize] to "protect" some actions/controllers, we can make next step and give some persmission for a Roles and Users.
[Authorize(Roles="Boss", User="Secretary"]
This working good but its kind of bad becaue in real life we dont know who will have rights for this. So idea was make strings of Roles and Users and back to authorize to make Microsoft magic on this.
[Authoize(Role=RoleString(), User=UserString())]
Ofcourse, its not working, how make this work?
The problem is that AuthorizeAttribute expects a constant for both the User and the Role strings. You will need to make a CustomAuthorizeAttribute that is something like what is found in this blog post.
So lets say you have a string that you store in your web.config that is something like this:
<add key="authorizedUsers" value="Dave,Chuck,Sally" />
and then you have your custom authorize attribute that would be something like this:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public IAuthorizationService _authorizationService { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var user = httpContext.User;
if (!user.Identity.IsAuthenticated)
{
return false;
}
var users = System.Configuration.ConfigurationManager.AppSettings["authorizedUsers"].Split(',');
if users.Contains(user.Identity.Name)
{
return true;
}
return _authorizationService.Authorize(httpContext);
}
}
Note that I threw this together rather quickly so it is not tested. You can easily modify this to get user or group names from a database so that it can be fully dynamic.

MVC Routes based on POST parameters

We have an a PHP application that we are converting to MVC. The goal is to have the application remain identical in terms of URLs and HTML (SEO and the like + PHP site is still being worked on). We have a booking process made of 3 views and in the current PHP site, all these view post back to the same URL, sending a hidden field to differentiate which page/step in the booking process is being sent back (data between pages is stored in state as the query is built up).
To replicate this in MVC, we could have a single action method that all 3 pages post to, with a single binder that only populates a portion of the model depending on which page it was posted from, and the controller looks at the model and decides what stage is next in the booking process. Or if this is possible (and this is my question), set up a route that can read the POST parameters and based on the values of the POST parameters, route to a differen action method.
As far as i understand there is no support for this in MVC routing as it stands (but i would love to be wrong on this), so where would i need to look at extending MVC in order to support this? (i think multiple action methods is cleaner somehow).
Your help would be much appreciated.
I have come upon two solutions, one devised by someone I work with and then another more elegant solution by me!
The first solution was to specify a class that extends MVcRouteHandler for the specified route. This route handler could examine the route in Form of the HttpContext, read the Form data and then update the RouteData in the RequestContext.
MapRoute(routes,
"Book",
"{locale}/book",
new { controller = "Reservation", action = "Index" }).RouteHandler = new ReservationRouteHandler();
The ReservationRouteHandler looks like this:
public class ReservationRouteHandler: MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var request = requestContext.HttpContext.Request;
// First attempt to match one of the posted tab types
var action = ReservationNavigationHandler.GetActionFromPostData(request);
requestContext.RouteData.Values["action"] = action.ActionName;
requestContext.RouteData.Values["viewStage"] = action.ViewStage;
return base.GetHttpHandler(requestContext);
}
The NavigationHandler actually does the job of looking in the form data but you get the idea.
This solution works, however, it feels a bit clunky and from looking at the controller class you would never know this was happening and wouldn't realise why en-gb/book would point to different methods, not to mention that this doesn't really feel that reusable.
A better solution is to have overloaded methods on the controller i.e. they are all called book in this case and then define your own custome ActionMethodSelectorAttribute. This is what the HttpPost Attribute derives from.
public class FormPostFilterAttribute : ActionMethodSelectorAttribute
{
private readonly string _elementId;
private readonly string _requiredValue;
public FormPostFilterAttribute(string elementId, string requiredValue)
{
_elementId = elementId;
_requiredValue = requiredValue;
}
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
if (string.IsNullOrEmpty(controllerContext.HttpContext.Request.Form[_elementId]))
{
return false;
}
if (controllerContext.HttpContext.Request.Form[_elementId] != _requiredValue)
{
return false;
}
return true;
}
}
MVC calls this class when it tries to resolve the correct action method on a controller given a URL. We then declare the action methods as follows:
public ActionResult Book(HotelSummaryPostData hotelSummary)
{
return View("CustomerDetails");
}
[FormFieldFilter("stepID", "1")]
public ActionResult Book(YourDetailsPostData yourDetails, RequestedViewPostData requestedView)
{
return View(requestedView.RequestedView);
}
[FormFieldFilter("stepID", "2")]
public ActionResult Book(RoomDetailsPostData roomDetails, RequestedViewPostData requestedView)
{
return View(requestedView.RequestedView);
}
[HttpGet]
public ActionResult Book()
{
return View();
}
We have to define the hidden field stepID on the different pages so that when the forms on these pages post back to the common URL the SelectorAttributes correctly determines which action method to invoke. I was suprised that it correctly selects an action method when an identically named method exists with not attribute set, but also glad.
I haven't looked into whether you can stack these method selectors, i imagine that you can though which would make this a pretty damn cool feature in MVC.
I hope this answer is of some use to somebody other than me. :)

Resources