MVC 2 Validation and Entity framework - asp.net-mvc-2

I have searched like a fool but does not get much smarter for it..
In my project I use Entity Framework 4 and own PoCo classes and I want to use DataAnnotations for validation. No problem there, is how much any time on the Internet about how I do it. However, I feel that it´s best to have my validation in ViewModels instead and not let my views use my POCO classes to display data.
How should I do this smoothly? Since my repositories returns obejekt from my POCO classes I tried to use AutoMapper to get everything to work but when I try to update or change anything in the ModelState.IsValid is false all the time..
My English is really bad, try to show how I am doing today instead:
My POCO
public partial User {
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
And my ViewModel
public class UserViewModel {
public int Id { get; set; }
[Required(ErrorMessage = "Required")]
public string UserName { get; set; }
[Required(ErrorMessage = "Required")]
public string Password { get; set; }
}
Controller:
public ActionResult Edit(int id) {
User user = _userRepository.GetUser(id);
UserViewModel mappedUser = Mapper.Map<User, UserViewModel>(user);
AstronomiGuidenModelItem<UserViewModel> result = new AstronomiGuidenModelItem<UserViewModel> {
Item = mappedUser
};
return View(result);
}
[HttpPost]
public ActionResult Edit(UserViewModel viewModel) {
User user = _userRepository.GetUser(viewModel.Id);
Mapper.Map<UserViewModel, User>(viewModel, user);
if (ModelState.IsValid) {
_userRepository.EditUser(user);
return Redirect("/");
}
AstronomiGuidenModelItem<UserViewModel> result = new AstronomiGuidenModelItem<UserViewModel> {
Item = viewModel
};
return View(result);
}
I've noticed now that my validation is working fine but my values are null when I try send and update the database. I have one main ViewModel that looks like this:
public class AstronomiGuidenModelItem<T> : AstronomiGuidenModel {
public T Item { get; set; }
}
Why r my "UserViewModel viewModel" null then i try to edit?

If the validation is working, then UserViewModel viewModel shouldn't be null... or is it that the client side validation is working but server side isn't?
If that's the case it could be because of the HTML generated.
For instance, if in your view you have:
<%: Html.TextBoxFor(x => x.Item.UserName) %>
The html that gets rendered could possibly be:
<input name="Item.UserName" id="Item_UserName" />
When this gets to binding on the server, it'll need your action parameter to be named the same as the input's prefix (Item). E.g.
public ActionResult Edit(UserViewModel item) {
To get around this, do as above and change your action parameter to item OR you could encapsulate the form into a separate PartialView which takes the UserViewModel as it's model - that way the Html.TextBoxFor won't be rendered with a prefix.
HTHs,
Charles
Ps. If I'm totally off track, could you please post some code for the view.

Related

EF code first MVC4 - ArgumentNullException on edit - what am I doing wrong here?

Let's say I have 3 models:
[Table("UserProfile")]
public class UserProfile //this is a standard class from MVC4 Internet template
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public int UserProfileId { get; set; }
[ForeignKey("UserProfileId")]
public virtual UserProfile UserProfile { get; set; }
}
Now, I'm trying to edit Post
[HttpPost]
public ActionResult Edit(Post post)
{
post.UserProfileId = context.UserProfile.Where(p => p.UserName == User.Identity.Name).Select(p => p.UserId).FirstOrDefault();
//I have to populate post.Category manually
//post.Category = context.Category.Where(p => p.Id == post.CategoryId).Select(p => p).FirstOrDefault();
if (ModelState.IsValid)
{
context.Entry(post.Category).State = EntityState.Modified; //Exception
context.Entry(post.UserProfile).State = EntityState.Modified;
context.Entry(post).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}
And I'm getting ArgumentNullException.
Quick look into debug and I can tell that my Category is null, although CategoryId is set to proper value.
That commented out, nasty-looking trick solves this problem, but I suppose it shouldn't be there at all. So the question is how to solve it properly.
I would say it's something with EF lazy-loading, beacuse I have very similar code for adding Post and in debug there is same scenerio: proper CategoryId, Category is null and despite of that EF automagically resolves that Post <-> Category dependency, I don't have to use any additional tricks.
On edit method, EF has some problem with it, but I cannot figure out what I'm doing wrong.
This is working as intended. Your Post object is not attached to the Context, so it has no reason to do any lazy loading. Is this the full code? I don't understand why you need to set Category as Modified since you're not actually changing anything about it.
Anyway, I recommend you query for the existing post from the Database and assign the relevant fields you want to let the user modify, like such:
[HttpPost]
public ActionResult Edit(Post post)
{
var existingPost = context.Posts
.Where(p => p.Id == post.Id)
.SingleOrDetault();
if (existingPost == null)
throw new HttpException(); // Or whatever you wanna do, since the user send you a bad post ID
if (ModelState.IsValid)
{
// Now assign the values the user is allowed to change
existingPost.SomeProperty = post.SomeProperty;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}
This way you also make sure that the post the user is trying to edit actually exists. Just because you received some parameters to your Action, doesn't mean they're valid or that the post's Id is real. For example, some ill intended user could decide to edit posts he's not allowed to edit. You need to check for this sort of thing.
UPDATE
On a side note, you can also avoid manually querying for the current user's Id. If you're using Simple Membership you can get the current user's id with WebSecurity.CurrentUserId.
If you're using Forms Authentication you can do Membership.GetUser().ProviderUserKey.

ASP.net MVC4 Multiselect ListBox with Many-to-Many relationship

I'm new to ASP.net, MVC4 and EF and having trouble making the leap from relatively simple samples to something (only slightly) more complex. I have simplified my model for the question here so consider the following. Forgive me if I have some of the terminology incorrect.
I have an Employee object and a Division object. An employee can be in many divisions and a division can have many employees.
public class Employee
{
public int EmployeeId { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public virtual ICollection<Division> Divisions { get; set; }
}
public class Division
{
public int DivisionId { get; set; }
public String DivisionName { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
I have these mapped in the context file via the following:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasMany(e => e.Divisions)
.WithMany(d => d.Employees)
.Map(m =>
{
m.ToTable("EmployeesDivisionsId");
m.MapLeftKey("EmployeeId");
m.MapRightKey("DivisionId");
});
}
For my View Controller I have the following. I have used a ViewBag (what a name) for many to one relationships and they have worked fine, so I'm trying to modify that to work with the many to many here:
public ActionResult Index()
{
return View(db.Employees).ToList());
}
//
// GET: /Employees/Details/5
public ActionResult Details(int id = 0)
{
Employee employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}
return View(employee);
}
//
// GET: /Employees/Create
public ActionResult Create()
{
ViewBag.Divisions = new MultiSelectList(db.Divisions, "DivisionId", "DivisionName");
return View();
}
//
// POST: /Employees/Create
[HttpPost]
public ActionResult Create(Employee employee)
{
ViewBag.Divisions = new MultiSelectList(db.Divisions, "DivisionId", "DivisionName");
if (ModelState.IsValid)
{
db.Employees.Add(employee);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(employee);
}
Finally in the Edit View I have the following code. Again for a many to one relationship, a simple DropDownList works fine with the ViewBag in the controller, and with this many to many multiselect/ListBox method the 'Divisions' show up in the view, however when the Save button is hit, the validation of '1, 2' is not valid is displayed.
<div class="editor-label">
#Html.LabelFor(model => model.Divisions)
</div>
<div class="editor-field">
#Html.ListBox("Divisions")
#Html.ValidationMessageFor(model => model.Divisions)
</div>
So as I understand it, the list of 'Divisions' are being grabbed properly from the database and selected correctly in the view but when saving to the database the association through the mapped relationship isn't being made.
So my questions are:
How do I make this work so when I save, the correct 'Divisions' are saved to the Employee?
Also, I've heard people don't like using ViewBags, is there a (better?) alternate way?
This answer maybe a bit late. However, here's one or two things that might be helpful to others finding this question.
You are using ListBox() instead of ListBoxFor() so the data isn't being automatically bound to your model. So in your controller you need:
[HttpPost]
public ActionResult Create(Employee employee, string[] Divisions)
{
// iterate through Divisions and apply to your model
...
}

Automapper and lazy loading with EF and performance

I have a model like this
public class Exam
{
public int NewsId { get; set; }
public string Title { get; set; }
public string Description{ get; set; }
public string Program{ get; set; }
}
and a view model like this
public class ExamViewModel
{
public int NewsId { get; set; }
public string Title { get; set; }
}
and I do config Automapper like this
AutoMapper.Mapper.CreateMap<Exam, ExamViewModel>();
and in an ActionResult I used Automapper like this:
public ActionResult Exam()
{
var examsDb = db.Exams;
IEnumerable<ExamViewModel> examViewModel = AutoMapper.Mapper.Map<IEnumerable<Exam>, IEnumerable<ExamViewModel>>(examsDb);
return View(examViewModel);
}
and in view I loop through it
#model IEnumerable<AraParsESOL.ViewModels.ExamViewModel>
<ul>
#foreach (var e in Model)
{
<li>
#Html.ActionLink(e.Title, "Type", "Exam")
</li>
}
</ul>
My problem is that:
As you can see in the Model There are 4 properties but in viewModel there are only 2 properties.
How can i get only those 2 properties in viewModel and not the entire Model?
What happens here is that in view after each loop it goes and get the required column from the database but i want only those 2 properties and not going back to database.
i can get the database like this
db.Exam.ToList();
but it will cause the entire database gets back.
i want to use best practices here?
i know i can get the data from database by anonymouse type and select command but then what is the use of automapper?
what is the best solution here?
Don't use AutoMapper. It's not appropriate for IQueryable<T>. Instead, use LINQ projections:
public ActionResult Exam()
{
var examsDb = db.Exams;
IEnumerable<ExamViewModel> examViewModel =
from e in db.Exams
select new ExamViewModel
{
NewsId = e.NewsId,
Title = e.Title
};
return View(examViewModel);
}
If you look at the generated SQL, you will see that only the NewsId and Title columns are retured. It looks like the AutoMapper folks were interested in addressing this shortcoming, but I haven't heard anything about it since this.

order of MVC execution

Just trying to learn MVC2/ .net 4.0 at the same time. I am just using the generic template VS comes with when you start with a "MVC 2 Web" project (ie, the account controller and home controllers are setup for you).
So my question is that the view is strongly typed again a model. The model looks like this:
[PropertiesMustMatch("Password", "ConfirmPassword", ErrorMessage = "The password and confirmation password do not match.")]
public class RegisterModel {
[Required]
[DisplayName("User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[DisplayName("Email address")]
public string Email { get; set; }
[Required]
[ValidatePasswordLength]
[DataType(DataType.Password)]
[DisplayName("Password")]
public string Password { get; set; }
[Required]
[DataType(DataType.Password)]
[DisplayName("Confirm password")]
public string ConfirmPassword { get; set; }
[Required]
[DisplayName("School")]
public string School { get; set; }
}
then I guess I press "register" on my web page and it executes the following from my controller:
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus = MembershipService.CreateUser(model.UserName, model.Password, model.Email);
if (createStatus == MembershipCreateStatus.Success)
{
FormsService.SignIn(model.UserName, false /* createPersistentCookie */);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", AccountValidation.ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
return View(model);
}
so a couple of questions.
1) The classes that are [] above the method name.. are they executed (i dont know what term to use here) first? For example the model has a [ValidatePasswordLength] above its property. Does that mean upon submitting a password that dosnt validate, it dosnt even hit the controller? Can I also put this logic in the controller?
2) Where is ModelState class coming from ?
I just kinda wanna know in a flow chart way on how everything is connected in MVC. It seems like its a big circle, and there is no starting point.
Attributes aren't executed so much as checked.
During modelbinding the view model will be scanned for attributes, the model binder gets a list of these attributes and can then change its behaviour (eg the [bind] attribute affects if the model binder will try and populate a given property) or call the class (eg Validation attributes).
To answer your questions specifically:
1)Validation can happen in two places, either before the action is called, ie when your action takes a view model, or explicitly in the action when you call TryValidateModel. Either way the action is called, it is up to you to check validity and handle the response accordingly within the action as you did in your action above.
2) ModelState comes from the ModelBinder.
The easiest way to see how MVC works is to download the source, debug and step through a request.

Understanding Forms in MVC: How can I populate the model from a List<>

Conclusion in Images, can be found in the bottom
I'm having some trouble to get how Forms work in MVC (as I'm a WebForms Developer and really wanna start using MVC in one big project)
I took the MVC2 Web Project and add a simple ViewModel to it
namespace TestForms.Models
{
public class myFormViewModel
{
public myFormViewModel() { this.Options = new List<myList>(); }
public string Name { get; set; }
public string Email { get; set; }
public List<myList> Options { get; set; }
}
public class myList
{
public myList() { this.Value = this.Name = ""; this.Required = false; }
public string Name { get; set; }
public string Value { get; set; }
public string Type { get; set; }
public bool Required { get; set; }
}
}
Created a Strongly Typed View, passed a new object to the view and run it.
When I press submit, it does not return what's in the Options part... how can I bind that as well?
my view
alt text http://www.balexandre.com/temp/2010-10-11_1357.png
filling up the generated form
alt text http://www.balexandre.com/temp/2010-10-11_1353.png
when I press Submit the Options part is not passed to the Model! What am I forgetting?
alt text http://www.balexandre.com/temp/2010-10-11_1352.png
Conclusion
Changing the View loop to allocate the sequential number, we now have
<%= Html.TextBox("model.Options[" + i + "].Value", option.Value)%>
model is the name of our Model variable that we pass to the View
Options is the property name that is of type List
and then we use the property name
Looking at your UI it seems that you did not put the data from the Options member on it.
<% foreach (myList obj in Model.Options) { %>
// Add the object to your UI. they will be serialized when the form is submitted
<% } %>
Also check that you enclose the data in a form element
EDIT:
Sorry! I did'nt realized that you was filling the object inside the controller. Can you please show the code you have in the view?