I am new to ASP.NET MVC 5. I am trying to make a user-management software. but in the user registration(create) controller my model is getting invalidated. Don't know why. I might have a mistake in my model Binding. Here is the attached code. Any Help is appreciated.
Model file
public class UserData
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int userid { get; set; }
[Required(ErrorMessage = "Domain ID")]
[Display(Name = "Domain ID")]
public string domainid { get; set; }
[Required(ErrorMessage = "Choose Role")]
[Display(Name = "Role")]
public string role { get; set; }
[Required(ErrorMessage = "Choose Country")]
[Display(Name = "Country")]
public string country { get; set; }
[Required(ErrorMessage = "Choose BU")]
[Display(Name = "BU")]
public string bu { get; set; }
[Required]
[RegularExpression(#"^(([\w-]+\.)+[\w-]+|([a-zA-Z]{1}|[\w-]{2,}))#"
+ #"((([0-1]?[0-9]{1,2}|25[0-5]|2[0-4][0-9])\.([0-1]?
[0-9]{1,2}|25[0-5]|2[0-4][0-9])\."
+ #"([0-1]?[0-9]{1,2}|25[0-5]|2[0-4][0-9])\.([0-1]?
[0-9]{1,2}|25[0-5]|2[0-4][0-9])){1}|"
+ #"([a-zA-Z]+[\w-]+\.)+[a-zA-Z]{2,4})$", ErrorMessage = "Please Provide Valid Email-ID")]
[Display(Name = "Email"),DataType(DataType.EmailAddress)]
public string email { get; set; }
[HiddenInput(DisplayValue=true)]
public DateTime date_from { get; set; }
[HiddenInput(DisplayValue = true)]
public DateTime date_to { get; set; }
[HiddenInput(DisplayValue=true)]
public bool active { get; set; }
}
ViewModel File
public class UserRegistrationViewModel
{
public UserData userdata { get; set; }
public string SelectedRole { get; set; }
public IEnumerable<SelectListItem> RoleList { get; set; }
public string SelectedCountry { get; set; }
public IEnumerable<SelectListItem> CountryList { get; set; }
public string SelectedBU { get; set; }
public IEnumerable<SelectListItem> BUList { get; set; }
}
Controller file
public class UserDatasController : Controller
{
private ApplicationDataContext db = new ApplicationDataContext();
// GET: UserDatas
public ActionResult Index()
{
return View(db.UsersData.ToList());
}
public ActionResult Create()
{
var model = new UserRegistrationViewModel();
model.CountryList = from p in XDocument.Load("C:/Users/inkosah/Documents/Visual Studio 2013/Projects/Policy Assessment/Policy Assessment/country_list.xml").Descendants("Name")
//var a=Path.GetFullPath("Policy Asse")
let value = (string)p.Element("Text")
select new SelectListItem
{
Selected = (value == model.SelectedCountry),
Text = (string)p.Element("Text"),
Value = value
};
model.BUList = from q in XDocument.Load("C:/Users/inkosah/Documents/Visual Studio 2013/Projects/Policy Assessment/Policy Assessment/bu_list.xml").Descendants("BU")
let value2 = (string)q.Element("BU_Name")
select new SelectListItem
{
Selected = (value2 == model.SelectedBU),
Text = (string)q.Element("BU_Name"),
Value = value2
};
model.RoleList = from n in XDocument.Load("C:/Users/inkosah/Documents/Visual Studio 2013/Projects/Policy Assessment/Policy Assessment/UserRoleList.xml").Descendants("Role")
let value1 = (string)n.Element("Role_Name")
select new SelectListItem
{
Selected = (value1 == model.SelectedRole),
Text = (string)n.Element("Role_Name"),
Value = value1
};
return View(model);
}
// POST: UserDatas/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([Bind(Include = "SelectedRole,SelectedCountry,SelectedBU")]UserRegistrationViewModel RegisterData,[Bind(Include="domainid,email")] UserData userdata)
{
userdata.date_from = DateTime.Now;
userdata.date_to = DateTime.MaxValue;
userdata.active = false;
userdata.role = RegisterData.SelectedRole;
userdata.bu = RegisterData.SelectedBU;
userdata.country = RegisterData.SelectedCountry;
if (ModelState.IsValid)
{
db.UsersData.Add(userdata);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(userdata);
}
create.cshtml
#model Policy_Assessment.ViewModels.UserRegistrationViewModel
#{
ViewBag.Title = "User Registration Page";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>User Input</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.userdata.domainid, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.userdata.domainid)
#Html.ValidationMessageFor(model => model.userdata.domainid)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.userdata.role, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#*#Html.EditorFor(model => model.role)*#
#Html.DropDownListFor(model => model.SelectedRole, Model.RoleList, "-----Role-----")
#Html.ValidationMessageFor(model=>model.SelectedRole)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.userdata.country, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#*#Html.EditorFor(model => model.country)*#
#Html.DropDownListFor(model => model.SelectedCountry,Model.CountryList,"----Country-----")
#Html.ValidationMessageFor(model => model.userdata.country)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.userdata.bu, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model=>model.SelectedBU,Model.BUList,"--Select BU----")
#Html.ValidationMessageFor(model => model.userdata.bu)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.userdata.email, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.userdata.email)
#Html.ValidationMessageFor(model => model.userdata.email)
</div>
</div>
#*<div class="form-group">
#Html.HiddenFor(model => model.userdata.date_from, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.HiddenFor(model => model.userdata.date_from)
#Html.ValidationMessageFor(model => model.userdata.date_from)
</div>
</div>
<div class="form-group">
#Html.HiddenFor(model => model.userdata.date_to, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.HiddenFor(model => model.userdata.date_to)
#Html.ValidationMessageFor(model => model.userdata.date_to)
</div>
</div>
<div class="form-group">
#Html.HiddenFor(model => model.userdata.active, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.HiddenFor(model => model.userdata.active)
#Html.ValidationMessageFor(model => model.userdata.active)
</div>
</div>*#
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Register" class="btn btn-default" />
</div>
</div>
</div>
}
#*<div>
#Html.ActionLink("Back to List", "Index")
</div>*#
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
Context File
public class ApplicationDataContext : DbContext
{
public ApplicationDataContext()
: base("DefaultConnection")
{ }
public System.Data.Entity.DbSet<Policy_Assessment.ViewModels.UserRegistrationViewModel> UserRegistrationData { get; set; }
public System.Data.Entity.DbSet<Policy_Assessment.Models.UserData> UsersData { get; set; }
}
Please Note i am a beginner in MVC ASP.Net. Any Help or Explanation would be helpful.
Your posting back your data model (included as a property of your view model). The data model has a [Required] attribute for property role but you are creating a control for this property so nothing is bound, meaning its null and therefore its invalid. Two ways to solve this.
A. Remove property string SelectedRole from the view model and bind directly to the the data model that's included in the view model
#Html.DropDownListFor(m => m.userdata.role, Model.RoleList, ...)
Now, userdata.role will contain the selected option value and will be valid (note you would need to do this for the other 2 properties as well).
B. Remove property UserData userdata from the view model and include in the view model the properties from UserData that you are editing
public class UserRegistrationViewModel
{
[Required(ErrorMessage = "Domain ID")]
[Display(Name = "Domain ID")]
public string domainid { get; set; }
[Required(ErrorMessage = "Choose Role")]
[Display(Name = "Role")]
public string role { get; set; }
[Required(ErrorMessage = "Choose Country")]
[Display(Name = "Country")]
public string country { get; set; }
[Required(ErrorMessage = "Choose BU")]
[Display(Name = "BU")]
public string bu { get; set; }
[Required]
[Display(Name = "Email"),DataType(DataType.EmailAddress)]
[EmailAddress]
public string email { get; set; }
public SelectList RoleList { get; set; }
public SelectList CountryList { get; set; }
public SelectList BUList { get; set; }
}
Note I've excluded properties you don't appear to be editing, used [EmailAddress] rather than your Regex (could not see what the regex was doing that an EmailAddress attribute is not already doing) and used SelectList rather than IEnumerable<SelectListItem> which means you can simplify it to
public ActionResult Create()
{
var model = new UserRegistrationViewModel();
var roles = from n in XDocument.Load(....
model.RoleList = new SelectList(roles, "value", "value");
....
return View(model);
}
and then in the POST method, map the properties from the view model to a new instance of the data model, setting the other properties such as date_from if required (alternatively you could put these default values in a constructor) and save to the database.
Related
I am trying to define ViewModels that faithfully represent the view (to make strict use of that concept).
Some of the elements of the ViewModel are updated dynamically. The problem I have, is that when I do the Post, the ViewModel returns without the elements that were updated dynamically.
The update is done through jQuery, when an event is performed. An action is invoked through Url.Action, and a Div is updated.
I made an example to clarify the scenario. An application that only stores a location (state and city). For this I have three ViewModels: one to represent the States in a SelectList, one to represent the Cities in a SelectList, and finally one to represent the Location (formed by the two ViewModel that I mentioned first).
Models:
public class State
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
public class City
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int StateId { get; set; }
public virtual State State { get; set; }
}
ViewModels:
public class CitySelectListViewModel
{
public CitySelectListViewModel() { }
public CitySelectListViewModel(IEnumerable<Models.City> cities)
{
this.Cities = cities;
}
[Display(Name = "Cities")]
[Required]
public int? SelectedCityId { get; set; }
public IEnumerable<City> Cities { get; }
}
public class StateSelectListViewModel
{
public StateSelectListViewModel() { }
public StateSelectListViewModel(IEnumerable<State> states)
{
this.States = states;
}
[Display(Name = "States")]
[Required]
public int? SelectedStateId { get; set; }
public IEnumerable<State> States { get; }
}
public class LocationCreateViewModel
{
public LocationCreateViewModel() { }
public LocationCreateViewModel(ICollection<State> states)
{
this.StateSelectListViewModels = new StateSelectListViewModel(states);
this.CitySelectListViewModel = new CitySelectListViewModel();
}
public StateSelectListViewModel StateSelectListViewModels { set; get; }
public CitySelectListViewModel CitySelectListViewModel { set; get; }
}
Location [Controller]:
public class LocationController : Controller
{
private DALDbContext db = new DALDbContext();
// GET: Location/Create
public ActionResult Create()
{
LocationCreateViewModel locationCreateViewModel = new LocationCreateViewModel(db.States.ToList());
return View(locationCreateViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(LocationCreateViewModel pLocationCreateViewModel)
{
if (ModelState.IsValid)
{
//db.States.Add(state);
//db.SaveChanges();
return RedirectToAction("Index", "Home");
}
LocationCreateViewModel locationCreateViewModel = new LocationCreateViewModel(db.States.ToList());
return View(locationCreateViewModel);
}
public ActionResult CitySelectList(int? stateId)
{
CitySelectListViewModel citySelectListViewModel = new CitySelectListViewModel(db.Cities.Where(c => c.StateId == stateId).ToList());
return View(citySelectListViewModel);
}
}
Create [View]:
#model ViewModelExample.ViewModels.LocationCreateViewModel
....
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>State</h4>
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.StateSelectListViewModels.SelectedStateId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.StateSelectListViewModels.SelectedStateId, new SelectList(Model.StateSelectListViewModels.States, "Id", "Name"), "Select a State", htmlAttributes: new { #class = "form-control", #id = "StateSelectList" })
#Html.ValidationMessageFor(model => model.StateSelectListViewModels.SelectedStateId, "", new { #class = "text-danger" })
</div>
</div>
<div id="CityContainer">
#Html.Action("CitySelectList")
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
$(function () {
// Fill City DropDownList
$('#StateSelectList').change(function () {
var selectedStateId = this.value;
$('#CityContainer').load('#Url.Action("CitySelectList")?stateId=' + selectedStateId);
});
});
</script>
}
CitySelectList [View]:
#model ViewModelExample.ViewModels.CitySelectListViewModel
....
<div class="form-group">
#Html.LabelFor(model => model.SelectedCityId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.SelectedCityId, new SelectList(Model.Cities, "Id", "Name"), "Select a City", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.SelectedCityId, "", new { #class = "text-danger" })
</div>
</div>
I will show the execution of my example, and I will show the problem through the inspection of the ViewModel that I receive after the Post:
I select a State and a City, and I press Create.
I inspect the ViewModel received after the Post. We can see how CitySelectListViewModel is null, and what I want is to bring the last ViewModel that was updated through jQuery.
I admit that I have provided a long example, but it is the only way I found to explain what I need. Thanks in advance.
VS-Project of the example
I'ts because you are preventing the modelBinder to accurately bind to LocationCreateViewModel in your Create action when replacing the inner HTML of <div id="CityContainer"> (thats what you do with $('#CityContainer').load(...). You instruct the model binder to bind to
#model ViewModelExample.ViewModels.CitySelectListViewModel and as a result you get this HTML for the city select list:
One way of solving this is modifying CitySelectList.cshtml to:
#model ViewModelExample.ViewModels.LocationCreateViewModel
#{
Layout = null;
}
<div class="form-group">
#Html.LabelFor(model => model.CitySelectListViewModel.SelectedCityId,
htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model =>
model.CitySelectListViewModel.SelectedCityId, new
SelectList(Model.CitySelectListViewModel.Cities, "Id", "Name"), "Select a City", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.CitySelectListViewModel.SelectedCityId, "", new { #class = "text-danger" })
</div>
</div>
and your CitySelectList action to:
public ActionResult CitySelectList(int? stateId)
{
LocationCreateViewModel locationCreateViewModel = new LocationCreateViewModel();
locationCreateViewModel.CitySelectListViewModel = new CitySelectListViewModel(db.Cities.Where(c => c.StateId == stateId).ToList());
return View(locationCreateViewModel);
}
But I would recommend custom model binding as well.
I am currently working on a project to model a bikestore. In my 'Order' object, I have a lis object for the Bike items on the order. How would I add bikes to this list? I.E I want to display a list of availiable bikes in the Create view, an add one or more of them to the order.
My Controller:
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "OrderNumber,CustomerName,OrderDate,PickupDate,TotalCost,PaymentMethod")] Order order)
{
if (ModelState.IsValid)
{
db.Orders.Add(order);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(order);
}
My Inventory model
public class Inventory
{
public int Id { get; set; }
public string SerialNumber { get; set; }
public virtual Store Store { get; set; }
public int? StoreId { get; set; }
public string Model { get; set; }
public string Description { get; set; }
public Decimal InventoryCost { get; set; }
public Decimal RecSalePrice { get; set; }
public Decimal SalePrice { get; set; }
public string PaymentMethod { get; set; }
public virtual BikeCategory Category { get; set; }
public int? CategoryId { get; set; }
}
My Order model:
namespace BikeStore.Models
{
public class Order
{
public Order()
{
OrderedItems = new List<Inventory>();
}
public string CustomerName { get; set; } //FROM CONTROLLER User.Identity.Name
public virtual List<Inventory> OrderedItems { get; set; }
[Key, DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
public int OrderNumber { get; set; }
In the create view for orders:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Order</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.CustomerName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.CustomerName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CustomerName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.OrderDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.OrderDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.OrderDate, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PickupDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PickupDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PickupDate, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TotalCost, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.TotalCost, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.TotalCost, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PaymentMethod, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PaymentMethod, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PaymentMethod, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Start by creating view models to represent what you want to display/edit in the view (add display and validation attributes as appropriate)
public class InventoryVM
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
public class OrderVM
{
public string PaymentMethod { get; set; }
public List<InventoryVM> Inventory { get; set; }
}
Note that CustomerName, OrderDate and Total are not appropriate (you don't want a user editing these - they should be set in the POST method immediately before saving the order). Not sure what PickupDate represents but if its the actual date then that's not appropriate either (it would be set separately when the order is collected). I would also suggest that PaymentMethod be an enum or collection of PaymentType's and that you and use a dropdownlist in the view for selection.
Then the GET method would be
public ActionResult Create()
{
// Get all available bikes, for example
var inventory = db.Inventory;
OrderVM model = new OrderVM
{
Inventory = inventory.Select(i => new
{
ID = i.ID,
Name = i.Model // modify this to suit what you want to display in the view
}).ToList()
};
return View(model);
}
And in the view
#model yourAssembly.OrderVM
#using (Html.BeginForm())
{
for(int i = 0; i < Model.Inventory.Count; i++)
{
#Html.HiddenFor(m => m.Inventory[i].ID)
#Html.CheckBoxFor(m => m.Inventory[i].IsSelected)
#Html.LabelFor(m => m.Inventory[i].IsSelected, Model.Inventory[i].Name)
}
#Html.TextBoxFor(m => m.PayentMethod)
<input type="submit" value="Create" />
}
And the POST method would be
public ActionResult Create(OrderVM model)
{
// Initialize a new Order and map properties from view model
var order = new Order
{
CustomerName = User.Identity.Name,
OrderDate = DateTime.Now,
....
PaymentMethod = model.PaymentMethod
}
// Save the order so that you now have its `ID`
IEnumerable<int> selectedItems = model.Inventory.Where(i => i.IsSelected).Select(i => i.ID);
foreach(var item in selectedItems)
{
// You have not shown the model for this so just guessing
var orderItem = new OrderItem{ OrderID = order.Id, InventoryId = item };
db.OrderItems.Add(orderItem);
}
db.SaveChanges();
}
Side notes:
If you want to be able to allow users to select more that one of any
item, the you could change bool IsSelected to say int Quantity
If you you want to display additional information about the items,
say Description and Cost you can include additional properties
in the InventoryVM view model and display them with
#Html.DisplayFor(m => m.Inventory[i].Description)
If you want to display a total cost for all selected items in the
view, you will need to use javascript/jquery
If ModelState could be invalid, you will need to repopulate the
properties of InventoryVM before you return the view (as shown
only the ID and IsSelected properties post back) or include
hidden inputs for the other properties in the view
How to save data from Select box in Entity Framework Database relation Many to Many
There are two classes one weapon and other User..
public class Weapon { } public class User { }
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public string Type { get; set; }
}
public class Wepon
{ public int ID { get; set; }
public string Wepon_Name { get; set; }
public int Power { get; set; }
}
Which should have relation Many to Many Using FormCollection and Model
User Class
And Weapon Class
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public string Type { get; set; }
**public List<Wepon> WeposInList { get; set; }**
}
public class Wepon
{
public int ID { get; set; }
public string Wepon_Name { get; set; }
public int Power { get; set; }
public List<User> UsersHaveWeponsList { get; set; }// User the List for M to M
}
DBContext
public class DbContexFor : DbContext
{
public DbContexFor()
: base("name=ConnectionStringName")
{
}
public virtual DbSet<User> Users { get; set; }
public virtual DbSet<Wepon> Wepons { get; set; }
}
}
**
The Controller Code
**
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Name,Type")] User user, FormCollection formData)
{
if (ModelState.IsValid)
{
var ss = formData["ShipFromCountries"].ToString();
user.WeposInList = db.Wepons.Where(c => c.Wepon_Name == ss).ToList();
db.Users.Add(user);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(user);
}
And the Html Code
#model enumVarAction.Models.User
#{
var list = ViewBag.MyList;
ViewBag.Title = "Create";
}
Create Html Page
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>User</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Type, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Type, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Type, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.WeposInList, htmlAttributes: new { #class = "control-label col-md-2" })
<select id="ShipFromCountries" multiple="multiple" name="ShipFromCountries">
<div class="col-md-10">
#foreach (var VARIABLE in list)
{
<option value="#VARIABLE.Wepon_Name">#VARIABLE.Wepon_Name</option>
}
</div>
</select>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
**
Debug at Controller
**
**
Data is Database
**
I am very new to MVC and have just added a cascading drop down to my create page so when a Practice is selected the Optician drop down is populated with the names of opticians that work at that practice.
Model:
public class Booking
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid BookingId { get; set; }
[ForeignKey("Patient")]
public Guid PatientId { get; set; }
public virtual Patient Patient { get; set; }
public IEnumerable<SelectListItem> PatientList { get; set; }
[ForeignKey("Practice")]
public Guid PracticeId { get; set; }
public virtual Practice Practice { get; set; }
public IEnumerable<SelectListItem> PracticeList { get; set; }
[ForeignKey("Optician")]
public Guid OpticianId { get; set; }
public virtual Optician Optician { get; set; }
public IEnumerable<SelectListItem> OpticiansList { get; set; }
[Display(Name = "Date")]
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime Date { get; set; }
[ForeignKey("Time")]
public Guid? TimeId { get; set; }
public virtual Time Time { get; set; }
public IEnumerable<SelectListItem> TimeList { get; set; }
public bool isAvail { get; set; }
}
My Controller:
// GET: Bookings1/Create
public ActionResult Create()
{
var practices = new SelectList(db.Practices, "PracticeId", "PracticeName");
ViewData["Practice"] = practices;
Booking booking = new Booking();
ConfigureCreateModel(booking);
return View(booking);
}
public void ConfigureCreateModel(Booking booking)
{
booking.PatientList = db.Patients.Select(p => new SelectListItem()
{
Value = p.PatientId.ToString(),
Text = p.User.FirstName
});
booking.TimeList = db.Times.Select(t => new SelectListItem()
{
Value = t.TimeId.ToString(),
Text = t.AppointmentTime
});
}
// POST: Bookings1/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(Booking booking)
{
// to ensure date is in the future
if (ModelState.IsValidField("Date") && DateTime.Now > booking.Date)
{
ModelState.AddModelError("Date", "Please enter a date in the future");
}
// Sets isAvail to false
booking.isAvail = false;
//Checks if model state is not valid
if (!ModelState.IsValid)
{
ConfigureCreateModel(booking);
return View(booking); // returns user to booking page
}
else // if model state is Valid
{
// Generates a new booking Id
booking.BookingId = Guid.NewGuid();
// Adds booking to database
db.Bookings.Add(booking);
// Saves changes to Database
db.SaveChanges();
// Redirects User to Booking Index
return RedirectToAction("Index");
}
}
My View:
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script>
$(document).ready(function () {
$("#Optician").prop("disabled", true);
$("#Practice").change(function () {
$.ajax({
url : "#Url.Action("Opticians","Bookings")",
type : "POST",
data : {Id : $(this).val() }
}).done(function (opticianList) {
$("#Optician").empty();
for (var i = 0; i < opticianList.length; i++) {
$("#Optician").append("<option>" + opticianList[i] + "</option>");
}
$("#Optician").prop("disabled", false);
});
});
});
</script>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Booking</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.PatientId, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.PatientId, Model.PatientList, "-Please select-", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.PatientId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PracticeId, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Practice", ViewData["Practice"] as SelectList,"-Please Select-", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.PracticeId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.Label("Select Optician :", new { #class = "col-md-2 control-label" })
<div class="col-md-10">
<select id="Optician"></select>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Date, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Date, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TimeId, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.TimeId, Model.TimeList, "-Please select-", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.TimeId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
The cascading drop down works as it should how ever when I click the button to create the appointment the following exception is thrown:
Exception:
An exception of type 'System.InvalidOperationException' occurred in System.Web.Mvc.dll but was not handled in user code
Additional information: There is no ViewData item of type 'IEnumerable' that has the key 'Practice'.
Any help would be greatly appreciated.
Thanks
You model already contains a property for the collection of practices
public IEnumerable<SelectListItem> PracticeList { get; set; }
although it should not contain
public virtual Practice Practice { get; set; }
In the GET method, you create a new SelectList for practices, but instead of assigning it to the model property, you add it to ViewData using
ViewData["Practice"] = practices;
and then in the view use
#Html.DropDownList("Practice", ViewData["Practice"] as SelectList, ..)
which is not even binding to a property in your model and would never post back to anything. Then when you return the view in the POST method (because your mode will always be invalid), you do not assign a value to ViewData["Practice"] so its null, hence the error.
Instead, in your ConfigureCreateModel() method, populate the PracticeList property (as your doing for PatientList) and remove the use of ViewData, and in the view use
#Html.DropDownListFor(model => model.PracticeId, Model.PracticeList, ...)
so your strongly binding to your model and when your submit the form, the value of PracticeId will be the value of the selected practice.
Side note: You will need to change your script to $("#PracticeId").change(function () { ...
I have a little blog application with posts and tags. This is my model for Post:
namespace HelloWorld.Models
{
public class Post
{
[Required]
[DataType(DataType.Text)]
public string Title { get; set; }
[Required]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Required]
[DataType(DataType.DateTime)]
public DateTime PostDate { get; set; }
public List<Tag> Tags { get; set; }
[Required]
public int PostId { get; set; }
}
public class CreatePostView
{
[Required]
[DataType(DataType.Text)]
public string Title { get; set; }
[Required]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Display(Name = "Tags")]
[Required(ErrorMessage = "Please select a tag")]
public string SelectedTag { get; set; }
public SelectList TagList { get; set; }
[Required]
public int PostId { get; set; }
}
}
And model of Tag consist of string TagName, int TagId, List Posts.
When I create a new Post I use CreatePostView and my view is:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="create-post-form">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<strong>Title</strong>
<div class="col-md-10">
#Html.EditorFor(model => model.Title, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Title, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<strong>Description</strong>
<div class="col-md-10">
#Html.EditorFor(model => model.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>
#Html.DropDownListFor(m => m.SelectedTag, Model.TagList, "Add tag")
#Html.ValidationMessageFor(m => m.SelectedTag)
<div class="post-create-button">
<input type="submit" value="Create">
</div>
<div class="back-to-list-button">
#Html.ActionLink("Back", "Index")
</div>
</div>
}
And now I want to display my tag that I selected. I put value of selected tag in ViewBag, but it does not display. Maybe it's silly, but I do not know how to fix it. My Create action of PostsController:
// POST: Posts/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreatePostView post)
{
Post currPost = new Post {
Title = post.Title,
Description = post.Description,
PostDate = DateTime.Now,
Tags = null };
ViewBag.Tag = post.SelectedTag.ToString();
ViewBag.Trash = "texttexttexttexttext"; // It's strange, but it not displayed.
if (ModelState.IsValid)
{
//var tags = db.Tags.Where(s => s.TagName.Equals(post.SelectedTag)).ToList();
//currPost.Tags = tags;
db.Posts.Add(currPost);
db.SaveChanges();
return RedirectToAction("Index", "Posts");
}
return View(currPost);
}
My view with all Posts (use model Post)
#foreach (var item in Model)
{
<article class="post">
<h3>#Html.DisplayFor(modelItem => item.Title)</h3>
<p>#Html.DisplayFor(modelItem => item.Description)</p>
<!--None of them is not shown-->
<p><strong>Tag: #ViewBag.Tag</strong></p>
<p><strong>Trash: #ViewBag.Trash</strong></p>
</article>
}
ViewBag is used when returning a view, not when redirecting to another action. Basically it doesn't persist across separate requests. Try using TempData instead:
TempData["Tag"] = post.SelectedTag.ToString();
and in the view:
<p><strong>Tag: #TempData["Tag"]</strong></p>