Generating Data Annotations from Generated Classes - asp.net-mvc-2

I have a linq to sql object or if neccessary Entity Framework object.
I want to do MVC 2 Data Annotations for them, but I am endlessly lazy.
Is there a way to automatically generate the data annotations a-la
[Bind(Include = "Title,Description,EventDate,Address,Country,ContactPhone,Latitude,Longitude")]
[MetadataType(typeof(Dinner_Validation))]
public partial class Dinner
{
public bool IsHostedBy(string userName)
{
return HostedBy.Equals(userName, StringComparison.InvariantCultureIgnoreCase);
}
public bool IsUserRegistered(string userName)
{
return RSVPs.Any(r => r.AttendeeName.Equals(userName, StringComparison.InvariantCultureIgnoreCase));
}
}
public class Dinner_Validation
{
[Required(ErrorMessage = "Title is required")]
[StringLength(50, ErrorMessage = "Title may not be longer than 50 characters")]
public string Title { get; set; }
[Required(ErrorMessage = "Description is required")]
[StringLength(265, ErrorMessage = "Description may not be longer than 256 characters")]
public string Description { get; set; }
[Required(ErrorMessage = "HostedBy is required")]
public string HostedBy { get; set; }
[Required(ErrorMessage = "Address is required")]
public string Address { get; set; }
[Required(ErrorMessage = "Country is required")]
public string Country { get; set; }
[Required(ErrorMessage = "Phone# is required")]
public string ContactPhone { get; set; }
}
So that I don't have to do it all myself?

I think it would be redundant to generate data annotations.
Instead, I'd suggest writing an associated metadata provider which will simply cause the MVC model binding and validation to see the correct metadata for your types without requiring data annotations at all (or will supplement any data annotations you may already have).
There's an example here.

I borrowed a little from my Silverlight toolbox for this, but it seems to work just fine for MVC3 in VS2010.
Compile your project. This is important if you just created your Entity Framework model.
Right-click on your project. Click Add/New Item.
Select 'Domain Service Class' as the type. Click Add.
Select your model in the drop down.
In the list of entities, select all objects the you want data annotations for.
Check the box labeled 'Generate associated classes for metadata'. Click OK.
You will get two classes generated. Just delete the one without the .metadata. tag.
That should do it. You should now have a metadata class ready to add your annotations. (It's possible that the Domain Service Class used above was installed with the WCF RIA Services toolkit in VS2010. Not positive about that, but if you don't have this in your list of available items, that's probably the issue.)

Related

How to use Remote validation with view models

I have a asp.NET MVC 5 project with 4 assembly projects. Those are;
Common
Domain
Service
Web.UI
I have a model class in domain layer.
public class Employee
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required(ErrorMessage = "*Please insert employee's first name")]
[Display(Name = "First Name", Prompt = "First Name")]
public string firstName { get; set; }
[Required(ErrorMessage = "*Please insert employee's last name")]
[Display(Name = "Last Name", Prompt = "Last Name")]
public string lastName { get; set; }
[Required(ErrorMessage = "*Please insert employee's email address")]
[Display(Name = "E-Mail", Prompt = "E-Mail")]
[Remote("IsEmailExists", "Employee", ErrorMessage = "This email is already registered: please enter a different email.")]
[RegularExpression(#"^([0-9a-zA-Z]([\+\-_\.][0-9a-zA-Z]+)*)+#(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]*\.)+[a-zA-Z0-9]{2,3})$",
ErrorMessage = "*This email is invalid. Please enter a valid email")]
public string email { get; set; }
}
The Controller class for Employee is in a Web.UI project/Layer. In model class for email attribute, I have used remote validation to check whether the email is existing when registering a new employee. Relavent method to check that is in the Employee Controller.
public JsonResult IsEmailExists(string UserEmail)
{
//check if any of the Email matches the UserEmail specified in the Parameter using the ANY extension method.
return Json(!db.Employees.Any(x => x.email == UserEmail), JsonRequestBehavior.AllowGet);
}
This is not working as I expected. I searched a solution for the problem and what I figured out is, I have to use View models, because I'm using assembly projects. But I have no idea about how to do this. If anybody can help, it would be a great help.
Thanks.
Your [RemoteAttribute] is not working because the method you are calling has an incorrect parameter. It needs to be
public JsonResult IsEmailExists(string email)
The name of the parameter must match the name of the property you apply the attribute to.
Having said that, you still should be using a view model. The int ID for example is not appropriate (the user has not been created yet). And attributes such as [Remote] and [Display] are not applicable to a data model. And assuming you need a password to register, your view model would need an additional property such as ConfirmPasswrd with a [Compare] attribute which would not be applicable in a data model.
Refer What is ViewModel in MVC? for more information on why you should be using a view model and its benefits.
Side note: I recommend you use the [EmailAddress] attribute in lieu of your [RegularExpression] attribute.

ASP.NET MVC4 Web API Controller serialization

I am trying to create a RESTful web service that returns a list of products using ASP.NET MVC4 Web API. Here is my controller class
public class ProductController : ApiController
{
public IEnumerable<Product> GetProducts()
{
WebCatalogContext dbcontext = DatabaseConfig.Instance.Context;
List<Product> plist = dbcontext.Products.ToList();
return plist;
}
}
When I run my service and call the following URL from my browser :/api/Product, I get System.Runtime.Serialization.SerializationException. I looked into my plist object and there is no problem with it.
Here is my data model:
[DataContract(Name = "p")]
[Serializable]
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[DataMember(Name = "id")]
public int Id { get; set; }
[Required, MaxLength(50)]
[DataMember(Name = "ti")]
public string Title { get; set; }
[Required]
[DataMember(Name = "de")]
public string Description { get; set; }
[Required]
[DataMember(Name = "ph")]
public string PhotoURL { get; set; }
[DataMember(Name = "ca")]
public virtual ProductCategory Category { get; set; }
}
[DataContract(Name="pc")]
[Serializable]
public class ProductCategory
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[DataMember(Name="id")]
public int Id { get; set; }
[DataMember(Name="nm")]
public string Name { get; set; }
}
When I remove the reference to ProductCategory from my Product class, all things work just fine. But, when I include it I get the following exception.
Type 'System.Data.Entity.DynamicProxies.Product_664E9A0AA1F165A26C342B508BFFF1279FD3FE059285225BDA19F407A29A9CAD' with data contract name 'Product_664E9A0AA1F165A26C342B508BFFF1279FD3FE059285225BDA19F407A29A9CAD:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.
Any idea about what I am missing?
Regards
Entity Framework has wrapped your POCO with an EF Proxy POCO so it can perform lazy loading - this uses the Virtual attribute to create a 'lazy-loadable' navigation property. I expect that is where the serialization error comes from.
You could make a new class and map the POCO to that - then pass the DTO style class from the controller. I've never returned an EF object directly from the API (I always map to some something else) so I don't know another option.
EF POCO to DTO (data transfer object) is relatively painless if you use a tool like http://valueinjecter.codeplex.com/ or http://automapper.org/
To support Lazy Loading for navigation properties which is declared as virtual, EF will generate the proxies for any models which have navigation properties which leads to this kind of exception.
For very simple application, you can use model from EF as DTOs (if having no navigation properties), but for complex application, you should do separate and differ between DTOs and domain models. It should not be mapping 1:1 between DTO and domain model.
Therefore, in your case, you create more DTO model for Web API layer, it will be fine.

ef5 database first data annotation

I am starting MVC4 with VS2012. I am also using EF5 with the "Database First" method of creating my classes.
However because the generated glasses can be regenerated I cannot put the Data Annotation details to assist with validation.
I have seen some code snippets that use MetaData and partial classes but I was wondering if anyone knows of a small compilable example that I can look at and pull apart to better understand how the vasious classes interlink.
Many many thanks for any help.
Dave
You can achieve what you need through extending models. Suppose that EF generated the following entity class for you:
namespace YourSolution
{
using System;
using System.Collections.Generic;
public partial class News
{
public int ID { get; set; }
public string Title { get; set; }
public int UserID { get; set; }
public virtual UserProfile User{ get; set; }
}
}
and you want do some work arounds to preserve your you data annotations and attributes. So, follow these steps:
First, add two classes some where (wherever you want, but it's better to be in Models) like the following:
namespace YourSolution
{
[MetadataType(typeof(NewsAttribs))]
public partial class News
{
// leave it empty.
}
public class NewsAttribs
{
// Your attribs will come here.
}
}
then add what properties and attributes you want to the second class - NewsAttribs here. :
public class NewsAttrib
{
[Display(Name = "News title")]
[Required(ErrorMessage = "Please enter the news title.")]
public string Title { get; set; }
// and other properties you want...
}
Notes:
1) The namespace of the generated entity class and your classes must be the same - here YourSolution.
2) your first class must be partial and its name must be the same as EF generated class.
Go through this and your attribs never been lost again ...

Entity Framework code first: DbSets and navigation properties

A little new to EF, so please bear with me if the answer to this is obvious. I'm doing a tutorial that uses EF, and two DbSets are defined like this:
public DbSet<BrokerageAccount> BrokerageAccounts { get; set; }
public DbSet<Customer> Customers { get; set; }
The customer class looks like this-- it's a POCO (some code cut for brevity):
public class Customer
{
public Customer()
{
BrokerageAccounts = new HashSet<BrokerageAccount>();
}
// Primitive properties
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
// Navigation properties
public ICollection<BrokerageAccount> BrokerageAccounts { get; set; }
}
}
The BrokerageAccount class is a POCO as well, very similar in design to Customer.
So far so good. The code I have a question about is below. There is an association made in the main program between Customer and BrokerageAccount that I don't follow. The code reads like this:
public Customer GetCustomer(string custId)
{
using (var context = DataContext)
{
return context.Customers
.Include("BrokerageAccounts").SingleOrDefault(c => c.CustomerCode == custId);
}
}
I can't figure out how the association/join is made between Customer and BrokerageAccount. I don't see any config or other files in my VS 2010 project that tells what associates the two DbSets, what foreign key column to use, etc.
Perhaps I'm missing something obvious or a mapping file of some sort, but just because Customer has an ICollection of BrokerageAccount along with a comment above that says "Navigation Properties", doesn't make it so. In EF, how are those associations established?
The normal way of setting up the navigation properties is to use the ModelBuilder, This gives you a fluent api to set up the associations, take a look at this for some in depth stuff about how you go about this.
http://xhalent.wordpress.com/2011/01/21/configuring-entity-framework-4-codefirst/
Entity framework will guess at what you meant if you dont set up the nav properties manually, in the above case it will probably set up your nav properties as expected as you only have a single 1-* relationship between customer and BrokerageAccount which appears to be named sensibly.
There is also an attribute method that you can use to set up the navigation properties.

Entity Framework Confusion with `virtual`s

I am using Entity Framwwork and Code First and getting really confused. I have this class:
public class Blocks
{
[Display(Name = "ID"),Required(ErrorMessage = "ID is required")]
[Key,HiddenInput(DisplayValue=false)]
public int BlockId { get;set; }
[Display(Name = "Blocked By"),Required(ErrorMessage = "Blocked By is required")]
public int ProfileId { get;set; }
[Display(Name = "Blocked"),Required(ErrorMessage = "Blocked is required")]
public int ProfileBlockedId { get;set; }
[Display(Name = "Date Blocked"),Required(ErrorMessage = "Date Blocked is required")]
public DateTime BlockDateTime { get;set; }
[Display(Name = "Block Reason")] public string BlockReason { get;set; }
public virtual Profiles Profile { get; set; }
public virtual Profiles ProfileBlocked { get; set; }
}
The profile class is more or less the same and that adds fine and has the correct SQL, but when I run /Blocks I get this error:
MySql.Data.MySqlClient.MySqlException (0x80004005): Unknown column 'Extent1.Profile_ProfileId' in 'field list'
at MySql.Data.MySqlClient.MySqlStream.ReadPacket()
at MySql.Data.MySqlClient.NativeDriver.GetResult(Int32& affectedRow, Int32& insertedId)
at MySql.Data.MySqlClient.Driver.GetResult(Int32 statementId, Int32& affectedRows, Int32& insertedId)
at MySql.Data.MySqlClient.Driver.NextResult(Int32 statementId)
at MySql.Data.MySqlClient.MySqlDataReader.NextResult()
at MySql.Data.MySqlClient.MySqlCommand.ExecuteReader(CommandBehavior behavior)
at MySql.Data.Entity.EFMySqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior)
at System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior)
this is because the sql produced is:
SELECT
`Extent1`.`BlockId`,
`Extent1`.`ProfileId`,
`Extent1`.`ProfileBlockedId`,
`Extent1`.`BlockDateTime`,
`Extent1`.`BlockReason`,
`Extent1`.`Profile_ProfileId`,
`Extent1`.`ProfileBlocked_ProfileId`
FROM `Blocks` AS `Extent1`
Notice the Profile_ and ProfileBlocked_. I have them virtual so I have a dropdown of profiles when adding or editing or have the profile name when shown on a list. The strange thing is the other tables. Everything has worked fine except for this one.
Here is the code that creates the wrong SQL and breaks:
//
// GET: /Blocks/
public ViewResult Index()
{
try {
return View(context.Blocks.Include(blocks => blocks.Profile).Include(blocks => blocks.ProfileBlocked).ToList());
}
catch (Exception ex)
{
ModelState.AddModelError("",ex.Message);
CompileAndSendError(ex);
return View(context.Blocks.ToList());
}
}
I am using:
ASP.net MVC 3
Razor Templates
Entity Framework
MVC Scaffolding [custom T4 ]
Give EF a little hint what are your Foreign Key properties by putting annotations on the properties:
...
[ForeignKey("Profile")]
public int ProfileId { get;set; }
...
[ForeignKey("ProfileBlocked")]
public int ProfileBlockedId { get;set; }
...
I believe that this is always necessary when you have more than one navigation property referencing to the same target class. The conventions don't detect in this case which properties could be the foreign keys - and EF creates their own FK column names (the Profile_ and ProfileBlocked_ things). And because the column names in the DB are different you get the exception.
(I think, the problem has nothing to do with properties being virtual or not.)
Edit
You can also put the ForeignKey attribute on the navigation properties and specify what's the name of the FK properties:
...
[ForeignKey("ProfileId")]
public virtual Profiles Profile { get; set; }
...
[ForeignKey("ProfileBlockedId")]
public virtual Profiles ProfileBlocked { get; set; }
...
This leads to the same mapping and it's only a matter of taste what you prefer, as far as I can tell.
I'm facing the same problem, but I can;t sole it using the suggested ForeignKey attribute.
I have installed the MySQL Connector/NET 6.4.3.0. When I run my project I get almost the same error, but referenced to System.Data.Entity. Shouldn;t that be MySql.Data.Entity?
Can you show me how to possibly modify my Web.config or references to work with MySQL.
EDIT
With some help (other post) and trail and error, I got it working too.