I have the following viewmodel in my mvc project.
public class AddGISViewModel
{
public myproject.Models.DomainModels.GIS gis { get; set; }
public IEnumerable<myproject.Models.DomainModels.Companies> Companies { get; set; }
public long CompanyID { get; set; }
}
I have created a view as this
#model myproject.ViewModels.GIS.AddGISViewModel
#using (Ajax.BeginForm("Create", "GIS", new AjaxOptions { HttpMethod = "Post", Url = "/GIS/Create" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.Label("company", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.CompanyID, new SelectList(Model.Companies, "CompanyID", "Name"), "select company please ", htmlAttributes: new {#class = "form-control" })
#Html.ValidationMessageFor(model => model.CompanyID)
</div>
</div>
also I have created following metadata
[Required(ErrorMessage = "you should select company")]
[DisplayName("company")]
[Display(Name = "company")]
public long CompanyID { get; set; }
When I run my project #validationmessagefor show 'CompanyID is a required field' not 'you should select company' that I define in metadata. How can I fix this problem?
You have created a view model, so apply those attributes to the property in the view model. Since the purpose of a view model is to represent what you want to display/edit in the view there is no point creating a separate class for metadata.
Your view model should be
public class AddGISViewModel
{
[Required(ErrorMessage = "you should select company")]
[DisplayName("company")]
public long? CompanyID { get; set; }
public IEnumerable<SelectListItem> Companies { get; set; }
....
}
Note that the CompanyID property should be nullable to protect against under-posting attacks, and the collection property for displaying the companies n the dropdownlist should be IEnumerable, so that the view is
#Html.DropDownListFor(model => model.CompanyID, Model.Companies, "select company please ", new { #class = "form-control" })
In addition, view models should not contain data models if your editing any properties of that model. If so replace GIS gis with each property of GIS that you need in the view.
Related
I have an ever-changing list of Industries that I'd like a user to select from when creating a new Survey.
I could accomplish this with either a ViewModel or ViewBag (I think). I'm attempting to do it with ViewBag. Getting the DropDownListFor error:
CS1928 HtmlHelper<Survey> does not contain a definition for DropDownListFor and the best extension method overload SelectExtensions.DropDownListFor<TModel, TProperty>(HtmlHelper<TModel>, Expression<Func<TModel, TProperty>>, IEnumerable<SelectListItem>, object) has some invalid arguments 2_Views_Surveys_Create.cshtml
Survey model, with foreign key to Industry:
public class Survey
{
[Key]
public int id { get; set; }
[Display(Name = "Survey Name")]
public string name { get; set; }
[Display(Name = "Industry")]
public int industryId { get; set; }
[ForeignKey("industryId")]
public virtual Industry industry { get; set; }
}
Controller to load Industries SelectList into ViewBag:
// GET: Surveys/Create
public ActionResult Create()
{
ViewBag.Industries = new SelectList(db.industry, "Id", "Name");
return View();
}
Create view:
<div class="form-group">
#Html.LabelFor(model => model.industryId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.industryId, ViewBag.Industries, "-Select Industry-")
#Html.ValidationMessageFor(model => model.industryId, "", new { #class = "text-danger" })
</div>
</div>
Properties of the ViewBag have no type that the compiler can use to decide which overload of the method to call. Help the compiler by using an explicit cast.
#Html.DropDownListFor(model => model.industryId, (IEnumerable<SelectListItem>)ViewBag.Industries, "-Select Industry-")
I am currently new to Asp.net MVC .In one of the view I add a dropdownlist and I bind this dropdownlist with my database like this
Controller CollegeController
[HttpGet]
public ActionResult Create()
{
IEnumerable<SelectListItem> items = db.College_Names.Select(c => new SelectListItem { Value = c.id.ToString(), Text = c.Name });
IEnumerable<SelectListItem> item = db.Stream_Names.Select(c => new SelectListItem { Value = c.id.ToString(), Text = c.Stream });
ViewBag.CollName=items;
ViewBag.StreamName = item;
return View();
}
[HttpPost]
public ActionResult Create(College college)
{
try
{
if(ModelState.IsValid)
{
db.Colleges.Add(college);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CollName = db.Colleges;
return View(college);
}
catch
{
return View();
}
}
This is my model
public class College
{
[Required]
public int Id { get; set; }
[Required]
[Display(Name="College Name")]
public int CollegeName { get; set; }
[Required]
public int Stream { get; set; }
[Required]
[Column(TypeName="varchar")]
public string Name { get; set; }
....
public virtual College_Name College_Name { get; set; }
public virtual Stream_Name Stream_Name { get; set; }
}
This is My View
<div class="form-group">
#Html.LabelFor(model => model.CollegeName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("CollName", (IEnumerable<SelectListItem>)ViewBag.CollName, "Select College", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.CollegeName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Stream, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("StreamName", (IEnumerable<SelectListItem>)ViewBag.StreamName, "Select Stream", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Stream, "", new { #class = "text-danger" })
</div>
</div>
Now when I check my database after I save the CollegeName and Stream in the database is zero from the dropdownlist.
You have multiple problems with your code. Firstly you dropdownlists are binding to a properties named CollName and StreamName which do not even exist in your model.
Next you cannot name the property your binding to the same as the ViewBag property.
Your view code would need to be (and always use the strongly typed xxxFor() HtmHelper methods
#Html.DropDownListFor(m => m.CollegeName, (IEnumerable<SelectListItem>)ViewBag.CollName, "Select College", new { #class = "form-control" })
....
#Html.DropDownListFor(m => m.Stream, (IEnumerable<SelectListItem>)ViewBag.StreamName, "Select Stream", new { #class = "form-control" }
and in your POST method, the values of college.CollegeName and college.Stream will contain the ID's of the selected options.
You also need to repopulate the ViewBag properties when you return the view in the POST method (as you did in the GET method) or an exception will be thrown (and note that your current use of ViewBag.CollName = db.Colleges; will also throw an exception)
I also strongly suggest you start learning to use view models (views for editing should not use data models - refer What is ViewModel in MVC?) - and use naming conventions that reflect what your properties are, for example CollegeNameList, or CollegeNames, not CollName
So I have a simple database table in the form of ID, EmployeeID, date etc. Which creates a normal model:
public partial class WorkItem
{
public int ID { get; set; }
public int EmployeeID { get; set; }
public int LocationID { get; set; }
[DataType( DataType.Date )]
[Display( Name = "Start date" )]
public System.DateTime StartDate { get; set; }
My problem occurs when I need to augment the functionality of this model and so I create a view model to group work items on a weekly basis.
public class WeeklyWorkItemsViewModel
{
public WorkItem WorkItemMonday { get; set; }
public WorkItem WorkItemTuesday { get; set; }
All works perfectly well for the DateTime field in my view (which is bound to the view model):
<div class="form-group">
#Html.LabelFor( model => model.WorkItemMonday.StartDate, "Week start date", htmlAttributes: new { #class = "control-label col-md-2" } )
<div class="col-md-10">
#Html.EditorFor( model => model.WorkItemMonday.StartDate, new { htmlAttributes = new { #class = "form-control" } } )
#Html.ValidationMessageFor( model => model.WorkItemMonday.StartDate, "", new { #class = "text-danger" } )
</div>
</div>
The problem occurs trying to bind the dropdownlilst, it gets populated correctly but the changes are not seen in the controller.
<div class="form-group">
#Html.LabelFor( model => model.WorkItemMonday.EmployeeID, "EmployeeID", htmlAttributes: new { #class = "control-label col-md-2" } )
<div class="col-md-10">
#Html.Hidden( "selectedEmployee" )
#Html.DropDownList( "EmployeeID", null, htmlAttributes: new { #class = "form-control" } )
#Html.ValidationMessageFor( model => model.WorkItemMonday.EmployeeID, "", new { #class = "text-danger" } )
</div>
</div>
The StartDate is updated in the controller.
After mucho head scratching, I finally had to get around this using:
#Html.Hidden( "selectedEmployee" )
And updating this in JQuery. I did try using #html.DropDownListFor but no joy so far.
Can anyone see what's wrong before I pull ALL my hair out.
You model does not contain a property named EmployeeID. But it does have ones named WorkItemMonday.EmployeeID and WorkItemTuesday.EmployeeID.
Stop using DropDownList() and use the strongly typed DropDownListFor() method so that you correctly bind to your model properties.
Modify you view model to include a property for the SelectList
public IEnumerable<SelectListItem> EmployeeList { get; set; }
and populate it in the GET method before you pass the model to the view. Then in the view use
#Html.DropDownListFor(m => m.WorkItemMonday.EmployeeID, Model.EmployeeList, new { #class = "form-control" })
....
#Html.DropDownListFor(m => m.WorkItemTuesday.EmployeeID, Model.EmployeeList, new { #class = "form-control" })
which will correct generate the name="WorkItemMonday.EmployeeID" and name="WorkItemTuesday.EmployeeID" attributes so that they will bind to your model when you post.
Right now I have added a Region to the ApplicationUser model in Identity 2.0
On the UsersAdmin view, Edit action, I have the following stock code to display/edit the Region of the User:
<div class="form-group">
#Html.LabelFor(model => model.Region, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Region, new { #class = "form-control" })
</div>
</div>
How do I make that TextBox into a DropDownList that allows the user to choose from a list of Region names where Regions is part of ApplicationDbContext?
public class Region
{
[Key]
public Guid ID { get; set; }
public string Name { get; set; }
public Company Company { get; set; }
public Region()
{
this.ID = Guid.NewGuid();
}
}
You could use a view model. In order to render a dropdown you need 2 properties in your view model: a scalar property to hold the selected value and a collection property to represent the list of possible values to be displayed:
public class MyViewModel
{
public Guid SelectedRegionID { get; set; }
public IEnumerable<SelectListItem> Regions { get; set; }
}
That your controller action will populate and pass to the view:
public ActionResult Index()
{
var viewModel = new MyViewModel();
viewModel.Regions = db.Regions.ToList().Select(x => new SelectListItem
{
Value = x.ID.ToString(),
Text = x.Name,
});
return View(viewModel);
}
and in the corresponding strongly typed view you could use the DropDownListFor helper:
#model MyViewModel
<div class="form-group">
#Html.LabelFor(model => model.Region, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.SelectedRegionID, Model.Regions, new { #class = "form-control" })
</div>
</div>
I am new to Asp.net MVC and could really use some clarification on how View models work.
From my understanding, View models are used to only expose necessary fields from the domain model to the Views. What I am finding hard to understand is that domain models are linked to the Db via Dbset. So it makes sense to me that when data is posted to a controller using a domain model, that this data can find its way into the Db.
From the examples of View models I have seen, they are not referenced by a Dbset. So how does data posted to a View model find its way into the database. Does EF just match the fields from the View model to fields which match from the domain model?
thanks for your help
As Jonathan stated, AutoMapper will help you map your ViewModel entities to your Domain model. Here is an example:
In your view you work with the View Model (CreateGroupVM):
#model X.X.Areas.Group.Models.CreateGroupVM
#using (Html.BeginForm(null,null, FormMethod.Post, new { #class="form-horizontal", role="form"}))
{
#Html.ValidationSummary()
#Html.AntiForgeryToken()
#Html.LabelFor(model => model.Title, new { #class = "col-lg-4 control-label" })
#Html.TextBoxFor(model => model.Title, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Title)
#Html.LabelFor(model => model.Description, new { #class = "col-lg-4 control-label" })
#Html.TextBoxFor(model => model.Description, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Description)
#Html.LabelFor(model => model.CategoryId, new { #class = "col-lg-4 control-label" })
#Html.DropDownListFor(x => x.CategoryId, Model.Categories)
#Html.ValidationMessageFor(model => model.CategoryId)
<div class="form-group">
<div class="col-lg-offset-4 col-lg-8">
<button type="submit" class="btn-u btn-u-blue">Create</button>
</div>
</div>
}
ViewModel (CreateGroupVM.cs):
Notice how we pass in a list of Categories - you could not do this had you strictly used your domain model because you cant pass a list of categories in the Group model. This gives us strongly typed helpers in our views, and no ViewBag usage.
public class CreateGroupVM
{
[Required]
public string Title { get; set; }
public string Description { get; set; }
[DisplayName("Category")]
public int CategoryId { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
}
Domain Model (Group.cs):
public class Group
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int CategoryId { get; set; }
public int CreatorUserId { get; set; }
public bool Deleted { get; set; }
}
In your HttpPost Create Action - you let AutoMapper do the mapping then save to the DB. Note that by default AutoMapper will map fields that are the same name. You can read https://github.com/AutoMapper/AutoMapper/wiki/Getting-started to get started with AutoMapper.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateGroupVM vm)
{
if (ModelState.IsValid)
{
var group = new InterestGroup();
Mapper.Map(vm, group); // Let AutoMapper do the work
db.Groups.Add(group);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(vm);
}
The view models are in no way tied to your database. You would need to create a new domain model and populate it with the data from the view model in order to save it to the database. Of course, having to do that is very annoying and someone created AutoMapper to handle that.
With automapper you could just match the properties from your view models to properties in the domain model and then add them to the database as needed.