mvc validating binded to entitiy with reference to other entities - asp.net-mvc-2

Hi just thought about mapping and binding my entity in controller.
How should i correctly bind entity in model so i can use modelstate
I am creating new MenuItem using MenuItemModel.
public class MenuItemModel
{
public List<SelectListItem> Menus { get; set; }
public MenuItem MenuItem { get; set; }
}
where my MenuItem class is defined as follows:
public class MenuItem:Entity
{
public virtual int MenuItemId { get; set; }
public virtual Menu Menu { get; set; }
[Required]
public virtual string Name { get; set; }
public virtual int ItemOrder { get; set; }
public virtual string ExternalUrl { get; set; }
public virtual DateTime Created { get; set; }
public virtual bool Deleted { get; set; }
public virtual DateTime? DisplayUntil { get; set; }
public virtual User Author { get; set; }
}
now when i bind my entity in controller.
//
// POST: /Administrator/MenuItem/Create
[HttpPost]
public ActionResult Create(MenuItem menuItem)
{
if (ModelState.IsValid)
{
// do saving logic
menuItem.Created = DateTime.Now;
menuItem.Author = this._userProvider.GetCurrentUser();
menuItem.Menu = _menuRepository.Load(menuItem.Menu.MenuId);
}
//restore
MenuItemModel menuItemModel = new MenuItemModel();
menuItemModel.MenuItem = menuItem;
menuItemModel.Menus =
this._menuRepository.All.Select(x => new SelectListItem() { Text = x.Name, Value = x.MenuId.ToString() }).ToList();
return View(menuItemModel);
}
the only problem is i am getting validation not for only MenuItem but for Menu, User too.
How shall set this validation to accept validation only for MenuItem Entity ?
PS i know that i can go into modelstate items and find only the entities that i need and check if they are valid but i believe there will be better way of doing this...
Any idea is appreciated.

How shall set this validation to accept validation only for MenuItem Entity ?
You should use a view model which contains only the properties that are needed to be validated in your controller action (usually those are the properties contained on the form and entered by the user). View models are classes which are specifically designed for the requirements of a given view. A controller action should never pass/take a domain model to/from a view. A controller action should always pass/take a view model to/from a view. For example:
public class MenuItemViewModel
{
public int MenuItemId { get; set; }
[Required]
public string Name { get; set; }
... put any properties that are contained on the form with their
respective validation
}
then have your POST controller action take this view model as argument:
[HttpPost]
public ActionResult Create(MenuItemViewModel viewModel)
{
if (!ModelState.IsValid)
{
// there were some validation errors => redisplay the view
// so that the user can fix them
return View(viewModel);
}
// at this stage validation went fine
// TODO: map the view model back to a domain model
// (MenutItem or whatever you are willing to update)
// I would recommend you AutoMapper for this task: http://automapper.codeplex.com/
// TODO: once you get the domain model pass it to a service layer
// method in order to perform the necessary business operation with it
// (in your case creating a menu item)
return RedirectToAction("Success");
}

Related

Entity Framework and RESTful WebAPI - possible circular reference

Here is a simplified version of my model:
public class User {
public int UserID { get; set; }
public string FirstName { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
}
public class Recipe {
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
}
I have a controller that I'd like to return a User as well as some summary information about their recipes. The scaffolded controller code looks like this:
var user = await _context.Users.SingleOrDefaultAsync(m => m.UserID == id);
It works fine. Now I try to add the Recipes, and it breaks:
var user = await _context.Users.Include(u => u.Recipes).SingleOrDefaultAsync(m => m.UserID == id);
My web browser starts to render the JSON, and it flickers and I get a message in the browser saying the connection has been reset.
My Theory - I believe that the parent (User) renders, which exposes the child (Recipe) which contains a reference to the parent (User), which contains a collection of the child (Recipe) and so on which is causing an infinite loop. Here's why I think this is happening:
The Visual Studio debugger allows me to navigate the properties in that way infinitely.
If I comment out the Recipe.User property, it works fine.
What I've tried
I tried to just include the data from Recipe that I need using Entity Framework projection (I'm attempting to not include Recipe.User). I tried to only include Recipe.RecipeName... but when I try to use projection to create an anonymous type like this:
var user = await _context.Users.Include(u => u.Recipes.Select(r => new { r.RecipeName })).SingleOrDefaultAsync(m => m.UserID == id);
I receive this error:
InvalidOperationException: The property expression 'u => {from Recipe r in u.Recipes select new <>f__AnonymousType1`1(RecipeName = [r].RecipeName)}' is not valid. The expression should represent a property access: 't => t.MyProperty'.
What is the solution? Can I project with different syntax? Am I going about this all wrong?
Consider using POCOs for serialization rather than doubly-linked entity classes:
public class UserPOCO {
public int UserID { get; set; }
public string FirstName { get; set; }
public ICollection<RecipePOCO> Recipes { get; set; }
}
public class RecipePOCO {
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public int UserID { get; set; }
}
Copy the entity contents to the corresponding POCO and then return those POCO objects as the JSON result. The removal of the User property via usage of the RecipePOCO class will remove the circular reference.
I can propose you 3 options.
U sing [JsonIgnore] on property, but it will work on every use of Recipe class, so when you would like to just return Recipe class you won't have User in it.
public class Recipe {
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public int UserID { get; set; }
[JsonIgnore]
public virtual User User { get; set; }
}
You can this solution to stop reference loop in all jsons https://stackoverflow.com/a/42522643/3355459
Last option is to create class (ViewModel) that will only have properties that you want send to the browser, and map your result to it. It is propably best from security reason.

EF avoiding fetch when Count a List

I have a scenario where I'm trying to count items in a List. This List is a proxy coming from EF. But, when I call Count method, each item from list is fetched, and it decreases a lot the performance. Is there a way to avoid it?
Look for an example:
Domain Classes:
public class Desire
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
public virtual List<Vote> Votes { get; set; }
}
public class Vote
{
public virtual int Id { get; set; }
public virtual UserProfile User { get; set; }
public virtual DateTime DateTime { get; set; }
}
Repository:
public IQueryable<Desire> GetQuery()
{
return db.Desires;
}
Domain Service:
public IQueryable<Desire> GetDesires()
{
return repository.GetQuery();
}
ASP MVC View:
<!-- here Votes is a proxy from EF -->
<!-- When Count is called, the items are fetched decreasing the performance -->
<h2>Total Votes: #item.Votes.Count</h2>
Always try to use View Model instead of EF's entity. Use view model to fill your needed data on it and pass view model to view instead of model.
public class DesireViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public int VotesCount{ get; set; }
// add votes themselves if you really need them
// public IEnumerable<VoteViewModel> Votes { get; set; }
}
In your action method fill DesireViewModel instead of Desire:
public ActionResult MyAction()
{
var model=_db.Desires.Select(d=>
new DesireViewModel
{
Id=d.Id,
Title=d.Title,
VotesCount=d.Votes.Count(),
});
return View(model);
}
Your view's model is now IEnumerable<DesireViewModel> instead of IEnumerable<Desire>
#model IEnumerable<.Your.Namespace.DesireViewModel>
// inside loop
<h2>Total Votes: #item.VotesCount</h2>
Because you are querying item.Votes with your Count funktion, EF has to fetch all items (Count() forces execution of Query before it).
The only way to not fetch all data (with materializing) would be, as far as I know, with a raw sql statement:
item.Database.SqlQuery<int>("SELECT Count(*) FROM Vote;");

.net mvc dropdownlist selected value not being updated

I have two models defined as follows:
public class Division
{
public int DivisionID { get; set; }
[Required]
public string DivisionName { get; set; }
public virtual Employee Contact{ set;get; }
public virtual ICollection<Employee> Employees { get; set; }
}
public class Employee
{
public int EmployeeID{ get; set; }
public string Name{ get; set; }
public virtual Division Division{set;get;}
}
Entity framework sets a field in division table called employee_employeeid, how can I create dropdown for employees for the contact attribute in the division table.
Here is what I have tried but nothing is being sent to the database.
ViewBag.contact = new SelectList(db.Employees,"EmployeeID","Name");
In the view I have:
#Html.DropDownList("contact",String.Empty)
Is there a naming convention I have to use?
Edit
POST action:
[HttpPost]
public ActionResult Edit(Division division)
{
if (ModelState.IsValid)
{
db.Entry(division).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.employeeid = new SelectList(
db.Employees, "EmployeeID", "EmployeeFirstName", division.employee);
return View(division);
}
I would recommend actually using ViewData instead of the ViewBag. In your controller have the following:
var employees = db.Employees.Select(e => new DropDownItem{ Text = e.Name, Value = e.EmployeeID });
ViewData["Employees"] = employees;
Then, in your view, have the following to display it:
#Html.DropDownList("Contact", ((IEnumerable<DropDownItem>)(ViewData["Employees"])))
I would suggest that you expose a foreign key property in your model. It makes the binding to the dropdown list and the later update much easier. Your Division model would look like this:
public class Division
{
public int DivisionID { get; set; }
[Required]
public string DivisionName { get; set; }
[ForeignKey("Contact")]
public int ContactId { set;get; }
public virtual Employee Contact { set;get; }
public virtual ICollection<Employee> Employees { get; set; }
}
Then in the Edit GET action you fill the ViewBag as you did:
ViewBag.Contacts = new SelectList(
db.Employees, "EmployeeID", "Name", division.ContactId);
You have a strongly typed view with the Division as model:
#model MyNamespace.Division
In this view you can bind the dropdown list to the ContactId property:
#Html.DropDownListFor(model => model.ContactId, ViewBag.Contacts)
Your POST action can be similar to your current version:
[HttpPost]
public ActionResult Edit(Division division)
{
if (ModelState.IsValid)
{
db.Entry(division).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Contacts = new SelectList(
db.Employees, "EmployeeID", "Name", division.ContactId);
return View(division);
}
Note, that it is in many cases the better practice (especially for security reasons) to use a special ViewModel instead of a database entity for your views. You could then incorporate the Contacts collection into your ViewModel (instead of using the ViewBag). To update the entity in the database you would load it and write the changed properties from the ViewModel to the entity and then save it.

Entity Framework Code First - Many-to-many with a single class

I have a navigation menu provider that I am trying to move over to EF Code First (EF4, CPT5) using a MenuItem object. This is more of an exercise to get comfortable with building different relationships with EF Code First than anything else.
My MenuItem has a field called SubMenuItems that is a collection of MenuItems. When I use EF Code First (without modifying the classes) the table that is created for the MenuItems adds a column for the parent menu item. The menu will display properly, but this removes any ability to have a menu item appear in more than one sub menu. What is the proper way to tell EF Code First that I want each MenuItem to be a standalone item and to create another table that links a menu item's SubMenuItems to other MenuItems?
public class MenuItem
{
public int ID { get; set; }
public virtual SubMenuItems { get; set; }
public string Text { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
public bool IsRoot { get; set; }
public bool RequiresAuthentication { get; set; }
public virtual ICollection<MenuPermission> RequiredPermissions { get; set; }
public bool HiddenIfAuthenticated { get; set; }
public int DisplayOrder { get; set; }
}
...
public class MyDbContext : DbContext
{
public DbSet<MenuItems> MenuItems { get; set; }
public DbSet<MenuItemPermission> MenuItemPermissions { get; set; }
...
}
I have attempted to override OnModelCreating but each attempt ended in either being broken or doing the exact same thing that it does without me touching OnModelCreating. I'm sure I'm just "doing it wrong."
Thanks!
You need to setup a Many-to-Many Self Referencing Association for MenuItem entity so that each MenuItem could have multiple Parent Items:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MenuItem>()
.HasMany(m => m.SubMenuItems)
.WithMany();
}

Html.DropDownList AutoMapper problem

I am receiving this error:
The ViewData item that has the key 'DepartmentId' is of type 'System.Int32' but must be of type 'IEnumerable'.
with the following set up. I am not sure how to resolve it. The error is happening in the Model View code. This line: public void MapTo(Person domainModel). I am using AutoMapper to map ViewModel back to DomainModel (reversing the initial mapping of DomainModel to ViewModel).
Domain model (using LINQ to SQL, so this is a partial class):
public partial class Person { }
// Validation rules
public class Person_Validation
{
[HiddenInput(DisplayValue = false)]
[ScaffoldColumn(false)]
public object PersonId { get; set; }
[HiddenInput(DisplayValue = false)]
[ScaffoldColumn(false)]
public object DepartmentId { get; set; }
[DisplayName("Employee Name")]
[Required(ErrorMessage = "Employee Name is required")]
[StringLength(50, ErrorMessage = "Employee Name cannot be more than 50 characters")]
public object Name { get; set; }
[HiddenInput(DisplayValue = false)]
public object Active { get; set; }
[HiddenInput(DisplayValue = false)]
public object DateAdded { get; set; }
[HiddenInput(DisplayValue = false)]
public object DateDeleted { get; set; }
public object Department { get; set; }
}
This is my Model View:
public class PersonViewModel
{
public object PersonId { get; set; }
public object DepartmentId { get; set; }
public object Name { get; set; }
public object Active { get; set; }
public object DateAdded { get; set; }
public object DateDeleted { get; set; }
public object DepartmentName { get; set; }
//helper method
public void MapTo(Person domainModel)
{
Mapper.Map(this, domainModel);
}
}
Controller Class Code:
[HttpPost]
public ActionResult Edit(PersonViewModel viewModel)
{
var domainModel = new Person();
try
{
viewModel.MapTo(domainModel);
UpdateModel(domainModel);
_personRepository.Save();
return RedirectToAction("Index", "Person");
}
catch
{
return View(viewModel);
}
}
And my View HTML code:
<div class="editor-field">
<%: Html.DropDownList("DepartmentId", (IEnumerable<SelectListItem>)ViewData["DepartmentList"])%>
<%: Html.ValidationMessageFor(model => model.DepartmentId) %>
</div>
you're not really following the best practices of developing a mvc application.
About the error:
the Html.DropDownList looks for Data of type IEnumerable<SelectListItem> in the model but it finds an int instead (DepartmentId)
your ViewModel should not have the MapTo method, it breaks the single responsibility principle
in your action method you don't do any server side validation like:
if(!ModelState.IsValid)
{
//rebuild the viewmodel and return the view
}
catching everything in the action is also not necessary (and bad)
you do this in Global.asax Application_Error instead
attributes like HiddenInput, ScaffoldColumn, Validation and anything else UI related should be on you ViewModel not in your domain model
for a good sample of using viewmodels & validation & mapping between entity <-> viewmodel I recommend you to look at the Samples solution from here
I did this sample and it's main purpose is to demonstrate the usage of ValueInjecter (mapping technology)