Dynamic Range Validation in ASP.NET MVC 2 - asp.net-mvc

I am using ASP.NET MVC2 and trying to validate my view models using the attributes in System.ComponentModel.DataAnnotations namespace.
How can I dynamically set the permitted valid range of a RangeAttribute?
For example, if I want to validate that a date entered is within an expected range.
This doesn't compile:
[Range(typeof(DateTime),
DateTime.Today.ToShortDateString(),
DateTime.Today.AddYears(1).ToShortDateString())]
public DateTime DeliveryDate { get; set; }
because "an attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type".
Do I need to resort to creating my own custom validator?

OK, found the answer. .NET Framework 4 provides a new CustomValidationAttribute which makes the following possible:
[Required]
[DisplayName("Ideal Delivery Date")]
[CustomValidation(typeof(HeaderViewModel), "ValidateDeliveryDate")]
public DateTime DeliveryDate { get; set; }
public static ValidationResult ValidateDeliveryDate(DateTime deliveryDateToValidate)
{
if (deliveryDateToValidate.Date < DateTime.Today)
{
return new ValidationResult("Delivery Date cannot be in the past.");
}
if (deliveryDateToValidate.Date > DateTime.Today.AddYears(1))
{
return new ValidationResult("Delivery Date must be within the next year.");
}
return ValidationResult.Success;
}
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.customvalidationattribute%28VS.100%29.aspx

You need to create your own attribute or use a none attribute based validation framework. As the message say, all parameters to any attribute needs to be constant values.

Related

Ignore property from serialization in a collection of a type with Json.Net

Is there an "easy way" to ignore a property from Model serialization on a collection of a type in Asp.Net Core?
For example
public sealed class MainViewModel
{
public Guid Id { get; set; }
[JsonIgnore("PropertyInSubViewModel")]
public ICollection<SubViewModel> Products { get; set; }
}
The idea was to remove some property in SubViewModel from model serialization, so when I get it in my action, it would have its default value set, not the one set through the request.
Not with JsonIgnore. That can only be applied on the actual property that you want to ignore, and is constant at that point. However, JSON.NET does have support for conditional serialization. The easiest and most straight-forward is adding a ShouldSerialize* method to your class. You'd obviously need to determine some condition you could lean on for the determination, but that could be a straight-forward as literally setting some boolean on your sub view model instances. Basically, you just add something like:
public class SubViewModel
{
...
public bool ShouldSerializePropertyInSubViewModel()
{
// return true or false to either allow or disallow serializing the property on this instance
}
}

Shielding nullable domain properties with ViewModel

I am using Entity Framework 4.0, and making use of POCO objects. When I populate POCO objects from the DB, I translate property values to my own Domain objects, which we can call my Model.
Necessarily, whether or not the fields of my Model are Nullable depends on whether the value it maps to in the database comes from a NULL or NOT NULL column. I won't go into detail, but the values must be nullable in the DB, because a user can partially save a draft of the object before publishing it to the public. That being the case, I have several fields that are nullable. So let's say my model looks like:
public class MyModel
{
public int? Field1 {get; set; }
public DateTime? Field2 {get; set; }
public int Field3 {get; set; }
}
If I use this Model in my View, complete with nullable fields, I begin receiving errors that tell me I cannot use nullable properties as values in various places, like HTML helpers, etc. I could say something like if (Model.MyBoolField.HasValue && Model.MyBoolField.Value) { // etc }, but that feels bulky for a view.
I considered creating a ViewModel object that inherits from my original domain object and has new, non-nullable versions of my nullable fields that return an appropriate value if the base version is null. So something like:
public class MyViewModel : MyModel
{
public new int Field1
{
get { return base.Field1 ?? 7; }
}
public new DateTime Field2
{
get { return base.Field2 ?? DateTime.Now; }
}
}
My problem with this is that I don't always know a good "default" value to display. What if I threw an exception in the View Model's getter when the base value is null? Is that poor practice?
I'm basically looking for a best practice on how to handle nullable fields in a model, particularly when displaying in a View.
If you just need to display these fields in a View, you don't need to specify or check whether is has a value or not.
Using Model.Field1 in your View file is enough. It will simple not display anything, and it won't throw an exception. You can always use ?? to set a default when it makes sense.
#(Model.Field1 ?? "There is nothing to see here")
In most of the cases I use the "For" helpers, which seem OK with Nullable values (PublishedCount is a nullable property):
#Html.TextBoxFor(m => m.BillPull.PublishedCount, new { id="txtPublishedCount" })
#Html.ValidationMessageFor(m => m.BillPull.PublishedCount)
When I need to use just TextBox, I use the GetValueOrDefault method, with whatever default value the framework provides:
#Html.TextBox("BillPull.AutoPublishDate", Model.BillPull.AutoPublishDate.GetValueOrDefault().ToString(dateFormat), new { id = "dtpAutoPublishDate" })
#Html.ValidationMessageFor(m => m.BillPull.AutoPublishDate)

Can I access the discriminator value in TPH mapping with Entity Framework 4 CTP5

Using Entity Framework 4 CTP5 Code First and this example
Is it possible to access the discriminator value?
I would like to use it in a projection like
context.BillingDetails.Select(x => new { Number = x.Number, DiscrimitatorValue = /* how do I get the discriminator value? */ });
From this post I understand the discriminator cannot be mapped to a property but is there any other way of accessing it?
I may be late to the game on this one, but I just added a getter property to the base class that returned the name of the current type:
public string DiscriminatorValue {
get {
return this.GetType().Name;
}
}
Since by default EF is going to use this same value for the Discriminator field, they will match up.
In EF Core 2.1 (I haven't checked previous versions) it's enough to add Discriminator to the base abstract class as private set property. It will be mapped with adequate value.
public abstract class Entity
{
public int Id { get; set; }
public string Discriminator { get; private set; }
}
EF by itself will automatically insert appropriate discriminator value to the database and will automatically set it to an object on read.
After further information from Morteza Manavi in the comments of his post the simple answer is no
you should be aware that the discriminator column is used internally by Code First and you cannnot read/write its values from an inheritance mapping standpoint.
To access the discriminator I would have to execute a SqlQuery against the database or change my mapping strategy.
Reason aside, I recently ran into the same problem but believe this is still relevant for v4 of the EF Framework.
First, create a view which selects the discriminator value into two columns.
create view dbo.vw_BillingDetail
as
select BillingDetailId, DiscriminatorValue, DiscriminatorValue as DiscriminatorValue2 from dbo.BillingDetail
go
Secondly, map the view to your entity during context creation:
modelBuilder
.Entity<BillingDetail>()
.HasKey(n => n.BillingDetailId)
.Map(map =>
{
map.ToTable("vw_Person");
})
Thirdly, define your discriminator mapping for your derived class using one of the columns in your view:
.Map<MyDerivedBillingDetail>(map =>
{
map.Requires("DiscriminatorValue2").HasValue("YourValue");
})
Finally, define a getter and a private setter for the other discriminator column in your view with the DatabaseGenerated annotation set as Computed to prevent EF from updating/inserting for this field:
class BillingDetail
{
public BillingDetailId { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DiscriminatorValue { get; private set; }
}
You can change the private setter to be protected and set this value explicitly during the construction of your derived entities so that the discriminator has a value prior to being persisted:
class MyDerivedBillingDetail : BillingDetail
{
public MyDerivedBillingDetail()
{
this.DiscriminatorValue = "MyValue";
}
}
To expand on #Michael Black's answer for Entity Framework Core 2.1 (earlier? tested in 2.1.4)
You can use any property name, database field name and data type you want.
Create a property:
[Column("foo_type_id")]
class Foo {
public FooTypesEnum TypeId {get; set;}
}
Then in your context class with the fluent API via modelBuilder:
modelBuilder.Entity<Foo>(b => {
b.HasDiscriminator(foo => foo.TypeId)
.HasValue<SubFooA>(FooTypesEnum.SubFooA)
.HasValue<SubFooB>(FooTypesEnum.SubFooB);
});
This is really useful if you need to build composable queries that e.g., group on the discriminator, etc.
Why don't you use the following query instead?
var q = con.BillingDetails.OfType<BankAccount>().ToList();
You can add a property with the name you gave to the discriminator in EF Core. Example:
In DBContext:
...HasDiscriminator<string>("Type")..
In base class do:
public string Type { get; private set; }

Localizing DataAnnotations Custom Validation Attribute

I'm currently working in an MVC 2 app which has to have everything localized in n-languages (currently 2, none of them english btw). I validate my model classes with DataAnnotations but when I wanted to validate a DateTime field found out that the DataTypeAttribute returns always true, no matter it was a valid date or not (that's because when I enter a random string "foo", the IsValid() method checks against "01/01/0001 ", dont know why).
Decided to write my own validator extending ValidationAtribute class:
public class DateTimeAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
DateTime result;
if (value.ToString().Equals("01/01/0001 0:00:00"))
{
return false;
}
return DateTime.TryParse(value.ToString(), out result);
}
}
Now it checks OK when is valid and when it's not, but my problem starts when I try to localize it:
[Required(ErrorMessageResourceType = typeof(MSG), ErrorMessageResourceName = "INS_DATA_Required")]
[CustomValidation.DateTime(ErrorMessageResourceType = typeof(MSG), ErrorMessageResourceName = "INS_DATA_DataType")]
public DateTime INS_DATA { get; set; }
If I put nothing in the field I get a localized MSG (MSG being my resource class) for the key=INS_DATA_Required but if I put a bad-formatted date I get the "The value 'foo' is not valid for INS_DATA" default message and not the localized MSG.
What am I misssing?
It could be that your ToString() is using a 'localized' format so your hard coded string will not match.
try replacing your "if" condition with:
if((DateTime)value == DateTime.MinValue)

ASP.NET MVC - Custom validation message for value types

When I use UpdateModel or TryUpdateModel, the MVC framework is smart enough to know if you are trying to pass in a null into a value type (e.g. the user forgets to fill out the required Birth Day field) .
Unfortunately, I don't know how to override the default message, "A value is required." in the summary into something more meaningful ("Please enter in your Birth Day").
There has to be a way of doing this (without writing too much work-around code), but I can't find it. Any help?
EDIT
Also, I guess this would also be an issue for invalid conversions, e.g. BirthDay = "Hello".
Make your own ModelBinder by extending DefaultModelBinder:
public class LocalizationModelBinder : DefaultModelBinder
Override SetProperty:
base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
foreach (var error in bindingContext.ModelState[propertyDescriptor.Name].Errors.
Where(e => IsFormatException(e.Exception)))
{
if (propertyDescriptor.Attributes[typeof(TypeErrorMessageAttribute)] != null)
{
string errorMessage =
((TypeErrorMessageAttribute)propertyDescriptor.Attributes[typeof(TypeErrorMessageAttribute)]).GetErrorMessage();
bindingContext.ModelState[propertyDescriptor.Name].Errors.Remove(error);
bindingContext.ModelState[propertyDescriptor.Name].Errors.Add(errorMessage);
break;
}
}
Add the function bool IsFormatException(Exception e) to check if an Exception is a FormatException:
if (e == null)
return false;
else if (e is FormatException)
return true;
else
return IsFormatException(e.InnerException);
Create an Attribute class:
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]
public class TypeErrorMessageAttribute : Attribute
{
public string ErrorMessage { get; set; }
public string ErrorMessageResourceName { get; set; }
public Type ErrorMessageResourceType { get; set; }
public TypeErrorMessageAttribute()
{
}
public string GetErrorMessage()
{
PropertyInfo prop = ErrorMessageResourceType.GetProperty(ErrorMessageResourceName);
return prop.GetValue(null, null).ToString();
}
}
Add the attribute to the property you wish to validate:
[TypeErrorMessage(ErrorMessageResourceName = "IsGoodType", ErrorMessageResourceType = typeof(AddLang))]
public bool IsGood { get; set; }
AddLang is a resx file and IsGoodType is the name of the resource.
And finally add this into Global.asax.cs Application_Start:
ModelBinders.Binders.DefaultBinder = new LocalizationModelBinder();
Cheers!
With the DefaultModelBinder it is possible to override the default required error message but unfortunately it would apply globally which IMHO renders it completely useless. But in case you decide to do it here's how:
Add the App_GlobalResources folder to your ASP.NET site
Add a resources file called Messages.resx
Inside the resources file declare a new string resource with the key PropertyValueRequired and some value
In Application_Start add the following line:
DefaultModelBinder.ResourceClassKey = "Messages";
As you can see there's no link between the model property you are validating and the error message.
In conclusion it is better to write custom validation logic to handle this scenario. One way would be to use a nullable type (System.Nullable<TValueType>) and then:
if (model.MyProperty == null ||
/** Haven't tested if this condition is necessary **/
!model.MyProperty.HasValue)
{
ModelState.AddModelError("MyProperty", "MyProperty is required");
}
I've been using the awesome xVal validation framework. It lets me do all my validation in the model (Even LINQ-SQL :)). It also emits the javascript required for client side validation.
EDIT: Sorry left out the link for how to get it working for LINQ-SQL
The basic workflow goes something like this.
public partial class YourClass
{
[Required(ErrorMessage = "Property is required.")]
[StringLength(200)]
public string SomeProperty{ get; set; }
}
try
{
// Validate the instance of your object
var obj = new YourClass() { SomeProperty = "" }
var errors = DataAnnotationsValidationRunner.GetErrors(obj);
// Do some more stuff e.g. Insert into database
}
catch (RulesException ex)
{
// e.g. control name 'Prefix.Title'
ex.AddModelStateErrors(ModelState, "Prefix");
ModelState.SetModelValue("Prefix.Title", new ValueProviderResult(ValueProvider["Prefix.Title"].AttemptedValue, collection["Prefix.Title"], System.Globalization.CultureInfo.CurrentCulture));
}
how about this?
[RegularExpression(#"^[a-zA-Z''-'\s]{1,40}$",
ErrorMessage = "Characters are not allowed.")]
That should allow you to tag properties with specific error messages for whatever MVC validators you want to use...
In ASP.NET MVC 1, I met this problem too.
In my project, there is a model or business object named "Entry", and its primary key EntryId is int? type, and the value of EntryId can be allowd to input by users.
So the problem is, when the field is blank or zero or some integer value that has existed, the custom error messages can be shown well, but if the value is some non-integer value like "a", i can not find a way to use the custom message to replace the default message like "The value 'a' is invalid".
when i track the error message in ModelState, i found when the value is non-integer, there will be two errors related to EntryId, and the first item's error message is blank...
Now i have to use such an ugly code to hack the problem.
if (ModelState["EntryId"].Errors.Count > 1)
{
ModelState["EntryId"].Errors.Clear(); //should not use ModelState["EntryId"].remove();
ModelState.AddModelError("EntryId", "必须为大于0的整数"); //必须为大于0的整数 means "it should be an integer value and great than 0"
}
but this makes controller fat, hope there is a real solution to solve it.
Look up ModelState.AddError.
yes, there is a way, you must use System.ComponentModel.DataAnnotations in combination with xVal and you are going to be able to set validation rules and messages (u can even use resource files for localization) for each of your property using Attributes
look here http://blog.codeville.net/2009/01/10/xval-a-validation-framework-for-aspnet-mvc/

Resources