Applying DataAnnotations (MVC3) - asp.net-mvc-3

I consume an API that has a full object set. I don't want to have to re-create an object model in the mvc3 code just to use the DataAnnotations.
Is there a way to use this feature with out a wrapper class? The objects have their own validation rules that I can reuse, but want to use the built in mvc framework to display the messages back.

You can create a class which inherits from DataAnnotationsModelMetadataProvider, where you can consume your object set, and write out the items as DataAnnotations. A brief example:
public class MyModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
private MyEntities _db = new MyEntities();
protected override ModelMetadata CreateMetadata(
IEnumerable<Attribute> attributes,
Type containerType,
Func<object> modelAccessor,
Type modelType,
string propertyName)
{
var modelMetadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
// load object
var objectSet = _db.ObjectSets.FirstOrDefault(x => x.PropertyName == propertyName);
// check attributes
if (objectSet.IsRequired)
modelMetadata.IsRequired = true;
return modelMetadata;
}
}
Then, register your provider in Global.asax.cs in the Application_Start method, as follows:
ModelMetadataProviders.Current = new MyModelMetadataProvider();

You could use a metadata class as in the following example from this link though I guess you'd have to add the MetaDataType attribute programatically...
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace MvcApplication1.Models
{
[MetadataType(typeof(MovieMetaData))]
public partial class Movie
{
}
public class MovieMetaData
{
[Required]
public object Title { get; set; }
[Required]
[StringLength(5)]
public object Director { get; set; }
[DisplayName("Date Released")]
[Required]
public object DateReleased { get; set; }
}
}

I never tried this. You can write a base class in MVC model that all API classes inherit. Implement IValidatableObject on that base class and use your existing validation rules. Using MetadataType, you will still have to rewrite the class with those fileds you need validation on.

Related

Cannot map Interface to a class with AutoMapper, but manually I can

I currently work on a small ASP.NET Core Web API Project. In my "VehicleModelsController" I have a class "VehicleModel":
public class VehicleModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid VehicleMakeId { get; set; }
}
In the controller I also have a method for retrieving single vehicle model:
// GET api/<controller>/Guid
[HttpGet("{id}")]
public async Task<IActionResult> GetModelById(Guid id)
{
var model = await Service.GetVehicleModelById(id);
if (model == null)
return NotFound();
var vehicleModel = new VehicleModelsController.VehicleModel()
{
Id = model.Id,
Name = model.Name,
VehicleMakeId = model.VehicleMakeId,
};
return Ok(vehicleModel);
}
As you can see in this method I am calling "GetVehicleModelById" method in my Service which is defined as follows:
public async Task<IVehicleModel> GetVehicleModelById(Guid vehicleModelId)
{
return await Repository.GetModelById(vehicleModelId);
}
As you can see, it returns vehicle model object of type "IVehicleModel" which is an interface defined as follows:
public interface IVehicleModel
{
Guid Id { get; set; }
string Name { get; set; }
Guid VehicleMakeId { get; set; }
}
This everything works when I'm doing manual mapping of IVehicleModel interface to a VehicleModel class in controller, as you already seen above, but when I try to do mapping with AutoMapper in controller, like this:
var vehicleModel = AutoMapper.Mapper.Map<VehicleModelsController.VehicleModel>(model);
I get an error:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Why is that so? Why can't I do the same with Automapper, what I already done manually?
I have defined mapping in my Mapping Profile class:
CreateMap<VehicleModelsController.VehicleModel, IVehicleModel>().ReverseMap();
so that is not a problem.
EDIT
This is my Mapping Profile class:
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<VehicleModelsController.VehicleModel, IVehicleModel>().ReverseMap();
}
}
In "ConfigureServices" method in "Startup.cs" class I have:
services.AddAutoMapper();
EDIT #2
This is exact error that I get:
AutoMapper Mapping Exception
From Automapper Source, now you can pass an assembly containing your automapper profile implementation or any type from that assembly.
Also mentioned in ReadMe page
To use, with an IServiceCollection instance and one or more assemblies:
services.AddAutoMapper(assembly1, assembly2 /*, ...*/);
or marker types:
services.AddAutoMapper(type1, type2 /*, ...*/);
So you can pass Startup.cs as marker type as below
services.AddAutoMapper(typeof(Startup));
Or your assembly containing Automapper implementation class.

How to add data annotation for entities automatically created by Data-First?

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;}
}

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!

Using Multiple Interfaces with MVC DataAnnotations and MetaDataType

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;
}
}
}
}

Using Data Annotations Validation Manually and Object Graphs

Let's assume that I have two simple classes:
public class CustomerDetails
{
[Required]
public string Address
{
get;
set;
}
}
public class Customer
{
public Customer()
{
Details = new CustomerDetails();
}
[Required]
public string Name
{
get;
set;
}
public CustomerDetails Details
{
get;
private set;
}
}
When I try to manually validate Customer class in a Console application in this way:
var customer = new Customer() { Name = "Conrad" };
var context = new ValidationContext(customer, null, null);
var results = new List<ValidationResult>();
Validator.TryValidateObject(customer, context, true);
Then -even though I chose to validate all properties of the customer instance- Validator just validates the Name property of the customer instance, but not the Address property of the Details.
Is this by design or am I missing something here? Moreover, if this is by design then is there a robust way to manually validate the full object graph decorated with validation attributes, including nested types instead of using validator for the whole object graph manually?
Please note that this is tested within a Console application and not an ASP.NET MVC application.
Kind regards.
I had almost the same problem but with the collection of nested objects. I was able to resolve it by implementing IValidatableObject on a container class. In your case it's slightly easier. Something like this:
public class Customer : IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var context = new ValidationContext(this.Details, validationContext.ServiceContainer, validationContext.Items);
var results = new List<ValidationResult>();
Validator.TryValidateObject(this.Details, context, results);
return results;
}
}
Hope this helps.

Resources