I am developing an ASP.Net MVC 3 Web application with Entity Framework 4.1 and I am getting a bit confused with regards using Data Annotations for form validation.
I always return a ViewModel to a View as opposed to passing the actual object as I realise this is poor practice. For example:
public class ViewModelTeam
{
public Team Team { get; set; }
}
My View might then have something like this
#model UI.ViewModels.ViewModelTeam
#Html.HiddenFor(model => model.Team.teamID)
<div class="editor-label">
#Html.LabelFor(model => model.Team.description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Team.description)
#Html.ValidationMessageFor(model => model.Team.description)
</div>
To validate this View, I have created Data Annotations in a partial class like so
[MetadataType(typeof(TeamMetaData))]
public partial class Team
{
public class TeamMetaData
{
[DisplayName("Team Name")]
[Required(ErrorMessage = "Please enter a Team Name")]
public object description { get; set; }
And then in my create Controller I have this
[HttpPost]
public ActionResult Create(Team team)
{
if (ModelState.IsValid)
{
//Add team and redirect
}
//Got this far then errors have happened
//Add Model State Errors
ViewModelTeam viewModel = new ViewModelTeam
{
Team = team
};
return View(viewModel);
}
Now, this works fine, however, the more I read about ViewModels and validation, the more it seems that it is the ViewModel that should be validated, because at the end of the day, it is the ViewModel that is being displayed in the View, not the object.
Therefore, I changed my ViewModel to look like the following
public class ViewModelListItem
{
public int teamID { get; set; }
[DisplayName("Item Name")]
[Required(ErrorMessage = "Please enter a Team Name")]
public string description { get; set; }
And I also changed my create Controller to this
[HttpPost]
public ActionResult Create(Team team)
{
if (ModelState.IsValid)
{
//Add team and redirect
}
//Got this far then errors have happened
//Add Model State Errors
ViewModelTeam viewModel = new ViewModelTeam();
viewModel.description = team.description;
return View(viewModel);
}
Again, this works, but I just get the feeling the 2nd method is a bit messy or not as efficient at the first way of doing this.
I would be interested to hear other people’s thoughts on this. Thank you for your help and I apologise for such a long post.
I always use view models and AutoMapper to help me simplify the mapping between my domain and view models.
view model:
public class TeamViewModel
{
[DisplayName("Team Name")]
[Required(ErrorMessage = "Please enter a Team Name")]
public string Description { get; set; }
}
and then a commonly used pattern:
public class TeamsController: Controller
{
public ActionResult Create()
{
var model = new TeamViewModel();
return View(model);
}
[HttpPost]
public ActionResult Create(TeamViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
Team team = Mapper.Map<TeamViewModel, Team>(model);
Repository.DoSomethingWithTeam(team);
return RedirectToAction("Success");
}
}
Related
Could you tell me please does MVC4 has different dropdownlist then mvc3?
I made the same test project in mvc4 and mvc 3:
in TransactionController
public ActionResult Create()
{
var listme=db.Transacts.ToList();
ViewBag.TransactId = new SelectList(listme, "TransactId", "TransactionName");
return View();
}
in create view
#model HomeAccounting.Domain.Transaction
...some code
#using (Html.BeginForm())
...some code
#Html.DropDownList("TransactId",String.Empty)
...some code
< input type="submit" value="Create" />
Result
Dropdown list provides mistake in mvc4 when you submit form. But MVC3 works perfectly
What is the reason? If mvc4 and mvc3 helpers are different how to find out there difference? F12? but there are almost the same.. do I have to investigate this differences everytime, it's not comfortable?
*Error
There is no ViewData item of type 'IEnumerable< SelectListItem >' that has the key « TransactId »*
*info
public class Transaction
{
public virtual int Id { get; set; }
public int TransactId { get; set; }
public virtual Transact Transact { get; set; }
}
public class Transact
{
[Key]
public virtual int TransactId { get; set; }
public virtual string TransactionName { get; set; }
}
Try using #Html.DropDownListFor(model => model.Field) and then pass in your model instead of a string id.
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 need to populate a dropdown in ASP.NET MVC 3. I was hoping to get the answers to the following questions:
What options do I have. I mean what's the difference between #Html.DropDownList and #Html.DropDownListFor. Also are there any other options?
I have the following classes/code. What would be the razor code/HTML syntax I need (I have included what I was trying) in my .cshtml assuming I need to only show this dropdown using the classes below.
public class SearchCriterion
{
public int Id { get; set; }
public string Text { get; set; }
public string Value { get; set; }
}
public class SearchCriteriaViewModel
{
public IEnumerable<SelectListItem> SearchCriteria { get; set; }
}
public class HomeController : Controller
{
public ActionResult Index()
{
IList<System.Web.WebPages.Html.SelectListItem> searchCriteriaSelectList =
new List<System.Web.WebPages.Html.SelectListItem>();
SearchCriteriaViewModel model = new SearchCriteriaViewModel();
//Some code to populate searchCriteriaSelectList goes here....
model.SearchCriteria = searchCriteriaSelectList;
return View(model);
}
}
//Code for Index.cshtml
#model SearchCriteriaViewModel
#{
ViewBag.Title = "Index";
}
<p>
#Html.DropDownListFor(model => model.SearchCriteria,
new SelectList(SearchCriteria, "Value", "Text"))
</p>
the right side of the lambda => has to be a simple type not the complex type modify your code like
public class SearchCriteriaViewModel
{
public int Id { get; set; }
public IEnumerable<SearchCriterion> SearchCriteria { get; set; }
}
public class SearchCriterion
{
public string Text { get; set; }
public string Value { get; set; }
}
the controller will look like
public ActionResult Index()
{
//fill the select list
IEnumerable<SearchCriteria> searchCriteriaSelectList =
Enumerable.Range(1,5).Select(x=>new SearchCriteria{
Text= x.ToString(),
Value=ToString(),
});
SearchCriteriaViewModel model = new SearchCriteriaViewModel();
//Some code to populate searchCriteriaSelectList goes here....
model.SearchCriteria = searchCriteriaSelectList;
return View(model);
}
in the view
#model SearchCriteriaViewModel
#{
ViewBag.Title = "Index";
}
<p>
#Html.DropDownListFor(model => model.Id,
new SelectList(model.SearchCriteria , "Value", "Text"))
</p>
After extensively using ASP.NET MVC for the past 3 years, I prefer using additionalViewData from the Html.EditorFor() method more.
Pass in your [List Items] as an anonymous object with the same property name as the Model's property into the Html.EditorFor() method.
The benefit is that Html.EditorFor() method automatically uses your Editor Templates.
So you don't need to provide CSS class names for your Drop Down Lists.
See comparison below.
//------------------------------
// additionalViewData <=== RECOMMENDED APPROACH
//------------------------------
#Html.EditorFor(
m => m.MyPropertyName,
new { MyPropertyName = Model.ListItemsForMyPropertyName }
)
//------------------------------
// traditional approach requires to pass your own HTML attributes
//------------------------------
#Html.DropDown(
"MyPropertyName",
Model.ListItemsForMyPropertyName,
new Dictionary<string, object> {
{ "class", "myDropDownCssClass" }
}
);
//------------------------------
// DropDownListFor still requires you to pass in your own HTML attributes
//------------------------------
#Html.DropDownListFor(
m => m.MyPropertyName,
Model.ListItemsForMyPropertyName,
new Dictionary<string, object> {
{ "class", "myDropDownCssClass" }
}
);
If you want more details, please refer to my answer in another thread here.
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.
I have created MVC3 application using Entity Framework Code First method. My model is very simple:
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int YearsAtCompany { get; set; }
}
and context class is
public class EmployeeDB : DbContext
{
public DbSet<Employee> Employees { get; set; }
}
and controller looks like this:
EmployeeDB context = new EmployeeDB();
public ActionResult Index()
{
return View(context.Employees);
}
}
I have created EmployeesDb.mdf and Employee table.
but I get this error:
The model item passed into the dictionary is of type 'System.Data.Entity.DbSet`1[DFEmployees.Models.Employee]', but this dictionary requires a model item of type 'DFEmployees.Models.Employee'.
[Updated]
#model DFEmployees.Models.Employee
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
Please suggest solution.
It's looks like your view are waiting for a single employee, and you are triying to fill the view with a DBSet of employees.
To solve it, you can set the #model of the view to an IEnumerable of employees, or send only one employee to the view, depending of what are you showing in the view.
EDIT: I think this problem is not related with the previous one. Check this link, I hope it helps you: LINK
Your controller action returns a list of employees so adapt your model respectively in the view:
#model IEnumerable<DFEmployees.Models.Employee>
Or if you wanted to use a single employee make sure you pass a single employee to the view:
public ActionResult Index()
{
return View(context.Employees.FirstOrDefault());
}
and then you can have:
#model DFEmployees.Models.Employee