I have been playing around with MVC 3 and looking at populating dropdownlists. I have seen a few examples online that recommend using view models, so here is my first attempt. My code seems to work, but can anybody tell me if this is the correct way to do this?
My model :
public class ContactGP
{
public int TeamID { get; set; }
[Required(ErrorMessage = "Please select a Team Name")]
[DataType(DataType.Text)]
[DisplayName("Team Name")]
public string TeamName { get; set; }
}
My view model :
public class ContactGPViewModel
{
public string SelectedTeamID { get; set; }
public IEnumerable<Team> Teams { get; set; }
}
My controller :
public IEnumerable<Team> PopulateTeamsDropDownList()
{
IEnumerable<Team> lstTeams = _Base.DataRepository.GetTeams();
return lstTeams;
}
public ActionResult ContactGP()
{
var model = new ContactGPViewModel
{
Teams = PopulateTeamsDropDownList()
};
return View(model);
}
And my view :
<p>
#Html.DropDownListFor(
x => x.SelectedTeamID,
new SelectList(Model.Teams, "TeamID", "TeamName")
)
</p>
Your code seems correct. You have defined a view model containing the necessary properties your view will require, filled it up in the controller and passed to this strongly typed view.
I have only a minor remark on the following line inside the PopulateTeamsDropDownList method:
_Base.DataRepository.GetTeams();
I hope you have abstracted this repository with interfaces (or abstract classes) and used DI in order to inject some concrete implementation into your controller. This will weaken the coupling between your controller and the way data is accessed and to simplify unit testing the different layers of your application in isolation.
Related
I am working on creating a datagrid in ASP MVC 3 where I have two tables in one view. Each table is being used from its own Model. Therefore, I would have to call two Models into one View which does not seem as simple as I wish it was.
I am pretty new to MVC and I was looking through Stack and found this link:
Two models in one view in ASP MVC 3
Which seems to be the direction that i would want to go... I think.
Here is the code for my first model:
[Table]
public class Model1
{
[Column(IsPrimaryKey = true, IsDbGenerated = true)]
public int Column1 { get; set; }
[Column]
public string Column2 { get; set; }
[Column]
public string Column3 { get; set; }
}
Here is the code for my second model:
[Table]
public class Model2
{
[Column(IsPrimaryKey = true, IsDbGenerated = true)]
public int Column1 { get; set; }
[Column]
public int Column2 { get; set; }
[Column]
public string Column3 { get; set; }
[Column]
public string Column4 { get; set; }
}
Here is the code for the Parent View Model that the other Stack Forum suggested:
public class ParentModelView
{
public Model1 Model1 { get; set; }
public Model2 Model2 { get; set; }
}
I was able to get each one to work individually so i know that it isn't any other issue. The other Stack Forum seemed to be a little too vague for my understanding. I feel like there would have to be more to it than just adding another parent model that is using each model within it (what i am getting out of it).
Some other information that you may find useful is that each model is in its own file. Also, Here is my error:
The model item passed into the dictionary is of type
'System.Data.Linq.Table 1[Model1]', but this dictionary requires a model
item of type 'System.Collections.Generic.IEnumerable 1[ParentModelView]'.
Description: An unhandled exception occurred during the execution of the
current web request. Please review the stack trace for more information
about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The model item passed
into the dictionary is of type 'System.Data.Linq.Table 1[Model1]', but this
dictionary requires a model item of type
'System.Collections.Generic.IEnumerable 1[ParentModelView]'.
Source Error:
An unhandled exception was generated during the execution of the current
web request. Information regarding the origin and location of the exception
can be identified using the exception stack trace below.
---EDIT 1---
Here is the code inside of my View:
#model IEnumerable<ParentModelView>
#{
WebGrid gridModel1 = new WebGrid(Model);
WebGrid gridModel2 = new WebGrid(Model);
}
<div class="Table">
<h2>Model1</h2>
#gridModel1.GetHtml(columns: new[] {
gridModel1.Column("Column1"),
gridModel1.Column("Column2"),
gridModel1.Column("Column3")
})
</div>
<div class="Table" id="rightTable">
<h2>Model2</h2>
#*#gridModel2.GetHtml(columns: new[] {
gridModel2.Column("Column1"),
gridModel2.Column("Column2"),
gridModel2.Column("Column3"),
gridModel1.Column("Column4")
})*#
</div>
EDIT 2
As most of you have requested, here is my controller code. I know that it is not right because i am not quite sure how to pass through the information between two models into the same view. If someone would be able to help with that, it would be much appreciated.
public class HomeController : Controller
{
public ActionResult Index()
{
Model1Repo repoModel1 = new Model1Repo ();
var Model1RepoSQL = repoModel1.All();
Model2Repo repoModel2 = new Model2Repo();
var Model2RepoSQL = repoModel2.All();
return View(Model1RepoSQL);
}
}
Any other information would be much appreciated. Thanks!
I think what you want is something more like this:
public class ParentModelView
{
public IEnumerable<Model1> Model1 { get; set; }
public IEnumerable<Model2> Model2 { get; set; }
}
And in your view
#model ParentModelView
#{
WebGrid gridModel1 = new WebGrid(Model.Model1);
WebGrid gridModel2 = new WebGrid(Model.Model2);
}
EDIT;
Apparently, you aren't populating the parent model correctly. I assumed you would understand how to do that.
public ActionResult MyAction() {
var model1 = // get rows of model1
var model2 = // get rows of model2
return View("myview", new ParentModelView { Model1 = model1, Model2 = model2 }) ;
}
You can always use ViewModel in this case. For example create a viewmodel
public class ViewModel{
public int Table1Column1 { get; set; }
public string Table1Column2 { get; set; }
public string Table1Column3 { get; set; }
public int Table2Column1 { get; set; }
public int Table2Column2 { get; set; }
public string Table2Column3 { get; set; }
public string Table2Column4 { get; set; }
}
Get the data into ViewModel from both the models in business layer or data access layer.
P.S: What McGarnagle said is true but it's vice versa, you are sending child models into the view while it is expecting ParentModelView. If you can post parts of code for controller and view, it would be helpful.
I am newbie to MVC3 and I wonder if this is even possible and good practice?
I have a model + view + controller which works fine. This view shows a list of people - I want to be able to click on a person's name and be redirected to a new view that will show that persons details. This new view only has a ViewModel, but no controller because I plan to pass in the object in the action.
The Person object contains all the properties my view needs to show:
#Html.ActionLink(item.Person.FirstName, "PersonDetails", item.Person)
Is this possible/good practice??
I believe you have an misunderstanding of how MVC works. Your ActionLink will ALWAYS redirect to a corresponding ActionMethod of a Controller. What you'll want to do is create an action method in your controller that accepts the necessary parameters and then returns to the View your ViewModel.
Here is a very quick example to get you started:
public class HomeController : Controller
{
public ActionResult List()
{
return View();
}
public ActionResult DetailById(int i)
{
// load person from data source by id = i
// build PersonDetailViewModel from data returned
return View("PersonDetails", PersonDetailViewModel);
}
public ActionResult DetailByVals(string FirstName, Person person)
{
// build PersonDetailViewModel manually from data passed in
// you may have to work through some binding issues here with Person
return View("PersonDetails", PersonDetailViewModel);
}
}
Not a good way to do it like you want to (in your original post). A view should always have a view model. A view model represents only the data that you want to have on the view, nothing more and nothing less. Do not pass your domail model to the view, but rather use a view model. This view model might contain just a portain of the properties of your domain model.
In your list view you probably have a grid, and next to each row you probably have a details link, or a link on the name (as you have it). When either of these links are clicked then you are directed to a details view. This details view will have its own view model with only the properties that you need to display on the details view.
A domail model might look something like:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string ExampleProperty1 { get; set; }
public string ExampleProperty2 { get; set; }
public string ExampleProperty3 { get; set; }
}
Let say you only want to display the person's id, first name, last name and age then your view model will look like this:
public class PersonDetailsViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
You don't need ExampleProperty1, ExampleProperty2 and ExampleProperty3 because they are not required.
Your person controller might look like this:
public class PersonController : Controller
{
private readonly IPersonRepository personRepository;
public PersonController(IPersonRepository personRepository)
{
// Check that personRepository is not null
this.personRepository = personRepository;
}
public ActionResult Details(int id)
{
// Check that id is not 0 or less than 0
Person person = personRepository.GetById(id);
// Now that you have your person, do a mapping from domain model to view model
// I use AutoMapper for all my mappings
PersonDetailsViewModel viewModel = Mapper.Map<PersonDetailsViewModel>(person);
return View(viewModel);
}
}
I hope this clears things up a little more.
I'm developing a small app in order to better understand how MVC3 anda Razor works. I'm using MVC3, all code was generated automatically (dbContext via T4, Controller via Add Controller, Databese from EDMX model...).
In my model I have this simple model:
http://i.stack.imgur.com/nyqu4.png
public partial class Application
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ApplicationType ApplicationType { get; set; }
}
public partial class ApplicationType
{
public int Id { get; set; }
public string Type { get; set; }
}
As you can see, ApplicationType is basically an enum (shame that EF 4 has no support for enums). So, in my ApplicationController I have this:
public ActionResult Create()
{
ViewBag.AppTypes = new SelectList(db.ApplicationTypes.OrderBy(c => c.Type), "Id", "Type");
return View();
}
[HttpPost]
public ActionResult Create(Application application)
{
if (ModelState.IsValid)
{
db.Applications.Add(application);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(application);
}
And in my view:
#Html.DropDownListFor(model => model.ApplicationType.Id, (SelectList)ViewBag.AppTypes, "Choose...")
Now I'm facing two problems:
1) ApplicationType not being populated:
As #Html.DropDownListFor renders only a simple select, it fills the ID, but does not fill Type property as you can see below (sorry, I can't post images as I'm new here):
http://i.stack.imgur.com/96IR1.png
In the picture you can see that the ID is ok, but Type is empty.
What I'm doing wrong?
2) Duplicated Data
The second problem is that if I fill the Type property manually during debug (simulating a correct workflow scenario), ApplicationType is being duplicated in the database, instead of only referring to an old registry.
So, how can I make #Html.DropDownListFor refer to a previous existing item instead of creating a new one?
Thanks for your help!
I believe the mistake you're making is using your domain models in the view and assuming that on post the entire model should be completely binded and ready to store in the database. While it is possible to use domain models in the view, it's better practice to create separate View Models.
For example :
public class ApplicationViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public SelectList ApplicationTypeList { get; set; }
public string ApplicationTypeId { get; set; }
}
In your view:
#Html.DropDownListFor(model => model.ApplicationTypeId, Model.ApplicationTypeList , "Choose...")
In your controller
[HttpPost]
public ActionResult Create(ApplicationViewModel model)
{
if (ModelState.IsValid)
{
Application application = new Application()
{
Id = model.Id,
Name = model.Name,
ApplicationType = db.ApplicationTypes
.First(a => a.Id == model.ApplicationTypeId);
};
db.Applications.Add(application);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
You can then make verifying that your View Model's ApplicationTypeId corresponds to a real application type part of your modelstate's verification. You can use AutoMapper to speed up the process of converting view models to domain models.
Have you tried:
#Html.DropDownListFor(model => model.ApplicationType.Id, m => m.ApplicationType.Type, "Choose...")
Note the second parameter change.
I am developing an ASP.Net MVC 3 Web application and I recently posted a question about how to display a checkboxlist
ASP.Net MVC 3 Retrieve Checkbox List Values
and thanks to the help from RubbleFord and Darin Dimitrov I was able to get this working.
This works nicely for one checkboxlist, however, I now need to be able to display several checkboxlists on the same View, ie, see image attached.
The ViewModels I use to currently display one list are as follows;
public class ViewModelShiftSubSpecialties
{
public ListItem specialtyName { get; set; }
public IEnumerable<ViewModelCheckBox> SubSpecialityList { get; set; }
}
public class ViewModelCheckBox
{
public string Id { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
}
And within my Controller I populate ViewModelShiftSubSpecialties like so:
var subSpecialties = GetSubSpecialtiesForSpecialty(5);
ViewModelShiftSubSpecialties viewModel = new ViewModelShiftSubSpecialties();
var checkBoxList = new List<ViewModelCheckBox>();
viewModel.specialtyName = _listService.GetListItemByID(5); //Medicine Specialty
foreach (var item in subSpecialties)
{
ViewModelCheckBox chkBox = new ViewModelCheckBox { Id = item.subID.ToString(), Name = item.ListSub.description, Checked = false };
checkBoxList.Add(chkBox);
}
viewModel.SubSpecialityList = checkBoxList;
In my View, I display the list name and also use an Editor template to display the checkboxlist
<h3>#Model.specialtyName.description</h3>
#Html.EditorFor(m => m.SubSpecialityList)
However, I am totally stumped as how to get the above code to work with multiple checkboxlists on one View. Is this even possible?
I would really appreciate if someone could please help me with this.
Thanks.
It looks like you've done all the work already. You already have an Editor Template that works correctly with a ViewModelCheckBox IEnumerable. Editor templates wouldn't be useful if you couldn't reuse them for the same datatype. You just need to use it three times. Just extend your ViewModel
public class ViewModelShiftSubSpecialties
{
public ListItem specialtyName { get; set; } //Might need 3 of these
public IEnumerable<ViewModelCheckBox> SubSpecialityList1 { get; set; }
public IEnumerable<ViewModelCheckBox> SubSpecialityList2 { get; set; }
public IEnumerable<ViewModelCheckBox> SubSpecialityList3 { get; set; }
}
Create all three in your controller (and give them better names then I did).
And then in your View
#Html.EditorFor(m => m.SubSpecialityList1)
#Html.EditorFor(m => m.SubSpecialityList2)
#Html.EditorFor(m => m.SubSpecialityList3)
Alternatively you could create a class that contains a single specialty name and IEnumerable ViewModelCheckBox, and have your ViewModel have an IEnumerable of this new class. Then create a new Editor Template for this new class. I think this is worth it if your list size is variable/ might change. Otherwise I'd use the earlier solution for a simple fix.
public class ViewModelShiftSubSpecialties
{
public class IEnumerable<SubSpecialty> { get; set; }
}
public class SubSpecialty
{
public ListItem specialtyName { get; set; }
public IEnumerable<ViewModelCheckBox> SubSpecialityList
}
I have a Solution under the solution there is few Projects one of the called DomainModel,
in which i write my models and other stuff mainly infrastructure.
Now i have another project called WebUI in which i do my UI (Views, Controllers , etc...)
I want to use Remote attribute in DomainModel project which must implemented in WebUI certain view.
When i use it in DomainModel it's gives me an error, that it does not recognize the Controller and it's correct it does not recognize it because the if I add the reference of WebUI the Vs begin to swear at me because it will be a circular reference.
How to implement this?
this is my code
Controller that serves the RemoteValidation
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
public class RemoteValidationController : Controller
{
public JsonResult CheckPassword(string SmsCode)
{
return Json(12345, JsonRequestBehavior.AllowGet);
}
}
//The real entity in DomainModel project
public class SmsCustomer
{
public int CustomerId { get; set; }
public string Cli { get; set; }
//this is what i have to validate on server
public virtual string SmsCode { get; set; }
public DateTime InsertDate { get; set; }
public int CustomerDaysChoiceId { get; set; }
public int CustomerAmountChoiceId { get; set; }
[Required(ErrorMessage = "error")]
[StringLength(128, ErrorMessage = "error")]
public string SelectedWords { get; set; }
public SmsCustomerDaysChoice CustomerDaysChoice { get; set; }
public SmsCustomerAmountChoice CustomerAmountChoice { get; set; }
}
this is my entity after i extend it with the remote attr in WebUI.Models
public class Customer : SmsCustomer
{
[Required(ErrorMessage = "Error required")]
[StringLength(9, ErrorMessage = "Error length")]
[Remote("CheckPassword", "RemoteValidation", ErrorMessage = "Error remote")]
public override string SmsCode { get; set; }
}
this is my view
#Html.TextBoxFor(c => c.SmsCode)
//error span
<span class="checkbox-form-error" data-valmsg-for="SmsCode" data-valmsg-replace="true"> </span>
The remote validation stuff is very specific to the WebUI project.
Because of this, I'd create a View model that inherits from the actual class, and then override the property that needs remote validation. Then you should be able to specify the controller/action for remote validation.
You can also put your validation in a class of its own, like ScottGu demonstrates here:
http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx
(Look down the post, before the last step)
Also take a look at this: Adding DataAnnontations to Generated Partial Classes