Telerik MVC3 grid - batch editing issue - asp.net-mvc-3

I using a Telerik MVC Grid and configured it for Batch Mode Editing http://demos.telerik.com/aspnet-mvc/grid/editingbatch. I am trying to edit one of my entity "State" which has List of Cities, where City is another entity. Here is how the State Entity looks.
public class State {
...Some Scalar Properties
public virtual List<City> Cities { get; set; } //Navigation Property
public State() {
Cities = new List<City>();
}
}
My City Entity points back to State as given below.
public class City {
... Some Scalar Properties
public virtual State State { get; set; } //Navigation property
}
I am using this Model in one of my cshtml pages some thing like this
#(Html.Telerik().Grid<State>()
.Name("tlkStateGrid")
.Editable(e => e.Mode(GridEditMode.InCell).DisplayDeleteConfirmation(false))
.ToolBar(t => {
t.Insert().ButtonType(GridButtonType.Image);
t.SubmitChanges().ButtonType(GridButtonType.Image);
})
...Some More of code here.
In my Controller I am handling the batch updates in normal way.
public ActionResult _SaveChanges(IEnumerable<State> inserted, IEnumerable<State> updated, IEnumerable<State> deleted) {
.....
}
When I try to edit State entity using batching editing of Telerik Grid, the (IEnumerable updated) parameter of the above controller action has entries for all the States that have been modified. The States however have a Cities List with one city (which is null) even if there aren't any Cities in the State.
So the point is that I have not created any City in any part of my code, but when I receive the States as parameter to the controller action listed above, there is a null City sitting inside the Cities List. Why does this happen?

I'm not entirely sure I understand the problem. So when the grid posts, you create a new State. Attached to that State object, there's a null City object. Is that the issue? Or did I miss something?
If that's the issue, it's normal behavior and should be expected. That is how the automatic JSON de-serialization in MVC3 works--any time you create a parent object and don't define the nested object, the nested object will be returned as null. Just handle the nulls in your code.

Related

How to Save Navigation property in ASP.NET MVC3 using Code first

i have been using code first technique in ASP.NET MVC3 app. here it is very basic issue i.e. how to update a navigation property. following is detailed code.
public class Destination
{
public int ID {get;set;}
// some other properties
public Country {get;set;}
}
public class Country
{
int ID {get;set;}
string Name {get;set;}
}
//i have simple structure as above. when i go to update destination entity. Country is not getting updated.even i tried following:
_db.Entry(Destination.Country).State = System.Data.EntityState.Modified;
_db.Entry(Destination).State = System.Data.EntityState.Modified;
//_db.ChangeTracker.DetectChanges();
_db.SaveChanges();
Secondly when i go for Add it works fine. is there any need to have foriegnKey relationship explicityly required?
You can add entity in three ways:
Calling the Add() method on DbSet. This puts the entity into the Added state, meaning that it will be inserted into the database the next time that SaveChanges() is called.
context.Destination.Add(yourDestination);
Changing its state to Added.
context.Entry(yourDestination).State = EntityState.Added;
Adding a new entity to the context by hooking it up to another entity that is already being tracked
context.Destination.Country.Add(yourCountry);
Regards.

mvc3 composing page and form element dynamically

I'm developing an MVC3 application and I have a page (well, a view) that let the users edit document's metainfo (a classic #Html.BeginForm usage). For general documents users will see standard fields to fill up, but through a dropdownlist they will be able to specify the type of the document: this, through an ajax call, will load new fields on the edit-document-form.
Whem the user submit the completed form, at last, the controller should read all the standard fields, plus all the fields loaded as being specific to the type of document selected.
Question is, how can I handle all this extra fields in a controller?
Say that I have Document class and a bunch of other classes extendinf Document, like Contract : Document, Invoice : Document, Complaint : Document and so forth, each having specific property (and this fields loaded on the form), how do I write the action in the controller?
I thought to use something like (I'll omitt all the conversions, validations, etc, for brevity)
[HttpPost]
public ActionResult Save(dynamic doc)
{
int docType = doc.type;
switch (docType)
{
case 1:
var invoice = new Invoice(doc);
invoice.amount = Request.Form["amount_field"];
invoice.code = Request.Form["code_field"];
//and so forth for every specific property of Invoice
Repository.Save(invoice);
break;
case 2:
var contract = new Contract(doc);
contract.fromDate = Request.Form["fromDate_field"];
contract.toDate = Request.Form["toDate_field"];
//and so forth for every specific property of Contract
Repository.Save(contract);
break;
..... // and so forth for any document types
default:
break;
}
}
But it seems a very dirty approach to me. Do you have a better idea on how to achive this? Maybe there's a pattern that I don't know nothing about to approach this kind of scenario.
Update
A second idea comes to my mind. After commenting Rob Kent's answer, I thought I could take a different approach, having just one class Document with a property like
public IEnumerable<Field> Tipologie { get; set; }
where
public class Field
{
public int IdField { get; set; }
public String Label { get; set; }
public String Value { get; set; }
public FieldType ValueType { get; set; }
public List<String> PossibleValues { get; set; } // needed for ENUMERATION type
}
public enum FieldType
{
STRING, INT, DECIMAL, DATE, ENUMERATION
}
Is this a better approach? In this case I can have just an action method like
[HttpPost]
public ActionResult Save(Document doc)
But shoud I create the fields in the view in order to make the MVC engine do the binding back to the model?
Given that the class inheriting from Document in the first approach will probably be generated at run-time, would you prefer this second approach?
To keep it all hard-typed on the server, you could use an abstract base type with a custom binder. See my answer here to see how this works: MVC generic ViewModel
The idea is that every time they load a new set of fields, you change the BindingType form variable to the instantiated type of the handler. The custom binder is responsible for creating the correct type on submission and you can then evaluate that in your action, eg:
if (model is Contract) ...
I'm not sure if you will be able to set up different actions each with a different signature, eg,:
public ActionResult Save(Contract contract) ...
public ActionResult Save(Invoice invoice) ...
Pretty sure that won't work because Mvc will have already decided which method to call, or maybe it will firstly see what type it gets back and then decides.
In my linked example, I am checking for overridden base members but if that is not an issue for you, you just need to create the correct type.

Model binding in the controller when form is posted - navigation properties are not loaded automatically

I'm using the Entity Framework version 4.2. There are two classes in my small test app:
public class TestParent
{
public int TestParentID { get; set; }
public string Name { get; set; }
public string Comment { get; set; }
public virtual ICollection<TestChild> TestChildren { get; set; }
}
public class TestChild
{
public int TestChildID { get; set; }
public int TestParentID { get; set; }
public string Name { get; set; }
public string Comment { get; set; }
public virtual TestParent TestParent { get; set; }
}
Populating objects with data from the database works well. So I can use testParent.TestChildren.OrderBy(tc => tc.Name).First().Name etc. in my code.
Then I built a standard edit form for my testParents. The controller look like this:
public class TestController : Controller
{
private EFDbTestParentRepository testParentRepository = new EFDbTestParentRepository();
private EFDbTestChildRepository testChildRepository = new EFDbTestChildRepository();
public ActionResult ListParents()
{
return View(testParentRepository.TestParents);
}
public ViewResult EditParent(int testParentID)
{
return View(testParentRepository.TestParents.First(tp => tp.TestParentID == testParentID));
}
[HttpPost]
public ActionResult EditParent(TestParent testParent)
{
if (ModelState.IsValid)
{
testParentRepository.SaveTestParent(testParent);
TempData["message"] = string.Format("Changes to test parents have been saved: {0} (ID = {1})",
testParent.Name,
testParent.TestParentID);
return RedirectToAction("ListParents");
}
// something wrong with the data values
return View(testParent);
}
}
When the form is posted back to the server the model binding appears to be working well - i.e. testParent looks okay (id, name and comment set as expected). However the navigation property TestChildren remains at NULL.
This I guess is not sooo surprising since the model binding merely extracts the form values as they were sent from the browser and pushes them into an object of the TestParent class. Populating testParent.TestChildren however requires an immediate roundtrip to the database which is the responsibility of the Entity Framework. And EF probably doesn't get involved in the binding process.
I was however expecting the lazy loading to kick in when I call testParent.TestChildren.First(). Instead that leads to an ArgumentNullException.
Is it necessary to tag an object in a special way after model binding so that the Entity Framework will do lazy loading? How can I achieve this?
Obviously I could manually retrieve the children with the second repository testChildRepository. But that (a) doesn't feel right and (b) leads to problems with the way my repositories are set up (each using their own DBContext - which is an issue that I haven't managed to come to terms with yet).
In order to get lazy loading for your child collection two requirements must be fulfilled:
The parent entity must be attached to an EF context
Your parent entity must be a lazy loading proxy
Both requirements are met if you load the parent entity from the database through a context (and your navigation properties are virtual to allow proxy creation).
If you don't load the entity from the database but create it manually you can achieve the same by using the appropriate EF functions:
var parent = context.TestParents.Create();
parent.TestParentID = 1;
context.TestParents.Attach(parent);
Using Create and not new is important here because it creates the required lazy loading proxy. You can then access the child collection and the children of parent with ID = 1 will be loaded lazily:
var children = parent.TestChildren; // no NullReferenceException
Now, the default modelbinder has no clue about those specific EF functions and will simply instantiate the parent with new and also doesn't attach it to any context. Both requirements are not fulfilled and lazy loading cannot work.
You could write your own model binder to create the instance with Create() but this is probably the worst solution as it would make your view layer very EF dependent.
If you need the child collection after model binding I would in this case load it via explicit loading:
// parent comes as parameter from POST action method
context.TestParents.Attach(parent);
context.Entry(parent).Collection(p => p.TestChildren).Load();
If your context and EF is hidden behind a repository you will need a new repository method for this, like:
void LoadNavigationCollection<TElement>(T entity,
Expression<Func<T, ICollection<TElement>>> navigationProperty)
where TElement : class
{
_context.Set<T>().Attach(entity);
_context.Entry(entity).Collection(navigationProperty).Load();
}
...where _context is a member of the repository class.
But the better way, as Darin mentioned, is to bind ViewModels and then map them to your entities as needed. Then you would have the option to instantiate the entities with Create().
One possibility is to use hidden fields inside the form that will store the values of the child collection:
#model TestParent
#using (Html.BegniForm())
{
... some input fields of the parent
#Html.EditorFor(x => x.TestChildren)
<button type="submit">OK</button>
}
and then have an editor template for the children containing the hidden fields (~/Views/Shared/EditorTemplates/TestChild.cshtml):
#model TestChild
#Html.HiddenFor(x => x.TestChildID)
#Html.HiddenFor(x => x.Name)
...
But since you are not following good practices here and are directly passing your domain models to the view instead of using view models you will have a problem with the recursive relationship you have between the children and parents. You might need to manually populate the parent for each children.
But a better way would be to query your database in the POST action and fetch the children that are associated to the given parent since the user cannot edit the children inside the view anyway.

MVC3 - how to link a newly created child object to its parent?

I am a complete novice at MVC, and can't seem to get my head around a very basic concept.
I have a parent object, that contains a collection of child objects. I want to create a new child object, and link it to the parent object, persisted in the database via EF4
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Child> Children { get; set; }
}
So far, what happens in my very basic application is this. My user goes to a page displaying the details of a parent object, which includes a list of the current children. There is also a link on that page to add a new child. That link points to a Create action on the child Controller, passing the parent Id, which in turn displays a view to create a new child. And this is where I've got stuck. I don't know how to persist the supplied parent Id so that when I click Save, I can retrieve that parent object and add my new child object to its collection.
I'm probably approaching this in completely the wrong way, but I can't seem to find any basic tutorials on how to add new child items to a parent, which is odd considering how common a scenario it is.
Any help is really appreciated!
Many thanks
Gerry
[Update 1]
I have two CreateChild actions, initially generated by MVC and then modified by myself. I can see just by looking at them that they don't do what I need, but I'm not at all sure how they need to change. Specifically, I store the parent ID within the ViewBag but between the calls to the Create actions, it gets lost, and so is not available when the second Create is called to persist the new child item to the database.
public ActionResult Create(int parentId)
{
Parent parent = db.Parents.Find(parentId);
ViewBag.ParentId = parent.Id;
return View();
}
[HttpPost]
public ActionResult Create(Child child)
{
if (ModelState.IsValid)
{
Parent parent = db.Parents.Find(ViewBag.ParentId);
parent.Children.Add(child);
db.Children.Add(child);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(child);
}
Thanks again
Gerry
When you pass the ParentId to the add child action, it looks like you are doing it with a route parameter.
Instead of storing it in the ViewBag, write it as a hidden field in your child form. Then, when someone submits the form, the ParentId will be submitted to your HttpPost action method.
You can do this by making ParentId a property on your Child viewmodel.
public class Child
{
public int ParentId { get; set; }
}
public ActionResult Create(int parentId)
{
Parent parent = db.Parents.Find(parentId);
var model = new Child { ParentId = parent.Id };
return View(model);
}
In your view, render it like this:
#Html.HiddenFor(m => m.ParentId)
Then, during your HttpPost, Child will already contain ParentId -- the default model binder will get it from the hidden field on your form.
[HttpPost]
public ActionResult Create(Child child)
{
if (ModelState.IsValid)
{
Parent parent = db.Parents.Find(child.ParentId);
parent.Children.Add(child);
db.Children.Add(child); // don't think you need this line
db.SaveChanges();
return RedirectToAction("Index");
}
return View(child);
}
Update after posting answer
Looking at your HttpPost code, it may be incorrect to add the child to the db twice. If you are using EF 4.1 or 4.2, I am pretty sure this is incorrect, but I'm not sure about previous EF versions. Adding the child to the parent.Children collection should be enough -- I don't think you should also add it to the db.Children set.
ViewBag is not ViewState (ASP.NET MVC doesn't have any built-in equivalent to ASP.NET WebForms ViewState) - it will not keep ParentId between calls. It will just allow you passing ParentId to view (in your first action) so you can for example store it in hidden field.

How to update tables with a many-to-many join with a bridging table that has payload

I am building a personal Movie Catalogue and have the following structure:
Movie table/entity
MovieID (PK identifier) +
Other movie related properties
Person table/entity
PersonID (PK identifier) +
Other person related properties.
PersonMovie table/entity
MovieID (FK)
PersonID (FK)
Other columns containing information about what the person did on the movie (I.e. charactor name or job).
I want to have a view that allows a user to create/update a movie, or a person, and have a checkbox to then allow them to select existing or create new cast members (persons), or movies.
I am struggling on two fronts:
1) how to present this type of multi-page data collection. A movie has many cast members & a person can be involved in many movies.
2) how to update 2 or 3 of the tables above depending on what the user whats to enter. A user may want to add a movie but doesnt know the cast members yet or vice versa. A user may want to add a movie and add people who already exist as cast members of the movie.
Also I do not want cascading deletes and have struggled switching it off for the relationships between the above entities.
I can do this easily with webforms but am learning MVC 3 & Entity Framework 4 and am still getting my head around it all. I have looked around and haven't come across solutions/tutorials on what I would like to achieve.
Any help would be much appreciated.
Tony
I had a similar issue when I switched from another MVC framework (Rails as in ROR). For the starters, check out Leniency's reply on the similar question, that is; relationship-with-payload or non-PJT (pure-join-table) which unfortunately ASP.NET MVC3 doesn't support explicitly.
You can create a ModelView (a virtual entity) to wrap the collections of other entity types and pass it to the View. The aforementioned post has the detailed example with the code for Model, ViewModel, View, Partial and the Controller. (read both the answers on that post, my answer is continuation of Leniency's answer there)
Hope it helps!
Vulcan's on the right track, and my response that she linked too will help you get the model setup where the linking table contains extra data.
For building the views, you'll mostly likely find that ViewModels are the way to go for more complicated setup like you're describing, then your controller and service layer will deal with processing the view model data and translating it into EF entities. Viewmodels are built specifically to the view that you need, rather than trying to hammer a domain model into a view that may not fit it.
Here's a very rough start for one of the workflows for creating a movie, with an optional list of people.
Domain - your Movie and Person class, plus a linking table similar to what I described here.
View Models - Create a movie and attach people to it
public class MovieCreatePage
{
public MovieInput Input { get; set; } // Form field data...
public IEnumerable<People> People { get; set; } // list of people for drop downs
// ... other view data needed ...
}
public class MovieInput
{
[Required, StringLength(100)]
public string Name { get; set; }
// Easiest to just submit a list of Ids rather than domain objects.
// During the View Model -> Domain Model mapping, there you inflate them.
public int[] PeopleIds { get; set; }
// ... other input fields ...
}
Create.cshtml - just make a form for your view model.
Controller:
// NOTE! The parameter name here matches the property name from the view model!
// Post values will come across as 'Input.Name', 'Input.Year', etc...
// Naming the parameter the same as the view model property name will allow
// model binding. Otherwise, you'll need an attribute: [Bind(Prefix=".....")]
[HttpPost]
public ActionResult Create(MovieInput input)
{
if (ModelState.IsValid)
{
//
// Now do your mapping - I'd suggest Automapper to help automate it,
// but for simplicity, lets just do it manually for now.
var movie = new Movie
{
Name = input.Name,
Actors = input.PeopleIds != null
? input.PeopleIds.Select(id => new Person { Id = id })
: null
};
//
// Now save to database. Usually in a service layer, but again,
// here for simplicity
// First, attach the actors as stubbed entities
if (movie.Actors != null)
{
foreach (var actor in movie.Actors)
_db.People.Attach(actor); // Attach as unmodified entities
}
_db.Movies.Add(movie);
_db.SaveChanges();
TempData["Message"] = "Success!"; // Save a notice for a successful action
return RedirectToAction("Index");
}
// Validation failed, display form again.
return View(new MovieCreatePage
{
Input = input,
// ... etc ...
});
}
Hopefully this helps you some and points you in a good direction. It does, of course, bring up a lot of other questions that will just take time (ie, automapper, service layers, all the various EF gotcha's, etc...).

Resources