So I have an issue around a custom attribute I want to add to all required fields. Now instead of extending all my helper classes to include this attribute to the input if required, I wondered if I could just save a lot of time and add it in the custom required attribute itself.
Something like this:
model:
[MyCustomRequiredValidator]<--I want to add it in the validationAttribute extension
public int? myField
I obviously can't just add it via the ModelClientValidationRule because it prefixes the attribute with data-val- which is no good but I can't seem to get access to the attributes themselves. I've tried using metadata.AdditionValue.add but no joy there.
The attribute I want to add is aria-required="true" for screen reader support.
Is this possible?
Any advice would be great because I've hit a wall.
thanks for looking.
OK so for what it's worth I found a solution. I added a tag to the meta data through all the required validators on creation of the metadata (I had to also inherit the IMetadataAware interface on the class declaration):
public void OnMetadataCreated(ModelMetadata metadata) {
metadata.AdditionalValues.Add("AriaRequired", "true");
}
I then extended all the input field helpers to check for this value and add a custom attribute to the input by checking the existence of the above attribute, in this case AriaRequired.
if (htmlAttributes == null) htmlAttributes = new Dictionary<string, object>();
if (!htmlAttributes.ContainsKey("aria-required"))
{
ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
htmlAttributes.Add("aria-required", metaData.IsAriaRequiredValue());
}
}
Hope this helps people :-)
Related
i am new in mvc .here scott shows how to Creating a Custom [Email] Validation Attribute in mvc. here is the picture.
http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx
1) now see how they did it. first create a class give a name and extend regular expression attribute class and in its ctor they use regex to validate email address
my question is when they use [Email(Errormessage="blah blah")]
then how MVC can understand this email attribute is pointing to email attribute class which extend regularexpression attribute class. how relation will be extanlish. the class name is email attribute but when they use then they use attribite name email. this is not clear to me please explain.
2) if i validate the email the above way they where validation will occur means at server side or client side ?
if not client side then how can i make it client and required js will be render for that.
please explain me with sample code example. thanks
The first question is best answered with a principle widely used in MVC: convention over configuration. That basically means: do the less config possible, use the most default functionalities. Several examples in ASP.NET MVC
Folder Controllers contain controllers by default.
The name of a view corresponds to the name of a Action in a Controller.
The folder name where a view is located corresponds to the Controller name without 'Controller' ending.
The class name of the controller ends with 'Controller' which is omitted when calling the controller.
The same with Attributes; the class name ends with 'Attribute' which is omitted in usage
etc, etc, etc,
There are many more like this and it is not configured. It is convention.
The second question is already partially answered in the question itself: you cannot inherit from EmailAddressAttribute as it's a sealed class. But you can use
RegularExpressionAttribute the way it's described in your question, or create a new attribute, like I will do it below.
However this way the validation will take place only on server side. To make it on client side you need to do the following:
public class EmailAttribute : ValidationAttribute, IClientValidatable
{
private const string VALIDATION_TYPE = "customEmail";
private const string EMAIL_REGEX = #"put your regex here";
public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule { ValidationType = VALIDATION_TYPE, ErrorMessage = ErrorMessageString };
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var sValue = value as string;
if (!string.IsNullOrEmpty(sValue) && Regex.Match(sValue, EMAIL_REGEX).Success)
{
return ValidationResult.Success;
}
return new ValidationResult(string.Format(ErrorMessageString, validationContext.MemberName));
}
}
Then in Javascript (I suppose you've included jQuery, jQuery.validate and jQuery.validate.unobtrusive) use the following:
$.validator.addMethod('customEmail', function (value, element) {
let regex = /put your regex here/;
return regex.test($(element).val());
});
$.validator.unobtrusive.adapters.add('customEmail', [], function (options) {
options.messages['customEmail'] = options.message;
options.rules['customEmail'] = options.params;
});
In razor engine I have used LabelFor helper method to display the name
But the display name is seems to be not good to display.
so i need to change my display name how to do it....
#Html.LabelFor(model => model.SomekingStatus, new { #class = "control-label"})
You could decorate your view model property with the [DisplayName] attribute and specify the text to be used:
[DisplayName("foo bar")]
public string SomekingStatus { get; set; }
Or use another overload of the LabelFor helper which allows you to specify the text:
#Html.LabelFor(model => model.SomekingStatus, "foo bar")
And, no, you cannot specify a class name in MVC3 as you tried to do, as the LabelFor helper doesn't support that. However, this would work in MVC4 or 5.
This was an old question, but existing answers ignore the serious issue of throwing away any custom attributes when you regenerate the model. I am adding a more detailed answer to cover the current options available.
You have 3 options:
Add a [DisplayName("Name goes here")] attribute to the data model class. The downside is that this is thrown away whenever you regenerate the data models.
Add a string parameter to your Html.LabelFor. e.g. #Html.LabelFor(model => model.SomekingStatus, "My New Label", new { #class = "control-label"}) Reference: https://msdn.microsoft.com/en-us/library/system.web.mvc.html.labelextensions.labelfor(v=vs.118).aspx The downside to this is that you must repeat the label in every view.
Third option. Use a meta-data class attached to the data class (details follow).
Option 3 - Add a Meta-Data Class:
Microsoft allows for decorating properties on an Entity Framework class, without modifying the existing class! This by having meta-data classes that attach to your database classes (effectively a sideways extension of your EF class). This allow attributes to be added to the associated class and not to the class itself so the changes are not lost when you regenerate the data models.
For example, if your data class is MyModel with a SomekingStatus property, you could do it like this:
First declare a partial class of the same name (and using the same namespace), which allows you to add a class attribute without being overridden:
[MetadataType(typeof(MyModelMetaData))]
public partial class MyModel
{
}
All generated data model classes are partial classes, which allow you to add extra properties and methods by simply creating more classes of the same name (this is very handy and I often use it e.g. to provide formatted string versions of other field types in the model).
Step 2: add a metatadata class referenced by your new partial class:
public class MyModelMetaData
{
// Apply DisplayNameAttribute (or any other attributes)
[DisplayName("My New Label")]
public string SomekingStatus;
}
Reference: https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.metadatatypeattribute(v=vs.110).aspx
Notes:
From memory, if you start using a metadata class, it may ignore existing attributes on the actual class ([required] etc) so you may need to duplicate those in the Meta-data class.
This does not operate by magic and will not just work with any classes. The code that looks for UI decoration attributes is designed to look for a meta-data class first.
You can change the labels' text by adorning the property with the DisplayName attribute.
[DisplayName("Someking Status")]
public string SomekingStatus { get; set; }
Or, you could write the raw HTML explicitly:
<label for="SomekingStatus" class="control-label">Someking Status</label>
Decorate the model property with the DisplayName attribute.
#Html.LabelFor(model => model.SomekingStatus, "foo bar")
Does anybody know a way to turn off MVC3 automatically decorating primitive types with a data-val-* attribute.
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
removes the data-val-required attribute, but I can't seem to find a way to turn off primitive types eg: data-val-number
I have a lot of hidden int fields which don't required validating on a form, but because of these attributes they are getting validated, causing my app to appear frozen.
I imagine that the hidden int fields have the [Required] data annotations defined on them in the viewmodel? If so then I believe you just need to remove the data annotation to prevent the data-val-required attribute from being displayed.
I could be wrong, but I suspect you will then say that the field is required when that viewmodel is used in some other views?
If this is the case, then rather than turning off the data annotations (which is essentially a work around) then you need to define your view models correctly. Ideally, each view model should be specific for the view that it is defined (see pattern 3 of the following link). This will avoid the issues where you have fields that are required on some views and are not required on others.
I couldn't seem to find a way to turn this off, so created my own HtmlHelper as a way to get around this issue.
public static IHtmlString HiddenInputFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
var memberExpression = (MemberExpression)expression.Body;
string fullID = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(memberExpression.Member.Name);
var builder = new TagBuilder("input");
builder.MergeAttribute("type", "hidden");
var value = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
builder.MergeAttribute("value", value.ToString());
string fullName = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(expression));
builder.MergeAttribute("name", fullName);
builder.GenerateId(fullID);
var tag = builder.ToString(TagRenderMode.SelfClosing);
return new HtmlString(tag);
}
I've noticed that if you load a partial view from an ajax request, the validations (data-val-*) inside the partial view are not automatically added. So I finally changed my code to load from ajax the heavy form data that doesn't need validations.
You can specify data-val="false" in the HTML input which you are creating on the page, for example:
<input type="checkbox" name="foo" value="#item.foo" class="input-validation-error"
data-val="false">
I have a view model sent to the edit action of my controller. The ViewModel contains references to EntityObjects. (yea i'm fine with it and don't need to want to duplicate all the entities properties in the viewmodel).
I instantiate the view model and then call UpdateModel. I get an error that a property is "null" which is fine since it is a related model. I am trying to exclude the property from being bound during model binding. On debugging it I see in the entity where the model binder is trying to set the value of the property to null.
Here is my edit action:
var model = new SimplifiedCompanyViewModel(id);
var excludeProperties = new string[] {
"Entity.RetainedEarningsAccount.AccountNo"
,"Property.DiscountEarnedAccount.ExpenseCodeValue"
,"Entity.EntityAlternate.EntityID"
,"Property.BankAccount.BankAccountID"
,"Entity.PLSummaryAccount.AccountNo"
,"Property.RefundBank.BankAccountID"
,"Company.Transmitter.TCC"
};
try
{
UpdateModel<SimplifiedCompanyViewModel>(model, String.Empty, null, excludeProperties);
if (ModelState.IsValid)
{
//db.SaveChanges();
}
return RedirectToAction("Index");
}
catch
{
return View(model);
}
I have looked at a few other issues about specifying a "prefix" but I don't think that is the issue since I am telling it to bind to the viewmodel instance not just the entity object.
Am I excluding the properties correctly? Strange thing is is only seems to happen on this item. I suspect it may be an issue with the fact that there is actually no refund bank related to my entity. But I have other related items that don't exist and don't see the same issue.
More info... since I'm told me model isn't designed well.
The Company is related to a BankAccount. The Company view shows the currently related BankAccount.BankAccountId and there is a hidden field with the BankAccount.Key. I use jQueryUI autocomplete feature to provide a dropdown of bank account displaying the BankAccount.BankAccountId and when one is selected the jQuery code changes the hidden field to have the correct Key value. So, when this is posted I don't want the current bankaccounts BankAccountID modified, hence I want it to skip binding that field.
If I exclude BankAccountId in the model then on the BankAccount edit view the user would never be able to change the BankAccountId since it won't be bound. I'm not sure how this indicates a poor model design.
Use the Exclude property of the Bind attribute:
[Bind(Exclude="Id,SomeOtherProperty")]
public class SimplifiedCompanyViewModel
{
public int Id { get; set; }
// ...
}
This is part of the System.Web.Mvc namespace. It takes a comma-separated list of property names to exclude when binding.
Also you should consider using TryUpdateModel instead of UpdateModel. You can also just have the default model binder figure it out by passing it as an argument to the constructor:
public ActionResult Create([Bind(Exclude="Id")]SimplifiedCompanyViewModel model)
{
// ...
}
A very simple solution that I figured out.
try
{
UpdateModel<SimplifiedCompanyViewModel>(model, String.Empty, null, excludeProperties);
ModelState.Remove("Entity.RetainedEarningsAccount.AccountNo");
ModelState.Remove("Property.DiscountEarnedAccount.ExpenseCodeValue");
ModelState.Remove("Entity.EntityAlternate.EntityID");
ModelState.Remove("Property.BankAccount.BankAccountID");
ModelState.Remove("Entity.PLSummaryAccount.AccountNo");
ModelState.Remove("Property.RefundBank.BankAccountID");
ModelState.Remove("ompany.Transmitter.TCC");
if (ModelState.IsValid)
{
//db.SaveChanges();
}
return RedirectToAction("Index");
}
catch
{
return View(model);
}
Another option here is simply don't include this attribute in your view and it won't be bound. Yes - you are still open to model injection then if someone creates it on the page but it is another alternative. The default templates in MVC will create your EditorFor, etc as separate items so you can just remove them. This prevents you from using a single line view editor with EditorForModel, but the templates don't generate it that way for you anyways.
EDIT (adding above comment)
DRY generally applies to logic, not to view models. One view = one view model. Use automapper to easily map between them. Jimmy Bogard has a great attribute for this that makes it almost automatic - ie you create the view model, load up your Customer entity for example, and return it in the action method. The AutpMap attribute will then convert it to a ViewModel. See lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models
Try the Exclude attribute.
I admit that I haven't ever used it.
[Exclude]
public Entity Name {get; set;}
I'm writing an MVC2 app using DataAnnotations. I have a following Model:
public class FooModel
{
[ScaffoldColumn("false")]
public long FooId { get; set; }
[UIHint("BarTemplate")]
public DateTime? Bar { get; set;}
}
I want to create a custom display template for Bar. I have created following template:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DateTime?>" %>
<div class="display-label">
<span><%: Html.LabelForModel() %></span>
</div>
<div class="display-field">
<span><%: Html.DisplayForModel()%></span>
<%: Html.ActionLink("Some link", "Action", new { id = ??FooId?? }) %>
</div>
Now, my problem is that inside template for Bar I want to access another property from my model. I don't want to create a separate template for FooModel because than I will have to hardcode all other FooModel properties.
After a brief investigation with a debugger I can see that:
this.ViewData.ModelMetadata.ContainerType
is FooModel (as expected)
this.ViewData.TemplateInfo has a
non-public property VisitedObjects
(of type
System.Collections.Generic.HashSet<object>)
which contains two elements:
FooModel and DateTime?.
How can I get access to my FooModel? I don't want to hack my way around using Reflection.
Update:
I've accepted mootinator's answer as it looks to me as the best solution that allows type-safety. I've also upvoted Tx3's answer, as mootinator's answer builds upon it. Nevertheless, I think that there should be a better support form MVC in those kind of scenarios, which I believe are quite common in real world but missing from sample apps.
Maybe you could create new class, let's say UserDateTime and it would contain nullable DateTime and rest of the information you need. Then you would use custom display template for UserDateTime and get access to information you require.
I realize that you might be looking for other kind of solution.
I think you may be better off extracting this functionality to an HtmlHelper call from the Parent View.
Something like RenderSpecialDateTime<TModel>(this HtmlHelper html, Expression<Func<TModel,DateTime?>> getPropertyExpression) would probably do the job.
Otherwise, you will have to do something like what Tx3 suggested. I upvoted his answer, but posted this as an alternative.
Couldn't you use the ViewData dictionary object in the controller and then grab that in the ViewUserControl? It wouldn't be strongly typed but...you could write a helper to do nothing if it's empty, and link to say the example login history page if it had a value.
It would appear that somewhere between MVC 5.0 and 5.2.2 a "Container" property was added on to the ModelMetadata class.
However, because all of the methods in a provider responsible for metadata creation (GetMetadataForProperty, Create etc) do not have container in their signature, the Container property is assigned only in certain cases (GetMetadataForProperties and GetMetadataFromProvider according to reflected code) and in my case was usually null.
So what I ended up doing is overriding the GetMetadataForProperty in a new metadata provider and setting it there:
public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
{
var propMetaData = base.GetMetadataForProperty(modelAccessor, containerType, propertyName);
Object container = modelAccessor.Target.GetType().GetField("container").GetValue(modelAccessor.Target);
propMetaData.Container = container;
return propMetaData;
}
I know this is reflection but it's fairly succinct. It would appear that MS is correcting this oversite so maybe it will be possible to replace the reflection code in the future.
Sorry if this suggestion seems daft, I haven't tried it, but couldn't you do what Tx3 suggested without having to create a bunch of new classes by defining a generic class to reference whatever type of parent you want?
public class FooModel
{
[ScaffoldColumn("false")]
public long FooId { get; set; }
[UIHint("BarTemplate")]
public ParentedDateTime<FooModel> Bar { get; set;}
public FooModel()
{
Bar = new ParentedDateTime<FooModel>(this);
}
}
public class ParentedDateTime<T>
{
public T Parent {get; set;}
public DateTime? Babar {get; set; }
public ParentedDateTime(T parent)
{
Parent = parent;
}
}
You could expand that to encapsulate any old type with a <Parent, Child> typed generic, even.
That would also give you the benefit that your strongly typed template would be for
Inherits="System.Web.Mvc.ViewUserControl<ParentedDateTime<FooType>> thus you would not have to explicity name which template to use anywhere. This is more how things are intended to work.