How to create/edit models via view in asp.net mvc 5 - c#

I got one question related to my model you can see in the picture below.
As you can see I got 3 entities and 1:n and m:n relations between them.
I want that I can edit these models through a web interface. Therefore I scaffold (add controller with entity framework) these three models and got edit/delete/create/ views and of course one controller for each entity.
But there is no input/fields created for the relations automatically by VS. So I thought to implement them manually. Before I want to do that is there an simpler way to implement/scaffold this model, so I can even edit the relations(Checkboxes or (multi)select would be the best)?
Thanks in advance!

For one-many you can use a DropDownList for Tip in the Partner View (see Scott Allen's solution. Many-many can be handled by ViewModels and JavaScript frameworks like Knockout.

No, the scaffolds are intentionally unopinionated here, as there's many different ways you could handle this. Perhaps you just want to choose from a select list? Maybe you want checkboxes, instead? Or, maybe you want to actually add/edit related items inline? And with that last one, would you like to post all at once or use AJAX?
So, instead of picking for you, the framework rightly leaves the decision up to you, since only you know how your application should be built. Regardless, relying on the scaffolds is going to bite you more often than not. They only work in the most basic and ideal scenarios, and when have application requirements ever been either basic or ideal? I don't even bother with them at this point, preferring to just create my controllers/views manually. It ends up being quicker than dealing with the scaffold and undoing all the things that aren't applicable.
So, since you're looking for select boxes (either single-select or multi-select), first, I'd recommend creating view models for your entities. For example, with Tip:
public class TipViewModel
{
[Required]
public string Name { get; set; }
[Required]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Required]
public int? SelectedPartnerId { get; set; }
public IEnumerable<SelectListItem> PartnerChoices { get; set;}
[Required]
public int? SelectedBookId { get; set; }
public IEnumerable<SelectListItem> BookChoices { get; set; }
}
Here, I've added nullable int (using a nullable allows them to be initially unselected, instead of just set to the first option) properties to track the id of the selected Book/Partner because it doesn't appear you have explicit properties on your entities for the foreign keys. That's fine, but it doesn't make it slightly more complicated to save the relationship, as you'll see in a bit. If you did have explicit foreign key properties, then you should mirror those in your view models instead.
Now in the GET version of your action, you'll need to do something like the following:
public ActionResult Create()
{
var model = new TipViewModel();
PopulateChoices(model);
return View(model);
}
...
protected void PopulateChoices(TipViewModel model)
{
model.PartnerChoices = db.Partners.Select(m => new SelectListItem
{
Value = m.Id.ToString(),
Text = m.Name
});
model.BookChoices = db.Books.Select(m => new SelectListItem
{
Value = m.Id.ToString(),
Text = string.Format("{0} by {1}", m.Name, m.Author)
});
}
I've abstracted out the code for populating these select lists because the code will be used multiple times throughout your controller. Also, I used string.Format on the Text value for the books just to show that you can do whatever you want with the text for the select list item. Also, the code above would be for a create action, obviously. Doing an edit would be similar but slightly different:
public ActionResult Edit(int id)
{
var tip = db.Tips.Find(id);
if (tip == null)
{
return new HttpNotFoundResult();
}
var model = new TipViewModel
{
Name = tip.Name,
Description = tip.Description,
SelectedPartnerId = tip.Partner != null ? tip.Partner.Id : new int?(),
SelectedBookId = tip.Book != null ? tip.Book.Id : new int?()
}
PopulateChoices(model);
return View(model);
}
The main difference is that you're obviously dealing with an existing instance so you need to pull it from the database. Then, you just need to map the data from your entity onto your view model. Since, again, you don't have explicit foreign key properties, you have to do a little extra leg work to get the currently chosen Partner/Book values, otherwise you could just copy the values for the foreign key properties over directly. Also, here, I'm just doing a manual mapping, but there's third-party libraries to make this task easier (see: AutoMapper).
With that, you can implement your views. Everything will work the same as it did when you were using the entity directly, you just need to make a couple of modifications. First, you'll need to change your view's model declaration:
#model Namespace.To.TipViewModel
Then, add the select lists for your two related properties:
#Html.DropDownListFor(m => m.SelectedPartnerId, Model.PartnerChoices)
...
#Html.DropDownListFor(m => m.SelectedBookId, Model.BookChoices)
The fun happens in the POST version of your actions. Most of the code will stay the same from the GET version, but now you'll have an if (ModelState.IsValid) block:
[HttpPost]
public ActionResult Create(TipViewModel model)
{
if (ModelState.IsValid)
{
// map the data from model to your entity
var tip = new Tip
{
Name = model.Name,
Description = model.Description,
Partner = db.Partners.Find(model.SelectedPartnerId),
Book = db.Books.Find(model.SelectedBookId)
}
db.Tips.Add(tip);
db.SaveChanges();
return RedirectToAction("Index");
}
// Form has errors, repopulate choices and redisplay form
PopulateChoices(model);
return View(model);
}
The edit version, again, is similar, except you're going to map onto you existing instance, for example:
tip.Name = model.Name;
tip.Description = model.Description;
tip.Partner = db.Partners.Find(model.SelectedPartnerId);
tip.Book = db.Books.Find(model.SelectedBookId);
That's all there is to it for reference properties. You don't actually have any thing that's M2M or even one-to-many on your entities in your question. Everything is one-to-one, but if you did have a collection property, you'd need to handle it slightly differently. You still need a property on your view model to hold the selected values and the available choices:
public List<int> SelectedFooIds { get; set; }
public IEnumerable<SelectListItem> FooChoices { get; set; }
Populating the choices would also be the same. The options are the options; it doesn't matter if you're select just one or many as far as that is concerned.
Mapping onto your entity in your create action would be different though, as you'd need to select all of the chosen items from the database and set your collection property on your entity to that:
var tip = new Tip
{
...
Foos = db.Foos.Where(m => model.SelectedFooIds.Contains(m.Id)),
}
And, you'd need to make changes to both the GET and POST versions of your edit action. For the GET, you need to condense your collection property down to a list of ids:
var model = new TipViewModel
{
...
SelectedFooIds = tip.Foos.Select(m => m.Id).ToList(),
}
And in the edit version, you set new selected items:
tip.Foos = db.Foos.Where(m => model.SelectedFooIds.Contains(m.Id);
Finally, in your views, you'd use ListBoxFor instead of DropDownListFor to enable the multiselect:
#Html.ListBoxFor(m => m.SelectedFooIds, Model.FooChoices)

Related

ASP.NET Multiple DTOs to one Model and validation inside DTO

In my web app I have class with many properties. User is able to modify these properties with bunch of selectboxes, each responsible for one property. Everytime when given property is changed, the change event is triggered and ajax sends the new value of this property to Update method in the Controller.
So far I have one UpdateDto which consists of nullable fields. In my Update controller I check each DTO's field if it is null or not. If it's not null it means that user want to change this property and it is updated and saved in database.
Unfortunately the Update method's code looks a little bit ugly for me. It checks each property and it's quite long. Have a look:
Update Method in the controller:
public IHttpActionResult UpdateScrumTask(int id, ScrumTaskDetailsDto scrumTaskDto)
{
var scrumTaskFromDb = _context.ScrumTasks
.SingleOrDefault(s => s.Id == id);
if (scrumTaskFromDb == null)
return NotFound();
if (!ModelState.IsValid)
return BadRequest();
if (!string.IsNullOrWhiteSpace(scrumTaskDto.UserId))
{
var user = _context.Users
.SingleOrDefault(u => u.Id.Equals(scrumTaskDto.UserId));
scrumTaskFromDb.UserId = user?.Id;
}
else if (scrumTaskDto.EstimationId != null)
{
var estimation = _context.Estimations
.SingleOrDefault(u => u.Id == scrumTaskDto.EstimationId.Value);
scrumTaskFromDb.EstimationId = estimation?.Id;
}
else if (scrumTaskDto.Priority != null)
{
if (scrumTaskDto.Priority.Value == 0)
scrumTaskDto.Priority = null;
scrumTaskFromDb.Priority = scrumTaskDto.Priority;
}
else if (scrumTaskDto.TaskType != null)
{
scrumTaskFromDb.TaskType = scrumTaskDto.TaskType.Value;
}
_context.SaveChanges();
return Ok();
}
UpdateDTO:
public class ScrumTaskDetailsDto
{
public int? EstimationId { get; set; }
[Range(0, 5)]
public byte? Priority { get; set; }
[Range(0, 2)]
public TaskType? TaskType { get; set; }
public string UserId { get; set; }
}
Note that these properties are also nullable in the database. That's why if for example UserId is not found the property in database is set to null.
I wonder how it should look like. What is better solution?
Keep one Update method and use one UpdateDto with many nullable fields OR
Devide Update method into many methods, each responsible for one property and create separate DTOs. It brings that there will be a lot of DTOs connected with one model with single property (is it good?)
Another question is:
Should I use validtion (DataAnnotation) inside DTO? If not, what is alternative solution?
I'd suggest couple of improvements:
Separate your query logic from your core business logic, using repository pattern or query and command object using a library like MediatR, also controller only should call other methods to do something for it and return a response, it shouldn't have any query or any other logic
The DTO looks ok to me, as long as it's centered around one specific task
I would definitely separate the update from controller, as for it being one method or more, it's 35... lines, which is not ideal, maybe you could separate it into two methods, one responsible for validation and one responsible for the actual update, also you can use dependency injection to decouple the update and validation method from your controller

controller action method to update and add properties to database

In an ASP.NET MVC application using entity framework, I have an Edit view where I am updating some properties on one of my entity models. I'd like to put an "add" button for one of the properties if the user wants to add a new property of that type (but without deleting the older entry, I understand this requires a List of those type of properties). When I click this add button, using Ajax.ActionLink will retrieve a new Html.EditorFor helper from a partial view and add it to the current form.
Currently my ActionMethod looks like it's only updating the fields that are already there, what kind of logic can I add to say "if this is a new property, then db.Add"?
heres what my method looks like now:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult TimeSeriesData(List<TimeSeriesData> List1)
{
int empid = List1[0].EmployeeID;
if (ModelState.IsValid)
{
foreach (TimeSeriesData ln in List1)
{
db.Entry(ln).State = EntityState.Modified;
}
db.SaveChanges();
return RedirectToAction("Edit", new { id = empid });
}
return new EmptyResult();
}
I'd like to add logic somewhere inside the foreach loop and say if the item in the list is a new item, then db.MyModel.List.Add(item); but I'm not sure how to check if the list item is new
would it be something like
if (newproperty.propertyid == null) {
db.MyModel.List.Add(newproperty);
}
Please let me know if you need more clarification
EDIT
Or another way to ask my question is: how can I check if the item already exists in the database,
models:
//summarized
class Employee {
int EmployeeID {get;set;}
List<TimeSeriesData> TimeSeriesData {get;set;}
}
class TimeSeriesData {
int TimeSeriesDataID {get; set;}
int EmployeeID {get;set;}
string Value {get;set;}
[ForeignKey("EmployeeID")]
public Employee Employee { get; set; }
}
There's a problem within your code structure. If you will notice,
db.Entry(ln).State = EntityState.Modified;
you are just updating/editing that specific entity and not adding a property. And also, I assume that you kinda missed out some logic.
If you're going to add another property for it, you should put it in a separate table and reference it to the primary key of the parent table. By this, you will not have any problem adding another value for that.
To answer your second question, you can use .Any property of the context to check out if the item exists in the database. You can have something like this:
var db = new Context();
if(!db.mytable.Any(m => m.field == value)){
db.mytable.Add(value);
db.SaveChanges();
}
Should you have any question, please feel free to ask.

Updating related data using MVC 4 and Entity Framework?

So, I have a problem in save data which contains related entities, when I save it a new relation blank is created.
Exemple:
Entities:
public class Project
{
public int Id { get; set; }
public int Code{ get; set; }
public string Description{ get; set; }
public virtual Client Client { get; set; }
}
public class Client
{
public int Id { get; set; }
public int Code { get; set; }
public string Name { get; set; }
}
The Controller GET:
public ActionResult Create()
{
PopulateDropDownClienteList(String.Empty); //Returns to ViewBag to create a combobox .in view
return View();
}
The View:
#Html.DropDownListFor(m => m.Client.Id, new SelectList(ViewBag.Client_Id, "Id", "Name"), new { Name = "Client.Id" });
The Controller POST:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(string command, Project project)
{
try
{
if (ModelState.IsValid)
{
projectRepository = new ProjeRepository();
Project pro = projectRepository.ReturnByCode(project.Code);
if (pro == null)
projectRepository.Save(project);
else
projectRepository.Update(project);
PopulateDropDownClienteList(String.Empty);
Return View();
}
else
{
return View(project);
}
}
catch (Exception ex)
{
return View();
}
}
So when I save the data, the client is not associated with the project. just creating a new blank Client.
You Project Save code is not updating the entity, it is ADDING a new one all the time.
You should have update logic similar to following grounds -
To Add new FK Entry and associate it with parent record -
var entity = entities.Students.Where(p => p.Id == "2").First();
entity.StudentContact = new StudentContact() { Contact = "xyz", Id = "2" };
entities.Students.Attach(entity);
var entry = entities.Entry(entity);
// other changed properties
entities.SaveChanges();
To update a FK record with new details -
var entity = entities.Students.FirstOrDefault();
entity.StudentContact.Contact = "ABC";
entities.Students.Attach(entity);
var entry = entities.Entry(entity);
entry.Property(e => e.StudentContact.Contact).IsModified = true;
// other changed properties
entities.SaveChanges();
The above code, I have a Student records which has FK relationship with StudentContacts. I updated Contact information of a student and then updated it to database using ATTACH.
You've got a number of issues here, so let me break them down.
First and foremost, do not ever catch Exception (at least without throwing it again). There's two very important things about using try...catch blocks: you should only wrap the code where you're expecting an exception (not nearly your entire method as you've done here), and you should only catch the specific exception you're expecting (not the base type Exception). When you catch Exception, any and every exception that could possibly be generated from your code will be caught, and in this case, simply discarded, which means you really will never know if this code works at all.
Second, you have a fine method that generates a dropdown list of choices, but never store the user's selection anywhere meaningful. To understand why, you need to stop and think about what's happening here. An HTML select element has a string value and a string text or label component. It does not support passing full objects back and forth. I can't see what your PopulateDropDownClienteList method does, but what it should be doing is creating an IEnumerable<SelectListItem>, where each item gets its Text property set to whatever you want displayed and its Value property to the PK of the Client. However, once you have that, you need some property on Project to post back to. Your virtual Client won't work as that needs a full Client instance, which your form will never have. So, you have two choices:
Implement a view model to feed to the view (and accept in the post). In that view model, in addition to all other editable fields, you'll include something like ClientId which will be an int type, and you'll bind this to your drop down list. Once you're in your post method, you map all the posted values to your project instance, and then use the ClientId to look up a client from the database. You then set the resulting client as the value for your Client property and save as usual.
You alter your database a bit. When you just specify a virtual, Entity Framework smartly creates a foreign key and a column to hold that relationship for you behind the scenes. That's great, but in situations like this, where you actually need to access that foreign key column, you're screwed. That way around that is to explicitly define a property to hold that relationship on your model and tell Entity Framework to use that instead of creating its own.
[ForeignKey("Client")]
public int ClientId { get; set; }
public virtual Client Client { get; set; }
With that, you can now directly use ClientId without worrying about filling in Client. You again bind your drop down list to ClientId, but now, you do not need to look up the client explicitly from the database. Entity Framework will just save the ClientId as it should to the database, and then restore the Client based on that when you look up the project again in the future.

View Model virtual properties and drop down lists

I'm having trouble grasping the proper way to create view models and save that info back to the database using Entity Framework, and I can't seem to find the info I'm looking for, so please forgive me if I have overlooked it.
I came across this post here and he seems to be asking the same question but doesn't get an answer.
My main questions are,
For editing purposes, If I have a ProductModel model that has a Warranty model relationship, should I be using virtual property Warranty in the view model or should I be using int WarrantyId?
If I should be using a virtual property, why doesn't this code save the Warranty properly?
Do I need to explicitly flag or populate the Warranty for update?
Please not this does populate my edit view and select lists as intended.
My (simplified) code is setup as follows:
Model:
public int ModelId{ get; set; }
public int ModelNumber { get; set; }
public virtual Warranty Warranty { get; set;}
View Model:
public int ModelId { get; set; }
[Required(ErrorMessage = "Model Number required")]
[StringLength(25, ErrorMessage = "Must be under 25 characters")]
[Display(Name="Model Number")]
public string ModelNumber { get; set; }
//related objects and necesary properties
public virtual Warranty Warranty { get; set; }
public IEnumerable<SelectListItem> WarrantySelectListItems { get; set; }
Controller (GET):
public ActionResult Edit(int? id)
{
//check the id
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
//get the model and make sure the object is populated
var model = _modelService.GetModel(id.Value);
if (model == null)
{
return HttpNotFound();
}
//pass our entity (db) model to our view model
var editModelModel = new EditModelModel();
editModelModel.InjectFrom(model);
//warranty select list
editModelModel.WarrantySelectListItems = WarrantySelectList(editModelModel.Warranty.WarrantyId);
//option multi select list
editModelModel.OptionSelectListItems = OptionSelectList();
return View(editModelModel);
}
Controller (POST) (work in progress):
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditModelModel editModelModel)
{
if (!ModelState.IsValid)
{
return View(editModelModel);
}
var modelEntity = new Model();
modelEntity.InjectFrom(editModelModel);
_modelService.Update(modelEntity);
_unitOfWork.Save();
return RedirectToAction("Index");
}
View (simplified):
<div class="form-group">
#Html.Label("Warranty", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(x => x.Warranty.WarrantyId, Model.WarrantySelectListItems, "--Select--")
#Html.ValidationMessageFor(model => model.Warranty.WarrantyId)
</div>
</div>
Again, I just want to know the proper/best way to set up these viewmodels and models so the EF is doing as much of the work as possible. I feel like if I have to create a WarrantyId field, I'm doing something wrong, but maybe that isn't the case.
Thanks in advance. Any insight/help is greatly appreciated.
For editing purposes, If I have a ProductModel model that has a
Warranty model relationship, should I be using virtual property
Warranty in the view model or should I be using int WarrantyId?
You don't use virtual keyword for the property of your ViewModel, because the ViewModel has nothing to do with Entity Framework.
The reason to use the virtual keyword is to allow lazy loading in Entity Framework. In your case, if you add the virtual keyword for
the Warranty navigation property in the Product POCO class, you can access the Warranty property like below:
Model.Warranty.WarrantyId
And the reason it didn't save the Warranty information into your Database is because you need to define a Warranty foreign key property in the Product class.
In your case, if you're using code first approach and Product is your POCO class, just keep it simply like below:
public class Product
{
public int ModelId { get; set; }
public int ModelNumber { get; set; }
public int WarrantyId {get;set;}
[ForeignKey("WarrantyId ")]
public virtual Warranty Warranty { get; set; }
}
Then your ViewModel :
public class MyViewModel
{
public Product Product { get; set; }
public IEnumerable<SelectListItem> WarrantySelectListItems { get; set; }
}
Finally your view
#model MyViewModel
#Html.DropDownList("Product.Warranty.WarrantyId", Model.WarrantySelectListItems, "--Select--")
#Html.ValidationMessageFor("Product.Warranty.WarrantyId")
Of course, you need to change your action methods to meet the ViewModel.
For editing purposes, If I have a ProductModel model that has a Warranty model relationship, should I be using virtual property Warranty in the view model or should I be using int WarrantyId?
You shouldn't be using virtual properties in your view models. A view model simply represents the slice of data that is necessary to display a view. As you are mapping to that view model from your entities, you don't need to mark anything as virtual. See this answer, if you want to know what virtual is doing with regards to the Entity Framework.
Also, you should only be including the information necessary to render that view. So if you just need the WarrantyId in the view, then only include that.
As you're also model-binding back to the same view model in your POST action, you should be very specific about what you want your view model to represent, otherwise you leave yourself open to an over-posting attack.
I feel like if I have to create a WarrantyId field, I'm doing something wrong, but maybe that isn't the case.
It isn't the case. Each of your views should be self-contained. When you first starting using view models, one-per-view, your initial reaction is that of violating DRY. However, each view has different requirements. In terms of view models themselves, the most obvious distinction is validation. If you use entities in your views, all of those views are tied to the validation rules you've applied to your entities. (You'd also be vulnerable to over-posting if you don't want the user to be able to edit the entire entity.)
However, by having separate view models for your views, and applying the validation rules on the view models themselves, you can now have different validation requirements in your views. For example:
public class ViewAViewModel
{
[Required]
public int WarrantyId { get; set; }
}
public class ViewBViewModel
{
// No longer required.
public int WarrantyId { get; set; }
}
If you'd have included Warranty directly in both of these views, you'd have been stuck with one set of validation rules.
That aside, I'm wondering why you have this on your model (which I assume is an entity):
public IEnumerable<SelectListItem> WarrantySelectListItems { get; set; }
That doesn't belong here. This is a presentation detail, and it should not exist in your business objects. It should exist on your view model.
What you're dealing with are definitely navigation properties (the virtual properties on your model classes), and this does a good job of explaining them:
http://msdn.microsoft.com/en-us/data/jj713564.aspx
The tricky parts in defining these are really in how you set up your DbContext for the database. The official doc on this is here:
http://msdn.microsoft.com/en-us/data/jj591620
Simple parent-child relationships are pretty easy to handle, and there are additional situations (trickier) where you can define a number of models that physically come from the same row in a table, but I don't think you're dealing with that here.
The MVC part is a separate concern, and ideally you should treat it as such. Controller code should only delegate real "work" to other classes. The unit of work pattern, if you choose to use it, isn't really necessary until you get into situations where you've got a big lump of stuff to persist/edit across many tables or entity sets, with the idea that you may want it all to fail or succeed as a whole. If you're just handling simple persistence of single objects, don't even complicate it with the unit of work pattern.
The other thing to keep in mind with EF, or any ORM framework, is that it needs to track changes or compare to existing records, so the key values become super important as you work through this.
A ViewModel is a simplified view of your data that is UI aware and includes only information you need for UI rendering and User Input.
It might seem wrong to do more work - why not use the model directly? But with complex systems you end up with a lot of complexity and often you need to change the Model to accomodate the UI and it's a mess.
Also, ViewModels allow you to test the UI without having a database present and without that complexity. You really decouple UI issues and Data Modeling issues.
I usually end up NEVER using Models on the UI at all, always through ViewModel that simplifies my life in the end even if it's more work first.
So let's do a couple of changes.
View Model (Renamed to EditViewModel for clarity):
public int ModelId { get; set; }
// Removed for clarity, include needed properties in the UI
public int WarrantyId { get; set; }
public IEnumerable<SelectListItem> WarrantySelectListItems { get; set; }
Controller (GET):
public ActionResult Edit(int? id)
{
//check the id
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
//get the model and make sure the object is populated
var model = _modelService.GetModel(id.Value);
if (model == null)
{
return HttpNotFound();
}
//pass our entity (db) model to our view model
var editViewModel = new EditViewModel();
editViewModel.InjectFrom(model);
// You could instead create a custom injection like FlatLoopValueInjection
// That would flatten and remove duplicates from
// Model.Warranty.WarrantyId to ViewModel.WarrantyId
editViewModel.WarrantyId = model.Warranty.Id;
//warranty select list
editViewModel.WarrantySelectListItems = WarrantySelectList(editViewModel.WarrantyId);
return View(editViewModel);
}
Custom Injection Flatten - FlatLoopValueInjection:
http://valueinjecter.codeplex.com/wikipage?title=flattening&referringTitle=Home
Controller (POST):
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditViewModel editViewModel)
{
if (!ModelState.IsValid)
{
return View(editViewModel);
}
// You need to reconstruct the model itself, there are faster ways but I wanted
// to showcase the logic behind it
// I didn't do any null check or anything to simplify
// Load the model used from the database
var modelEntity = _modelService.GetModel(editViewModel.ModelId);
// You can do an InjectFrom for the other properties you need
// with custom Injection to unflatten
modelEntity.InjectFrom(editViewModel);
// Load the selected warranty from the database
var warrantyEntity = _warrantyService.GetWarranty(editViewModel.WarrantyId);
// Update the warranty of the model with the one loaded
modelEntity.Warranty = warrantyEntity;
_modelService.Update(modelEntity);
_unitOfWork.Save();
return RedirectToAction("Index");
}
Now in your view:
<div class="form-group">
#Html.Label("Warranty", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(x => x.WarrantyId, Model.WarrantySelectListItems, "--Select--")
#Html.ValidationMessageFor(model => model.WarrantyId)
</div>
</div>
As a side note, in your models and view models, you should try to never repeat prefixes in names like:
Model.ModelId
Warranty.WarrantyId
Unless it's a foreign key or value:
Model.WarrantyId
Why? It's a LOT easier to flatten/unflatten them by convention with InjectFrom:
Model.Warranty.Id => (flatten) => Model.WarrantyId => (unflatten) => Model.Warranty.Id
Also, it's a best practice. The name of the model/table already tells you the entity type, no need to repeat it.
You have to have int WarrantyId in your view model.
Than in your view
#Html.DropDownListFor(x => x.WarrantyId, Model.WarrantySelectListItems, "--Select--")
In Controller (POST) take WarrantyId (selected from dropdown) and find object from database (var warranty = db.Warranties.Where(w=>w.WarrantyId == editModelModel.WarrantyId or something like that) and that object assign to modelEntity.

ViewModels and object manipulation in MVC

I want to understand (and finally appreciate because now it's only pain...) more ViewModels and strongly-typed Views in MVC.
My ViewModel
public class Combined
{
public IEnumerable<Domain> Domains { get; set; }
public IEnumerable<RegInfo> RegInfos { get; set; }
public Combined(IEnumerable<Domain> domains, IEnumerable<RegInfo> reginfos)
{
this.Domains = domains;
this.RegInfos = reginfos;
}
In Controller I pass data from repositories to an object of type Combined.
public ActionResult RegDetails(int id = 0)
{
var domain = from x in unitofwork.DomainRepository.Get(n => n.ID == id)
select x;
var reginfo = from y in unitofwork.ReginfoRepository.Get(n => n.ID == id)
select y;
var regdetails = new Combined(domain, reginfo);
return View(regdetails);
}
In a View (using Razor) I have #model project.namespace.Combined so I'm passing an object that holds two lists.
1/ Why can't I access each list item like this #Model.Domain.Name (noobish question but please help me to understand logic behind it)? I can do it form View "level" by using join but it's totally against MVC pattern. I think that only place to join those two tables is in Controller but it will create totally new object so do I need to create a Model for it?
2/ What's the best approach to get an IEnumerable object that will hold data from 2 or more tables that can be used to populate View (join, automapper)?
3/ Is there an approach that will allow me to create a Model that I will be able to use to POST to multiple tables from one FORM?
Thanks in advance.
The logic of fetching the entities in your controller action is fine; that's the job of the controller. You don't, however, need a custom constructor on your view model: just use object initialization.
var regdetails = new Combined { Domains = domain, RegInfos = reginfo }
Now, as far as your view model goes, Domains and RegInfos are IEnumerables, but you're only fetching a single object for each. If your intention is to have a list type, then you should modify your LINQ to select multiple items, but if your intention is to in fact have just one object for each, then you should not use IEnumerables in your view model.
public class Combined
{
public Domain Domain { get; set; }
public RegInfo RegInfo { get; set; }
}
If you do that, then you will be able to access the Name property on the Domain instance with just #Model.Domain.Name. However, if you keep them list-types, then you must loop through them in your view (even if there's only one item in the list):
#foreach (var domain in Model.Domains)
{
// Do something with domain.Name
}
You get indeed a Model property in your view, and you can use Domains and RegInfos properties like you would do in c#. For example :
#{
var firstDomain = Model.Domains.FirstOrDefault();
// do some process with this variable
}
Or :
#foreach(var regInfo in Model.RegInfos)
{
// do some other process
}
If displayed data come from various data sources, it is best to make a specific view model. You need to avoid making calculations or applying business rules in your view, data should be preformatted before that. Another benefit is to pass only required data to your view : you don't want to fetch a huge object (or collection) from your database just for 2 displayed properties.
That view model can also be used when you submit a form. You can get it back if your post action has a parameter of the view model type, and if you correctly generate inputs in your view (by using some HtmlHelper like Html.Editor(For), etc).
Anyway, there's a lot to say about strongly typed views, and many resources / tutorials can be found across the web.

Categories