Spring 5 Binder, #InitBinder, not populating model correctly - spring-mvc

I am upgrading a Spring 2.5 web app to Spring 5.0.3. I am using String form tags. In my controller I have:
#InitBinder
public void initBinder(WebDataBinder binder, HttpServletRequest request) {
CapTypeEditor capTypeEditor = new CapTypeEditor(this.getDAOFactory());
binder.registerCustomEditor(CapType.class, "order.capType.id", capTypeEditor);
}
I see that this is called twice (why?) on GET and twice on POST. On GET, the request.getParameter("order.capType.id") is null, the same in POST has the correct ID. But then in my submit() POST method, capType is not null, but it has only the id populated and not its name:
#RequestMapping(value = "/es/orderinfo.html", method=RequestMethod.POST)
public ModelAndView submit(#RequestParam("id") long id,
#ModelAttribute("command")OrderInfoBean bean,
BindingResult errors, ModelMap model,
HttpServletRequest request) {
Order order = bean.getOrder();
CapType ct = order.getCapType();
...
}
My CapType editor is never called:
public class CapTypeEditor extends PropertyEditorSupport {
DAOFactory daoFactory;
public CapTypeEditor(DAOFactory daoFactory){
this.daoFactory = daoFactory;
}
public void setAsText(String text){
if(StringUtils.isBlank(text)||StringUtils.isEmpty(text) ){
this.setValue(null);
return;
}
Long id = Long.valueOf(text);
CapType capType = daoFactory.getCapTypeDAO().read(id);
this.setValue(capType);
}
public String getAsText(Object value){
if(value == null) return StringUtils.EMPTY;
CapType capType = (CapType)value;
return capType.getId().toString();
}
}
My JSP looks like this:
<form:select path="order.orderType.id" tabindex="100" cssStyle="width:149px">
<form:option value="">none</form:option>
<form:options items="${refData.orderTypes }" itemValue="id" itemLabel="typeName" />
</form:select>

You are putting invalid property path while registering the custom editor. Do this:
binder.registerCustomEditor(CapType.class, "capType", capTypeEditor);
Assuming, OrderInfoBean contains a field capType.
binder.registerCustomEditor(CapType.class, "order.capType", capTypeEditor);
As OrderInfoBean cotnains Order which contains CapType.
And in JSP, use capType order.capType directly as bindpath.

Actually my old editors registered in #InitBinder were OK. And #minarmahmud was right about the not having .id in the . Once I added a proper equals and hashcode function to my hibernate mapped model classes (e.g. CapType) everything worked, both the default values on the view HTML and the full auto mapping of my models together on POST. So in Model CapType:
#Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final CapType capType = (CapType) o;
return Objects.equals(id, capType.id) &&
Objects.equals(typeName, capType.getTypeName());
}
#Override
public int hashCode() {
return Objects.hash(id, typeName);
}

Related

A durable entity does not deserialize

I am trying to use a durable entity in my Azure Function to cache some data. However, when I try to retrieve the entity (state) for the first time, I get an exception indicating an issue during the entity deserialization.
Here is my entity class and related code
[JsonObject(MemberSerialization.OptIn)]
public class ActionTargetIdCache : IActionTargetIdCache
{
[JsonProperty("cache")]
public Dictionary<string, ActionTargetIdsCacheItemInfo> Cache { get; set; } = new Dictionary<string, ActionTargetIdsCacheItemInfo>();
public void CacheCleanup(DateTime currentUtcTime)
{
foreach (string officeHolderId in Cache.Keys)
{
TimeSpan cacheItemAge = currentUtcTime - Cache[officeHolderId].lastUpdatedTimeStamp;
if (cacheItemAge > TimeSpan.FromMinutes(2))
{
Cache.Remove(officeHolderId);
}
}
}
public void DeleteActionTargetIds(string officeHolderId)
{
if (this.Cache.ContainsKey(officeHolderId))
{
this.Cache.Remove(officeHolderId);
}
}
public void DeleteState()
{
Entity.Current.DeleteState();
}
public void SetActionTargetIds(ActionTargetIdsCacheEntry entry)
{
this.Cache[entry.Key] = entry.Value;
}
public Task<ActionTargetIdsCacheItemInfo> GetActionTargetIdsAsync(string officeHolderId)
{
if (this.Cache.ContainsKey(officeHolderId))
{
return Task.FromResult(Cache[officeHolderId]);
}
else
{
return Task.FromResult(new ActionTargetIdsCacheItemInfo());
}
}
// public void Reset() => this.CurrentValue = 0;
// public int Get() => this.CurrentValue;
[FunctionName(nameof(ActionTargetIdCache))]
public static Task Run([EntityTrigger] IDurableEntityContext ctx)
=> ctx.DispatchAsync<ActionTargetIdCache>();
}
public class ActionTargetIdsCacheEntry
{
// officeHolderId
public string Key { get; set; } = string.Empty;
public ActionTargetIdsCacheItemInfo Value { get; set; } = new ActionTargetIdsCacheItemInfo();
}
[JsonObject(MemberSerialization.OptIn)]
public class ActionTargetIdsCacheItemInfo : ISerializable
{
public ActionTargetIdsCacheItemInfo()
{
lastUpdatedTimeStamp = DateTime.UtcNow;
actionTargetIds = new List<string>();
}
public ActionTargetIdsCacheItemInfo(SerializationInfo info, StreamingContext context)
{
lastUpdatedTimeStamp = info.GetDateTime("lastUpdated");
actionTargetIds = (List<string>)info.GetValue("actionTargetIds", typeof(List<string>));
}
[JsonProperty]
public DateTimeOffset lastUpdatedTimeStamp { get; set; } = DateTimeOffset.UtcNow;
[JsonProperty]
public List<string> actionTargetIds { get; set; } = new List<string>();
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("lastUpdated", lastUpdatedTimeStamp);
info.AddValue("actionTargetIds", actionTargetIds);
}
}
public interface IActionTargetIdCache
{
void CacheCleanup(DateTime currentUtcTime);
void DeleteActionTargetIds(string officeHolderId);
void DeleteState();
void SetActionTargetIds(ActionTargetIdsCacheEntry item);
// Task Reset();
Task<ActionTargetIdsCacheItemInfo> GetActionTargetIdsAsync(string officeHolderId);
// void Delete();
}
Here is the exception I get during the first attempt to access the state from an orchestration using the GetActionTargetIdsAsync method:
Exception has occurred: CLR/Microsoft.Azure.WebJobs.Extensions.DurableTask.EntitySchedulerException
Exception thrown: 'Microsoft.Azure.WebJobs.Extensions.DurableTask.EntitySchedulerException' in System.Private.CoreLib.dll: 'Failed to populate entity state from JSON: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'PolTrack.CdbGetFunctionApp.ActionTargetIdsCacheItemInfo' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path 'cache.officeHolderId1', line 1, position 29.'
Inner exceptions found, see $exception in variables window for more details.
Innermost exception Newtonsoft.Json.JsonSerializationException : Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'PolTrack.CdbGetFunctionApp.ActionTargetIdsCacheItemInfo' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path 'cache.officeHolderId1', line 1, position 29.
Could someone with the sufficient SO privileges please add the tag azure-durable-entities.
I did manage to get around this by following #silent suggestion. I re-designed the entity class to only use CLR types. In my case, that meant replacing Dictionary<string, ActionTargetIdsCacheItemInfo> with two dictionaries Dictionary<string, List<string>> and Dictionary<string, DateTimeOffset>.

Spring MVC checkboxes HTTP Status 400 The request sent by the client was syntactically incorrect

I have this simple form with 2 checkboxes and a submit button. When I submit the form, I get this error
HTTP Status 400 The request sent by the client was syntactically incorrect.
This is my POJO:
public class Menu{
private String day;
private String name;
private int price;
public Menu(){
}
public Menu(String day, String name, int price) {
this.day = day;
this.name = name;
this.price = price;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDay() {
return day;
}
public void setDay(String l) {
this.day = l;
}
#Override
public int hashCode() {
int hash = 3;
hash = 7 * hash + this.day.hashCode();
hash = 7 * hash + this.name.hashCode();
return hash;
}
#Override
public boolean equals(Object object) {
boolean result = false;
System.out.println("ARE YOU EVER CALLLED HOW MANY TIMES");
if (object == null || object.getClass() != getClass()) {
result = false;
} else {
Menu sc = (Menu) object;
if (this.day == sc.getDay() && this.name == sc.getName()
&& this.price == sc.getPrice()) {
result = true;
}
}
return result;
}
This is my Order class:
public class Order {
private List<Menu> menus = new ArrayList<Menu>();
public Order(){}
public Order(ArrayList<Menu> menus){
this.menus = menus;
}
public List<Menu> getMenus() {
return menus;
}
public void setMenus(ArrayList<Menu> menus) {
this.menus = menus;
}
}
And this is my controller:
#Controller
public class RestaurantController {
#RequestMapping(value = "/menu", method = RequestMethod.GET)
public String menuPage(Model model){
Order o = new Order();
ArrayList<Menu> m = new ArrayList<Menu>();
m.add(new Menu("Sunday", "Phir Aloo", 12));
m.add(new Menu("Sunday", "Phir Cholay", 9));
model.addAttribute("today", m);
model.addAttribute("order", o);
return "/menu";
}
#RequestMapping(value = "/confirm", method = RequestMethod.POST)
public String done(#ModelAttribute(value="order") Order order, Model model){
return "/confirm";
}
And this is my menu.jsp: (http://localhost:9080/res/menu)
<form:form modelAttribute="order" method="post" action="/res/confirm">
<c:forEach items="${today}" var="r">
<form:checkbox path="menus" value="${r}" label="${r.name } ${r.price }" />
</c:forEach>
<input type="submit" value="Submit Data">
</form:form>
Now I just expect Class Order's property 'menus' to be filled with selected checkboxes. Instead I get this error "The request sent by the client was syntactically incorrect. I have looked up every possible answer on this website but nothing seems to be solving the problem.
After #R Sawant's suggestion I was able to solve the problem. Here is my Property Editor.
public class MenuTypeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
setValue(new Menu(text.toUpperCase()));
}
}
I kept this class inside the same package which has Menu.java and Order.java
Now inside my controller wrote this:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Menu.class, new MenuTypeEditor());
}
And voila! Magic happened.
I hope this answer can help someone.
The problem is with the value you are posting when the check box is ticked. Look at the below code
<form:checkbox path="menus" **value="${r}"** label="${r.name } ${r.price }" />
See what have assigned to value attribute in the above line. Its whole object of menu. It will essentially post the toString() representation of the object. Since you have not implemented toString() for Menu class, something like Menu#1ed2e55e gets posted for the check box value. Spring is unable to convert this to something meaningful and hence the problem.
You have to make use of property editor support to deal with these type of situations. Property editor will help you convert string to Object and vice versa. In your case String to Menu object and vice versa. Take a look at examples of property editors. Hope this helps
Edit:- a google search got this result. Take a look at it, may help you to understand.

Access current Model in spring-mvc

I'm learning Spring-MVC 4 comming from asp.net MVC and I was looking for a way to pass data to the View without having to declare a Model Atrribute in every call.
For example, now I have this.
public class BaseController {
public void AddMessage(Model model, String m) {
Model.addAttribute("msg", m);
}
}
public class PersonController extends BaseController{
#RequestMapping("details/{Id}")
public String details(#PathVariable int Id, Model model) {
Person p = service.LoadById(Id);
if(p == null) {
AddMessage(model, "Record not found...");
} else {
model.addAttribute("bean", q);
}
return "person/details";
}
}
But what I would really like is to have a way to acess that Model instance in my base controller methods without having to pass it on as an argument. Similar to the usage of ViewData or TempData in asp.net MVC.
Is it possible to pass data to the view in this fashion?
Thank you
If you want to avoid passing the Model as a method parameter, you can use ModelAttribute annotation in a method:
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/ModelAttribute.html
Just annotate the method and Spring will automatically add what the method returns to the model.
#ModelAttribute
public Stuff addStuffToModel() {
Stuff stuff = new Stuff("dummy data");
return stuff; // stuff is added to the model
}
I managed to work around this issue using a request interceptor. Essentially:
On my base controller class:
public abstract class BaseController {
protected List<UserViewMessage> viewMessages;
public List<UserViewMessage> getViewMessages() {
if (viewMessages == null) {
viewMessages = new ArrayList<UserViewMessage>();
}
return viewMessages;
}
public void addMessage(String message, UserViewMessageType type) {
getViewMessages().add(new UserViewMessage(message, type));
}
public void clearMessages() {
if (viewMessages != null) {
viewMessages.clear();
}
}
}
Then, I added an interceptor to copy the messages collection to the Model:
public class RequestInterceptor extends HandlerInterceptorAdapter {
private static String MODEL_MESSAGES_KEY = "ModelMessageList_";
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (handler instanceof org.springframework.web.method.HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
if (handlerMethod != null) {
Object bean = handlerMethod.getBean();
if (bean != null && bean instanceof BaseController) {
BaseController bc = (BaseController) bean;
bc.clearMessages();
}
}
}
return super.preHandle(request, response, handler);
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
if (handler instanceof org.springframework.web.method.HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
if (handlerMethod != null && modelAndView != null) {
Object bean = handlerMethod.getBean();
if (bean != null && bean instanceof BaseController) {
BaseController bc = (BaseController) bean;
if (bc.getViewMessages() != null) {
modelAndView.addObject(MODEL_MESSAGES_KEY, bc.getViewMessages());
}
}
}
}
super.postHandle(request, response, handler, modelAndView);
}
}
Which, on PreHandle, clears any messages on the base controller collection. After the request (PostHandle) and since the Model is available, I copy the message collection to the Model, thus making it available on my views like so:
<div class="row">
<div class="col-lg-12">
<c:forEach var="messageItem" items="${_ModelMessageList_}">
<div class="alert alert-info"><c:out value="${messageItem.message}" /></div>
</c:forEach>
</div>
</div>
It's not optimal, but it works.

How to return a view for HttpNotFound() in ASP.Net MVC 3?

Is there a way to return the same view every time a HttpNotFoundResult is returned from a controller? How do you specify this view? I'm guessing configuring a 404 page in the web.config might work, but I wanted to know if there was a better way to handle this result.
Edit / Follow up:
I ended up using the solution found in the second answer to this question with some slight tweaks for ASP.Net MVC 3 to handle my 404s: How can I properly handle 404s in ASP.Net MVC?
HttpNotFoundResult doesn't render a view. It simply sets the status code to 404 and returns an empty result which is useful for things like AJAX but if you want a custom 404 error page you could throw new HttpException(404, "Not found") which will automatically render the configured view in web.config:
<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite">
<error statusCode="404" redirect="/Http404.html" />
</customErrors>
This solution combines IResultFilter and IExceptionFilter to catch either thrown HttpException or returned HttpStatusCodeResult from within an action.
public class CustomViewForHttpStatusResultFilter: IResultFilter, IExceptionFilter
{
string viewName;
int statusCode;
public CustomViewForHttpStatusResultFilter(HttpStatusCodeResult prototype, string viewName)
: this(prototype.StatusCode, viewName) {
}
public CustomViewForHttpStatusResultFilter(int statusCode, string viewName) {
this.viewName = viewName;
this.statusCode = statusCode;
}
public void OnResultExecuted(ResultExecutedContext filterContext) {
HttpStatusCodeResult httpStatusCodeResult = filterContext.Result as HttpStatusCodeResult;
if (httpStatusCodeResult != null && httpStatusCodeResult.StatusCode == statusCode) {
ExecuteCustomViewResult(filterContext.Controller.ControllerContext);
}
}
public void OnResultExecuting(ResultExecutingContext filterContext) {
}
public void OnException(ExceptionContext filterContext) {
HttpException httpException = filterContext.Exception as HttpException;
if (httpException != null && httpException.GetHttpCode() == statusCode) {
ExecuteCustomViewResult(filterContext.Controller.ControllerContext);
// This causes ELMAH not to log exceptions, so commented out
//filterContext.ExceptionHandled = true;
}
}
void ExecuteCustomViewResult(ControllerContext controllerContext) {
ViewResult viewResult = new ViewResult();
viewResult.ViewName = viewName;
viewResult.ViewData = controllerContext.Controller.ViewData;
viewResult.TempData = controllerContext.Controller.TempData;
viewResult.ExecuteResult(controllerContext);
controllerContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
}
You can register this filter so, specifying either the http status code of the HttpException or the concrete HttpStatusCodeResult for which you want to display the custom view.
GlobalFilters.Filters.Add(new CustomViewForHttpStatusResultFilter(new HttpNotFoundResult(), "Error404"));
// alternate syntax
GlobalFilters.Filters.Add(new CustomViewForHttpStatusResultFilter(404, "Error404"));
It handles exceptions and HttpStatusCodeResult thrown or returned within an action. It won't handle errors that occur before MVC selects a suitable action and controller like this common problems:
Unknown routes
Unknown controllers
Unknown actions
For handling these types of NotFound errors, combine this solution with other solutions to be found in stackoverflow.
Useful info from #Darin Dimitrov that HttpNotFoundResult is actually returning empty result.
After some study. The workaround for MVC 3 here is to derive all HttpNotFoundResult, HttpUnauthorizedResult, HttpStatusCodeResult classes and implement new (overriding it) HttpNotFound() method in BaseController.
It is best practise to use base Controller so you have 'control' over all derived Controllers.
I create new HttpStatusCodeResult class, not to derive from ActionResult but from ViewResult to render the view or any View you want by specifying the ViewName property. I follow the original HttpStatusCodeResult to set the HttpContext.Response.StatusCode and HttpContext.Response.StatusDescription but then base.ExecuteResult(context) will render the suitable view because again I derive from ViewResult. Simple enough is it? Hope this will be implemented in the MVC core.
See my BaseController bellow:
using System.Web;
using System.Web.Mvc;
namespace YourNamespace.Controllers
{
public class BaseController : Controller
{
public BaseController()
{
ViewBag.MetaDescription = Settings.metaDescription;
ViewBag.MetaKeywords = Settings.metaKeywords;
}
protected new HttpNotFoundResult HttpNotFound(string statusDescription = null)
{
return new HttpNotFoundResult(statusDescription);
}
protected HttpUnauthorizedResult HttpUnauthorized(string statusDescription = null)
{
return new HttpUnauthorizedResult(statusDescription);
}
protected class HttpNotFoundResult : HttpStatusCodeResult
{
public HttpNotFoundResult() : this(null) { }
public HttpNotFoundResult(string statusDescription) : base(404, statusDescription) { }
}
protected class HttpUnauthorizedResult : HttpStatusCodeResult
{
public HttpUnauthorizedResult(string statusDescription) : base(401, statusDescription) { }
}
protected class HttpStatusCodeResult : ViewResult
{
public int StatusCode { get; private set; }
public string StatusDescription { get; private set; }
public HttpStatusCodeResult(int statusCode) : this(statusCode, null) { }
public HttpStatusCodeResult(int statusCode, string statusDescription)
{
this.StatusCode = statusCode;
this.StatusDescription = statusDescription;
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
context.HttpContext.Response.StatusCode = this.StatusCode;
if (this.StatusDescription != null)
{
context.HttpContext.Response.StatusDescription = this.StatusDescription;
}
// 1. Uncomment this to use the existing Error.ascx / Error.cshtml to view as an error or
// 2. Uncomment this and change to any custom view and set the name here or simply
// 3. (Recommended) Let it commented and the ViewName will be the current controller view action and on your view (or layout view even better) show the #ViewBag.Message to produce an inline message that tell the Not Found or Unauthorized
//this.ViewName = "Error";
this.ViewBag.Message = context.HttpContext.Response.StatusDescription;
base.ExecuteResult(context);
}
}
}
}
To use in your action like this:
public ActionResult Index()
{
// Some processing
if (...)
return HttpNotFound();
// Other processing
}
And in _Layout.cshtml (like master page)
<div class="content">
#if (ViewBag.Message != null)
{
<div class="inlineMsg"><p>#ViewBag.Message</p></div>
}
#RenderBody()
</div>
Additionally you can use a custom view like Error.shtml or create new NotFound.cshtml like I commented in the code and you may define a view model for the status description and other explanations.
protected override void HandleUnknownAction(string actionName)
{
ViewBag.actionName = actionName;
View("Unknown").ExecuteResult(this.ControllerContext);
}
Here is true answer which allows fully customize of error page in single place.
No need to modify web.confiog or create sophisticated classes and code.
Works also in MVC 5.
Add this code to controller:
if (bad) {
Response.Clear();
Response.TrySkipIisCustomErrors = true;
Response.Write(product + I(" Toodet pole"));
Response.StatusCode = (int)HttpStatusCode.NotFound;
//Response.ContentType = "text/html; charset=utf-8";
Response.End();
return null;
}
Based on http://www.eidias.com/blog/2014/7/2/mvc-custom-error-pages
Please follow this if you want httpnotfound Error in your controller
public ActionResult Contact()
{
return HttpNotFound();
}

Best way to trim strings after data entry. Should I create a custom model binder?

I'm using ASP.NET MVC and I'd like all user entered string fields to be trimmed before they're inserted into the database. And since I have many data entry forms, I'm looking for an elegant way to trim all strings instead of explicitly trimming every user supplied string value. I'm interested to know how and when people are trimming strings.
I thought about perhaps creating a custom model binder and trimming any string values there...that way, all my trimming logic is contained in one place. Is this a good approach? Are there any code samples that do this?
public class TrimModelBinder : DefaultModelBinder
{
protected override void SetProperty(ControllerContext controllerContext,
ModelBindingContext bindingContext,
System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
{
if (propertyDescriptor.PropertyType == typeof(string))
{
var stringValue = (string)value;
if (!string.IsNullOrWhiteSpace(stringValue))
{
value = stringValue.Trim();
}
else
{
value = null;
}
}
base.SetProperty(controllerContext, bindingContext,
propertyDescriptor, value);
}
}
How about this code?
ModelBinders.Binders.DefaultBinder = new TrimModelBinder();
Set global.asax Application_Start event.
This is #takepara same resolution but as an IModelBinder instead of DefaultModelBinder so that adding the modelbinder in global.asax is through
ModelBinders.Binders.Add(typeof(string),new TrimModelBinder());
The class:
public class TrimModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueResult== null || valueResult.AttemptedValue==null)
return null;
else if (valueResult.AttemptedValue == string.Empty)
return string.Empty;
return valueResult.AttemptedValue.Trim();
}
}
based on #haacked post:
http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx
One improvement to #takepara answer.
Somewere in project:
public class NoTrimAttribute : Attribute { }
In TrimModelBinder class change
if (propertyDescriptor.PropertyType == typeof(string))
to
if (propertyDescriptor.PropertyType == typeof(string) && !propertyDescriptor.Attributes.Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute)))
and you can mark properties to be excluded from trimming with [NoTrim] attribute.
In ASP.Net Core 2 this worked for me. I'm using the [FromBody] attribute in my controllers and JSON input. To override the string handling in the JSON deserialization I registered my own JsonConverter:
services.AddMvcCore()
.AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Insert(0, new TrimmingStringConverter());
})
And this is the converter:
public class TrimmingStringConverter : JsonConverter
{
public override bool CanRead => true;
public override bool CanWrite => false;
public override bool CanConvert(Type objectType) => objectType == typeof(string);
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
if (reader.Value is string value)
{
return value.Trim();
}
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
With improvements in C# 6, you can now write a very compact model binder that will trim all string inputs:
public class TrimStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var attemptedValue = value?.AttemptedValue;
return string.IsNullOrWhiteSpace(attemptedValue) ? attemptedValue : attemptedValue.Trim();
}
}
You need to include this line somewhere in Application_Start() in your Global.asax.cs file to use the model binder when binding strings:
ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());
I find it is better to use a model binder like this, rather than overriding the default model binder, because then it will be used whenever you are binding a string, whether that's directly as a method argument or as a property on a model class. However, if you override the default model binder as other answers here suggest, that will only work when binding properties on models, not when you have a string as an argument to an action method
Edit: a commenter asked about dealing with the situation when a field should not be validated. My original answer was reduced to deal just with the question the OP had posed, but for those who are interested, you can deal with validation by using the following extended model binder:
public class TrimStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var shouldPerformRequestValidation = controllerContext.Controller.ValidateRequest && bindingContext.ModelMetadata.RequestValidationEnabled;
var unvalidatedValueProvider = bindingContext.ValueProvider as IUnvalidatedValueProvider;
var value = unvalidatedValueProvider == null ?
bindingContext.ValueProvider.GetValue(bindingContext.ModelName) :
unvalidatedValueProvider.GetValue(bindingContext.ModelName, !shouldPerformRequestValidation);
var attemptedValue = value?.AttemptedValue;
return string.IsNullOrWhiteSpace(attemptedValue) ? attemptedValue : attemptedValue.Trim();
}
}
Another variant of #takepara's answer but with a different twist:
1) I prefer the opt-in "StringTrim" attribute mechanism (rather than the opt-out "NoTrim" example of #Anton).
2) An additional call to SetModelValue is required to ensure the ModelState is populated correctly and the default validation/accept/reject pattern can be used as normal, i.e. TryUpdateModel(model) to apply and ModelState.Clear() to accept all changes.
Put this in your entity/shared library:
/// <summary>
/// Denotes a data field that should be trimmed during binding, removing any spaces.
/// </summary>
/// <remarks>
/// <para>
/// Support for trimming is implmented in the model binder, as currently
/// Data Annotations provides no mechanism to coerce the value.
/// </para>
/// <para>
/// This attribute does not imply that empty strings should be converted to null.
/// When that is required you must additionally use the <see cref="System.ComponentModel.DataAnnotations.DisplayFormatAttribute.ConvertEmptyStringToNull"/>
/// option to control what happens to empty strings.
/// </para>
/// </remarks>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class StringTrimAttribute : Attribute
{
}
Then this in your MVC application/library:
/// <summary>
/// MVC model binder which trims string values decorated with the <see cref="StringTrimAttribute"/>.
/// </summary>
public class StringTrimModelBinder : IModelBinder
{
/// <summary>
/// Binds the model, applying trimming when required.
/// </summary>
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Get binding value (return null when not present)
var propertyName = bindingContext.ModelName;
var originalValueResult = bindingContext.ValueProvider.GetValue(propertyName);
if (originalValueResult == null)
return null;
var boundValue = originalValueResult.AttemptedValue;
// Trim when required
if (!String.IsNullOrEmpty(boundValue))
{
// Check for trim attribute
if (bindingContext.ModelMetadata.ContainerType != null)
{
var property = bindingContext.ModelMetadata.ContainerType.GetProperties()
.FirstOrDefault(propertyInfo => propertyInfo.Name == bindingContext.ModelMetadata.PropertyName);
if (property != null && property.GetCustomAttributes(true)
.OfType<StringTrimAttribute>().Any())
{
// Trim when attribute set
boundValue = boundValue.Trim();
}
}
}
// Register updated "attempted" value with the model state
bindingContext.ModelState.SetModelValue(propertyName, new ValueProviderResult(
originalValueResult.RawValue, boundValue, originalValueResult.Culture));
// Return bound value
return boundValue;
}
}
If you don't set the property value in the binder, even when you don't want to change anything, you will block that property from ModelState altogether! This is because you are registered as binding all string types, so it appears (in my testing) that the default binder will not do it for you then.
Extra info for anyone searching how to do this in ASP.NET Core 1.0. Logic has changed quite a lot.
I wrote a blog post about how to do it, it explains things in bit more detailed
So ASP.NET Core 1.0 solution:
Model binder to do the actual trimming
public class TrimmingModelBinder : ComplexTypeModelBinder
{
public TrimmingModelBinder(IDictionary propertyBinders) : base(propertyBinders)
{
}
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
{
if(result.Model is string)
{
string resultStr = (result.Model as string).Trim();
result = ModelBindingResult.Success(resultStr);
}
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
}
Also you need Model Binder Provider in the latest version, this tells that should this binder be used for this model
public class TrimmingModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
{
var propertyBinders = new Dictionary();
foreach (var property in context.Metadata.Properties)
{
propertyBinders.Add(property, context.CreateBinder(property));
}
return new TrimmingModelBinder(propertyBinders);
}
return null;
}
}
Then it has to be registered in Startup.cs
services.AddMvc().AddMvcOptions(options => {
options.ModelBinderProviders.Insert(0, new TrimmingModelBinderProvider());
});
In case of MVC Core
Binder:
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Threading.Tasks;
public class TrimmingModelBinder
: IModelBinder
{
private readonly IModelBinder FallbackBinder;
public TrimmingModelBinder(IModelBinder fallbackBinder)
{
FallbackBinder = fallbackBinder ?? throw new ArgumentNullException(nameof(fallbackBinder));
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != null &&
valueProviderResult.FirstValue is string str &&
!string.IsNullOrEmpty(str))
{
bindingContext.Result = ModelBindingResult.Success(str.Trim());
return Task.CompletedTask;
}
return FallbackBinder.BindModelAsync(bindingContext);
}
}
Provider:
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;
public class TrimmingModelBinderProvider
: IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!context.Metadata.IsComplexType && context.Metadata.ModelType == typeof(string))
{
return new TrimmingModelBinder(new SimpleTypeModelBinder(context.Metadata.ModelType));
}
return null;
}
}
Registration function:
public static void AddStringTrimmingProvider(this MvcOptions option)
{
var binderToFind = option.ModelBinderProviders
.FirstOrDefault(x => x.GetType() == typeof(SimpleTypeModelBinderProvider));
if (binderToFind == null)
{
return;
}
var index = option.ModelBinderProviders.IndexOf(binderToFind);
option.ModelBinderProviders.Insert(index, new TrimmingModelBinderProvider());
}
Register:
service.AddMvc(option => option.AddStringTrimmingProvider())
I created value providers to trim the query string parameter values and the form values. This was tested with ASP.NET Core 3 and works perfectly.
public class TrimmedFormValueProvider
: FormValueProvider
{
public TrimmedFormValueProvider(IFormCollection values)
: base(BindingSource.Form, values, CultureInfo.InvariantCulture)
{ }
public override ValueProviderResult GetValue(string key)
{
ValueProviderResult baseResult = base.GetValue(key);
string[] trimmedValues = baseResult.Values.Select(v => v?.Trim()).ToArray();
return new ValueProviderResult(new StringValues(trimmedValues));
}
}
public class TrimmedQueryStringValueProvider
: QueryStringValueProvider
{
public TrimmedQueryStringValueProvider(IQueryCollection values)
: base(BindingSource.Query, values, CultureInfo.InvariantCulture)
{ }
public override ValueProviderResult GetValue(string key)
{
ValueProviderResult baseResult = base.GetValue(key);
string[] trimmedValues = baseResult.Values.Select(v => v?.Trim()).ToArray();
return new ValueProviderResult(new StringValues(trimmedValues));
}
}
public class TrimmedFormValueProviderFactory
: IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
if (context.ActionContext.HttpContext.Request.HasFormContentType)
context.ValueProviders.Add(new TrimmedFormValueProvider(context.ActionContext.HttpContext.Request.Form));
return Task.CompletedTask;
}
}
public class TrimmedQueryStringValueProviderFactory
: IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
context.ValueProviders.Add(new TrimmedQueryStringValueProvider(context.ActionContext.HttpContext.Request.Query));
return Task.CompletedTask;
}
}
Then register the value provider factories in the ConfigureServices() function in Startup.cs
services.AddControllersWithViews(options =>
{
int formValueProviderFactoryIndex = options.ValueProviderFactories.IndexOf(options.ValueProviderFactories.OfType<FormValueProviderFactory>().Single());
options.ValueProviderFactories[formValueProviderFactoryIndex] = new TrimmedFormValueProviderFactory();
int queryStringValueProviderFactoryIndex = options.ValueProviderFactories.IndexOf(options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>().Single());
options.ValueProviderFactories[queryStringValueProviderFactoryIndex] = new TrimmedQueryStringValueProviderFactory();
});
While reading through the excellent answers and comments above, and becoming increasingly confused, I suddenly thought, hey, I wonder if there's a jQuery solution. So for others who, like me, find ModelBinders a bit bewildering, I offer the following jQuery snippet that trims the input fields before the form gets submitted.
$('form').submit(function () {
$(this).find('input:text').each(function () {
$(this).val($.trim($(this).val()));
})
});
Late to the party, but the following is a summary of adjustments required for MVC 5.2.3 if you are to handle the skipValidation requirement of the build-in value providers.
public class TrimStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// First check if request validation is required
var shouldPerformRequestValidation = controllerContext.Controller.ValidateRequest &&
bindingContext.ModelMetadata.RequestValidationEnabled;
// determine if the value provider is IUnvalidatedValueProvider, if it is, pass in the
// flag to perform request validation (e.g. [AllowHtml] is set on the property)
var unvalidatedProvider = bindingContext.ValueProvider as IUnvalidatedValueProvider;
var valueProviderResult = unvalidatedProvider?.GetValue(bindingContext.ModelName, !shouldPerformRequestValidation) ??
bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
return valueProviderResult?.AttemptedValue?.Trim();
}
}
Global.asax
protected void Application_Start()
{
...
ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());
...
}
Update: This answer is out of date for recent versions of ASP.NET Core. Use Bassem's answer instead.
For ASP.NET Core, replace the ComplexTypeModelBinderProvider with a provider that trims strings.
In your startup code ConfigureServices method, add this:
services.AddMvc()
.AddMvcOptions(s => {
s.ModelBinderProviders[s.ModelBinderProviders.TakeWhile(p => !(p is ComplexTypeModelBinderProvider)).Count()] = new TrimmingModelBinderProvider();
})
Define TrimmingModelBinderProvider like this:
/// <summary>
/// Used in place of <see cref="ComplexTypeModelBinderProvider"/> to trim beginning and ending whitespace from user input.
/// </summary>
class TrimmingModelBinderProvider : IModelBinderProvider
{
class TrimmingModelBinder : ComplexTypeModelBinder
{
public TrimmingModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders) : base(propertyBinders) { }
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
{
var value = result.Model as string;
if (value != null)
result = ModelBindingResult.Success(value.Trim());
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) {
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++) {
var property = context.Metadata.Properties[i];
propertyBinders.Add(property, context.CreateBinder(property));
}
return new TrimmingModelBinder(propertyBinders);
}
return null;
}
}
The ugly part of this is the copy and paste of the GetBinder logic from ComplexTypeModelBinderProvider, but there doesn't seem to be any hook to let you avoid this.
I disagree with the solution.
You should override GetPropertyValue because the data for SetProperty could also be filled by the ModelState.
To catch the raw data from the input elements write this:
public class CustomModelBinder : System.Web.Mvc.DefaultModelBinder
{
protected override object GetPropertyValue(System.Web.Mvc.ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, System.Web.Mvc.IModelBinder propertyBinder)
{
object value = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
string retval = value as string;
return string.IsNullOrWhiteSpace(retval)
? value
: retval.Trim();
}
}
Filter by propertyDescriptor PropertyType if you are really only interested in string values but it should not matter because everything what comes in is basically a string.
There have been a lot of posts suggesting an attribute approach. Here is a package that already has a trim attribute and many others: Dado.ComponentModel.Mutations or NuGet
public partial class ApplicationUser
{
[Trim, ToLower]
public virtual string UserName { get; set; }
}
// Then to preform mutation
var user = new ApplicationUser() {
UserName = " M#X_speed.01! "
}
new MutationContext<ApplicationUser>(user).Mutate();
After the call to Mutate(), user.UserName will be mutated to m#x_speed.01!.
This example will trim whitespace and case the string to lowercase. It doesn't introduce validation, but the System.ComponentModel.Annotations can be used alongside Dado.ComponentModel.Mutations.
I posted this in another thread. In asp.net core 2, I went in a different direction. I used an action filter instead. In this case the developer can either set it globally or use as an attribute for the actions he/she wants to apply the string trimming. This code runs after the model binding has taken place, and it can update the values in the model object.
Here is my code, first create an action filter:
public class TrimInputStringsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
foreach (var arg in context.ActionArguments)
{
if (arg.Value is string)
{
string val = arg.Value as string;
if (!string.IsNullOrEmpty(val))
{
context.ActionArguments[arg.Key] = val.Trim();
}
continue;
}
Type argType = arg.Value.GetType();
if (!argType.IsClass)
{
continue;
}
TrimAllStringsInObject(arg.Value, argType);
}
}
private void TrimAllStringsInObject(object arg, Type argType)
{
var stringProperties = argType.GetProperties()
.Where(p => p.PropertyType == typeof(string));
foreach (var stringProperty in stringProperties)
{
string currentValue = stringProperty.GetValue(arg, null) as string;
if (!string.IsNullOrEmpty(currentValue))
{
stringProperty.SetValue(arg, currentValue.Trim(), null);
}
}
}
}
To use it, either register as global filter or decorate your actions with the TrimInputStrings attribute.
[TrimInputStrings]
public IActionResult Register(RegisterViewModel registerModel)
{
// Some business logic...
return Ok();
}
OK, I have this thing and it kinda works:
class TrimmingModelBinder : IModelBinder
{
public Task BindModelAsync (ModelBindingContext ctx)
{
if
(
ctx .ModelName is string name
&& ctx .ValueProvider .GetValue (name) .FirstValue is string v)
ctx .ModelState .SetModelValue
(
name,
new ValueProviderResult
((ctx .Result = ModelBindingResult .Success (v .Trim ())) .Model as string));
return Task .CompletedTask; }}
class AutoTrimAttribute : ModelBinderAttribute
{
public AutoTrimAttribute ()
{ this .BinderType = typeof (TrimmingModelBinder); }}
It is a shame that there is no standard feature for this though.
I adapted #Kai G's answer for System.Text.Json:
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class TrimmedStringConverter : JsonConverter<string>
{
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(string);
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.GetString() is string value ? value.Trim() : null;
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
}

Resources