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!
Related
If model-first, we use [MetadataType(typeof(ConceptMetadataSource))] to attach a MetadataSource file which contains all the data annotations like [HiddenInput(DisplayValue = false)] or [Display(Name = "Title")].
For example:
[MetadataType(typeof(ConceptMetadataSource))]
public partial class Concept
...
Now, I am using database-first approach since there is an existing database. This time, the entity classes are automatically created by edmx model. At the beginning of each entity class, there is lines of comment below:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
Since the code will be regenerated once we modify a table in the database, the data annotations will be wiped out each time the entity classes are regenerated.
Can anyone tell me what is the best method to annotate those entity classes? Thank you.
All you have to do is create another partial class and use metadatatype attribute. Here is the sample code
//This is generated by EDMX
namespace DataLayer
{
using System;
using System.Collections.Generic;
public partial class Customer
{
public Customer()
{
this.CustomerAddresses = new HashSet<CustomerAddress>();
this.CustomerOrders = new HashSet<CustomerOrder>();
}
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailId { get; set; }
public Nullable<System.DateTime> DateOfBirth { get; set; }
public virtual ICollection<CustomerAddress> CustomerAddresses { get; set; }
public virtual ICollection<CustomerOrder> CustomerOrders { get; set; }
}
}
Add following code manually
namespace DataLayer
{
[MetadataType(typeof(CustomerMetaData))]
public partial class Customer
{
}
public class CustomerMetaData
{
[StringLength(10, ErrorMessage = "First name must be 25 characters or less in length.")]
[Required(ErrorMessage = "First name is required.")]
public String FirstName { get; set; }
}
}
Okay, here is the answer.
The trick is, the auto-generated classes are all partial classes. The compilation process will combine all partial classes with the same name.
If we have public partial class Concept generated by DbContext, all we need to do is to create another one started with public partial class Concept. This new partial class can be created in a different folder, but we need to its namespace should be updated into the same as the auto-generated partial class.
In this newly created partial class, we can add all kinds of data-annotations such as
[Required(ErrorMesssage="This Field is required")]
Or, we can even add new properties like
FullName {get {return string.Format("{0} {1}", FirstName, LastName);}}
If the model is updated from the database again, only the auto-generated partial classes will be updated. Those newly manually added partial classes, which contain our annotations and other manipulations will remain intact.
define a view model like
public class VMConcept
{
public Concept NewConcept {get; set;}
}
[MetadataType(typeof(ConceptMetadataSource))]
public partial class Concept{}
public class ConceptMetadataSource {
[Required(ErrorMesssage="This Field is required")]
public string PropertyName {get; set;}
}
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.
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.
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
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;
}
}
}
}