Imagine there is a model defined as:
public class Book
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<Guid> RelatedBooks { get; set; } // List<Book> would be more ideal.
}
I'm trying to add a field in both the Create and Edit views whereby a user will be able to search for related items, in this example by name, and then add them to the new/current item as a related item.
I think this might be possible with a partial view + jQuery, where the partial view acts a search box and returns a list of items. Then with some buttons and jQuery magic these could be added to the current model upon submission of the parent form.
I'm not against using jQuery if it's the only option, but I was wondering if there were any alternatives?
If this is too vague I'm happy to add some more specific details, I've tried search for similar questions but I'm either using the wrong search terms or there aren't any.
Any help would be really appreciated - I'd rather not go down the jQuery + partial view route if there is a cleaner alternative.
First off, you need to create a view-model for what you're trying to achieve that will hold both the list of related books, but also a list of all books we can select.
public class CreateBookViewModel {
public Guid Id { get; set; }
public string Name { get; set; }
public List<Guid> RelatedBooks { get; set; }
public List<Book> AllBooks { get; set; }
}
Populate AllBooks in the Controller and return the view model. Then you could create some magic, maybe with jQuery, to have a select list with all books, that when selected are added to the list of related books by Id. At least you wouldn't need partial views, if you don't want any more functionality than just selecting one or more books.
Related
I've been searching for a way to post all the information of a model which contains other models and I believe I can just send the object to my view and go off of the 50 examples I've looked at and can render everything just fine.
Here's my model I'm talking about named Equipment.
public int id { get; set; }
public String name { get; set; }
public ManufacturerItem manufacturerItem { get; set; }
public EquipmentType equipmentType { get; set; }
public SupportItem supportItem{ get; set; }
public Placement placement{ get; set; }
public Boolean status { get; set; }
public DateTime endOfLife{ get; set; }
public String notes{ get; set; }
public Purchase purchase{ get; set; }
public Boolean mes{ get; set; }
public DateTime reviewedDate{ get; set; }
Based on the tons of examples I've read I know I can render these like this:
#Html.EditorFor(model => model.name)
#Html.EditorFor(model => model.manufacturerItem.model.name)
In other research I did stumble upon building forms for deep View Model graphs in ASP.NET MVC which I may consider in using, but that was posted back in MVC 2 days. I'm using MVC 5. So I don't know how relative that is today.
So let's say I have another model named Book with {id, Title, Author} and you could edit the book name and author. Now in this model, on edit, my controller could be as such:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,Author)"] Book book)
{ ... -insert code- ...}
Going off of this idea, what would be my controller method signature be for the Equipment model? Do I include the other objects as their own types?
I'm not using EF or linq-to-sql because I have to use stored procedures. So I want to get all this information neatly packaged and passed off to the repository that will take care of parameter assignment and calling of the stored procedure.
Going off of this idea, what would be my controller method signature
be for the Equipment model?
Have you tried using the following signature:
[HttpPost]
public ActionResult Edit(Equipment model)
{
...
}
By the way if your view doesn't contain a form allowing to edit all the properties of the Equipment model object graph you may consider using a view model containing only the properties that are included as input fields in your form. Then on the server you will get the corresponding Equipment instance from your backend using the id, update only the properties that were sent from the HTML form and save the results back.
For example:
[HttpPost]
public ActionResult Edit(EquipmentViewModel model)
{
Equipment equipement = backend.GetById(model.Id);
// set the properties that are coming from the UI:
equipment.name = model.Name;
equipment.supportItem = model.SupportItem;
...
// save the updated entity back
backend.Update(equipment);
}
In this example the EquipmentViewModel will contain only the properties that you have corresponding input fields in your view and which the user is supposed to edit and not the entire domain model object graph.
I have two entities, Customer and User as follows:
public class User
{
[Key]
public int Id { get; set; }
[StringLength(20)]
public string CustomerId { get; set; }
public string Password { get; set; }
public bool Locked { get; set; }
[ForeignKey("CustomerId")]
public virtual Customer Customer { get; set; }
}
public class Customer
{
[Key]
[Column("Id", TypeName = "nvarchar")]
[StringLength(20)]
public string Id { get; set; } // nvarchar(20)
[Required]
public string GivenName { get; set; } // nvarchar(100)
[Required]
public string Surname { get; set; } // nvarchar(100)
public virtual ICollection<User> Users { get; set; }
}
I have a simple strong typed view for editing a customer, and I want to add to the view a check-box with following logic - the check-box should be selected, when there is at least one user for that customer and the Locked property of the first user is set to false. I just can't find a way to accomplish this. What's the proper way to do this in MVC? And how the processing method (the [HttpPost]Edit) receives the value of this check-box, currently it simply gets the Customer object? Should I create an additional model for this view? Or there is another way?
Anticipating this question I should say that I'm taking care that there wont be more than one user for a customer.
Updates:
I've added a view model for customer and updated the edit view and the controller to work with this model:
public class CustomerViewModel
{
public Model.Data.Customer BaseCustomer { get; set; }
public bool HasActiveUser { get; set; }
}
My edits saving method looks now like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(ViewModel.Data.Customer customer)
{
if (ModelState.IsValid)
{
//db.Entry(customer.BaseCustomer).Collection("Users").Load();
db.Entry(customer.BaseCustomer).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CustomerTypeId = new SelectList(db.CustomerTypes, "Id", "Name", customer.BaseCustomer.CustomerTypeId);
return View(customer);
}
The only question remains is how do I access the Users navigation property which is null, I've tried to reload it but got an InvalidOperationException with error that reads Member 'Load' cannot be called for property 'Users' because the entity of type 'Customer' does not exist in the context. To add an entity to the context call the Add or Attach method of DbSet<Customer>. I've also tried to get the Customer again with Customer baseCustomer = db.Customers.Find(customer.Id); but then I can't set db.Entry(customer.BaseCustomer).State = EntityState.Modified; since it tells me that An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key. Any ideas, please?
I decided to go with creating a dedicated view model that will contain the domain model object(as suggested as second pattern ASP.NET MVC View Model Patterns by Steve Michelotti) and an additional property for binding to my check-box. Then in the controller I handle all the logic regarding when to show the check-box selected, and when to create a new user if one not exists. I've encountered several problems so I want to post my solutions, maybe they are far from best-practices but I think they might be of use to beginners, and I definitely would like to see comments or other solutions.
Objects aren't persisted if they don't participate in view since EF recreates them on post-back from the data received from the view. This prevented me from adding a User property that will be an accessor for the first User in navigation collection Users property of Customer (when I added it, it couldn't access Users since this property is null after post-back, as I understood this is because of the recreated Customer is detached from context).
In order to be able to use navigation properties I had to attach the recreated(by EF, as explained earlier) Customer object to the context by setting db.Entry(customer.BaseCustomer).State = EntityState.Modified;(thanks to Using DbContext in EF 4.1 Part 4: Add/Attach and Entity States ยง Attaching an existing but modified entity to the context) and to reload the collection by calling db.Entry(customer.BaseCustomer).Collection("Users").Load();
What are good strategies for rebuilding/enriching a nested or complex ViewModel?
A common way to rebuild a flat ViewModel is shown here
But building and rebuilding a nested ViewModel using that method is too complex.
Models
public class PersonInfo
{
public int Id { get; set; }
public string Name { get; set; }
public int Nationality { get; set; }
public List<Address> Addresses { get; set; }
}
public class Address
{
public int AddressTypeID { get; set; }
public string Country { get; set; }
public string PostalCode { get; set; }
}
public class AddressType
{
public int Id { get; set; }
public string Description { get; set; }
}
view models
public class PersonEditModel
{
public int Id { get; set; }
public string Name { get; set; } //read-only
public int Nationality { get; set; }
public List<AddressEditModel> Addresses { get; set; }
public List<SelectListItem> NationalitySelectList { get; set; } //read-only
}
public class AddressEditModel
{
public int AddressTypeId { get; set; }
public string AddressDescription { get; set; } //read-only
public string Country { get; set; }
public string PostalCode { get; set; }
public List<SelectListItem> CountrySelectList { get; set; } //read-only
}
actions
public ActionResult Update(int id)
{
var addressTypes = service.GetAddressTypes();
var person = service.GetPerson(id);
var personEditModel= Map<PersonEditModel>.From(person);
foreach(var addressType in addressTypes)
{
var address = person.Addresses.SingleOrDefault(i => i.AddressTypeId == addressType.Id)
if(address == null)
{
personEditModel.Addresses.Add(new AddressEditModel
{
AddressTypeId = addressType.Id
});
}
else
{
personEditModel.Addresses.Add(Map<AddressEditModel>.From(address));
}
}
EnrichViewModel(personEditModel, person, addressTypes); //populate read-only data such as SelectList
return Index(personEditModel);
}
[HttpPost]
public ActionResult Update(PersonEditModel editModel)
{
if(!ModelState.IsValid)
{
var person = service.GetPerson(editModel.Id);
var addressTypes = service.GetAddressTypes();
EnrichViewModel(editModel, person, addressTypes);
return View(editModel);
}
service.Save(...);
return RedirectToAction("Index");
}
//populate read-only data such as SelectList
private void EnrichViewModel(PersonEditModel personEditModel, Person person, IEnumerable<AddressType> addressTypes)
{
personEditModel.Name = person.Name;
personEditModel.NationalitySelectList = GetNationalitySelectList();
foreach(var addressEditModel in personEditModel.Addresses)
{
addressEditModel.Description = addressTypes.Where(i => i.Id = addressEditModel.AddressTypeId).Select(i => i.Description).FirstOrDefault();
addressEditModel.CountrySelectListItems = GetCountrySelectList(addressEditModel.AddressTypeId);
}
}
My code for building and rebuilding the ViewModels (PersonEditModel and AddressEditModel) is too ugly. How do I restructure my code to clean this mess?
One easy way is to always build a new view model instead of merging/rebuilding since MVC will overwrite the fields with the values in ModelState anyway
[HttpPost]
public ActionResult Update(PersonEditModel editModel)
{
if(!ModelState.IsValid)
{
var newEditModel = BuildPersonEditModel(editModel.Id);
return View(newEditModel);
}
but I'm not sure that this is a good idea. Is it? Are there other solutions besides AJAX?
I'm going to tackle your specific pain points one-by-one and I'll try to present my own experience and likely solutions along the way. I'm afraid there is no best answer here. You just have to pick the lesser of the evils.
Rebuilding Dropdownlists
They are a bitch! There is no escaping rebuilding them when you re-render the page. While HTML Forms are good at remembering the selected index (and they will happily restore it for you), you have to rebuild them. If you don't want to rebuild them, switch to Ajax.
Rebuilding Rest of View Model (even nested)
HTML forms are good at rebuilding the whole model for you, as long as you stick to inputs and hidden fields and other form elements (selects, textarea, etc).
There is no avoiding posting back the data if you don't want to rebuild them, but in this case you need to ask yourself - which one is more efficient - posting back few extra bytes or making another query to fetch the missing pieces?
If you don't want to post back the readonly fields, but still want the model binder to work, you can exclude the properties via [Bind(Exclude="Name,SomeOtherProperty")] on the view model class. In this case, you probably need to set them again before sending them back to browser.
// excluding specific props. note that you can also "Include" instead of "Exclude".
[Bind(Exclude="Name,NationalitySelectList")]
public class PersonEditModel
{
...
If you exclude those properties, you don't have to resort to hidden fields and posting them back - as the model binder will simply ignore them and you still will get the values you need populated back.
Personally, I use Edit Models which contain just post-able data instead of Bind magic. Apart from avoiding magic string like you need with Bind, they give me the benefits of strong typing and a clearer intent. I use my own mapper classes to do the mapping but you can use something like Automapper to manage the mapping for you as well.
Another idea may be to cache the initial ViewModel in Session till a successful POST is made. That way, you do not have to rebuild it from grounds up. You just merge the initial one with the submitted one in case of validation errors.
I fight these same battles every time I work with Forms and finally, I've started to just suck it up and go fully AJAX for anything that's not a simple name-value collection type form. Besides being headache free, it also leads to better UX.
P.S. The link you posted is essentially doing the same thing that you're doing - just that its using a mapper framework to map properties between domain and view model.
I have an MVC.NET application with a number of models, some of which include classes with these properties:
public class Species
{
[Key]
public int SpeciesID { get; set; }
public string SpeciesName { get; set; }
public virtual List<Photo> Photos { get; set; }
}
public class Photo
{
[Key]
public Guid PhotoID { get; set; }
public int? SpeciesID { get; set; }
[ForeignKey("SpeciesID")]
public virtual Species Species { get; set; }
}
Users can upload photos and tag them with species.
One species can have multiple photos.
One photo can only have one species.
Accessing the Photos property of the Species class automatically loads all photos of the species as expected.
What I'm trying to do now is add the following to the Species class:
public Guid? DefaultPhotoID { get; set; }
[ForeignKey("DefaultPhotoID")]
public virtual Photo DefaultPhoto { get; set; }
This allows users to select a default photo for the species.
This also works and the DefaultPhoto property is automatically populated based on the DefaultPhotoID property.
However as soon I add the above code the Photos property of a Species class stops returning anything.
I thing this is related to the fact that the Species class now has two virtual properties of Photo type (a single photo and a collection of photos) and for some reason doesn't know how to handle the collection anymore.
Does anyone have an idea why it's breaking and how to fix it?
To be more specific - I also have other models, which form a hierarchy (order->family->genus->species), which also work correctly until the DefaultPhoto property is added.
EDIT:
To better explain the problem I have uploaded a simplified version of the project with just the hierarchy of the classes here:
http://www.speedyshare.com/dgCJw/MvcTest-VirtualProperty.zip
It currently has a DB with 4 photo items and if you go to the Index view of any of the models (Orders, Families, Species etc.) you will see the total number of photos is displayed correctly.
However if you add the DefaultPhotoID and DefaultPhoto properties to the BaseModel (just uncomment the existing code) these numbers will not be displayed anymore (they will be 0).
I hope that explains the problem - I want to use the Photos property without the need to run custom queries to populate it.
Have you confirmed that removing the List<Photo> property makes DefaultPhoto work? If not, you might be barking up the wrong tree.
You may consider revising your relationships. Instead of putting the DefaultPhotoId on the Species table. Maybe instead you add a bool Default to the Photo table. Then you get the default photo by finding the first photo tagged for the species and flagged as the default.
I use the EF-CF, and have the following entities:
Countries, Companies
Companies has a property called CountryId with StringLength attribute and the min and max restrictions (min 3 chars, max 3 chars, country id is ISO-Alpha-3). When the user needs to create a Company, I show a html element with all available countries. This is perfect!
However, when the I execute the jquery validator to the form, this checks for 3 selected options and not the length selected option value.
I need the StringLengthAttribute in my Country Model, I cannot remove it.
I hope to "remove" or "hide" the StringLengthAttribute in the call:
#Html.ValidationMessageFor(model => model.CountryId)
Thanks!
I think I understand your question. A possible solution would be to use a ViewModel to pass to the view as oppose to using the Company entity directly. This would allow you to add or remove data annotations without changing the entity model. Then map the data from the new CompanyViewModel over to the Company entity model to be saved to the database.
For example, the Company entity might look something like this:
public class Company
{
public int Id { get; set; }
[StringLength(25)]
public string Name { get; set; }
public int EmployeeAmount { get; set; }
[StringLength(3, MinimumLength = 3)]
public string CountryId {get; set; }
}
Now in the MVC project a ViewModel can be constructed similar to the Company entity:
public class CompanyViewModel
{
public int Id { get; set; }
[StringLength(25, ErrorMessage="Company name needs to be 25 characters or less!")]
public string Name { get; set; }
public int EmployeeAmount { get; set; }
public string CountryId { get; set; }
}
Using a ViewModel means more view presentation orientated annotations can be added without overloading entities with unnecessary mark-up.
I hope this helps!
Ready!
I remove the rule for the html control.
$("##(Html.HtmlIdNameFor(model => model.CountryId))").rules("remove", "rangelength");
The "rangelength" is the jquery validation rule for the StringLengthAttribute.
Where "Html.HtmlIdNameFor" is a helper to get the "Id" generated by ASP.NET MVC.
Review How to get the HTML id generated by asp.net MVC EditorFor