mvc3 Reflect data annotations off a concrete class from an inteface - c#

I am trying to abstract my view model from various types of views. The entire thing compiles without a issue but I am having issues with "reflecting" (formally known as unboxing) the data annotations.
I have an interface:
public interface IPerson
{
string FirstName { get;set;}
string LastName {get;set;}
}
And I have two class which implement the interface as such:
public class Employee : IPerson
{
[Required]
[Display(Description = "Employee First Name", Name = "Employee First Name")]
public string FirstName {get;set;}
[Required]
[Display(Description = "Employee Last Name", Name = "Employee Last Name")]
public string LastName {get;set;}
public int NumberOfYearsWithCompany {get;set;}
}
public class Client : IPerson
{
[Required]
[Display(Description = "Your first Name", Name = "Your first Name")]
public string FirstName {get;set;}
[Display(Description = "Your last Name", Name = "Your last Name")]
public string LastName {get;set;}
[Display(Description = "Company Name", Name = "What company do you work for?")]
public string CompanyName {get;set;}
}
Person Edit View: views/Person/Edit as such:
#model IPerson
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
</div>
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
</div>
Employee Edit View: views/Employee/Edit:
#model Employee
Html.RenderAction("Edit", "Person", new { person = Model });
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => model.CompanyName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.CompanyName)
#Html.ValidationMessageFor(model => model.CompanyName)
</div>
</div>
where the PersonController is:
public ActionResult Edit(IPerson person)
{
return PartialView(person);
}
Everything compiles and renders fine. However, the data annotations are being lost.
So, Employee/Edit is coming out like:
FirstName [textfield]
LastName [textfield]
What Company do you work for? [textfield] Company Name is a required field
Is there anyway of unboxing those data annotations for the concrete class?
Side note
I tried explicitly casting the IPerson to Employee as such:
#model IPerson
#{
var employee = (Employee)Model;
}
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => employee.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => employee.FirstName)
#Html.ValidationMessageFor(model => employee.FirstName)
</div>
</div>
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => employee.LastName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => employee.LastName)
#Html.ValidationMessageFor(model => employee.LastName)
</div>
</div>
Doing this made the first name required but didn't take the display properties from the label.
Update
After much discussion as to whether this is or is not unboxing, I have not yet found a simple solution of grabbing the data annotation from the (more basic) concrete class. It would really defeat the goal of simplicity to use reflection in view (or helper) to get at the data annotations of the concrete class.
We have a few views that essentially are the same but have slightly different required fields and display names. It would be REALLY convenient if I could just pass a view model into an interfaced view and it would figure out the required fields and display properties. If anyone has figured out a way to do this it would be greatly appreciated.

I had the same problem with the TextBoxFor helper not generating the correct markup validation.
The way I was able to solve it was to use the TextBox helper instead of the TextBoxFor helper.
Here is the partial snippet which worked for me
#model Interfaces.Models.EntryPage.ICustomerRegisterVM
<p>
#Html.ValidationMessageFor(model => model.Department)
#Html.TextBox(Html.NameFor(model => model.Department).ToString(), Model.Department)
</p>
As you can see I used the Html.NameFor helper to generate the correct name from the expression and then passed in the property. Using this approach MVC was able to successfully generate the correct unobtrusive validation markup for the concrete class which implemented the interface which is referenced as the viewmodel.
I have not tried this approach for LabelFor or other helpers. But I hope that the result would be the same.
Please note the Html.NameFor helper is available in MVC5

You're specifying a model (IPerson) which has no data attributes when you're calling the PersonController.Edit action. The default metadata provider will only pick up the data attributes defined explicitly on the specified type (in this case, IPerson) or those that are inherited. You can share a metadata class or interface or copy the data annotations attributes to the interface.
However, I think you may want to redesign how this works a bit (for example, calling RenderAction to include another view into the current view is a code smell).
I would create a partial view for Person. Then, you can create a partial view for each type of person (Client, etc.). You can then add any additional markup, and include your Person view by using #Html.Partial("Person", Model).
You might also want to use a base class Person instead of an interface, otherwise it'll get tricky overriding the data attributes for FirstName and LastName.
public abstract class Person
{
public virtual string FirstName {get;set;}
public virtual string LastName {get;set;}
}
public class Employee : Person
{
[Required]
[Display(Description = "Employee First Name", Name = "Employee First Name")]
public override string FirstName {get;set;}
[Required]
[Display(Description = "Employee Last Name", Name = "Employee Last Name")]
public override string LastName {get;set;}
public int NumberOfYearsWithCompany {get;set;}
}
public class Client : Person
{
[Required]
[Display(Description = "Your first Name", Name = "Your first Name")]
public override string FirstName {get;set;}
[Display(Description = "Your last Name", Name = "Your last Name")]
public override string LastName {get;set;}
[Display(Description = "Company Name", Name = "What company do you work for?")]
public string CompanyName {get;set;}
}
Views/Shared/Person.cshtml
#model Person
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
</div>
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
</div>
Views/Employees/Edit.cshtml
#model Employee
#Html.Partial("Person", Model);
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => model.CompanyName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.CompanyName)
#Html.ValidationMessageFor(model => model.CompanyName)
</div>
</div>
Controllers/EmployeesController.cs
public class EmployeesController : Controller
{
public ActionResult Edit(int id)
{
var model = GetEmployee(id); // replace with your actual data access logic
return View(model);
}
}

I found this post struggling with the exact same issue.
(On a side note: I had already implemented my own DataAnnotationsModelMetadataProvider following this sample to be able to access custom attributes inside my editor templates:
http://weblogs.asp.net/seanmcalinden/archive/2010/06/11/custom-asp-net-mvc-2-modelmetadataprovider-for-using-custom-view-model-attributes.aspx. Ensure to not miss the step in application start if you want to use your own DataAnnotationsModelMetadataProvider for this problem)
So after almost giving up on getting this to work I decided to debug my CreateMetadata override to see what I could get hold of there. I also found this post:
Obtain containing object instance from ModelMetadataProvider in ASP.NET MVC
This in combination with some reflection of the DataAnnotationsModelMetadataProvider class lead me to the following solution:
public class MyModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(
IEnumerable<Attribute> attributes,
Type containerType,
Func<object> modelAccessor,
Type modelType,
string propertyName)
{
//If containerType is an interface, get the actual type and the attributes of the current property on that type.
if (containerType != null && containerType.IsInterface)
{
object target = modelAccessor.Target;
object container = target.GetType().GetField("container").GetValue(target);
containerType = container.GetType();
var propertyDescriptor = this.GetTypeDescriptor(containerType).GetProperties()[propertyName];
attributes = this.FilterAttributes(containerType, propertyDescriptor, Enumerable.Cast<Attribute>((IEnumerable)propertyDescriptor.Attributes));
}
var modelMetadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
//This single line is for the "sidenote" in my text above, remove if you don't use this:
attributes.OfType<MetadataAttribute>().ToList().ForEach(x => x.Process(modelMetadata));
return modelMetadata;
}
}
So now I can have an EditorTemplate that has an interface type as the model and then use different implementations of it to be able to have different field names and validation rules via data annotations. I'm using this for a form that takes three different addresses; home address, work address and invoice address. The user interface for these groups of inputs are exactly the same but the validation rules differ.
This is of course a bit of a convention based solution saying that this behaviour should always apply when the editor template model is an interface. If you have existing editor templates where the model is an interface type that it self has data annotations, this solution will of course break that. For my case we are just starting up our usage of MVC and for now this convention will work. It would be interesting to maybe send a combination of the attributes from the interface and the actual type to the base-implementation but I'll save that experiment for later.
Please also let me know if you're a reader that is aware of some serious flaw with this solution.

Related

MVC Model child object null on HTTP post

Hope someone can help me. I am new to MVC, coming from a winforms/console/vb6background.
Apologies if this has already been answered, I am stuggling to understand how I can resolve the below issue.
I have a view model :
public class testvm
{
public int id { get; set; }
public DateTime date { get; set; }
public student studentID { get; set; }
public testvm() { }
public testvm (student s)
{
studentID = s;
}
}
I am pre-populating the student child object of this ViewModel before it is passed to the view.
Student Model :
public class student
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
}
The problem I have is when the model is returned to the create HTTP post method the student child object is blank.
The controller code :
// GET: testvms/Create
public ActionResult Create(int sId)
{
student a = db.students.Find(sId);
testvm b = new testvm(a);
return View(b);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "id,date,student")] testvm testvm)
{
if (ModelState.IsValid)
{
db.testvws.Add(testvm);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(testvm);
}
View code:
#model WebApplication2.Models.testvm
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>testvm</h4>
<hr />
#Html.HiddenFor(model => model.studentID.ID)
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.date, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.date, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
The model object on the view is populated with the student information. When this is passed back to Create POST controller the student child object is null!
Can somebody please advise where I am going wrong or of the correct way to achieve this?
My application will have many forms that will all need to be pre-populated with student information. Each student will have many forms that will need to be filled out for them.
Many thanks in advance,
Rob
For every property in domain model (in your case testvm) you must have an EditorFor or Input element (like TextBoxFor or so) on your view(or HiddenFor for ID or other non user ui data).It may be a pain binding nested models in MVC as the DefaultModelBinder may not be able to bind whole object.However it would be safer approach to expose only the required properties on view like
#Html.HiddenFor(model => model.studentID.ID)
#Html.HiddenFor(model => model.studentID.Name)
and later on Controller Side
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(testvm testvm)
{
var originalobj=db.get //get fresh copy from data store
originalobj.Name=testvm.Name;
// ...other properties
//perform required operations on originalobj
}
you may use AutoMapper for this Purpose as
Mapper.CreateMap<testvm,testvm>();
originalobj=Mapper.Map<testvm,testvm>(testvm,originalobj);
you may find more information about Automapper on :
https://github.com/AutoMapper/AutoMapper/wiki/Getting-started
Your property name is called studentId (even though standard C# property naming convention dictates that it should have been called StudentId):
public student studentID { get; set; }
But in your Bind attribute you seem to have specified some student property which doesn't really exist on your view model:
[Bind(Include = "id,date,student")]
So you probably want to get rid of this Bind attribute from your controller action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(testvm testvm)
{
...
}
Also note that you only have a hidden field for the student id inside your form:
#Html.HiddenFor(model => model.studentID.ID)
You don't have a corresponding hidden field for the student name property, so it will never be posted back to your controller action.
Your attribute [Bind(Include = "id,date,student")] should include the names of the properties that you want to be set, student isn't in your model, but studentID is, they have to match.
You don't have to explicitly specify all of the field names that you want to be bound to your model, by default they will be bound anyway unless you tell the binder NOT to bind it by using [Bind(Exclude = "id,date,student")]. Therefore as it currently stands, I'd recommend removing your Include attribute to ease maintenance unless there is an important reason for using it and simply ensure that the models that you bind to only include the values you need.
Secondly, you have to make sure that any values that you are posting back from a form in your view have the same parameter names and are structured the same as the ones that you want to be bound to the request model.
This:
#Html.HiddenFor(model => model.studentID.ID)
Is not the same as:
#Html.HiddenFor(model => model.studentID)

EF 5 Database First using custom validation

In order to experience the new features of Entity Framework I created a new MVC 4 internet application. I connected it to an existing database and generated the Model classes with the dbContext generator.
By running the application I got some validation errors while editing a form. As example for a DateTime field the system was complaining if the date was inserted as "12/10/2012" instead of 2012-10-12 (as in the SQ Server notation). I tried to find the validation code in the project, but I could not find it anywhere in the generated code.
One of my model classes is the following:
public partial class Artist
{
public Artist()
{
}
public int Id { get; set; }
public string Name { get; set; }
public Nullable<System.DateTime> DateOfBirth { get; set; }
public virtual Countries Countries { get; set; }
}
How can I customize the validation errors if using the Database first approach? If I decor the models with my validation attributes, then they would be erased once the model classes are generated again.
Moreover in a "real world" project where the use of an existing database is mandatory, what would be the best approach with the development of the model classes? Extending the classes automatically generated by adding partial classes with the same name?
EDIT (Introduced part of the View):
#using (Html.BeginForm()) {
#Html.ValidationSummary(false)
<fieldset>
<legend>Movie</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DateOfBirth)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DateOfBirth)
#Html.ValidationMessageFor(model => model.DateOfBirth)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Well, you can always use a metadata class
public partial class ArtistMetaData
{
[Required]
[StringLength(20)]
public string Name;//it can be a field instead of a property, must just have the same name and type than in your Model class
}
and a partial class
[MetadataType(typeof(ArtistMetaData)]
public partial class Artist {
}
Or (my preferred solution) you can use an external Validation library, like the excellent FluentValidation
You have a "basic" validation by default (which can be removed in the global.asax), checking : that the non nullables values... are not null (like a default Required attribute), and that values are of the right type.
Check your browser and machine culture settings and then the jquery validation cultures.
I tend to have this problems since I have the Spanish config, but mvc comes with the English dates and currency formats, etc. For example dd/mm/yyyy vs mm/dd/yyyy
You could also use a regular expression attribute to validate the field as you wish.

The model item passed into the dictionary is of type 'System.String', but this dictionary requires a model item of type 'JFS.Data.Model.Address'

I am quite new to MVC but have been making steady progress however I have recently hit a problem that I can't seem to overcome despite reading a number of similar posts on similar topics.
I have a model as follows (simplified for brevity)
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Address Address { get; set; }
I have a strongly typed view as follows:
#model JFS.Data.Model.Supplier
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Supplier Address</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Address.AddressLine1)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Address.AddressLine1)
#Html.ValidationMessageFor(model => model.Address.AddressLine1)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Address.Country)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Address.Country)
#Html.ValidationMessageFor(model => model.Address.Country)
</div>
</fieldset>
And I have a shared EditorTemplate for the Country field as follows:
#model JFS.Data.Model.Address
#using System.Globalization
#Html.DropDownListFor(o => o.Country, GetCountries(Model), "Please select")
#functions
{
private static IEnumerable<SelectListItem> GetCountries(object country)
{
var regions = CultureInfo.GetCultures(CultureTypes.SpecificCultures)
.Select(cultureInfo => new RegionInfo(cultureInfo.LCID))
.OrderBy(r => r.EnglishName)
.Distinct()
.ToList();
return new SelectList(regions, "TwoLetterISORegionName", "EnglishName", country);
}
}
I understand the problem but not sure how best to overcome it, any advice would be very much appreciated.
Though your particular scenario was already answered by #ProNotion, I have had this issue before when you are passing null into your View or Templates. It may help someone else coming this way.
In your main view replace:
#Html.EditorFor(model => model.Address.Country)
with:
#Html.EditorFor(model => model.Address)
By the way if you are writing a shared editor template for an Address model you probably want to include the other properties as well such as AddressLine1 in this template.
You need to just pass the Address property:
#Html.EditorFor(model => model.Address)
You are trying to pass the Address properties which are strings, whereas the requirement of type is Address.

Validation on ASP.net MVC3 Model with Dynamic Properties

I am working on an app where I have a requirement to be able to load object properties at runtime from a database. The customer wants to be able to add attributes to the database and have them show up in the app. I am accomplishing this by giving my model a list of Field objects that contain a name, a type, and a value. This works well for displaying and editing project properties, but I'm having trouble with validation in the editor view. Thanks for your help.
I want to be able to do this in my Edit action:
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
Normal view:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
</fieldset>
}
What I need to do:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
for(i = 1 to n) {
<div class="editor-label">
#Html.LabelFor(model => model.Fields[i].Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Fields[i].Value)
#Html.ValidationMessageFor(model => model.Fields[i].Value)
</div>
}
</fieldset>
}
Model:
public class Movie
{
public Movie()
{
this.Fields = new List<Field>();
}
public List<Movie> Movies { get; set; }
}
Field Class:
public class Field
{
public string Name { get; set; }
public Type Type { get; set; }
public object Value { get; set; }
}
I believe something like this should work:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
#{int i = 0;}
#foreach(var field in model.Fields) {
var htmlFieldName = string.Format("Fields[{0}]", i);
<div class="editor-label">
<label for="#htmlFieldName">#field.Title</label>
</div>
<div class="editor-field">
#Html.EditorFor(model => field, null, htmlFieldName)
#Html.ValidationMessage(htmlFieldName)
</div>
}
</fieldset>
}
(Note that I made up how you're producing your label text, since using the actual value as the label didn't make sense to me).
The POST should end up with values like this:
ID=123
Fields[0]=Jaws
Fields[1]=VeggieTales
...
... and that should automatically bind to your Movie model, provided the model has, for example, a List<string> named Fields. If your model doesn't look like that, this should at least get you on the right track.
Update
In your comment, you explain that you are trying to produce an editor for an object. There are two major points of difficulty here:
MVC relies on the static type returned in the lambda expression you give to EditorFor to determine which kind of editor it should produce. To override this, you will need to provide a specific template name where my original suggestion shows you providing null:
#Html.EditorFor(model => field.Value, field.Type.Name, htmlFieldName + ".Value")
You'll probably need to tweak this to make it provide the right template name for types like Integer, but this should give you the general idea.
When posting back, there is no way for the server to know that Field[0] is an int, etc. You can either:
Provide hidden values to specify each type, and then use a custom model binder that can consume this information to build each Field based on the combined Type and Value.
Recreate the structure of the Movie object on the server side based on the Movie's ID, and then walk through each of it's Fields calling:
TryUpdateModel((dynamic)field, string.Format("Field[{0}]", i));
There are probably other options, but that's about all the time I'm willing to put into this today.

ASP.NET MVC3 Automapper Viewmodel/Model View validation

(Again, an MVC validation question. I know, I know...)
I'd like to use AutoMapper (http://automapper.codeplex.com/) to validate fields in my Create Views for fields that are not in my database (and thus not in my DataModel).
Example: I have an Account/Create View for users to create a new account and I want both a Password and ConfirmPassword field so users have to enter their password twice for confirmation.
The Account table in the database looks like this:
Account[Id(PK), Name, Password, Email]
I've generated an ADO.NET Entity Data Model and from that, I generated the Models using an ADO.NET Self-Tracking Entity Generator.
I've also written a custom AccountViewModel for validation annotations like [Required].
So, to summarize, this is my project structure:
Controllers:
AccountController
Models:
Database.edmx (auto-generated from database)
Model.Context.tt (auto-generated from edmx)
Model.tt (auto-generated from edmx)
AccountViewModel.cs
Views:
Account
Create.cshtml
The code of my AccountViewModel looks like this:
public class AccountViewModel
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Password { get; set; }
[Required]
[Compare("Password")]
public string ConfirmPassword { get; set; }
}
Now, my Create View looks like this:
#model AutoMapperTest.Models.Account
<script src="#Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Account</legend>
<div class="editor-label">
Name
</div>
<div class="editor-field">
#Html.TextBox("Name")
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
Email
</div>
<div class="editor-field">
#Html.TextBox("Email")
#Html.ValidationMessageFor(model => model.Email)
</div>
<div class="editor-label">
Password
</div>
<div class="editor-field">
#Html.TextBox("Password")
#Html.ValidationMessageFor(model => model.Password)
</div>
<div class="editor-label">
Confirm your password
</div>
<div class="editor-field">
#Html.TextBox("ConfirmPassword");
#Html.ValidationMessageFor(model => model.ConfirmPassword)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
My code fails because my Model does not contain the ConfirmPassword field of course.
Now, a little bird whispered to me the AutoMapper could fix that for me. But I can't figure it out... Can someone please tell me what I have to do to make this work? My AccountController looks like this now:
private readonly AccountViewModel _viewModel = new AccountViewModel();
private readonly DatabaseEntities _database = new DatabaseEntities();
//
// GET: /Account/Create
public ActionResult Create()
{
Mapper.CreateMap<AccountViewModel, Account>();
return View("Create", _viewModel);
}
//
// POST: /Account/Create
[HttpPost]
public ActionResult Create(AccountViewModel accountToCreate)
{
try
{
if (ModelState.IsValid)
{
var newAccount = new Account();
Mapper.Map(accountToCreate, newAccount);
_database.Account.AddObject(newAccount);
_database.SaveChanges();
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
But this doesn't work... (Got the example from http://weblogs.asp.net/shijuvarghese/archive/2010/02/01/view-model-pattern-and-automapper-in-asp-net-mvc-applications.aspx)
Can anyone please enlighten me on this matter? Thank you very much, and my apologies for the wall of text and the hundreds of questions about the same subject...
Few remarks about your code:
Your view is strongly typed (#model declaration) to the Account model whereas it should be typed to the AccountViewModel view model (there is no point in declaring a view model if you don't use it in the view).
AutoMapper is not used for validation, only for converting between types
You don't need to declare a readonly field for your view model (AccountViewModel) inside the controller. You could instantiate the view model inside the GET action and leave the default model binder instantiate it as action argument for the POST action.
You should do the AutoMapper configuration (Mapper.CreateMap<TSource, TDest>) only once for the entire application (ideally in Application_Start) and not inside a controller action
There is no Email field on your view model which might be the reason for the update to fail (especially if this field is required in your database)
So here's how your code might look like:
public ActionResult Create()
{
var model = new AccountViewModel();
return View("Create", model);
}
[HttpPost]
public ActionResult Create(AccountViewModel accountToCreate)
{
try
{
if (ModelState.IsValid)
{
var newAccount = Mapper.Map<AccountViewModel, Account>(accountToCreate);
_database.Account.AddObject(newAccount);
_database.SaveChanges();
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
replace the first line of you view with
#model AutoMapperTest.AccountViewModel
Also you only need to call Mapper.CreateMap once for app lifetime (e.g. at app start)

Categories

Resources