Uppercase attribute that converts the input to uppercase - asp.net-mvc

I am working in MVC4 and want to define a model using an Uppercase attribute. The idea would be that the presence of the Uppercase attribute would cause the model value to be converted to uppercase when it arrived at the server.
At the moment I have the following code within the model:
[Required]
[Display(Name="Account Code")]
[StringValidation(RegExValidation.AccountCode, Uppercase=true)]
public string Account
{
get { return _account; }
set
{
if (value != null)
_account = value.ToUpper();
}
}
But what I would really like is this:
[Required]
[Display(Name="Account Code")]
[StringValidation(RegExValidation.AccountCode)]
[Uppercase]
public string Account { get; set; }
I think that I may need to create the Uppercase attribute as a ValidationAttribute to ensure it gets fired when the model hits the server. But that seems a bit wrong, as I'm not really validating the data. Is there a better way?
Also, is there any way to ensure the invocation order on the attributes? I really want to convert the data to uppercase before the custom StringValidation attribute fires, as this checks the case of the text in the regex pattern.
To add a bit of background to this, I want to reduce the need to add code to uppercase the data. The nirvana would be a single attribute, which updates the data on the way into the server, either in the model binding or validation stage. This attribute can then be referenced in the StringValidation attribute to amend the RegEx value used in its checks. I can also then lookup this attribute in a custom TextBoxFor helper method, such that I can add text-transform: uppercase so it looks correct on the client side.
Does anyone have any ideas out there?

I have managed to get this working, to a point, so here's my solution for others to appraise.
Once point to note was that the full solution couldn't be achieved because I couldn't get the Modelmetadata inside the StringValidation.IsValid() attribute. The particular issue I had here was that I could get the Metadata, however I could not get the PropertyName from it, only the DisplayName. There were multiple options out there, but the fact that some of my properties have the same DisplayName means that I couldn't be sure that the ProprtyName was the one I was actually validating.
Here's the code for the ValidationAttribute:
public class StringValidationAttribute : ValidationAttribute, IClientValidatable, IMetadataAware {
private bool _uppercase;
public StringValidationAttribute(bool uppercase = false) {
_uppercase = uppercase;
}
...
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues["Uppercase"] = _uppercase;
}
}
I then created a new IModelBinder implementation:
public class StringBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (result == null)
return null;
if (bindingContext.ModelMetadata.AdditionalValues.ContainsKey("Uppercase")) {
if ((bool)bindingContext.ModelMetadata.AdditionalValues["Uppercase"]])
return result.AttemptedValue.ToUpper();
}
return result.AttemptedValue;
}
}
And registered that in myGlobal.asax file:
ModelBinders.Binders.Add(typeof(string), new StringBinder());
The code so far will cause any string input coming into MVC to be converted to Uppercase if it has StringValidationAttribute attached to it on the model, and where the uppercase indicator has been set.
Next, to achieve my desire of making the html forms be uppercase too, I implemented a new EditorTemplate named string.cshtml. In this view I added:
RouteValueDictionary htmlAttributes = new RouteValueDictionary();
if ((bool)ViewData.ModelMetadata.AdditionalValues["Uppercase"]) {
htmlAttributes.Add("class", "Uppercase");
}
#Html.TextBox("", Model, htmlAttributes)
With the CSS as;
.Uppercase {
text-transform: uppercase;
}
Hope this post helps some others out there.

For Web API purpose it is better to convert the incoming json to uppercase or lowercase.
public class ToUpperCase : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return reader.Value.ToString().ToUpper();
}
}
[Display(Name = "PNR NAME")]
[JsonConverter(typeof(Annotations.ToUpperCase))]
public string PNR { get; set; }
OR Globally;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
//.......... others
JsonMediaTypeFormatter jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
JsonSerializerSettings jSettings = new Newtonsoft.Json.JsonSerializerSettings();
jSettings.Converters.Add(new UpperCaseStringConverter());
jsonFormatter.SerializerSettings = jSettings;
}

You're right, ValidationAttribute is not the right fit. It seems like doing this at the Model Binding stage would be a better idea. See this article for a detailed explanation of how to customize this behavior.
Based on the information provided there, I believe you should be able to create an attribute based on CustomModelBinderAttribute like this:
[AttributeUsage(AttributeTargets.Property)]
public class UppercaseAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new UppercaseModelBinder();
}
private class UppercaseModelBinder : DefaultModelBinder
{
public override object BindModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var value = base.BindModel(controllerContext, bindingContext);
var strValue = value as string;
if (strValue == null)
return value;
return strValue.ToUpperInvariant();
}
}
}
I have not tested this. Let me know if it works or not.

NOTE:
I'm adding on to this post because until I discovered the approach I now use, I read this and tried all above unsuccessfully.
I generally use a two part process when dealing with forcing text data to be formatted as uppercase. 1. at the view and 2. at the controller
At the view layer so that the user knows data is going to be used in the uppercase form. This can be down through htmlAttributes used in the EditorFor HTML helper.
#HTML.EditorFor(model => model.Access_Code, new { htmlAttributes = new Style= "text-transform:uppercase"}})
Now this only forces the data seen and entered by the user to uppercase and not the data sent to the server. To do that requires some code in the associated method in the controller.
I add the ToUpper() method to the target attribute of the object being passed back to the contoller. Here is hypothetical example showing this.
public ActionResult verify(int? id)
{
var userData = db.user.Where (i=> i.userID == id).Single();
userData.Access_Code = userData.Access_Code.ToUpper();
...
}

Related

Bind input field to custom object instead of string

I'm using ASP.NET MVC 4 for an internal web application and I have a desire to bind HTML input fields to a custom object rather than string.
In the HTML I have input fields that will look like the following:
<input type="hidden" name="First" value="1;Simple" />
<input type="hidden" name="First" value="2;Sample" />
<input type="hidden" name="Second" value="1;Over" />
<input type="hidden" name="Third" value="22;Complex" />
<input type="hidden" name="Third" value="17;Whosit" />
This will happily bind to ViewModel properties like:
public string[] First { get; set; }
public string[] Second { get; set; }
public string[] Third { get; set; }
Each string is a delimited string of key+value that I'd love to have automatically parsed into a concrete object (I have one already defined.) Ideally I'd want it to bind exactly as above but using my object that would know how to split the delimited string into the proper properties.
I can't figure out how to get MVC to bind to a custom object. I've used constructors and implicit operator definitions but I can't get it to work with anything but string datatype.
I know I could get this to work if I pre-split the values into pairs in the HTML but I'm using a JavaScript library that doesn't give this ability. For instance I know repeating {name}.Label and {name}.Value would work to bind to the string properties on my complex object but this is prohibitive and a non-starter.
I have gotten this to work with a custom object to handle File Uploads but I suspect that worked only because it inherited from the same base object. I can't do this here since string is a sealed type and can't be extended.
My last resort is to find the default model binder code and reflect that to figure out how it's assigning the values to see if it teaches me anything that I can override. I'd prefer not to go the route of a custom binder I'd have to write myself and if it comes down to it I'll just have duplicate ViewModel fields and convert them myself but I'd really love to avoid this if there's already a capability for the model binder to do this for me.
Here is what you can do. Let's say your MyThing class is something like this:
public class MyThing
{
public int Id { get; set; }
public string Name { get; set; }
public override string ToString()
{
return string.Format("{0};{1}", this.Id, this.Name);
}
}
Then, you can create a custom model binder for it like below:
public class MyModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName);
ModelState modelState = new ModelState { Value = valueResult };
object actualValue = null;
if (valueResult != null && !string.IsNullOrEmpty(valueResult.AttemptedValue))
{
if(valueResult.AttemptedValue.Contains(';'))
{
try
{
var attemptedValue = valueResult.AttemptedValue.Split(';');
int id = int.Parse(attemptedValue.First());
string name = attemptedValue.Last();
actualValue = new MyThing { Id = id, Name = name };
}
catch(Exception e)
{
modelState.Errors.Add(e);
}
}
else
{
modelState.Errors.Add("Invalid value.");
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
}
return actualValue;
}
}
You'll need to register your ModelBinder in Application_Start event of Global.asax like this:
ModelBinders.Binders.Add(typeof(MyThing), new MyModelBinder());
The question didn't get a single bite so I looked at the default model binder to see what was happening under the covers. There are a number of stages it goes through to see if a value can be converted to the ViewModel type but most of them are inaccessible to me. I did find a segment of code that fell back to using a type converter which I'd never used before.
Using this MSDN Type Converter how-to, I made a simple converter and decorated my class with the appropriate attribute and it just worked. I'm not sure what the performance implications are but it really simplifies my ViewModel code.
This example below is working for me. Keep in mind I'm only converting from the simple string type used by the DefaultModelBinder so it doesn't look like it's doing much but it solves my need and taught me a new feature of the framework.
public class MyThingConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context,
Type sourceType)
{
if (sourceType == typeof(string))
return true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (value is string)
return new MyThing((string)value);
return base.ConvertFrom(context, culture, value);
}
}
[TypeConverter(typeof(MyThingConverter))]
public class MyThing
{
public MyThing(string combinedValue)
{
//Split combinedValue into whatever properties I need
...
}
public override string ToString()
{
return string.Format("{0};{1}", prop1, prop2);
}
...
}
And that's it. So far it's working as expected.

AntiXss protection Html model properties

Some of my model properties are marked by AllowHtml attribute. Is there any way to automatically apply AntiXss protection (i. e. filter only allowed tags) to these fields?
First, afaik, nothing is built-in for that.
But MVC allows to do such things easily via custom ModelBinders, you could define your
public class CustomAntiXssAttribute : Attribute { }
and decorate your properties with it (and even inherit from AllowHtmlAttribute if you wish). Then with a model binder you could add your specific anti-xss protection:
public class CutstomModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Attributes.OfType<CustomAntiXssAttribute>().Any())
{
var valueResult = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);
var filteredValue = SOME_CUSTOM_FILTER_FUNCTION_HERE(valueResult.AttemptedValue);
propertyDescriptor.SetValue(bindingContext.Model, filteredValue);
}
else // revert to the default behavior.
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
}
Then inside that SOME_CUSTOM_FILTER_FUNCTION_HERE you could use what #Yogiraj suggested, or use a Regexp, or even apply HtmlAgilityPack-based filtering.
P.S. Don't forget to add ModelBinders.Binders.DefaultBinder = new CutstomModelBinder(); to Application_Start (I forgot :))
There is no automatic way. The closest you can do is to get AntiXss Nuget package. Then you can use it like below in your controller :
Microsoft.Security.Application.Sanitizer.GetSafeHtml("YourHtml");
OR
Microsoft.Security.Application.Encoder.HtmlEncode("YourHtml");
If you use you can decode it using
Server.HtmlDecode("HtmlEncodedString");
Hope this helps.
I would go for replacing those AllowHtml attributes with RegularExpression data-annotation validations. The advantage is by this way you can trap the error and show to the user what went wrong while the former one fires the error at a global level.
For ex.
public class MyViewModel
{
[DataType(DataType.MultilineText)]
[RegularExpression(#"^[^\<\>]*$", ErrorMessage = "May not contain <,>")]
public string Text { get; set; }
}
Ref: RegularExpression validator encoding regex < & > symbols as < >, causing jQuery validation to fail
Untested Code,
public class ADefaultModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelMetadata.RequestValidationEnabled)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue;
value = value.Replace("&", "");// replace existing & from the value
var encodedValue = Microsoft.Security.Application.Encoder.HtmlEncode(value);
bindingContext.ModelMetadata.RequestValidationEnabled = encodedValue.Contains("&"); // Whether AntiXss encoded a char to &..
}
return base.BindModel(controllerContext, bindingContext);
}
}
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
ModelBinders.Binders.DefaultBinder = new ADefaultModelBinder();

ASP.NET Web API Model Binding

I'm using Web API within ASP .NET MVC 4 RC, and I have a method that takes a complex object with nullable DateTime properties. I want the values of the input to be read from the query string, so I have something like this:
public class MyCriteria
{
public int? ID { get; set; }
public DateTime? Date { get; set; }
}
[HttpGet]
public IEnumerable<MyResult> Search([FromUri]MyCriteria criteria)
{
// Do stuff here.
}
This works well if I pass a standard date format in the query string such as 01/15/2012:
http://mysite/Search?ID=1&Date=01/15/2012
However, I want to specify a custom format for the DateTime (maybe MMddyyyy)... for example:
http://mysite/Search?ID=1&Date=01152012
Edit:
I've tried to apply a custom model binder, but I haven't had any luck applying it to only DateTime objects. The ModelBinderProvider I've tried looks something like this:
public class DateTimeModelBinderProvider : ModelBinderProvider
{
public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(DateTime) || bindingContext.ModelType == typeof(DateTime?))
{
return new DateTimeModelBinder();
}
return null;
}
}
// In the Global.asax
GlobalConfiguration.Configuration.Services.Add(typeof(ModelBinderProvider), new DateTimeModelBinderProvider());
The new model binder provider is created, but GetBinder is only called once (for the complex model parameter, but not for each property within the model). This makes sense, but I would like to find a way to make it to use my DateTimeModelBinder for DateTime properties, while using the default binding for non-DateTime properties. Is there a way to override the default ModelBinder and specify how each property is bound?
Thanks!!!
Consider setting your view-model's Date property to type string
Then either write a utility function to handle the mapping between the viewmodel type and the domain-model type:
public static MyCriteria MapMyCriteriaViewModelToDomain(MyCriteriaViewModel model){
var date = Convert.ToDateTime(model.Date.Substring(0,2) + "/" model.Date.Substring(2,2) + "/" model.Date.Substring(4,2));
return new MyCriteria
{
ID = model.ID,
Date = date
};
}
or use a tool like AutoMapper, like this:
in Global.asax
//if passed as MMDDYYYY:
Mapper.CreateMap<MyCriteriaViewModel, MyCriteria>().
.ForMember(
dest => dest.Date,
opt => opt.MapFrom(src => Convert.ToDateTime(src.Date.Substring(0,2) + "/" src.Date.Substring(2,2) + "/" src.Date.Substring(4,2)))
);
and in the controller:
public ActionResult MyAction(MyCriteriaViewModel model)
{
var myCriteria = Mapper.Map<MyCriteriaViewModel, MyCriteria>(model);
// etc.
}
From this example it might not seem that AutoMapper is providing any added value. It's value comes when you are configuring several or many mappings with objects that generally have more properties than this example. CreateMap will automatically map properties with the same name and type, so it saves lots of typing and it's much DRYer.

Changing null strings to string.Empty using ValueInjecter

I'm using ValueInjecter to flatten/unflatten view models into domain objects created by Entity Framework (4.3.1) model-first. All of my VARCHAR columns in my database are NOT NULL DEFAULT '' (personal preference, no desire to open up a holy war here). On post, the view model comes back with any string property that has no value as null, so when I attempt to inject it back into my domain model class, EF barks at me for attempting to set a property with IsNullable=false to null. Example (over-simple):
public class ThingViewModel
{
public int ThingId{get;set;}
public string Name{get;set;}
}
public class Thing
{
public global::System.Int32 ThingId
{
//omitted for brevity
}
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.String Name
{
//omitted for brevity
}
}
Then, my controller post looks like this:
[HttpPost]
public ActionResult Edit(ThingViewModel thing)
{
var dbThing = _thingRepo.GetThing(thing.ThingId);
//if thing.Name is null, this bombs
dbThing.InjectFrom<UnflatLoopValueInjection>(thing);
_thingRepo.Save();
return View(thing);
}
I'm using UnflatLoopValueInjection because I have nested types in the actual domain version of Thing. I attempted to write a custom ConventionInjection to convert null strings to string.Empty, but it appears that UnflatLoopValueInjection switches it back to null. Is there a way I can get ValueInjecter not to do this?
Nuts, I just figured it out with help from the wiki. The solution appears to be to extend UnflatLoopValueInjection:
public class NullStringUnflatLoopValueInjection : UnflatLoopValueInjection<string, string>
{
protected override string SetValue(string sourceValue)
{
return sourceValue ?? string.Empty;
}
}

Model Validation with dictionary

Say I have a model like so:
public class MyViewModel {
//some properties
public string MyString {get;set;}
public Dictionary<string,string> CustomProperties {get;set;}
}
And I am presenting the dictionary property like this:
<%= Html.EditorFor(m => m.CustomProperties["someproperty"]) %>
All is working well, however I have implemented a custom validator to validate the properties of this dictionary, but when returning a ModelValidationResult I can not get the member name referenced correctly (which chould be CustomProperties[someproperty] I believe). All the items in the list which are properties are bound correctly to their errors (I want the error class in the text box so I can highlight it).
Here is my code for the custom validator so far
public class CustomValidator : ModelValidator
{
public Custom(ModelMetadata metadata, ControllerContext controllerContext) : base(metadata, controllerContext)
{
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
if (Metadata.PropertyName.Equals("mystring", StringComparison.OrdinalIgnoreCase))
{
yield return new ModelValidationResult() {Message = "normal property validator works!!"};
}
else if (Metadata.PropertyName.Equals("customproperties", StringComparison.OrdinalIgnoreCase))
{
yield return new ModelValidationResult() { MemberName = "CustomProperties[someproperty]", Message = "nope!" };
}
}
}
It appears like something is filling in the MemberName property further up, and ignoring what I put in there
Cheers,
Amar
It appears to me that you are making validation more difficult than it needs to be. Have you taken a look at DataAnnotations which are built into the framework? Scott Gu's blog talks about this. It's a really nice (and easy) way to do validation of models.

Resources