ASP.NET MVC ViewModel with IEnumerable Property - model-binding

What I am Attempting
I am trying to bind my ViewModel to my View which contains a Customer Name and a list of Contacts to reach that customer. Once the view is populated I also want the user to have the ability to add, delete, and edit contacts in the View.
What I've got so far
Currently I am able to bind to the ViewModel using the EditorTemplate and edit the data with no problems. The problem comes in when I want to add or remove rows in the list, if a user deletes row 5 from the middle of the ViewModel bound Editable List only rows 1 - 4 will end up being posted to the server.
The Code
Here is what my ViewModel looks like:
public class CustomerViewModel{
[Required]
public int CustomerUniqueID { get; set; }
[Required]
public string Name { get; set; }
public IEnumerable<CustomerContacts> Contacts { get; set; }
public CustomerViewModel() { }
public CustomerViewModel(Customer customer)
{
ICustomerContactRepository contacts = new LockShop.Domain.Concrete.EFCustomerContactRepository();
this.Contacts = contacts.FindContacts(customer.UniqueID);
this.Name = customer.Name;
}
}
I am passing my ViewModel to my view like this:
public ActionResult Edit(){
Customer customer = repository.Customers.FirstOrDefault(c => c.UniqueID == 23128);
return View(new CustomerViewModel(customer));
}
When it is time to render the list, I simply use the EditorFor helper:
#Html.EditorFor(model => model.Contacts)
What I love about this approach is that when I click the save button all of my changes are automatically posted to the controller!
My Question
What are the best practices of how to add the functionality of adding and removing rows to a list that is generated by an IEnumerable from my ViewModel?
What I Have Tried
I've read Steven Sanderson's blog post which talks about editing a variable length list in ASP.NET MVC. His post seems to only cover binding to a model and doesn't cover the possibility of binding to a list in a ViewModel. When I implemented his solution the post data from the Contacts list that got sent back to the controller was null.
Please feel free to ask any questions for clarification and thanks for your time.

Related

How to save views input data into different tables?

I'm an ASP.NET MVC beginner and currently I use EF with a database-first approach to do my work. Everything goes well and every model have its controller and views files. I face problem when I want to save multi model data on a view.
In my case:
I have ASP.NET MVC form for teacher details data, and another for teacher employment history (this two are auto generate from scaffolding database approach)
I want a view Create.cshtml where user inputs teacher details and employment history and this can be saved into their own tables. Therefore, I use tuple and follow what (Multiple models in a view) and (Two models in one view in ASP MVC 3) did.
As a result, I successfully create teacher details and employment history on a view (Just interface).
But I have no idea how to save the input into the different tables (2 tables: teacher, employmentHistory). Now, when I save the input, nothing happens.
I guess I need to do something on controllers parts, please give me some ideas on how to do it.
Thanks in advance
First, welcome to StackOverflow!
You are correct, you should do something on the controller part.
I would create a ViewModel for the view. By doing this you can project the data from the database in any way that is needed in the view. Also, with this approach you don't return the full entity, maybe there is some sensitive information there. And you can get the benefit of the model validations also. (For example, what if at some point you need to add another field from another entity?)
I would also create partial views for the information in the view model and pass that model to the partial view, this way enabling the re-use of the views, if needed.
When passing the data to the controller you can pass the ViewModel and then save the data in the database.
Since you didn't give no model info of your classes, I give below an example. Either way (view model or tuple example you followed), you should change the controller code similar to what I'm writing below.
public class TeacherViewModel
{
//teacher details
[Required]
public int TeacherId {get; set;}
[Required]
public string TeacherName {get; set;}
// {...}
//teacher employment info
//here you can have a list of information or the last entry in the history,
//or what's required to add a new entry
public string LastWorkPlace {get; set;}
public DateTime From {get; set;}
public To {get; set; }
//{...}
}
public class TeacherController: Controller
{
//{...}
[HttpPost]
public ActionResult SaveTeacherInfo(TeacherViewModel model){
if (ModelState.IsValid) {
var teacher = new TeacherEntity
{
TeacherId = model.TeacherId, //if update otherwise leave blank
TeacherName = model.TeacherName
//{...}
};
//your context name here
_dbContext.Teachers.Add(teacher);
var teacherEmploymentInfo = new TeacherEmploymentHistory
{
TeacherId = teacher.TeacherId,
LastWorkPlace = model.LastWorkPlace,
From = model.From,
To = model.To
//{...}
};
_dbContext.TeachersEmploymentInfo.Add(teacherEmploymentInfo);
_dbContext.SaveChanges();
//_dbContext.SaveChangesAsync(); if using async await
}
return View(model); //return any validation errors from the view model
// to the user to fix.
}
}
If you are using the ErrorHandler as a global filter no need for try..catch block if you are not planing to return custom errors (BusinessException or DatabaseException, for example) from the controller, if not, you should wrap the code in a try..catch block.

how should a controller pass back different entities or collections to a view

Most of the samples I see online are showing CRUD with single entity class.
If I were to create a CRM application and one of the views needs to display customer information in read only mode and on the same page, display Contacts, Notes, Attachments, Addresses, how should the controller pass these different entities to the view?
Should I be creating another model class which will be a container for the various other entities and populate that model class with the various entities and pass back to the View.
In another scenario, say I wanted to display the Customer entity in Edit mode and the View has dropdowns for Customer Active Status, Customer StateCode, Customer Satisfaction dropdowns. Each of these dropdowns have other entity collections that are bound to them. So, again in this scenario, how should a controller pass back the model which has all these entities and not just Customer entity.
I continue to read on ViewModel pattern and I think this may be the way to go but would certainly appreciate more guidance and pointers.
You're exactly right. Create a ViewModel which represents the objects which your page requires.
Your first scenario could be something like the following:
public class CustomerInformationViewModel
{
IEnumerable<Contact> Contacts { get; set; }
IEnumerable<Note> Notes { get; set; }
}
Populate these in your controller and then access them in your view.
#foreach (var contact in Model.Contacts)
{
#Html.DisplayFor(c => c.Name)
}
For your second scenario, exactly the same. You want to bind to your customer properties but you also want some additional collections for drop down lists.
public class CustomerEditViewModel
{
Customer Customer { get; set; }
IEnumerable<Status> StatusOptions { get; set; }
IEnumerable<StateCode> StateCodeOptions { get; set ; }
}
Just keep in mind that you will need to re-populate these collections in your controller if you discover that your ModelState is invalid at the controller.

MVC 3 Model Binding without Key

I have a question about ASP.NET MVC3 model binding. If I have a class I'm trying to use as a model, but I don't want the key put on the page, the model doesn't bind on a POST. Here is an example:
//Data Model
public class MyModel
{
[Key]
public string MyKey {get;set;} //Perhaps this is an ssn that I don't want on the form.
public string MyValueToGet {get;set;} //This is the value I want the user to enter.
}
//Conroller code.
public ViewResult Index()
{
MyModel model = new MyModel{ MyKey = "SecretInfo", MyValueToGet = "" };
return View(new model);
}
public ActionResult Edit(MyModel model)
{
repository.SaveChanges(model)
}
//View code.
#using(Html.BeginForm("Edit", "Home", FormMethod.Post))
{
Enter a value: #Html.EditorFor(m => m.MyValueToGet)
<input type="submit" value="Salve" />
}
So my problem is that model is null when the Edit method is called upon form submission. I can fix this by putting MyKey somewhere on the page (perhaps as a hidden field), but that is unacceptable if it is some sort of sensitive data. Is there a way to solve this problem? I am new to MVC, so I appreciate any help.
Create another unique but otherwise meaningless identifier like an (auto increment int) and use that to bind.
in other words modify your model to something like:
public class MyModel
{
[Key]
public int ID {get; set;}
public string MyKey {get;set;} //Now this can be sensitive, it doesn't matter because you no longer rely on it.
public string MyValueToGet {get;set;} //This is the value I want the user to enter.
}
EDIT
I believe your best choice would be to change the MyModel object, as it's design is flawed. The primary key in the majority of cases (and I think this is one of them) should be a simple auto incrementing integer, meaningless apart from it's role as the table's key.
While Luke's suggestion to use Session is a viable option and a solution that would work, I would personally do something similar to what I'll explain here, as it would seem to me to be more of the 'mvc way' of doing things.
Data model:
Either change your current model to something like what I suggest above, or, if that is not feasible for whatever reason (breaking dependancies or FK relationships), create a new table that can be used as a join, or proxy, if you will:
public class Proxy
{
public int ProxyId {get;set;}
public MyModel MyModel {get; set;}
}
Obviously, you'd have to do some work to populate this table, but you would then be able to use it to fetch records from MyModel without accessing the MyKey property directly.
It's not considered good practice to use your data models directly in your views, so you want to create a view model as well
public class MyModelViewModel
{
public int ModelId {get; set;}
public string ModelValueToGet {get; set;}
}
Notice we don't even need the key containing sensitive data in the view model.
Then type your view to the viewModel, not the data model, and include a hidden field for the ModelId
#using(Html.BeginForm("Edit", "Home", FormMethod.Post))
{
Enter a value: #Html.EditorFor(m => m.ModelValueToGet)
#Html.HiddenFor(m => m.ModelId)
<input type="submit" value="Save" />
}
Now in your controller you have your get method
public ViewResult Index()
{
//fetch the users record from the database
//if you're using the Proxy table, you'll want to write a LINQ query here
//instantiate a viewModel and populate it's properties using the fetched record
//remember, the viewModel.ModelId should be set to MyModel.ID or Proxy.ProxyId
//render the view
}
And the post method
public ViewResult Edit (MyModelViewModel viewModel)
{
//fetch the users record from the database using viewModel.ModelId
//If you're using the proxy table, you'll need to use that LINQ query again here
//update the record you fetched with the new data the user just entered
//you have complete control here of what gets updated and what stays the same
//pass the updated record to the repository to save the changes.
//redirect the user to be on their merry way
}
I think that's about as well as I can lay it out. Hope it makes sense.
An alternative is to encrypt the id before sending it to the client. Check this post for more information on how to accomplish this.
Asp MVC 3: Modifiy Values Sent to View

Dynamic form with indeterminite number of items

I have a class:
class Item
{
public string Name { get; set; }
public DateTime Date { get; set; }
}
I have a view where I want objects for the class above created using inputs (so I have a textbox for Name and a date select type thing for Date). However, I want thev user to be able to click a link and through jquery/javascript another textbox and date select will be added to the form, and this can happen unlimited times.
How can I bind this to a model so that I can return it to my action method? Ideally the model would be something like:
class MyModel
{
public string AProperty { get; set; }
public List<Item> Items { get; set; }
}
Apologies for the poor wording, struggling to describe what I want but I think this should get the point across.
You want to use a client-side template and then return JSON to your controller. If you are using MVC 3, JSON model binding is built-in, but in MVC 2 you need to set up your own binder. There is an example here.
I recommend using KnockoutJS for your client side. It's very simple for working with dynamic collections and very well documented. You can see an example similar to what you're trying to do here as well as in the previous link.

ASP.Net MVC - Passing posted value to view

I am creating a search page, the user types in the textbox and click 'Search', the controller takes the FormCollection in its Action method.
I do the search and return the results to the view however I want to return what the user searched for to the view. Is the TempData the best place for this or is this a risk?
I'd say that your model for the results view should contain both the results and the search criteria.
Examples:
public class ResultsViewModel
{
public SearchModel SearchCriteria { get; set; }
...
}
public class SearchModel
{
public string Category { get; set; }
...
}
then, just populate the SearchCriteria in your results view model and you can retrieve it from there.
This assumes that your results view is strongly typed to ResultsViewModel.
TempData is primarily used when the result of an action is a redirect to another action, and you need to keep some state around.
All you need to do is add another entry to the ViewData dictionary with "what the user searched for". Something roughly like this:
public ActionResult Search(FormCollection form)
{
// search algorithm
ViewData["keywords"] = form["keywords"];
return View();
}
The TempData is if you need that item the next time the user requests something else. Using the ViewData is what you're looking for. Also remember, the value he/she searched for is still available in the View as Request[searchBoxName].
Create a strongly typed view using a view model that will pass all the information you want on to the view and encode all user imputed information.
public class ResultsSetViewModel
{
public string Query { get; set; }
public IList<Result> Results { get; set; }
}
Encode the user imputed data.
<h3>Search Results For: <%=Html.Encode(Model.Query) %></h3>
Using both the suggestions above will work, however it is better practice to add the data you need into the model than passing it via the ViewData.
If you pass it in the model then you get the advantages of it being strongly typed and remove the need to add logic to cast the ViewData entry into your view.

Resources