Override MVC Model Display Name Annotation with Custom Functionality - c#

I have the following method where I read from a key-value XML file. I pass in a key and am returned a value where I used to display on my view.
public static class TextManager
{
public static string GetValue(string key)
{
string returnVal = null;
XmlSerializer serializer = new XmlSerializer(typeof(Entries));
string path = HttpContext.Current.Server.MapPath("/App_Data/text-key-value.xml");
if (File.Exists(path))
{
Entries entries = (Entries)serializer.Deserialize(File.OpenRead(path));
var entry = entries.Where(u => u.Key == key).FirstOrDefault();
if (entry != null)
{
returnVal = entry.Value;
}
}
return returnVal;
}
}
Basically I want to be able to use this method in my model class as a data-annotation that will pull directly from my site text file and set to the display name property.
For instance I want to replace
[Display(Name = "Reference Code")]
public string ReferenceCode { get; set; }
With this
[DisplaySiteText("ReferenceCodeKey")]
public string ReferenceCode { get; set; }
DisplaySiteText would pass the string reference "ReferenceCodeKey" to the GetValue method, file the reference in the file and then set the standard Display name attribute to whatever was in the file.
How do I create my own custom model annotation to do this, I've written custom validation annotations in the past by creating a class that inherits from ValidationAttribute, but I don't think that will work in this case.

You can inherit DisplayNameAttribute for this purpose
public class DisplaySiteTextAttribute : DisplayNameAttribute
{
private string _key;
public DisplaySiteTextAttribute(string key)
{
_key = key;
}
public override string DisplayName
{
get
{
return TextManager.GetValue(_key);
}
}
}

There are several options to customize model metadata:
Customize the way that framework provides metadata. (Create ModelMedatadaProvider)
Create new Metadata attributes. (Implement IMetadataAware)
Modify existing attributes. (Derive existing attributes.)
The 3rd option has been discussed in the other answer. Here in this post, I'll share first and second options.
Option 1 - Customize the way that framework provides metadata
You can change the logic of getting display text without changing the attribute.
In fact it's responsibility of ModelMetaDataProvider to get mete data for model, including display text for properties. So as an option, you can keep the Display attribute intact and instead, create a new model metadata provider and return property metadata from a different source.
To do so, you can create a new metadata provider by deriving from DataAnnotationsModelMetadataProvider. Then override GetMetadataForProperty and call base, to get metadata. Then change DisplayName based on your logic by reading from your text manager.
You also need to register the new metadata provider as ModelMetadataProviders.Current in App_Start.
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Mvc;
public class MyCustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor,
Type containerType,
PropertyDescriptor propertyDescriptor)
{
var metadata = base.GetMetadataForProperty(modelAccessor,
containerType, propertyDescriptor);
var display = propertyDescriptor.Attributes
.OfType<DisplayAttribute>().FirstOrDefault();
if (display != null)
{
metadata.DisplayName = TextManager.GetValue(display.Name);
}
return metadata;
}
}
And then register it in Application_Start():
ModelMetadataProviders.Current = new MyCustomModelMetadataProvider();
For more information take a look at DataAnnotationsModelMetadataProvider.cs source code in ASP.NET MVC sources.
This approach is useful when you want to change the way that you provide metadata for model. For example when you want to load display name and description from an external file rather than resources, without changing existing attributes.
Option 2 - Create new Metadata attributes
Another standard solution for creating metadata-aware attributes is creating an attribute and implementing IMetadataAware interface. Then in implementation of OnMetadataCreated you can easily set properties of metadata.
This approach doesn't need to register a new meta data provider. This approach is supported by the default metadata provider and is useful for creating new metadata-aware attributes:
using System;
using System.Web.Mvc;
public class CustomMetadataAttribure : Attribute, IMetadataAware
{
public string Key { get; set; }
public CustomMetadataAttribure(string key) => this.Key = key;
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.DisplayName = TextManager.GetValue(this.Key);
}
}
This approach is useful when you want to extend metadata attributes and add a few more attributes. For example when you want to add some attributes to control rendering. You can set ModelMetadata properties or add some new values to its AdditionalValues dictionary.

Maybe your DisplaySiteText attribute could inherit from the Display attribute and set the name using your helper class. Something like this:
public class DisplaySiteTextAttribute : DisplayAttribute
{
public DisplaySiteTextAttribute(string key)
{
Name = TextManager.GetValue(key);
}
}

Related

Is it wrong to dynamically add "data-val" and "data-val-required" in the View?

I have a ViewModel that I can decorate with the [Required] attribute (see below). I've come to the point where I need to let the client control which fields are required or not. They can configure this trough XML and all this info is stored in the Model when it's first created. Now I have fields that are not decorated with [Required] but still need to get validated (as per "user settings") before submitting (for example the Phone field).
public class MyBusinessObjectViewModel
{
[Required]
public string Email { get; set; } //compulsory
public string Phone { get; set; } //not (yet) compulsory, but might become
}
If the user will not enter the Phone number, the data will still get posted. Wanting not to mess with custom validators, I just add the "data-val" and "data-val-required" attributes to the Html, like this:
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("data-val", "true");
dict.Add("data-val-required", "This field is required.");
#Html.TextBoxFor(x => x, dict);
This forces the client side validation for all the properties that are dynamically set as required. Is this good practice? What kind of side effects can I expect?
You should look into extending the meta model framework with your own metadata provider to do the actual binding between your site's configuration and the model metadata. You can actually set the required property flag to true on the property model metadata during the metadata creation process. I can't remember for sure whether this causes the built in editor templates to generate the attribute, but I think it does. Worst case scenario you can actually create and attach a new RequiredAttribute to the property, which is a tad bit kluggy, but works pretty well in certain scenarios.
You could also do this with IMetadataAware attributes, especially if Required is the only metadata aspect your users can customize, but the implementation really depends on what you're trying to do.
One major advantage of using a custom ModelMetadataProvider in your specific case is that you can use dependency injection (via ModelMetadataProviders) to get your customer settings persistence mechanism into scope, whereas with the data attribute you only get to write an isolated method that runs immediately after the metadata model is created.
Here is a sample implementation of a custom model metadata provider, you'd just have to change the client settings to whatever you wanted to use.
UPDATED but not tested at all
public class ClientSettingsProvider
{
public ClientSettingsProvider(/* db info */) { /* init */ }
public bool IsPropertyRequired(string propertyIdentifier)
{
// check the property identifier here and return status
}
}
public ClientRequiredAttribute : Attribute
{
string _identifier;
public string Identifier { get { return _identifer; } }
public ClientRequiredAttribute(string identifier)
{ _identifier = identifier; }
}
public class RequiredModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
ClientSettings _clientSettings;
public RequiredModelMetadataProvider(ClientSettings clientSettings)
{
_clientSettings = clientSettings;
}
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
// alternatively here is where you could 'inject' a RequiredAttribute into the attributes list
var clientRequiredAttribute = attributes.OfType<ClientRequiredAttribute>().SingleOrDefault();
if(clientRequiredAttribute != null && _clientSettings.IsPropertyRequired(clientRequiredAttribute.Identifier))
{
// By injecting the Required attribute here it will seem to
// the base provider we are extending as if the property was
// marked with [Required]. Your data validation attributes should
// be added, provide you are using the default editor templates in
// you view.
attributes = attributes.Union(new [] { new RequiredAttribute() });
}
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
// REMOVED, this is another way but I'm not 100% sure it will add your attributes
// Use whatever attributes you need here as parameters...
//if (_clientSettings.IsPropertyRequired(containerType, propertyName))
//{
// metadata.IsRequired = true;
//}
return metadata;
}
}
USAGE
public class MyModel
{
[ClientRequired("CompanyName")]
public string Company { get; set; }
}
public class MyOtherModel
{
[ClientRequired("CompanyName")]
public string Name { get; set; }
public string Address { get; set; }
}
Both of these models would validate the string "CompanyName" against your client settings provider.
Not wanting to mess with custom validators, so you messed in the View obfuscating the logic of your validation by removing it from the place where it is expected to be found.
Really, don't be afraid of creating a custom attribute validator. What you are doing right now is getting a technical debt.

Telerik MVC Grid with Ajax Binding using EntityObjects gets Circular References exception

I have been using Telerik MVC Grid for quite a while now. It is a great control, however, one annoying thing keeps showing up related to using the grid with Ajax Binding to objects created and returned from the Entity Framework. Entity objects have circular references, and when you return an IEnumerable<T> from an Ajax callback, it generates an exception from the JavascriptSerializer if there are circular references. This happens because the MVC Grid uses a JsonResult, which in turn uses JavaScriptSerializer which does not support serializing circular references.
My solution to this problem has been to use LINQ to create view objects that do not have the Related Entities. This works for all cases, but requires the creation of new objects and the copying of data to / from entity objects to these view objects. Not a lot of work, but it is work.
I have finally figured out how to generically make the grid not serialize the circular references (ignore them) and I wanted to share my solution for the general public, as I think it is generic, and plugs into the environment nicely.
The solution has a couple of parts
Swap the default grid serializer with a custom serializer
Install the Json.Net plug-in available from Newtonsoft (this is a great library)
Implement the grid serializer using Json.Net
Modify the Model.tt files to insert [JsonIgnore] attributes in front of the navigation properties
Override the DefaultContractResolver of Json.Net and look for the _entityWrapper attribute name to ensure this is also ignored (injected wrapper by the POCO classes or entity framework)
All of these steps are easy in and of themselves, but without all of them you cannot take advantage of this technique.
Once implemented correctly I can now easily send any entity framework object directly to the client without creating new View objects. I don't recommend this for every object, but sometimes it is the best option. It is also important to note that any related entities are not available on the client side, so don't use them.
Here are the Steps required
Create the following class in your application somewhere. This class is a factory object that the grid uses to obtain JSON results. This will be added to the telerik library in the global.asax file shortly.
public class CustomGridActionResultFactory : IGridActionResultFactory
{
public System.Web.Mvc.ActionResult Create(object model)
{
//return a custom JSON result which will use the Json.Net library
return new CustomJsonResult
{
Data = model
};
}
}
Implement the Custom ActionResult. This code is boilerplate for the most part. The only interesting part is at the bottom where it calls JsonConvert.SerilaizeObject passing in a ContractResolver. The ContactResolver looks for properties called _entityWrapper by name and sets them to be ignored. I am not exactly sure who injects this property, but it is part of the entity wrapper objects and it has circular references.
public class CustomJsonResult : ActionResult
{
const string JsonRequest_GetNotAllowed = "This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet.";
public string ContentType { get; set; }
public System.Text.Encoding ContentEncoding { get; set; }
public object Data { get; set; }
public JsonRequestBehavior JsonRequestBehavior { get; set; }
public int MaxJsonLength { get; set; }
public CustomJsonResult()
{
JsonRequestBehavior = JsonRequestBehavior.DenyGet;
MaxJsonLength = int.MaxValue; // by default limit is set to int.maxValue
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if ((JsonRequestBehavior == JsonRequestBehavior.DenyGet) && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(JsonRequest_GetNotAllowed);
}
var response = context.HttpContext.Response;
if (!string.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
else
{
response.ContentType = "application/json";
}
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data != null)
{
response.Write(JsonConvert.SerializeObject(Data, Formatting.None,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new PropertyNameIgnoreContractResolver()
}));
}
}
}
Add the factory object to the telerik grid. I do this in the global.asax Application_Start() method, but realistically it can be done anywhere that makes sense.
DI.Current.Register<IGridActionResultFactory>(() => new CustomGridActionResultFactory());
Create the DefaultContractResolver class that checks for _entityWrapper and ignores that attribute. The resolver is passed into the SerializeObject() call in step 2.
public class PropertyNameIgnoreContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (member.Name == "_entityWrapper")
property.Ignored = true;
return property;
}
}
Modify the Model1.tt file to inject attributes that ignore the related entity properties of the POCO Objects. The attribute that must be injected is [JsonIgnore]. This is the hardest part to add to this post but not hard to do in the Model1.tt (or whatever filename it is in your project). Also if you are using code first then you can manually place the [JsonIgnore] attributes in front of any attribute that creates a circular reference.
Search for the region.Begin("Navigation Properties") in the .tt file. This is where all of the navigation properties are code generated. There are two cases that have to be taken care of the many to XXX and the Singular reference. There is an if statement that checks if the property is
RelationshipMultiplicity.Many
Just after that code block you need to insert the [JSonIgnore] attribute prior to the line
<#=PropertyVirtualModifier(Accessibility.ForReadOnlyProperty(navProperty))#> ICollection<<#=code.Escape(navProperty.ToEndMember.GetEntityType())#>> <#=code.Escape(navProperty)#>
Which injects the property name into the generated code file.
Now look for this line which handles the Relationship.One and Relationship.ZeroOrOne relationships.
<#=PropertyVirtualModifier(Accessibility.ForProperty(navProperty))#> <#=code.Escape(navProperty.ToEndMember.GetEntityType())#> <#=code.Escape(navProperty)#>
Add the [JsonIgnore] attribute just before this line.
Now the only thing left is to make sure the NewtonSoft.Json library is "Used" at the top of each generated file. Search for the call to WriteHeader() in the Model.tt file. This method takes a string array parameter that adds extra usings (extraUsings). Instead of passing null, construct an array of strings and send in the "Newtonsoft.Json" string as the first element of the array. The call should now look like:
WriteHeader(fileManager, new [] {"Newtonsoft.Json"});
That's all there is to do, and everything starts working, for every object.
Now for the disclaimers
I have never used Json.Net so my implementation of it might not be
optimal.
I have been testing for about two days now and haven't found any cases where this technique fails.
I also have not found any incompatibilities between the JavascriptSerializer and the JSon.Net serializer but that doesn't mean
there aren't any
The only other caveat is that the I am testing for a property called "_entityWrapper" by name to set its ignored property to true. This is obviously not optimal.
I would welcome any feedback on how to improve this solution. I hope it helps someone else.
The first solution works with the grid editing mode, but we have the same problem with the load of the grid that already has rows of objects with circular reference, and to resolve this we need to create a new IClientSideObjectWriterFactory and a new IClientSideObjectWriter.
This is what I do:
1- Create a new IClientSideObjectWriterFactory:
public class JsonClientSideObjectWriterFactory : IClientSideObjectWriterFactory
{
public IClientSideObjectWriter Create(string id, string type, TextWriter textWriter)
{
return new JsonClientSideObjectWriter(id, type, textWriter);
}
}
2- Create a new IClientSideObjectWriter, this time I do not implement the interface, I've inherited the ClientSideObjectWriter and overrided the AppendObject and AppendCollection methods:
public class JsonClientSideObjectWriter : ClientSideObjectWriter
{
public JsonClientSideObjectWriter(string id, string type, TextWriter textWriter)
: base(id, type, textWriter)
{
}
public override IClientSideObjectWriter AppendObject(string name, object value)
{
Guard.IsNotNullOrEmpty(name, "name");
var data = JsonConvert.SerializeObject(value,
Formatting.None,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new PropertyNameIgnoreContractResolver()
});
return Append("{0}:{1}".FormatWith(name, data));
}
public override IClientSideObjectWriter AppendCollection(string name, System.Collections.IEnumerable value)
{
public override IClientSideObjectWriter AppendCollection(string name, System.Collections.IEnumerable value)
{
Guard.IsNotNullOrEmpty(name, "name");
var data = JsonConvert.SerializeObject(value,
Formatting.Indented,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new PropertyNameIgnoreContractResolver()
});
data = data.Replace("<", #"\u003c").Replace(">", #"\u003e");
return Append("{0}:{1}".FormatWith((object)name, (object)data));
}
}
NOTE: The replace its because the grid renders html tags for the client template in edit mode and if we don't encode then the browser will render the tags. I didn't find a workarround yet if not using a Replace from string object.
3- On my Application_Start on Global.asax.cs I registered my new factory like this:
DI.Current.Register<IClientSideObjectWriterFactory>(() => new JsonClientSideObjectWriterFactory());
And it worked for all components that Telerik has. The only thing that I do not changed was the PropertyNameIgnoreContractResolver that was the same for the EntityFramework classes.
I put the new call into my Application_Start for implement the CustomGridActionResultFactory but the create method never called...
I have taken a slightly different approach which I believe might be a little easier to implement.
All I do is apply an extended [Grid] attribute to the grid json returning method instead of the normal [GridAction] attribute
public class GridAttribute : GridActionAttribute, IActionFilter
{
/// <summary>
/// Determines the depth that the serializer will traverse
/// </summary>
public int SerializationDepth { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="GridActionAttribute"/> class.
/// </summary>
public GridAttribute()
: base()
{
ActionParameterName = "command";
SerializationDepth = 1;
}
protected override ActionResult CreateActionResult(object model)
{
return new EFJsonResult
{
Data = model,
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
MaxSerializationDepth = SerializationDepth
};
}
}
and
public class EFJsonResult : JsonResult
{
const string JsonRequest_GetNotAllowed = "This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet.";
public EFJsonResult()
{
MaxJsonLength = 1024000000;
RecursionLimit = 10;
MaxSerializationDepth = 1;
}
public int MaxJsonLength { get; set; }
public int RecursionLimit { get; set; }
public int MaxSerializationDepth { get; set; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(JsonRequest_GetNotAllowed);
}
var response = context.HttpContext.Response;
if (!String.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
else
{
response.ContentType = "application/json";
}
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data != null)
{
var serializer = new JavaScriptSerializer
{
MaxJsonLength = MaxJsonLength,
RecursionLimit = RecursionLimit
};
serializer.RegisterConverters(new List<JavaScriptConverter> { new EFJsonConverter(MaxSerializationDepth) });
response.Write(serializer.Serialize(Data));
}
}
Combine this with my serializer Serializing Entity Framework problems and you have a simple way of avoiding circular references but also optionally serializing multiple levels (which I need)
Note: Telerik added this virtual CreateActionResult very recently for me so you may have to download the latest version (not sure but I think maybe 1.3+)
Another good pattern is to simply not avoid creating a ViewModel from the Model.
It is a good pattern to include a ViewModel. It gives you the opportunity to make last minute UI related tweaks to the model. For example, you can tweak a bool to have an associated string Y or N to help make the UI look nice, or vice versa.
Sometimes the ViewModel is exactly like the Model and the code to copy the properties seems unnecessary, but the pattern is a good one and sticking to it is the best practice.

How do you do web forms model validation?

We have an application with three layers: UI, Business, and Data. The data layer houses Entity Framework v4 and auto-generates our entity objects. I have created a buddy class for the entity VendorInfo:
namespace Company.DataAccess
{
[MetadataType(typeof(VendorInfoMetadata))]
public partial class VendorInfo
{
}
public class VendorInfoMetadata
{
[Required]
public string Title;
[Required]
public string Link;
[Required]
public string LinkText;
[Required]
public string Description;
}
}
I want this validation to bubble up to the UI, including custom validation messages assigned to them. In MVC this is a piece of cake but in web forms I have no clue where to begin. What is the best way to utilize model validation in asp.net web forms?
I did find an article that explains how to build a server control for it, but I can't seem to get it working. It compiles and even recognizes the control but I can never get it to fire.
Any ideas?
Thanks everyone.
I solved it. It would appear that the server control I found was not designed to read fields in a buddy class via the MetadataType attribute. I modified the code to look for its validation attributes in the buddy class rather than the entity class itself.
Here is the modified version of the linked server control:
[DefaultProperty("Text")]
[ToolboxData("<{0}:DataAnnotationValidator runat=server></{0}:DataAnnotationValidator>")]
public class DataAnnotationValidator : BaseValidator
{
#region Properties
/// <summary>
/// The type of the source to check
/// </summary>
public string SourceTypeName { get; set; }
/// <summary>
/// The property that is annotated
/// </summary>
public string PropertyName { get; set; }
#endregion
#region Methods
protected override bool EvaluateIsValid()
{
// get the type that we are going to validate
Type source = GetValidatedType();
// get the property to validate
FieldInfo property = GetValidatedProperty(source);
// get the control validation value
string value = GetControlValidationValue(ControlToValidate);
foreach (var attribute in property.GetCustomAttributes(
typeof(ValidationAttribute), true)
.OfType<ValidationAttribute>())
{
if (!attribute.IsValid(value))
{
ErrorMessage = attribute.ErrorMessage;
return false;
}
}
return true;
}
private Type GetValidatedType()
{
if (string.IsNullOrEmpty(SourceTypeName))
{
throw new InvalidOperationException(
"Null SourceTypeName can't be validated");
}
Type validatedType = Type.GetType(SourceTypeName);
if (validatedType == null)
{
throw new InvalidOperationException(
string.Format("{0}:{1}",
"Invalid SourceTypeName", SourceTypeName));
}
IEnumerable<MetadataTypeAttribute> mt = validatedType.GetCustomAttributes(typeof(MetadataTypeAttribute), false).OfType<MetadataTypeAttribute>();
if (mt.Count() > 0)
{
validatedType = mt.First().MetadataClassType;
}
return validatedType;
}
private FieldInfo GetValidatedProperty(Type source)
{
FieldInfo field = source.GetField(PropertyName);
if (field == null)
{
throw new InvalidOperationException(
string.Format("{0}:{1}",
"Validated Property Does Not Exists", PropertyName));
}
return field;
}
#endregion
}
This code only looks in the buddy class. If you want it to check an actual class and then its buddy class, you'll have to modify it accordingly. I did not bother doing that because usually if you are using a buddy class for validation attributes it's because you are not able to use the attributes in the main entity class (e.g. Entity Framework).
For model validation in web forms I'm using DAValidation library. It supports validation on client side (including unobtrusive validation), extensibility based on same principles as in MVC. It is MS-PL licensed and available via Nuget.
And here is bit out of date article describing with what thoughts control was build.

Disable Required validation attribute under certain circumstances

I was wondering if it is possible to disable the Required validation attribute in certain controller actions. I am wondering this because on one of my edit forms I do not require the user to enter values for fields that they have already specified previously. However I then implement logic that when they enter a value it uses some special logic to update the model, such as hashing a value etc.
Any sugestions on how to get around this problem?
EDIT:
And yes client validation is a problem here to, as it will not allow them to submit the form without entering a value.
This problem can be easily solved by using view models. View models are classes that are specifically tailored to the needs of a given view. So for example in your case you could have the following view models:
public UpdateViewView
{
[Required]
public string Id { get; set; }
... some other properties
}
public class InsertViewModel
{
public string Id { get; set; }
... some other properties
}
which will be used in their corresponding controller actions:
[HttpPost]
public ActionResult Update(UpdateViewView model)
{
...
}
[HttpPost]
public ActionResult Insert(InsertViewModel model)
{
...
}
If you just want to disable validation for a single field in client side then you can override the validation attributes as follows:
#Html.TextBoxFor(model => model.SomeValue,
new Dictionary<string, object> { { "data-val", false }})
I know this question has been answered a long time ago and the accepted answer will actually do the work. But there's one thing that bothers me: having to copy 2 models only to disable a validation.
Here's my suggestion:
public class InsertModel
{
[Display(...)]
public virtual string ID { get; set; }
...Other properties
}
public class UpdateModel : InsertModel
{
[Required]
public override string ID
{
get { return base.ID; }
set { base.ID = value; }
}
}
This way, you don't have to bother with client/server side validations, the framework will behave the way it's supposed to. Also, if you define a [Display] attribute on the base class, you don't have to redefine it in your UpdateModel.
And you can still use these classes the same way:
[HttpPost]
public ActionResult Update(UpdateModel model)
{
...
}
[HttpPost]
public ActionResult Insert(InsertModel model)
{
...
}
You can remove all validation off a property with the following in your controller action.
ModelState.Remove<ViewModel>(x => x.SomeProperty);
#Ian's comment regarding MVC5
The following is still possible
ModelState.Remove("PropertyNameInModel");
Bit annoying that you lose the static typing with the updated API. You could achieve something similar to the old way by creating an instance of HTML helper and using NameExtensions Methods.
Client side
For disabling validation for a form, multiple options based on my research is given below. One of them would would hopefully work for you.
Option 1
I prefer this, and this works perfectly for me.
(function ($) {
$.fn.turnOffValidation = function (form) {
var settings = form.validate().settings;
for (var ruleIndex in settings.rules) {
delete settings.rules[ruleIndex];
}
};
})(jQuery);
and invoking it like
$('#btn').click(function () {
$(this).turnOffValidation(jQuery('#myForm'));
});
Option 2
$('your selector here').data('val', false);
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
Option 3
var settings = $.data($('#myForm').get(0), 'validator').settings;
settings.ignore = ".input";
Option 4
$("form").get(0).submit();
jQuery('#createForm').unbind('submit').submit();
Option 5
$('input selector').each(function () {
$(this).rules('remove');
});
Server Side
Create an attribute and mark your action method with that attribute. Customize this to adapt to your specific needs.
[AttributeUsage(AttributeTargets.All)]
public class IgnoreValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var modelState = filterContext.Controller.ViewData.ModelState;
foreach (var modelValue in modelState.Values)
{
modelValue.Errors.Clear();
}
}
}
A better approach has been described here Enable/Disable mvc server side validation dynamically
Personally I would tend to use the approach Darin Dimitrov showed in his solution.
This frees you up to be able to use the data annotation approach with validation AND have separate data attributes on each ViewModel corresponding to the task at hand.
To minimize the amount of work for copying between model and viewmodel you should look at AutoMapper or ValueInjecter. Both have their individual strong points, so check them both.
Another possible approach for you would be to derive your viewmodel or model from IValidatableObject. This gives you the option to implement a function Validate.
In validate you can return either a List of ValidationResult elements or issue a yield return for each problem you detect in validation.
The ValidationResult consists of an error message and a list of strings with the fieldnames. The error messages will be shown at a location near the input field(s).
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if( NumberField < 0 )
{
yield return new ValidationResult(
"Don't input a negative number",
new[] { "NumberField" } );
}
if( NumberField > 100 )
{
yield return new ValidationResult(
"Don't input a number > 100",
new[] { "NumberField" } );
}
yield break;
}
The cleanest way here I believe is going to disable your client side validation and on the server side you will need to:
ModelState["SomeField"].Errors.Clear (in your controller or create an action filter to remove errors before the controller code is executed)
Add ModelState.AddModelError from your controller code when you detect a violation of your detected issues.
Seems even a custom view model here wont solve the problem because the number of those 'pre answered' fields could vary. If they dont then a custom view model may indeed be the easiest way, but using the above technique you can get around your validations issues.
this was someone else's answer in the comments...but it should be a real answer:
$("#SomeValue").removeAttr("data-val-required")
tested on MVC 6 with a field having the [Required] attribute
answer stolen from https://stackoverflow.com/users/73382/rob above
I was having this problem when I creating a Edit View for my Model and I want to update just one field.
My solution for a simplest way is put the two field using :
<%: Html.HiddenFor(model => model.ID) %>
<%: Html.HiddenFor(model => model.Name)%>
<%: Html.HiddenFor(model => model.Content)%>
<%: Html.TextAreaFor(model => model.Comments)%>
Comments is the field that I only update in Edit View, that not have Required Attribute.
ASP.NET MVC 3 Entity
AFAIK you can not remove attribute at runtime, but only change their values (ie: readonly true/false) look here for something similar .
As another way of doing what you want without messing with attributes I will go with a ViewModel for your specific action so you can insert all the logic without breaking the logic needed by other controllers.
If you try to obtain some sort of wizard (a multi steps form) you can instead serialize the already compiled fields and with TempData bring them along your steps. (for help in serialize deserialize you can use MVC futures)
What #Darin said is what I would recommend as well. However I would add to it (and in response to one of the comments) that you can in fact also use this method for primitive types like bit, bool, even structures like Guid by simply making them nullable. Once you do this, the Required attribute functions as expected.
public UpdateViewView
{
[Required]
public Guid? Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int? Age { get; set; }
[Required]
public bool? IsApproved { get; set; }
//... some other properties
}
As of MVC 5 this can be easily achieved by adding this in your global.asax.
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
I was looking for a solution where I can use the same model for an insert and update in web api. In my situation is this always a body content. The [Requiered] attributes must be skipped if it is an update method.
In my solution, you place an attribute [IgnoreRequiredValidations] above the method. This is as follows:
public class WebServiceController : ApiController
{
[HttpPost]
public IHttpActionResult Insert(SameModel model)
{
...
}
[HttpPut]
[IgnoreRequiredValidations]
public IHttpActionResult Update(SameModel model)
{
...
}
...
What else needs to be done?
An own BodyModelValidator must becreated and added at the startup.
This is in the HttpConfiguration and looks like this: config.Services.Replace(typeof(IBodyModelValidator), new IgnoreRequiredOrDefaultBodyModelValidator());
using Owin;
using your_namespace.Web.Http.Validation;
[assembly: OwinStartup(typeof(your_namespace.Startup))]
namespace your_namespace
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
Configuration(app, new HttpConfiguration());
}
public void Configuration(IAppBuilder app, HttpConfiguration config)
{
config.Services.Replace(typeof(IBodyModelValidator), new IgnoreRequiredOrDefaultBodyModelValidator());
}
...
My own BodyModelValidator is derived from the DefaultBodyModelValidator. And i figure out that i had to override the 'ShallowValidate' methode. In this override i filter the requierd model validators.
And now the IgnoreRequiredOrDefaultBodyModelValidator class and the IgnoreRequiredValidations attributte class:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
using System.Web.Http.Validation;
namespace your_namespace.Web.Http.Validation
{
public class IgnoreRequiredOrDefaultBodyModelValidator : DefaultBodyModelValidator
{
private static ConcurrentDictionary<HttpActionBinding, bool> _ignoreRequiredValidationByActionBindingCache;
static IgnoreRequiredOrDefaultBodyModelValidator()
{
_ignoreRequiredValidationByActionBindingCache = new ConcurrentDictionary<HttpActionBinding, bool>();
}
protected override bool ShallowValidate(ModelMetadata metadata, BodyModelValidatorContext validationContext, object container, IEnumerable<ModelValidator> validators)
{
var actionContext = validationContext.ActionContext;
if (RequiredValidationsIsIgnored(actionContext.ActionDescriptor.ActionBinding))
validators = validators.Where(v => !v.IsRequired);
return base.ShallowValidate(metadata, validationContext, container, validators);
}
#region RequiredValidationsIsIgnored
private bool RequiredValidationsIsIgnored(HttpActionBinding actionBinding)
{
bool ignore;
if (!_ignoreRequiredValidationByActionBindingCache.TryGetValue(actionBinding, out ignore))
_ignoreRequiredValidationByActionBindingCache.TryAdd(actionBinding, ignore = RequiredValidationsIsIgnored(actionBinding.ActionDescriptor as ReflectedHttpActionDescriptor));
return ignore;
}
private bool RequiredValidationsIsIgnored(ReflectedHttpActionDescriptor actionDescriptor)
{
if (actionDescriptor == null)
return false;
return actionDescriptor.MethodInfo.GetCustomAttribute<IgnoreRequiredValidationsAttribute>(false) != null;
}
#endregion
}
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
public class IgnoreRequiredValidationsAttribute : Attribute
{
}
}
Sources:
Using string debug = new StackTrace().ToString() to find out who is
handeling the model validation.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/configuring-aspnet-web-api to know how set my own validator.
https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http/Validation/DefaultBodyModelValidator.cs to figure out what this validator is doing.
https://github.com/Microsoft/referencesource/blob/master/System.Web/ModelBinding/DataAnnotationsModelValidator.cs to figure out why the IsRequired property is set on true. Here you can also find the original Attribute as a property.
If you don't want to use another ViewModel you can disable client validations on the view and also remove the validations on the server for those properties you want to ignore. Please check this answer for a deeper explanation https://stackoverflow.com/a/15248790/1128216
In my case the same Model was used in many pages for re-usability purposes. So what i did was i have created a custom attribute which checks for exclusions
public class ValidateAttribute : ActionFilterAttribute
{
public string Exclude { get; set; }
public string Base { get; set; }
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!string.IsNullOrWhiteSpace(this.Exclude))
{
string[] excludes = this.Exclude.Split(',');
foreach (var exclude in excludes)
{
actionContext.ModelState.Remove(Base + "." + exclude);
}
}
if (actionContext.ModelState.IsValid == false)
{
var mediaType = new MediaTypeHeaderValue("application/json");
var error = actionContext.ModelState;
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.OK, error.Keys, mediaType);
}
}
}
and in your controller
[Validate(Base= "person",Exclude ="Age,Name")]
public async Task<IHttpActionResult> Save(User person)
{
//do something
}
Say the Model is
public class User
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Range(18,99)]
public string Age { get; set; }
[MaxLength(250)]
public string Address { get; set; }
}
This one worked for me:
$('#fieldId').rules('remove', 'required');
Yes it is possible to disable Required Attribute. Create your own custom class attribute (sample code called ChangeableRequired) to extent from RequiredAtribute and add a Disabled Property and override the IsValid method to check if it is disbaled. Use reflection to set the disabled poperty, like so:
Custom Attribute:
namespace System.ComponentModel.DataAnnotations
{
public class ChangeableRequired : RequiredAttribute
{
public bool Disabled { get; set; }
public override bool IsValid(object value)
{
if (Disabled)
{
return true;
}
return base.IsValid(value);
}
}
}
Update you property to use your new custom Attribute:
class Forex
{
....
[ChangeableRequired]
public decimal? ExchangeRate {get;set;}
....
}
where you need to disable the property use reflection to set it:
Forex forex = new Forex();
// Get Property Descriptor from instance with the Property name
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(forex.GetType())["ExchangeRate"];
//Search for Attribute
ChangeableRequired attrib = (ChangeableRequired)descriptor.Attributes[typeof(ChangeableRequired)];
// Set Attribute to true to Disable
attrib.Disabled = true;
This feels nice and clean?
NB: The validation above will be disabled while your object instance is alive\active...

.NET: DataAnnotation attributes in general

ASP.NET MVC 2 will support validation based on DataAnnotation attributes like this:
public class User
{
[Required]
[StringLength(200)]
public string Name { get; set; }
}
How can I check that a current model state is valid using only pure .NET (not using MVC binding, controller methods, etc.)?
Ideally, it would be a single method:
bool IsValid(object model);
This code sample is from Steve Sanderson's blog about xVal (which uses the DataAnnotationsAttribute to validate properties). Basically, you just need to enumerate the attibutes using reflection and check IsValid():.
internal static class DataAnnotationsValidationRunner
{
public static IEnumerable<ErrorInfo> GetErrors(object instance)
{
return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>()
from attribute in prop.Attributes.OfType<ValidationAttribute>()
where !attribute.IsValid(prop.GetValue(instance))
select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), instance);
}
}

Categories