I have three models: VehicleType, VehicleModel, and VehicleManufacturer.
Both VehicleType and VehicleManufacturer point to VehicleModel in the model, like so:
public class VehicleModel
{
[Key]
public int ModelId { get; set; }
[Required(ErrorMessage = "Field is Required")]
public int TypeId { get; set; }
[Required(ErrorMessage = "Field is Required")]
public int ManufacturerId { get; set; }
public string ModelName { get; set; }
public VehicleType VehicleType { get; set; }
public VehicleManufacturer Manufacturer { get; set; }
}
From there, VehicleModel points to the InventoryModel:
public class Inventory
{
[Key]
public int InventoryId { get; set; }
public int Price { get; set; }
public int Mileage { get; set; }
public int Year { get; set; }
public int ModelId { get; set; }
public VehicleModel VehicleModel { get; set; }
}
My problem is when I try to get client-side validation working on all three dropdownlists (VehicleType, VehicleManufacturer, VehicleModel), it only works with VehicleModel.
What needs to be done to validate these two dropdownlists using these models?
Here is my controller (fyi):
// GET: /Inventory/Create
public ActionResult Create()
{
ViewBag.TypeId = new SelectList(db.Types, "TypeId", "TypeName"); //(Object List, Value Field (usually Id), Column)
ViewBag.ModelId = new SelectList(db.Models, "ModelId", "ModelName"); //(Object List, Value Field (usually Id), Column)
ViewBag.ManufacturerId = new SelectList(db.Manufacturers, "ManufacturerId", "ManufacturerName"); //(Object List, Value Field (usually Id), Column)
return View();
}
// POST: /Inventory/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Inventory inventory, VehicleManufacturer VehicleManufacturer, VehicleType VehicleType)
{
if (ModelState.IsValid)
{
db.Inventorys.Add(inventory);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.TypeId = new SelectList(db.Types, "TypeId", "TypeName");
ViewBag.ModelId = new SelectList(db.Models, "ModelId", "ModelName");
ViewBag.ManufacturerId = new SelectList(db.Manufacturers, "ManufacturerId", "ManufacturerName");
return View(inventory);
}
View:
<div class="editor-label">
#Html.LabelFor(model => model.VehicleModel.TypeId, "Some name for column")
</div>
<div class="editor-field">
#Html.DropDownList("TypeId", String.Empty)
#Html.ValidationMessageFor(model => model.VehicleModel.TypeId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ModelId, "Some name for column")
</div>
<div class="editor-field">
#Html.DropDownList("ModelId", String.Empty)
#Html.ValidationMessageFor(model => model.ModelId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.VehicleModel.ManufacturerId, "Some name for column")
</div>
<div class="editor-field">
#Html.DropDownList("ManufacturerId", String.Empty)
#Html.ValidationMessageFor(model => model.VehicleModel.ManufacturerId)
</div>
Please someone help. I've been on this for many, many hours!
There are actually two problems That I see above
1) That you're not mapping the DropDownList and the ValidationMessageFor to the same model attribute.
#Html.ValidationMessageFor(model => model.VehicleModel.ManufacturerId)
The above is binding it to VehicleModel_ManufacturerId where as:
#Html.DropDownList("ManufacturerId", String.Empty)
the above is mapping the DropDown to just ManufacturerId
You need to change one or the other to match each other.
2) In the above code, I don't see any Validation Attributes. did you forgot them when you copied the code over here?
Hope this helps, Let me know if you needed more details.
Related
My entities:
public class Meal
{
[HiddenInput(DisplayValue = false)]
public int Id { get; set; }
[Required(ErrorMessage = "Proszę podać nazwę posiłku")]
public string Name { get; set; }
[Required(ErrorMessage = "Proszę podać ilość białka")]
[Range(0.00, double.MaxValue, ErrorMessage = "Proszę podać dodatnią ilość.")]
public double Protein { get; set; }
[Required(ErrorMessage = "Proszę podać ilość węglowodanów")]
[Range(0.00, double.MaxValue, ErrorMessage = "Proszę podać dodatnią ilość.")]
public double Carbohydrates { get; set; }
[Required(ErrorMessage = "Proszę podać ilość tłuszczy")]
[Range(0.00, double.MaxValue, ErrorMessage = "Proszę podać dodatnią ilość.")]
public double Fat { get; set; }
[Required(ErrorMessage = "Proszę podać ilość kalorii")]
[Range(0.00, double.MaxValue, ErrorMessage = "Proszę podać dodatnią ilość.")]
public double Calories { get; set; }
}
public class EatenMeal
{
public int Id { get; set; }
public virtual Meal Meal { get; set; }
public virtual MealType MealType { get; set; }
public double Serving { get; set; }
public string Username { get; set; }
public DateTime Date { get; set; }
}
public class MealType
{
public int Id { get; set; }
public string Name { get; set; }
}
In MealController's view MealList which displays meals from datebase. And there is a button "Add" which refers to action AddEatenMeal in EatenMealController.
public ActionResult AddEatenMeal(int id)
{
var meal = mealRepository.GetMeal(id);
EatenMeal eatenMeal = new EatenMeal() { Meal = meal, Username = User.Identity.Name };
return View(eatenMeal);
}
[HttpPost]
public ActionResult AddEatenMeal(EatenMeal eatenMeal)
{
if(ModelState.IsValid)
{
eatenMealRepository.AddEatenMeal(eatenMeal);
RedirectToAction("Index", "Home");
}
return RedirectToAction("Index", "Home");
}
I am creating there object EatenMeal and partially initializing this object. Then I am passing this object to View to further initializing.
#model Domain.Entities.EatenMeal
#{
ViewBag.Title = "Dodawanie posiłku do dziennika";
}
#using (Html.BeginForm("AddEatenMeal","EatenMeal", FormMethod.Post, new {#class = "form"}))
{
#Html.HiddenFor(x => x.Meal.Name)
#Html.HiddenFor(x => x.Username)
#Html.HiddenFor(x => x.Meal.Calories)
#Html.HiddenFor(x => x.Meal.Carbohydrates)
#Html.HiddenFor(x => x.Meal.Fat)
#Html.HiddenFor(x => x..Meal.Protein)
#Html.HiddenFor(x => x.Meal.Id)
#Html.HiddenFor(x=>x.Username)
<div class="form-group">
#Html.Label("Nazwa posiłku")
#Html.Label(Model.Meal.Name, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.Label("Porcja (g)")
#Html.TextBoxFor(x => x.Serving, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.Label("Typ posiłku")
#Html.DropDownListFor(x=>x.MealType)????
</div>
<div class="form-group">
#Html.Label("Data spożycia")
#Html.TextBoxFor(x => x.Date, new { #class = "form-control", #id="date-eaten", #Value=DateTime.Today.ToShortDateString()})
</div>
<input type="submit" class="btn btn-info" value="Dodaj" />
}
Now I have a question. Is it correct to hiding fields? I don't know how I can save data from first controller to second in other way.
And is a second question. How I can make DropDownListFor for property MealTye in EatenMeal?
Rather than sending and receiving a whole lot of unused data across the wire and opening yourself to over posting attack, create a view model that represents what you want to display and edit. See What is a view model in MVC?
View model
public class EatenMealVM
{
public int MealID { get; set; }
[Display(Name="Nazwa posiłku")]
public string MealName { get; set; }
[Display(Name = "Typ posiłku")]
[Required(ErrorMessage = "Please select a meal")]
public int? MealTypeID { get; set; }
[Display(Name = "Porcja (g)")]
public double Serving { get; set; } // is this really double?
[Display(Name = "Data spożycia")]
[DataType(DataType.Date)]
public DateTime Date { get; set; }
public SelectList MealTypeList { get; set; }
}
Controller
public ActionResult AddEatenMeal(int id)
{
var meal = mealRepository.GetMeal(id);
var mealTypes = // get the list of meal types from the database
EatenMealVM model = new EatenMealVM()
{
MealID = meal.Id,
MealName = meal.Name,
MealTypeList = new SelectList(mealTypes, "ID", "Name")
};
return View(model);
}
View
#model EatenMealVM
....
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.MealID)
#Html.DisplayNameFor(m => m.MealName)
#Html.DisplayFor(m => m.MealName)
#Html.LabelFor(m => m.MealTypeID)
#Html.DropDownListFor(m => m.MealTypeID, Model.MealTypeList, "--Please select--")
#Html.ValidationMessageFor(m => m.MealTypeID)
#Html.LabelFor(m => m.Serving)
#Html.TextBoxFor(m => m.Serving, new { #class = "form-control")
#Html.ValidationMessageFor(m => m.Serving)
#Html.LabelFor(m => m.Date)
#Html.TextBoxFor(m => m.Date)
#Html.ValidationMessageFor(m => m.Date, new { #class = "form-control" })
<input type="submit" class="btn btn-info" value="Dodaj" />
}
Post method
[HttpPost]
public ActionResult AddEatenMeal(EatenMealVM model)
{
if (!ModelState.IsValid)
{
var mealTypes = // get the list of meal types from the database
model.MealTypeList = new SelectList(mealTypes, "ID", "Name");
return View(model);
}
// Initialize new EatenMeal class
// Map properties from view model (including setting user name)
// Save and redirect
}
Note also the use of [Display] attribute and #Html.LabelFor(). Currently you not creating 'real' labels (they are not associated with the corresponding control)
I have edit page and controller that creates new model object and fills some data from db into this object then send a model object to view. When I click the submit button, some fields in this object have been cleared.
For example:
Before:
user_id
name
birth_date
username
password
id_role
email
After (Fields that are not null or empty):
name
username
birth_date
The model:
public partial class Users
{
public Users()
{
this.Albums = new HashSet<Albums>();
this.History = new HashSet<History>();
this.Country = new HashSet<Country>();
this.Artists = new HashSet<Artists>();
this.SelectedCountries = new List<string>();
}
[DisplayName("User ID")]
public System.Guid user_id { get; set; }
[DisplayName("Name")]
public string name { get; set; }
[DisplayName("Birth date")]
public Nullable<System.DateTime> birth_date { get; set; }
[DisplayName("Username")]
public string username { get; set; }
[DisplayName("Password")]
public string password { get; set; }
[DisplayName("Rights")]
public System.Guid id_role { get; set; }
[DisplayName("User Email")]
public string email { get; set; }
public bool isRemember { get; set; }
public virtual ICollection<Albums> Albums { get; set; }
public virtual ICollection<History> History { get; set; }
public virtual Role Role { get; set; }
public virtual ICollection<Country> Country { get; set; }
public virtual ICollection<Artists> Artists { get; set; }
public virtual List<string> SelectedCountries { get; set; }
}
Edit method:
public ActionResult Edit()
{
if (HttpContext.User.Identity.IsAuthenticated)
{
var userName = HttpContext.User.Identity.Name;
var user = db.Users.Where(x => x.username == userName).FirstOrDefault();
ViewBag.Countries = new MultiSelectList(db.Country, "id_country", "name", user.SelectedCountries);
return View(user);
}
return HttpNotFound();
}
Edit method for handling post request:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Users users)
{
if (ModelState.IsValid)
{
foreach (var country in users.SelectedCountries)
{
var dbCountry = db.Country.Find(new Guid(country));
if (dbCountry != null)
users.Country.Add(dbCountry);
}
db.Entry(users).State = System.Data.Entity.EntityState.Modified;
//There handle of string array goes
db.SaveChanges();
return RedirectToAction("Index");
}
return View(users);
}
View:
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Users</legend>
<div class="editor-label">
#Html.LabelFor(model => model.name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.name)
#Html.ValidationMessageFor(model => model.name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.birth_date)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.birth_date)
#Html.ValidationMessageFor(model => model.birth_date)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.username)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.username)
#Html.ValidationMessageFor(model => model.username)
</div>
<div class="editor-label">
#Html.Label("Country")
</div>
<div class="editor-field">
#Html.DropDownList("SelectedCountries", (ViewBag.Countries as MultiSelectList), new { multiple = "multiple", #class = "chosen", style = "width: 350px;"})
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
Thanks in advance :)
You will only receive values that are in your form. Http is stateless..
What you need to do.. is create a ViewModel. That ViewModel is the subset of properties from your domain model that are displayed in the view. Like this:
public class UserViewModel {
public string Name { get; set; }
public string Username { get; set; }
public DateTime? DateofBirth { get; set; }
}
Use this model in your view. Then, in your controller.. get the user and update the appropriate fields:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(UserViewModel viewModel) {
var user = db.Users.Where(x => x.username == viewModel.Username).FirstOrDefault();
user.Name = viewModel.Name;
user.Username = viewModel.Username;
// .. etc.
db.SaveChanges();
}
If you are worried about all of the manual mapping involved in this, there exists frameworks to help you with that:
Automapper
ValueInjector
You are heading down a very very daunting path if you start adding hidden fields into your view. Its a maintenance nightmare and very error prone.
The post operation only collects the values you have in the form.
If you want the other values to proceed in your controllers post-method, you can for example, add hidden fields.
#Html.HiddenFor(x => x.HiddenPostBack)
I try to add new entity in database in controller action.
This is my model class
public class Product
{
public int ProductID { get; set; }
[Required(ErrorMessage = "Please enter product name")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter product model")]
public string Model { get; set; }
[Required(ErrorMessage = "Please enter product serial")]
public string Serial { get; set; }
[Required(ErrorMessage = "Please choose dealer")]
public int DealerID { get; set; }
[Required]
public Guid ClientID { get; set; }
[Required(ErrorMessage = "Please choose employee")]
public Guid EmployeeID { get; set; }
public virtual Dealer Dealer { get; set; }
public virtual Client Client { get; set; }
public virtual Employee Employee { get; set; }
[DisplayName("Commercial use")]
public bool UseType { get; set; }
}
This is actions for creating new product in database
public ViewResult Create()
{
PopulateDropDownLists();
var model = new Product();
return View(model);
}
[HttpPost]
public ActionResult Create(Product model)
{
try
{
if (ModelState.IsValid)
{
_repo.GetRepository<Product>().Add(model);
_repo.Save();
TempData["message"] = "Product was successfully created";
return RedirectToAction("List");
}
}
catch (DataException)
{
TempData["error"] =
"Unable to save changes. Try again, and if the problem persists, see your system administrator.";
return View("Error");
}
PopulateDropDownLists();
return View("Create");
}
CreateView has appropriate model type (Product type in this case). Code below
#using System.Web.Mvc.Html
#model STIHL.WebUI.Models.Product
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.Name)
#Html.EditorFor(m => m.Model)
#Html.EditorFor(m => m.Serial)
<div class="form-group">
#Html.LabelFor(m => m.DealerID, "Dealer")
#Html.DropDownListFor(m => m.DealerID, new SelectList((IEnumerable)TempData["Dealers"],"DealerID", "DealerNumber"), string.Empty, new {#class = "form-control"})
#Html.ValidationMessageFor(m => m.DealerID, null, new {#class = "help-block"})
</div>
<div class="form-group">
#Html.LabelFor(m => m.EmployeeID, "Employee",new {#class = "control-label"})
#Html.DropDownListFor(m => m.EmployeeID, new SelectList((IEnumerable)TempData["Employees"],"EmployeeID", "FullName"),string.Empty, new {#class="form-control"})
#Html.ValidationMessageFor(m => m.EmployeeID, null, new {#class = "help-block"})
</div>
<div class ="ok-cancel-group">
<input class="btn btn-primary" type="submit" value="Create" />
#Html.ActionLink("Cancel", "List","Product",new {#class = "btn btn-primary"})
</div>
}
i always get null reference instead model in [HttpPost] action, but if i use ViewModel instead Model everything is ok (ViewModel code below)
public class ProductViewModel
{
public Product Product { get; set; }
}
I think it cause model class has virtual properties, but anyway i don't understand why it's ok when i use ViewModel.
Can anyone answer me?
Thx in advance.
The virtual properties won't change the outcome. The issue is that the view is written to bind to the view model, therefore accepting the model isn't going to work. If you want to use the model; then bind the view to the model.
I'm using EF Code First and here is my class which references a Professor object.
public class Mark
{
public int MarkId { get; set; }
[Required(ErrorMessage = "Please provide Grade")]
[Display(Name = "Grade Value")]
public int MarkValue { get; set; }
[Required(ErrorMessage = "Please provide grade type")]
[Display(Name = "Grade Type")]
public string Type { get; set; }
[Required(ErrorMessage = "Please provide Subject")]
[Display(Name = "Subject")]
public virtual Subject Subject { get; set; }
public virtual User User { get; set; }
public virtual Professor Professor { get; set; }
}
In my view I have a dropdown to select a professor who put the mark.
I implemented it like this:
//controller
public MarksController() : this(new MarkRepository())
{
IProfessorRepository professorsRepo = new ProfessorRepository();
List<Professor> professors = professorsRepo.All.ToList<Professor>();
List<SelectListItem> items = new List<SelectListItem>();
foreach (Professor p in professors)
{
items.Add(new SelectListItem
{
Text = String.Concat(p.FirstName, " ", p.LastName), Value = p.ProfessorId.ToString()
});
}
items.Add(new SelectListItem
{
Text = "Please select Professor", Value = "0", Selected = true
});
ViewBag.Professors = items;
}
//view create or edit
<div class="editor-label">
#Html.LabelFor(model => model.MarkValue)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MarkValue)
#Html.ValidationMessageFor(model => model.MarkValue)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Type)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Type)
#Html.ValidationMessageFor(model => model.Type)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Professor)
</div>
<div class="editor-field">
#Html.DropDownList("Professors");
</div>
Yes, I know that there is is a metod in Controller called Create which appeals repository method InsertOrUpdate.
[HttpPost]
public ActionResult Create(Mark mark)
{
if (ModelState.IsValid) {
markRepository.InsertOrUpdate(mark);
markRepository.Save();
return RedirectToAction("Index");
} else {
return View();
}
}
public void InsertOrUpdate(Mark mark)
{
if (mark.MarkId == default(int)) {
// New entity
context.Marks.Add(mark);
} else {
// Existing entity
context.Entry(mark).State = EntityState.Modified;
}
}
Everything seems logic, but how can I save a newly created Mark object with the professor selected? How does ASP.NET arranges the object which is passed to Create method on Controller? How should I match the selected professor with the mark I am creating?
Take a look at this, you need a ProfessorId in the model.
Model binding dropdown selected value
public class Mark
{
// this will hold the selected value
public string ProfessorID { get; set; }
public IEnumerable<SelectListItem> Professors
{
get
{
return Proffessors
.Select(x => new SelectListItem {
Value = x.ProfessorId.ToString(),
Text = String.Concat(p.FirstName, " ", p.LastName)
});
}
}
public IEnumerable<Professor> Proffessors
{
get
{
IProfessorRepository professorsRepo = new ProfessorRepository();
return professorsRepo.All;
}
}
//the rest of your code
}
in the view do
#Html.DropDownListFor(
x => x.ProfessorId,
new SelectList(Model.Professors, "Value", "Text")
)
I have XML file which contains my data and I want from dropdownlist save choose string to this xml.
In my view I have this:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>MatchXML</legend>
...
<div class="editor-label">
#Html.LabelFor(model => model.Team)
</div>
<div class="editor-field">
#Html.DropDownList("Team", (SelectList)ViewBag.Team, String.Empty)
#Html.ValidationMessageFor(model => model.Team)
</div>
...
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
In controller:
public ActionResult Pridat()
{
ViewBag.Team = new SelectList(repo.GetTeams(), "Name", "Name");
return View();
}
[HttpPost]
public ActionResult Pridat(MatchXML match, string Team)
{
if (ModelState.IsValid)
{
try
{
ViewBag.Team = new SelectList(repo.GetTeams(), "Name", "Name");
match.Team = repo.GetTeamByName(Team);
repo.AddMatch(match);
return RedirectToAction("Index");
}
catch (Exception ex)
{
//error msg for failed insert in XML file
ModelState.AddModelError("", "Error creating record. " + ex.Message);
}
}
return View(match);
}
Models looks:
public class MatchXML
{
public int MatchXMLID { get; set; }
public string Opponent { get; set; }
public DateTime MatchDate { get; set; }
public string Result { get; set; }
public Team Team { get; set; }
public int Round { get; set; }
}
public class Team
{
public int TeamID { get; set; }
public string Name { get; set; }
public virtual User Coach { get; set; }
public virtual ICollection<Player> Players { get; set; }
}
I was trying do some modification to do this but it is not working. I can do it with TeamID and saving ID but I want in xml save string (name of team). Thanks for help
Edit:
I updated show code of controller and view method.
You are binding the dropdown to the Team complex property (first argument of the DropDownList helper). This doesn't make sense. You can bind only to scalar values. I would also recommend you using the strongly typed version of the helper:
#Html.DropDownListFor(x => x.Team.TeamID, (SelectList)ViewBag.Team, String.Empty)
This way you will populate the TeamID property in the POST action with the selected value from the dropdown.
Also replace:
#Html.ValidationMessageFor(model => model.Team)
with:
#Html.ValidationMessageFor(model => model.Team.TeamID)