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.
Related
This query below returns 4 rows, all rows corresponding an adres info so there is a "adresType" column which represent its HomeAddress, WorkAdress, etc.
And this is AllAddressViewModel:
public class AllAddressViewModel
{
public AddressModel homeAddress{ get; set; }
public AddressModel workAddress { get; set; }
}
public class AddressModel
{
public adresTypeEnum adresType{ get; set; }
...
This is what I am trying;
var result = ( from Muayene in muayeneRepo
join Adres in adresRepo on Muayene.HastaTc equals Adres.HastaTc
where Muayene.HastaTc == hastaTc.ToString() && Muayene.IsDeleted != true
select new HastaMuayeneKayitViewModel()
{
homeAddress= new AddressModel {
adresType= Adres.AddressType,
...
},
workAddress = new AddressModel
{
adresType= Adres.AdresTipi,
...
I think u already understand what bothers me, I should match all adresType with corresponding view model.. How can i populate the model properly within linq, dont want another step.
A join like that for a Muayene /w 4 addresses would result in 4 records for the same parent record, each having one address.
If the address records have a discriminating field (address type) then a first step might be to introduce inheritance to your domain model so that a Muayene contains a collection of Addresses, but that collection would contain 0-1 (or more) of each applicable address type as a distinct entity type that inherits from Address. This way you would potentially have an easier time converting these to a corresponding view model. (See Table per Hierarchy inheritance. https://msdn.microsoft.com/en-us/data/jj618292)
For building the view model structure you will want to group by the Muayene so that you get a group for each parent and it's collection of child addresses or better, utilize navigation properties to handle this.
It's hard to tell from your code sample but if muayeneRepo and addressRepo are Repositories returning IEnumerable or the like based on database tables this will be a potential issue for performance. Normally you'd have these pulling from DBSets. You should also take advantage of navigation properties in your domain so that a Muayene entity has an ICollection of Addresses.
Using a context-based example:
var muayenes = _context.Muayenes.Include(x => x.Addresses)
.Where(x => x.HastAc == hastAc && false == x.IsDeleted)
.ToList(); // Materializes these entities so further operations are Linq2Object
If you don't have navigation properties and want to join on the DbSets then you can accomplish the same by joining the entities then doing a GroupBy where the group key is the parent entity and the grouped values are the addresses.
The next step I would normally use is either add a constructor or factory class to handle populating a new instance of a view model from my entity, or wiring up something like AutoMapper to do this. Typically I just wire it up myself since it's generally easier to follow than looking elsewhere at mapping rules trying to figure out why something has changed.
for instance: (Constructor)
var viewModels = muayenes.Select(x => new HastaMuayeneKayitViewModel(x)).ToList();
or (Factory)
var viewModels = muayens.Select(x => HastaMuayeneKayitViewModelFactory.Create(x)).ToList();
Addresses can be constructed using the same principles.
The logic for populating the view model and child view models can be moved off to supporting code and re-used rather that ending up in a large Linq expression.
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)
I have been searching for a way to join 2 distantly related domain models into one view model with no luck.
I am working on an existing application and have been asked to add a field to a result of fax log search.
My controller is returning a viewModel, and I am just wanting to add an additional field to it. Which sounds like it should be an easy task.
Background Info:
This is the original viewModel:
public class VOEIFaxLogSearchListViewModel
{
public DateTime DateTimeAdded { get; set; }
public string Processor { get; set; }
public string FaxStatusCode { get; set; }
public string VendorOrderID { get; set; }
public string FromFaxNumber { get; set; }
}
I want to add an additional field to this viewmodel:
public string CustomerName { get; set; }
The way that the application is designed, a stored procedure is called to return a dataset of the search results. (I won't the search method (GetFaxLogSearchResult) or its SQL call as it isn't necessary)
var resultFaxDS = vOEIDAO.GetFaxLogSearchResult(startDate,endDate,userName,faxType);
The resulting DataSet is then converted to a DataTable.
DataTable faxTable = resultFaxDS.Tables[0];
A For loop interates through each of the result records and puts them into a Domain Model named FaxModel. Which is mapped with Automapper to the viewModel named FaxLogSearchListViewModel.
for (int i=0; i<faxTable.Rows.Count;i++)
{
var row = faxTable.Rows[i];
var faxLogModel = vOEIDAO.DRToFaxModel(row);
faxViewModel.Add(Mapper.Map<FaxModel,FaxLogSearchListViewModel>(faxLogModel));
}
}
return faxViewModel;
}
Here is what I have done so far to add this result field:
1) added the new property to the view model.
2) modified stored procedure that pulls back the search results so it returns CustomerName in the dataset
The dilemna:
The method adding each row of the dataset into the Domain model (DRToFaxModel) is doing just that... it is populating a domain model(FaxModel). The field that I want to add isn't in the Domain model.
As a result, I don't want to add a field to the domain model if it doesn't belong to the concrete class.
Here is the domain model and the method used to populate it with each row from the search results:
public class FaxModel
{
public int FaxID { get; set; }
public int FaxStatusID { get; set; }
public string ToFaxNumber { get; set; }
public string FromFaxNumber { get; set; }
public DateTime DateTimeAdded { get; set; }
public string FaxStatusCode { get; set; }
public string Processor { get; set; }
public string VendorOrderID { get; set; }
}
public FaxModel DRToFaxModel(DataRow dr)
{
FaxModel voObj = new FaxModel();
voObj.FaxID = GetVOInt(dr["FaxID"]);
voObj.FaxStatusID = GetVOSmallInt(dr["FaxStatusID"]);
voObj.ToFaxNumber = GetVOStr(dr["ToFaxNumber"]);
voObj.FromFaxNumber = GetVOStr(dr["FromFaxNumber"]);
voObj.DateTimeAdded = GetVODateTime(dr["DateTimeAdded"]);
voObj.FaxStatusCode = GetVOStr(dr["FaxStatusCode"]);
voObj.Processor = GetVOStr(dr["Processor"]);
voObj.VendorOrderID = GetVOStr(dr["VendorOrderID"]);
//Cant add CustomerName to the model without modifying the FaxModel domain model.
//Shouldn't do that because it is a domain model.
//CustomerName is in the CustomerModel domain Model
// voObj.CustomerName = GetVOStr(dr["CustomerName"]);
return voObj;
}
So currently, my ViewModel with the added CustomerName property is returned with a null for CustomerName.
My domain models are distantly related.
In the database the FAX table can be joined joined to the CUSTOMER table but only by joining through an ORDER table. (the FAX table has an orderID field and the ORDER table has a CustomerID field)
So my resulting question is:
how do you use autoMapper to map a Fax domain model to a Customer domain model since the 2 domains don't have any common fields to build the relationship without joining through another table?
Or can you map more than 2 tables into 1 viewModel using automapper? how is this done?
What a great question. First of all, it's so refreshing to see someone asking about the proper way to do something, rather that just seeking a quick fix. Second, the amount of documentation provided is exactly the way SO questions should be written. I wish I could give this more than +1.
That said, since you're essentially asking an architecture question, there aren't any concrete answers, just opinions.
Here's my opinion:
You state the result of the sproc is mapped to a domain model:
var resultFaxDS = vOEIDAO.GetFaxLogSearchResult(startDate,endDate,userName,faxType);
However, you've added a return field, CustomerName to your sproc which is not part of the domain model. I think that's the heart of your issue.
There's a choice to be made here: does this sproc return a domain model or doesn't it?
Right now, my opinion is that it does not represent a domain model anymore, due to the new field, so you should not be trying to map it to a domain model before mapping it to your view model. You need to create a new data type to map this result to, which represents what you are actually getting from the sproc, and map that to your view model.
The alternate option is that this sproc does in fact represent a domain model. If that is the case, you should not be adding a new field to it that is not part of the model. Rather, you'll need to get the FaxModel domain objects and CustomerModel domain objects separately, and assemble your view models from both objects.
This is an example of the Single Responsibility Principle, meaning that an object, function, assembly, heck, even a program, should have one purpose. By giving your sproc a return value that both is and isn't a domain model, you're giving it more than one purpose. It would be best to either decide that it represents a FaxModel, and accept that the customer name needs to come from another source, or decide that it returns something else, say CustomerFaxModel which contains both customer and fax information, and use it as such.
To answer your technical question, AutoMapper does allow you to pass an existing target object to the map function in addition to a source object. You can map the target from object A to get some fields, and then pass the already mapped target to Map() a second time with a source of object B to map other fields.
Always, always, keep asking questions like this and you'll do well.
I'm working with asp.net mvc and Entity Framwork. I'm still getting familiar with this stack. I want to include data that has no foreign key relationship with the model being passed to the view.
Initially, the model was passed to the view like this...
public ActionResult Edit(int id = 0)
{
booking booking = db.bookings.Find(id);
return View(booking);
}
The data I need in the view does not have a FK relationship with booking.
I tried creating a seperate class to put both entities in...
public ActionResult Edit(int id = 0)
{
booking booking = db.bookings.Find(id);
viewModel.bookingtraces = (from l in db.traces where l.bookingid == booking.bookingid select l);
viewModel.bookings = booking;
return View(viewModel);
}
Currently, I'm getting an error with this though. The GET page will load, but when attempting to update, I get
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
I also tried adding a modelBuilder entry to explicitly define the relationship, but that didn't work.
Ultimately, the question is, if there is no FK relationship between two entities, how do I access data in the view that isn't apart of the model being passed?
I would suggest rather than simply using Entity classes as your Model, you look into creating composite models, which contain the properties you require (I typically implement common functionality within a base view model, and inherit from that). This way you have full control of the instantiate of your model objects and what properties they include.
This way the entire model is posted back to the server when you POST back.
One advantage of the composite model, would be the ability to include many entities or POCO objects within a single model, for example:
public class MyModel {
public booking Booking {get;set;}
public SomeOtherEntityObject EObject{get;set;}
public SomePocoObject {get;set;}
}
These would then mean the entire contents of the model are posted back to the server.
You can can use your entity as model and pass additional data to the view by ViewBag
public ActionResult Edit(int id = 0) {
Booking booking = db.bookings.Find(id);
ViewBag.bookingtraces =
from l in db.traces
where l.bookingid == booking.bookingid
select l;
return View(booking);
}
OR
You can define a view model
public class MyViewModel {
public Booking booking { get; set; }
public IEnumerable<BookingTrace> traces { get; set; }
}
and then in your action method you can bind back only the booking property
[HttpPost]
public ActionResult Edit(Booking booking) {
...
}
I was wondering what is the best approach to having a select list on a form which contains values from a database without duplicating any code.
What I thought would make sense would be to load this data in the controller and pass it to a view model, so I can use SelectListFor<> or whatever in order to generate the list. However this means that I have to duplicate all the list loading in both the GET and the POST methods. The other way I can see would be to pass the database context into the view model constructor and have it load the list , but this then presents two more issues:
1) Should the view model know about the database context?
2) I then cannot use model binding by accepting the view model type as a method argument because it does not have a no-argument constructor (if I create a no-argument constructor then it won't have the lists if I want to redisplay the view containing the form).
Is there a better way to do this? This seems like it must be a fairly common scenario and any advice would be appreciated.
We typically implement our lookups through a ReferenceDataRepository that gets used within the controllers in the same way as any other repository interaction. This repository will usually recieve a high number of calls for predominantly static readonly data so we may implement a derived CachedReferenceDataRepository over this using an abstraction of your caching scheme of choice (Session, AppFabric etc).
Why not get you db or repository or business rule - whatever you call it send back an IDictionary???
This example assumes you have a list of users, you will send back an Key with their ID and the Value with lets say first name + last name:
Then use this inside the view....
<%= Html.DropDownListFor(model => model.UserID, new SelectList(Model.AvailableUsers, "Key", "Value",Model.UserID))%>
model.UserID = Key
Model.AvailableUsers = IDictionary<int,string>
I create my lists in some helper code sometimes then I lookup those values using this helper... so there is one centralized class (usually static) that will generate these "Users"...
Pass these users onto the view directly or alternatively a ViewModel as in your case- which is what I recommend
NOTE: You would not hookup your data context with the List/ Model Binding, that makes things too complex. Just take in the UserID as the selected user from the list then in your post handle apporpriately...
ViewModel:
public class UsersViewModel
{
public int UserID { get; set; }
public IDictionary<int,string> AvailableUsers{ get; set; }
}
In your post...
[HttpPost]
[ValidateAntiForgeryToken]
[DemandAuthorization(RoleNames.AdminUser, AlwaysAllowLocalRequests = true)]
public ActionResult AllUsers(int UserID)
{
try
{
//save to db or whatever...
return RedirectToAction("Index", "Users");
}
catch (RulesException ex)
{
ex.CopyTo(ModelState); //custom validation copied to model state
}
var _users= _service.GetUsers();
return View("AllUsers", new UsersViewModel
{
UserID = UserID,
AvailableUsers = _users
});
}
Well, in my opinion, you have to have context or repository object in your viewmodel to keep dry in this scenario. Furthermore, it is also correct that your viewmodel should not know about your database. To handle this issue you can have viewmodel's constructor accept an interface like
public Interface IUserRepository
{
public IEnumerable<User> GetAll();
}
and you can have your view model like
public class CreateVM
{
private IUserRepository _repo;
public CreateVM(IUserRepository repo)
{
_repo = repo;
}
public IEnumerable<SelectListItem> AvailableUsers
{
get{
return _repo.GetAll().Where(x=>x.isAvailable).Select(x=>new SelectListinItem{Text = x.UserName, Value = x.UserID});
}
}
}
Last piece of the puzzle is your DI setup. tell ur IOC container to inject IUserRepository in constructor of your viewmodel whenever it is instantiated. I don't know if DI can work when modelbinder creates an instance of your view model but it it does you are there at least in theory. your viewmodel does not know your repository but only an interface and your list is created at single point so you are dry too.
The biggest problem I see with passing IRepository to ViewModel is that this can easily cause performance issues - select n+1's are so natural in this case, that it is hard to avoid them. You want to have as little rountrips to DB as possible for the request, and having IRepository passed around all those multi-level ViewModels just doesn't help that.
Instead, you can introduce ViewModel factory which is responsible for creating ViewModels of desired type. MyViewModelFactory would depend on IRepository, and would create MyViewModel which is just plain DTO.