Using Multiple Interfaces with MVC DataAnnotations and MetaDataType - model-view-controller

I am applying validation using DataAnnotations to an MVC ViewModel which is a composite of several entity framework objects and some custom logic. The validation is already defined for the entity objects in interfaces, but how can I apply this validation to the ViewModel?
My initial idea was to combine the interfaces into one and apply the combined interface to the ViewModel, but this didn't work. Here's some sample code demonstrating what I mean:
// interfaces containing DataAnnotations implemented by entity framework classes
public interface IPerson
{
[Required]
[Display(Name = "First Name")]
string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
string LastName { get; set; }
[Required]
int Age { get; set; }
}
public interface IAddress
{
[Required]
[Display(Name = "Street")]
string Street1 { get; set; }
[Display(Name = "")]
string Street2 { get; set; }
[Required]
string City { get; set; }
[Required]
string State { get; set; }
[Required]
string Country { get; set; }
}
// partial entity framework classes to specify interfaces
public partial class Person : IPerson {}
public partial class Address : IAddress {}
// combined interface
public interface IPersonViewModel : IPerson, IAddress {}
// ViewModel flattening a Person with Address for use in View
[MetadataType(typeof(IPersonViewModel))] // <--- This does not work.
public class PersonViewModel : IPersonViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
My real-world problem involves about 150 properties on the ViewModel, so it's not as trivial as the sample and retyping all the properties seems like a horrible violation of DRY.
Any ideas on how to accomplish this?

In order for this to work you will need to manually associate the interfaces as metadata for your concrete classes.
I expected to be able to add multiple MetadataType attributes but that is not permitted.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] // Notice AllowMultiple
public sealed class MetadataTypeAttribute : Attribute
Therefore, this gives a compilation error:
[MetadataType(typeof(IPerson))]
[MetadataType(typeof(IAddress))] // <--- Duplicate 'MetadataType' attribute
public class PersonViewModel : IPersonViewModel
However, it works if you only have one interface. So my solution to this was to simply associate the interfaces using a AssociatedMetadataTypeTypeDescriptionProvider and wrap that in another attribute.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class MetadataTypeBuddyAttribute : Attribute
{
public MetadataTypeBuddyAttribute(Type modelType, Type buddyType)
{
TypeDescriptor.AddProviderTransparent(
new AssociatedMetadataTypeTypeDescriptionProvider(
modelType,
buddyType
),
modelType);
}
}
In my situation (MVC4) the data annotation attributes on my interfaces already worked. This is because my models directly implement the interfaces instead of having multi-level inheritance. However custom validation attributes implemented at the interface level do not work.
Only when manually associating the interfaces all the custom validations work accordingly. If I understand your case correctly this is also a solution for your problem.
[MetadataTypeBuddy(typeof(PersonViewModel), typeof(IPerson))]
[MetadataTypeBuddy(typeof(PersonViewModel), typeof(IAddress))]
public class PersonViewModel : IPersonViewModel

based on answer here, I couldn't somehow make that MetadataTypeBuddy attribute works. I'm sure that we must set somewhere that MVC should be calling that attribute. I managed to get it work when I run that attribute manually in Application_Start() like this
new MetadataTypeBuddyAttribute(typeof(PersonViewModel), typeof(IPerson));
new MetadataTypeBuddyAttribute(typeof(PersonViewModel), typeof(IAddress));

The MetadataTypeBuddy attribute did not work for me.
BUT adding "new" MetadataTypeBuddyAttribute in the "Startup" did work BUT it can lead to complex code where the developer is not aware to add this in the "Startup" for any new classes.
NOTE: You only need to call the AddProviderTransparent once at the startup of the app per class.
Here is a thread safe way of adding multiple Metadata types for a class.
[AttributeUsage(AttributeTargets.Class)]
public class MetadataTypeMultiAttribute : Attribute
{
private static bool _added = false;
private static readonly object padlock = new object();
public MetadataTypeMultiAttribute(Type modelType, params Type[] metaDataTypes)
{
lock (padlock)
{
if (_added == false)
{
foreach (Type metaDataType in metaDataTypes)
{
System.ComponentModel.TypeDescriptor.AddProviderTransparent(
new AssociatedMetadataTypeTypeDescriptionProvider(
modelType,
metaDataType
),
modelType);
}
_added = true;
}
}
}
}

Related

MVC3 same ViewModel used for two cases with Required attribute in only one case

I have a ViewModel. something like this
public class ViewModel
{
public int Id { get; set; }
public int? Value { get; set; }
}
I have a table of existing ViewModels, and below that I have a form where you can add a new ViewModel
For existing ViewModels that are fetched from DB i want no validation on the Value property, but for the case when adding a new ViewModel I want required validation.... The real model is more complex then this one so I want to use the same model in both cases.. Is it possible?
edit: this works
public class AddNewViewModel : ViewModel
{
public new int Value { get; set; }
}
Is it better to use new or virtual/override and why?
Required attributes are compiled into the class. You could do something like this:
public class BaseViewModel
{
public int Id { get; set; }
public virtual int? Value { get; set; }
}
public class CreateViewModel : BaseViewModel
{
[Required]
public override int? Value { get; set; }
}
This way, you only add the validation attribute to the properties where you need them.

Losing DataAnnotations when using POCO Entity Framework 4

I'm using the new EntityFramework 4.1 with POCO objects, in conjunction with the DataAnnotation framework.
When EntityFramework needs to create a proxy class (for example, lazy loading virtual properties), all of my data annotations are lost. Is there some way that my ModelMetaData can come from the class that was proxied, instead of the proxy class?
I know that I have the choice to disable proxy creating (context.Configuration.ProxyCreationEnabled) but this seems like a poor answer. This should be something that's been solved, I would think.
Here's some example code:
public class Person
{
[Required, Display(Name = "Display That Name")]
public string DisplayName { get; set; }
}
And then in my model metadata in the view, the type is: Person_9C92B92D56F6F4F0FB1686526D585438A05323CC72E457C2152796E8866297E1 (FullName = "System.Data.Entity.DynamicProxies.Person_9C92B92D56F6F4F0FB1686526D585438A05323CC72E457C2152796E8866297E1"}), my metadata is gone, and the displayname renders out at "DisplayName" not "Display That Name".
Any suggestions?
You could make a Metadata version of your model classes.
We do that thisway... what EF generate is never touched by hand.
Let said you have a Person class:
public partial class Person
{
public int idPerson { get; set; }
public int idTenant { get; set; }
public string Name { get; set; }
}
Then you could make the Metadata class, that holds the metadata and won't be overrided:
[MetadataType(typeof(PersonMD))]
public partial class Person
{
//leave it empty
}
public partial class PersonMD
{
[Required]
public int idPerson { get; set; }
[Required]
public int idTenant { get; set; }
[Required, Display(Name = "Display That Name")]
public string Name { get; set; }
}
What we do is as follows:
Modify the T4 templates to generate partial classes for your entities.
For those entities that you wish to add annotations to, create a partial class of the same name of your entity.
Create a buddy class within this class that provides your annotation details.
Apply the attribute at the top of the partial class to specify that your buddy class is where the annotation details can be found.
See here for more details http://msdn.microsoft.com/en-us/library/ee256141.aspx
I figured out one possible solution. Not sure if there are better ones. First I wrote a new ModelMetadataProvider:
public class IgnoreProxiesDataAnnotationsModelMetadataProvider : System.Web.Mvc.DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
modelType = ObjectContext.GetObjectType(modelType);
containerType = ObjectContext.GetObjectType(containerType);
return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
}
}
And then registered it in Global.asax application start:
ModelMetadataProviders.Current = new IgnoreProxiesDataAnnotationsModelMetadataProvider();
If there's a better solution, please let me know!

ASP.NET MVC3 Conditional Validation of nested model for EditorTemplate

Suppose you have a viewModel:
public class CreatePersonViewModel
{
[Required]
public bool HasDeliveryAddress {get;set;}
// Should only be validated when HasDeliveryAddress is true
[RequiredIf("HasDeliveryAddress", true)]
public Address Address { get; set; }
}
And the model Address will look like this:
public class Address : IValidatableObject
{
[Required]
public string City { get; set; }
[Required]
public string HouseNr { get; set; }
[Required]
public string CountryCode { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string ZipCode { get; set; }
[Required]
public string Street { get; set; }
#region IValidatableObject Members
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
string[] requiredFields;
var results = new List<ValidationResult>();
// some custom validations here (I removed them to keep it simple)
return results;
}
#endregion
}
Some would suggest to create a viewmodel for Address and add some custom logic there but I need an instance of Address to pass to my EditorTemplate for Address.
The main problem here is that the validation of Address is done before the validation of my PersonViewModel so I can't prevent it.
Note: the RequiredIfAttribute is a custom attribute which does just what I want for simple types.
Would have been a piece of cake if you had used FluentValidation.NET instead of DataAnnotations or IValidatableObject which limit the validation power quite in complex scenarios:
public class CreatePersonViewModelValidator : AbstractValidator<CreatePersonViewModel>
{
public CreatePersonViewModelValidator()
{
RuleFor(x => x.Address)
.SetValidator(new AddressValidator())
.When(x => x.HasDeliveryAddress);
}
}
Simon Ince has an alpha release of Mvc.ValidationToolkit which seems to be able to do what you want.
Update
As I understand it, the 'problem' lies in the DefaultModelBinder class, which validates your model on the basis that if it finds a validation attribute it asks it if the value is valid (quite reasonable really!), it has no notion of hierarchy. In order to support your required functionality you'll have to write a custom model binder that binds and then validates, if required, as determined by your declarative markup.
If you do write such a class it may be a good candidate for MVC futures.

How to update a property of an abstract with an inheriting/using a subblass in MVC

I have an abstract class
public abstract class Member
{
public string ID { get; set; }
public string Username { get; set; }
public int MemberType { get; set; }
public abstract string MemberName { get; set; }
public int Status { get; set; }
}
public class Person : Member
{
public string LastName { get; set; }
public string FirstName{ get; set; }
}
public class Business : Member
{
public string BusinessName { get; set; }
public string TaxNo { get; set; }
}
The class was mapped using fluent API,
Now, is there a way to update the "Status" property from the view(having Member model) without using or going to a subclass (Person/Business)?
I just tried it, but it says "Cannot create an abstract class.", when submitting the page.
Or there is a correct way to do this?
Thanks
Not in EF. You have to instantiate an object to work with EF, and you can't instantiate an abstract class.
You could make the class not be abstract. Or you could use a stored proc to update the field, or some direct sql.
It sounds like your problem is that your action method has an abstract type as a parameter, which the default model binder can't do anything with. If you are dead set on using the same view for two different classes, you may need to implement your own model binder to inspect in the incoming request and decide which type, Person or Business, to instantiate.
Check out this link for more information on creating a custom model binder:
http://odetocode.com/blogs/scott/archive/2009/05/05/iterating-on-an-asp-net-mvc-model-binder.aspx
This seems like a similar problem to the one I've answered previously here:
ASP.NET MVC 3: DefaultModelBinder with inheritance/polymorphism

MVC3 Remote attribute, how to implement?

I have a Solution under the solution there is few Projects one of the called DomainModel,
in which i write my models and other stuff mainly infrastructure.
Now i have another project called WebUI in which i do my UI (Views, Controllers , etc...)
I want to use Remote attribute in DomainModel project which must implemented in WebUI certain view.
When i use it in DomainModel it's gives me an error, that it does not recognize the Controller and it's correct it does not recognize it because the if I add the reference of WebUI the Vs begin to swear at me because it will be a circular reference.
How to implement this?
this is my code
Controller that serves the RemoteValidation
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
public class RemoteValidationController : Controller
{
public JsonResult CheckPassword(string SmsCode)
{
return Json(12345, JsonRequestBehavior.AllowGet);
}
}
//The real entity in DomainModel project
public class SmsCustomer
{
public int CustomerId { get; set; }
public string Cli { get; set; }
//this is what i have to validate on server
public virtual string SmsCode { get; set; }
public DateTime InsertDate { get; set; }
public int CustomerDaysChoiceId { get; set; }
public int CustomerAmountChoiceId { get; set; }
[Required(ErrorMessage = "error")]
[StringLength(128, ErrorMessage = "error")]
public string SelectedWords { get; set; }
public SmsCustomerDaysChoice CustomerDaysChoice { get; set; }
public SmsCustomerAmountChoice CustomerAmountChoice { get; set; }
}
this is my entity after i extend it with the remote attr in WebUI.Models
public class Customer : SmsCustomer
{
[Required(ErrorMessage = "Error required")]
[StringLength(9, ErrorMessage = "Error length")]
[Remote("CheckPassword", "RemoteValidation", ErrorMessage = "Error remote")]
public override string SmsCode { get; set; }
}
this is my view
#Html.TextBoxFor(c => c.SmsCode)
//error span
<span class="checkbox-form-error" data-valmsg-for="SmsCode" data-valmsg-replace="true"> </span>
The remote validation stuff is very specific to the WebUI project.
Because of this, I'd create a View model that inherits from the actual class, and then override the property that needs remote validation. Then you should be able to specify the controller/action for remote validation.
You can also put your validation in a class of its own, like ScottGu demonstrates here:
http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx
(Look down the post, before the last step)
Also take a look at this: Adding DataAnnontations to Generated Partial Classes

Resources