I have a table called Roles with three fields
Guid RoleId
string RoleName
string Description
In my register.cshtml view I want to have a dropdownlist which shows the list of RoleName from Roles table. I also need to be able to get that value and work with it, like assigning the Role to user, which will in done in controller.
My view currently looks like the one below, i'm using model as AspNetUser but it doesn't have knowledge about Role which is what I want to show in dropdownlist.
#model Sorama.CustomAuthentiaction.Models.AspNetUser
#{
ViewBag.Title = "Register";
Layout = "~/Views/shared/_BootstrapLayout.empty.cshtml";
}
#section Styles{
<link href="#Url.Content("~/Content/bootstrap.css")" rel="stylesheet" type="text/css" />
}
<div class ="form-signin">
#using (Html.BeginForm("Register", "Account"))
{
#Html.ValidationSummary(true)
<h2 class="form-signin-heading"> Register </h2>
<div class ="input-block-level">#Html.TextBoxFor(model=>model.Email, new{#placeholder = "Email"})</div>
<div class ="input-block-level">#Html.TextBoxFor(model=>model.UserName, new{#placeholder = "UserName"})</div>
<div class ="input-block-level">#Html.PasswordFor(model=>model.Password, new{#placeholder ="Password"})</div>
<div class ="input-block-level">#Html.DropDownListFor(//don't know what to do
<button class="btn btn-large btn-primary" type="submit">Register</button>
}
</div>
My controller looks like this
public class AccountController : Controller
{
//private readonly IDbContext dbContext;
//
// GET: /Account/
[HttpGet]
public ActionResult Login()
{
return View();
}
[HttpPost]
[AllowAnonymous]
public ActionResult Login(LoginModel model)
{
if(Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
[HttpGet]
public ActionResult Register()
{
string [] roles = Roles.GetAllRoles();
return View(roles);
}
[HttpPost]
public ActionResult Register(AspNetUser model)
{
return View();
}
public ActionResult Index()
{
return View();
}
}
What do I need to do, to have that dropdownlist?
In your controller you need to pass the string[] (IEnumerable<string>) representing your roles into your view somehow...
There are many ways to achieve this, but in your AccountController you could do the following:
public class AccountController : Controller
{
private IDbContext dbContext;
public AccountController(IDbContext dbContext)
{
// Made up field that defines your GetAllRoles method
this.dbContext = dbContext;
}
public ActionResult Register()
{
// Call the GetAllRoles() and capture the result in a variable called roles
var roles = dbContext.GetAllRoles();
return View(new AspNetUser {
Roles = roles
});
}
}
Note: I do not enforce the list to be in any form in the controller (I do not specify it should be a select list), I may want to display the items as in a different way and I let the view be flexible by passing the values only and allowing the view to decide how to render the values.
In your View you can then use where you want the dropdown list to appear:
#Html.DropDownListFor(model => model.Roles, Model.Roles
.Select(role => new SelectListItem { Text = role, Value = role })
As I mentioned, there are many ways to achieve what you are wanting but almost one thing is certain, that with aspnet mvc you will most likely be using the Html helper DropDownListFor MSDN Documentation here:
http://msdn.microsoft.com/en-us/library/system.web.mvc.html.selectextensions.dropdownlistfor(v=vs.108).aspx
EDIT 1:
Create a model to hold the User and Role informations like so:
public class RegisterViewModel
{
public AspNetUser AspNetUser { get; set; }
public IEnumerable<string> Roles { get; set; }
}
In the controller it could look like so:
public class AccountController : Controller
{
private RoleProvider roleProvider;
public AccountController(RoleProvider roleProvider)
{
this.roleProvider = roleProvider;
}
public ActionResult Register()
{
// Call the GetAllRoles() and capture the result in a variable called roles
// var roles = roleProvider.GetAllRoles();
// Or, as you have specified:
var roles = Roles.GetAllRoles();
return View(new RegisterViewModel {
AspNetUser = GetTheAspNetUser(),
Roles = roles
});
}
}
In the View you need to update the model to use:
#model Sorama.CustomAuthentiaction.Models.RegisterViewModel
If you are unwilling/unable to make such a change you could add the list of Roles to the Viewbag:
ViewBag.RoleList = roleProvider.GetAllRoles();
Or as you alluded to:
ViewBag.RoleList = Roles.GetAllRoles();
Then access in the View like so:
#Html.DropDownListFor(model => model.Roles, ViewBag.RoleList
.Select(role => new SelectListItem { Text = role, Value = role })
In a similar scenario, I've done something like this:
private void BagSelectList()
{
ViewBag.List = new SelectList(
db.SetOfCandidateValues,
"KeyPropertyOfTheSet",
"NameOfThePropertyToAppearInTheDropDownList",
selectedValue);
}
And in the view:
#Html.DropDownListFor(
model => model.ForeignKeyProperty,
(SelectList)ViewBag.List)
(Of course, if you dislike the ViewBag, you can do it using the strongly typed view model.)
Related
I am having a problem returning a partial view from a razor page, my scenario is
I have a partial view which is a form and that has a model. I have 3 forms residing on a single razor pages
Form A post a ModelA
Form B post ModelB
My problem is, i want to handle thier specific post event on the parent Page which is a razor page.
How would i return this partial view
OnPostModelA(ModelA model)
{
if(! ModelState.IsValid)
return Partialview("_CreateModelA", model);
}
Is this possible using razor pages or this is not possible?
I just want to return the partialview with its designated model using ajax.
As you know ,Razor Pages have no equivalent PartialView method on the PageModel. If you do want to invoke different parial views in PageModel method , simply add a PartialView Helper Method in you PageModel:
[NonAction]
public virtual PartialViewResult PartialView(string viewName, object model)
{
ViewData.Model = model;
return new PartialViewResult()
{
ViewName = viewName,
ViewData = ViewData,
TempData = TempData
};
}
Here I use a ViewData.Model to store your model object , let's say your Model type is named as X1Model :
you can use it across the partial views .
Create a simple partial view named as _CreateModelA.cshtml :
#model HelloModel
AAAAA
<div>
#Model.Model.Welcome
</div>
and another partial view named as _CreateModelB.cshtml :
#model HelloModel
BBBBBBBB
<div>
#Model.Model.Welcome
</div>
At last , you can return PartialView in your PageModel:
public class HelloModel : PageModel
{
public X1Model Model { get; set; }
public ActionResult OnGet(int rand = 0)
{
var flag = rand % 2 == 0 ? true : false;
var model = new HelloModel() {
Model = new X1Model {
Welcome = "Hello,world",
}
};
if (flag)
{
return PartialView("_CreateModelA", model);
}
else
{
return PartialView("_CreateModelB", model);
}
}
[NonAction]
public virtual PartialViewResult PartialView(string viewName, object model)
{
// ...
}
}
Here's a screenshot :
However , it is not recommended to put partial view logic in PageModel . Using it in the Page file as below is much nicer:
#if(){
<partial name="" />
}else{
<partial name="" />
}
In asp dotnet core 2.2, Microsoft added a Partial method to the PageModel class that works similar to the PartialView method on the Controller class. It however doesn't allow you to pass ViewData to the view. So, if you need to do that, then you can create your own PartialViewResult like so:
var resultViewData = new ViewDataDictionary<YourModelType>(ViewData, model);
resultViewData[YourViewDataProperty] = yourViewDataValue;
return new PartialViewResult
{
ViewName = "_Branch",
ViewData = resultViewData,
TempData = TempData
};
I have controller called Documents with three action methods:
public ActionResult Save(string returnUrl){
TempData["returnUrl"]=returnUrl;
return View(viewName: "Save");
}
[HttpPost]
public ActionResult Save(string returnUrl){
return Redirect(returnUrl);
}
and
[HttpPost]
public ActionResult Cancel(string returnUrl){
return Redirect(returnUrl);
}
And here's the content of the Save.cshtml view:
#Html.Hidden(TempData["returnUrl"].ToString())
#using (Html.BeginForm){
<!--Some html here-->
<input type="submit" value="Save"/>
}
#using (Html.BeginForm(actionName:"Cancel",controllerName:"Documents")){
<input type="submit" value="Cancel"/>
}
Of course, the above code does not reflect what I need to do in real world but one problem made me strip my code down to this simplest stage. The problem is that the returnUrl argument is null when I call the Cancel action method. Why is that?
In order to post back to FormCollection, inputs associated with the form need to be located inside the <form> tag (except if using the form attribute). In your case where you have 2 forms and need to post back the value of returnUrl, you would need 2 inputs. This will create elements with duplicate id's if using html helpers. A better approach would be to include the value in the form element, for example
Controller
[HttpGet]
public ActionResult Save(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View("Save");
}
[HttpPost]
public ActionResult Save(string returnUrl, MyModel, model)
{
....
}
[HttpPost]
public ActionResult Cancel(string returnUrl, MyModel, model)
{
....
}
View
#using (Html.BeginForm("Save", "Documents", new { ReturnUrl = ViewBag.ReturnUrl })) {
....
#using (Html.BeginForm("Cancel", "Documents", new { ReturnUrl = ViewBag.ReturnUrl })) {
....
I am stuck in a problem, I have retrieved all the value for a drop down in a view bag and want to display them at run time. I have achieved it by using the following code for the controller
[HttpGet]
public ActionResult Index()
{
var categoryList = new PersonalApp();
SelectList catList = new SelectList(categoryList.GetAffinity().ToList(), "ClientName", "AffinityNum");
ViewBag.categoryList = catList;
return View();
}
and the following code for the view
#using (Html.BeginForm("index", "Home"))
{
#Html.DropDownList("categoryList", "Branch Type")
}
It really works but now I want to bind it with a model now. I have use the following code for this:
#Html.DropDownListFor(model=>model.AffinityNum, "categoryList", "BranchType")
But it gives me an error as CategoryList cannot be used as a parameter with the above code. How will I get this resolved as I can have all the values of a dropdown in the categorylist and I can bind it with a model property affinityNum with it as well.
Thanks
Your model should have an IEnumerable<SelectListItem> property that will hold the values:
public class PersonalApp
{
public string AffinityNum { get; set; }
public IEnumerable<SelectListItem> CategoryList { get; set; }
}
that you will populate in your controller action and pass to the view:
[HttpGet]
public ActionResult Index()
{
var model = new PersonalApp();
var categories = categoryList.GetAffinity().ToList();
SelectList catList = new SelectList(categories, "ClientName", "AffinityNum");
model.CategoryList = catList;
return View(model);
}
and finally in your strongly typed view you will use this property to bind the dropdown list to:
#model PersonalApp
...
#Html.DropDownListFor(x => x.AffinityNum, Model.CategoryList, "BranchType")
Please try;
#Html.DropDownListFor(model=>model.AffinityNum, (SelectList)ViewBag.categoryList)
This question should be very simple.. I am trying to pass values in my drop down list in my view to the controller.. I'm not getting errors but it's sending a null value for that property. Please help..
My code is as follows:
Controller:
public ActionResult Create()
{
var list = new []
{
new Room{ RoomID = 1, Building = "FAYARD HALL"},
new Room{ RoomID = 2, Building = "WHATEVER HALL"},
new Room{ RoomID = 3, Building = "TIME SQUARE"},
new Room{ RoomID = 4, Building = "MISSISSIPPI"},
new Room{ RoomID = 5, Building = "NEW YORK"},
};
var selectList = new SelectList(list,"RoomID", "Building");
ViewData["BuildingList"] = selectList;
return View();
}
//
// POST: /Room/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Room room)
{
if (ModelState.IsValid)
{
db.Rooms.Add(room);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(room);
}
MY VIEW:
<div>
#Html.LabelFor(model => model.Building, "Building")
</div>
<div>
#Html.DropDownList("BuildingList", String.Empty)
#Html.ValidationMessageFor(model => model.Building)
</div>
Please help...
Thank you.
Is your drop down populated? Given your code I think you need the following to do so:
#Html.DropDownListFor(model => model.Building, ViewData["BuildingList"])
ie. bind the selected value to the Building property of your Room and use the drop down list from your view model to populate the list.
I'm also not sure this is what your intention is. It seems a bit fishy that you are populating a drop down list with rooms and then based on the selection you are creating a new room.
Edit
Ok I'm going to make things a lot easier for you.
I'll start with your classes. Here is the room I am assuming you're working with:
public class Room
{
public int RoomId { get; set; }
public string Building { get; set; }
}
Now let's do something a bit better than using ViewData. I've created a view model for you. You will populate this with your select list and the item you choose in the view will be bound into the SelectedRoomId when you post the form.
public class ViewModel
{
public int SelectedRoomId { get; set; }
public SelectList RoomOptions { get; set; }
}
Controller
private SelectList GetSelectList()
{
var list = new[]
{
new Room { RoomId = 1, Building = "FAYARD HALL"},
new Room { RoomId = 2, Building = "WHATEVER HALL"},
new Room { RoomId = 3, Building = "TIME SQUARE"},
new Room { RoomId = 4, Building = "MISSISSIPPI"},
new Room { RoomId = 5, Building = "NEW YORK"}
};
return new SelectList(list, "RoomId", "Building");
}
public ActionResult Create()
{
ViewModel viewModel = new ViewModel
{
RoomOptions = GetSelectList()
};
return View(viewModel);
}
[HttpPost]
public ActionResult Create(ViewModel viewModel)
{
if (ModelState.IsValid)
{
// Save here
// create a new room using the SelectedOptionId in the viewModel
return RedirectToAction("Index", "Home");
}
// repopulate the list if something failed
viewModel.RoomOptions = GetSelectList();
return View(viewModel);
}
View
#model PathToYourViewModel.ViewModel
#using (Html.BeginForm())
{
#Html.DropDownListFor(model => model.SelectedRoomId, Model.RoomOptions, "-- select an option --")
<button type="submit">Submit</button>
};
Tried and tested. Good luck!
The model binding takes place with help of the names property in mvc .
In your case the name of your control is BuildingList:
#Html.DropDownList("BuildingList", (SelectList)ViewData["BuildingList"])
Therefore at your controller Action will go as follows:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(FormCollection collection)
{
var selectedValue = collection["BuildingList"];
}
I am a beginner programmer and having trouble with the #Html.DropDownListFor helper...
I am using a General Repository and Unit of Work pattern based off of the tutorial here:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
Here is my code for the Repository:
public class GenericRepository<TEntity> where TEntity : class
{
internal UsersContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(UsersContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public virtual TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
// Delete methods not shown
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
Here is my code for my UnitOfWork class:
public class UnitOfWork : IDisposable
{
private UsersContext context = new UsersContext();
private GenericRepository<UserProfile> userProfileRepository;
private GenericRepository<Lead> leadRepository;
private GenericRepository<UnitedStatesState> unitedStatesStateRepository;
public GenericRepository<UserProfile> UserProfileRepository
{
get
{
if (this.userProfileRepository == null)
{
this.userProfileRepository = new GenericRepository<UserProfile(context);
}
return userProfileRepository;
}
}
public GenericRepository<Lead> LeadRepository
{
get
{
if (this.leadRepository == null)
{
this.leadRepository = new GenericRepository<Lead>(context);
}
return leadRepository;
}
}
public GenericRepository<UnitedStatesState> UnitedStatesStateRepository
{
get
{
if (this.unitedStatesStateRepository == null)
{
this.unitedStatesStateRepository = new GenericRepository<UnitedStatesState>(context);
}
return unitedStatesStateRepository;
}
}
I am trying to use strongly typed views and models in order to pass the selectlist data to the view without using ViewData/ViewBag. From what I understand, the best practice is to do something similar to what I saw here:
validate a dropdownlist in asp.net mvc
I tried following that as closely as possible and this is what I came up with
My View Model looks like this:
public class Lead
{
public int LeadID { get; set; }
public int CompanyID { get; set; }
[Required(ErrorMessage = "Please enter state")]
[Display(Name = "State")]
[MaxLength(2)]
public string State { get; set; }
[Display(Name = "Assigned To")]
public string AssignedTo { get; set; }
[Timestamp]
public Byte[] Timestamp { get; set; }
public virtual Company Company { get; set; }
// IEnumerables for Dropdown Lists passed to views
public IEnumerable<UnitedStatesState> UnitedStatesStates { get; set; }
public IEnumerable<UserProfile> UserProfiles { get; set; }
// Objects passed to views
public Lead lead { get; set; }
}
These IEnumerables for my dropdown lists are then populated in my controller from my database through my repository. The odd part is that I am using these dropdown lists in two different views, Create and Edit. When I use the dropdown lists in the Create view they work perfectly both on the GET and POST ActionResults. When I try and use the same dropdown lists for my Edit view they work for the GET ActionResult (the view loads and the dropdowns work) but when I try to POST them to my Edit ActionResult I get the following error:
{"Value cannot be null.\r\nParameter name: items"} // This is the error as shown in Visual Studio 2012
System.ArgumentNullException: Value cannot be null.
Parameter name: items // This is the error shown in Google Chrome
Below is my Lead Controller with the Edit and Create ActionResults:
public class LeadController : Controller
{
// create instance of Repository Unit of Work
private UnitOfWork unitOfWork = new UnitOfWork();
public ActionResult Create()
{
// Get the current users profile
UserProfile userProfile = UserProfile.GetCurrentUserProfile();
// Creates Dropdown Lists to pass to view
var model = new Lead
{
UnitedStatesStates = unitOfWork.UnitedStatesStateRepository.Get(u => u.StateAbbreviation != null),
UserProfiles = unitOfWork.UserProfileRepository.Get(u => u.CompanyID == userProfile.CompanyID)
};
// Return View
return View(model);
}
[HttpPost]
public ActionResult Create(Lead model)
{
try
{
if (ModelState.IsValid)
{
// Call the current users profile
UserProfile userProfile = UserProfile.GetCurrentUserProfile();
// Create a new lead and apply all attirbutes that were entered
Lead lead = new Lead();
lead.CompanyID = userProfile.CompanyID;
lead.State = model.State;
lead.AssignedTo = model.AssignedTo;
// Add the lead and save the changes. Redirect to Lead Index.
unitOfWork.LeadRepository.Insert(lead);
unitOfWork.Save();
return RedirectToAction("Index");
}
}
catch (DataException)
{
ModelState.AddModelError("", "Unable to save changes. Try again and if the problem persists, see your system administrator.");
}
// Return view if ModelState is not valid
return View();
}
public ActionResult Edit(int id = 0)
{
// Get Users Profile
UserProfile userProfile = UserProfile.GetCurrentUserProfile();
// Check to see if Lead Exists
if (unitOfWork.LeadRepository.GetByID(id) == null)
{
return HttpNotFound();
}
// Creates Dropdown Lists and Gets current lead values to pass to view
var model = new Lead
{
lead = unitOfWork.LeadRepository.GetByID(id),
UnitedStatesStates = unitOfWork.UnitedStatesStateRepository.Get(u => u.StateAbbreviation != null),
UserProfiles = unitOfWork.UserProfileRepository.Get(u => u.CompanyID == userProfile.CompanyID)
};
return View(model);
}
[HttpPost]
public ActionResult Edit(Lead lead)
{
try
{
// Update lead if model state is valid
if (ModelState.IsValid)
{
unitOfWork.LeadRepository.Update(lead);
unitOfWork.Save();
return RedirectToAction("Index");
}
}
// Catch any concurrency exceptions
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var databaseValues = (Lead)entry.GetDatabaseValues().ToObject();
var clientValues = (Lead)entry.Entity;
if (databaseValues.State != clientValues.State)
ModelState.AddModelError("State", "Current value: "
+ databaseValues.State);
if (databaseValues.AssignedTo != clientValues.AssignedTo )
ModelState.AddModelError("Assigned To ", "Current value: "
+ databaseValues.AssignedTo );
ModelState.AddModelError(string.Empty, "The record you attempted to edit "
+ "was modified by another user after you got the original value. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again. Otherwise click the Back to List hyperlink.");
lead.Timestamp = databaseValues.Timestamp;
}
catch (DataException)
{
//Log the error (add a variable name after Exception)
ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
}
// Return View if Model State is not valid
return View(lead);
}
The POST Edit ActionResult includes code to catch concurrencies which I created following the tutorial shown here:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application
Below is my view for Create (this works perfectly):
#model SolarToolbase.Models.Lead
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<div>
<div>
#Html.LabelFor(model => model.State)
#Html.DropDownListFor(model => model.State, new SelectList(Model.UnitedStatesStates, "StateAbbreviation", "UnitedStatesStateName"),"Choose State")<br />
#Html.ValidationMessageFor(model => model.State)
</div>
<div>
#Html.LabelFor(model => model.AssignedTo)
#Html.DropDownListFor(model => model.AssignedTo, new SelectList(Model.UserProfiles, "FullName", "FullName"),"Choose User")<br />
#Html.ValidationMessageFor(model => model.AssignedTo)
</div>
<p>
<input type="submit" value="Create" />
</p>
</div>
}
Below is my view for Edit(this throws the aforementioned errors when I hit the submit button. I inserted a comment below to show the line that the error is being thrown from):
#model SolarToolbase.Models.Lead
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.lead.LeadID)
#Html.HiddenFor(model => model.lead.Timestamp)
<div>
<div>
#Html.LabelFor(model => model.lead.State)
#Html.DropDownListFor(model => model.lead.State, new SelectList(Model.UnitedStatesStates, "StateAbbreviation", "UnitedStatesStateName"))<br /> // Error thrown from this line
#Html.ValidationMessageFor(model => model.lead.State)
</div>
<div>
#Html.LabelFor(model => model.lead.AssignedTo)
#Html.DropDownListFor(model => model.lead.AssignedTo, new SelectList(Model.UserProfiles, "FullName", "FullName"))<br />
#Html.ValidationMessageFor(model => model.lead.AssignedTo)
</div>
<p>
<input type="submit" value="Save" />
</p>
</div>
}
I apologize in advance for posting so much code, I just honestly don't know where this error is coming from and I've beat my head against the wall trying to figure it out for about 4 hours now. Free virtual high fives and good karma for anyone that can help.
Thanks!
In the case of a POST to both the Create and Edit actions, when there's an error or the ModelState is invalid, you catch any exceptions and return the default View with the constructed Lead view model, created and populated by the model binder.
In the Edit POST action though, if there is an error condition, you return the lead object to the View as its Model. Note that the UnitedStatesStates and the UserProfiles properties are not repopulated upon a POST. You populate them in the GET actions, but you have to do that in the POST actions too. You need to be careful that whatever model you are sending to the view is in proper shape, and it has all expected members populated.
Also notice your view model is of type Lead which has a property called lead. That's a code smell there; I wouldn't have a view model class having a reference to an instance of its own class. It's causing confusion for you already. I'd have Lead be LeadViewModel to be explicit and just have it hold all the properties and values it needs when going to and from the views, with no lead property.
In your Edit view, you're referencing the model's properties as model.lead.State for example, but in the Create view you're referencing the parent-level properties, as in model.State. But in the Edit view, when it comes to the SelectListItems you're using Model.UnitedStatesStates instead of Model.lead.UnitedStatesStates. As I said I'd do away with this pattern and do what the Create view does now, not having a child lead property at all. Just do model.State for example, for all properties and in both views.
So make sure your collection properties are populated whenever you pass the model to the view, as in
[HttpPost]
public ActionResult Edit(Lead lead)
{
try
{
// Update lead if model state is valid
if (ModelState.IsValid)
{
unitOfWork.LeadRepository.Update(lead);
unitOfWork.Save();
return RedirectToAction("Index");
}
}
// Catch any concurrency exceptions
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var databaseValues = (Lead)entry.GetDatabaseValues().ToObject();
var clientValues = (Lead)entry.Entity;
if (databaseValues.State != clientValues.State)
ModelState.AddModelError("State", "Current value: "
+ databaseValues.State);
if (databaseValues.AssignedTo != clientValues.AssignedTo )
ModelState.AddModelError("Assigned To ", "Current value: "
+ databaseValues.AssignedTo );
ModelState.AddModelError(string.Empty, "The record you attempted to edit "
+ "was modified by another user after you got the original value. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again. Otherwise click the Back to List hyperlink.");
lead.Timestamp = databaseValues.Timestamp;
}
catch (DataException)
{
//Log the error (add a variable name after Exception)
ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
}
// Return View if Model State is not valid
/////////// CHANGES HERE
lead.UnitedStatesStates = unitOfWork.UnitedStatesStateRepository.Get(u => u.StateAbbreviation != null),
lead.UserProfiles = unitOfWork.UserProfileRepository.Get(u => u.CompanyID == userProfile.CompanyID)
return View(lead); // pass the model to the view for Create and Edit POST actions when there's an error
}
Do that in both POST actions. If there's an error, the view will be instantiated by the action with a populated model. Also change the Edit view to work just like the Create view, and not use the Lead view model's lead property. Presumably that will take care of any null reference exceptions in the views.