MVC3 ModelState invalid on HttpPost - asp.net-mvc-3

I'm having an issue where the model is valid when it creates the HttpGet page, but invalid (some properties are null) on the HttpPost.
Here is my call to open the page:
var quote = new Quote
{
Agency = assignedAgency,
Insured = insured,
RiskAddress = insured.Address
};
db.Quotes.Add(quote);
db.SaveChanges();
return RedirectToAction("Edit", "Quote", quote.QuoteID);
And the HttpGet ActionResult - At this point, the model is valid. Insured and Agency properties are populated and have their respective Address properties populated as well.
[HttpGet]
public ActionResult Edit(int quoteID)
{
var model = db.Quotes.Find(quoteID);
return View(model);
}
And the HttpPort ActionResult - At the entry of this method, the Insured, Agency and their respective Address properties are null, causing an invalid Model state.
[HttpPost]
public ActionResult Edit(Quote model)
{
if (ModelState.IsValid)
{
if (model.SubCosts > 0)
{
model.InsuredSubs = true;
model.SubClassCode = "95626 - Subcontractor - TEST CLASS";
model.SubClassExposure = model.SubCosts;
}
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Edit", new { quoteID = model.QuoteID });
}
return View(model);
}
And the HTML:
#model DomaniOnline.Models.DomaniData.Quote
#{
ViewBag.Title = "Classification";
}
<h2>General Liability Classification</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
#Html.HiddenFor(model => model.QuoteID)
#Html.HiddenFor(model => model.Insured)
#Html.HiddenFor(model => model.Insured.Address)
#Html.HiddenFor(model => model.RiskAddress)
#Html.HiddenFor(model => model.Agency)
#Html.HiddenFor(model => model.Agency.Address)
.
.
.
.
<p>
#Html.ActionLink("Back to Insured Info", "Edit", "Insured", new { insuredID = Model.Insured.InsuredID }, null)
<input type="submit" value="Save & Continue" />
</p>
</fieldset>
}
I'd like to point out that the "Back to Insured Info" ActionLink at the bottom of the page works just fine and is able to supply the correct model.Insured.InsuredID. But for some reason, when submitting the form to the HttpPost, any property that is of a custom datatype is null. the Html.HiddenFor<> were added as a test while trying to get this to work.
edit: Classes:
public class Quote
{
public virtual int QuoteID { get; set; }
public virtual Address RiskAddress { get; set; }
public virtual Agency Agency { get; set; }
public virtual Insured Insured { get; set; }
public virtual DateTime PropEffDate { get; set; }
public virtual bool InsuredSubs { get; set; }
public virtual int SubCosts { get; set; }
.
.
.
}
public class Address
{
[Required]
public virtual string StreetAddress { get; set; }
[Required]
public virtual string City { get; set; }
[Required]
public virtual string State { get; set; }
[Required]
public virtual string ZipCode { get; set; }
}
public class Insured
{
public virtual int InsuredID { get; set; }
[Required]
public virtual string Name { get; set; }
[Required]
public virtual Address Address { get; set; }
public virtual string DBA { get; set; }
[Required]
public virtual string BusinessType { get; set; }
public virtual string Phone { get; set; }
}

Your problem is that your using HiddenFor on complex types. This only knows how to render simple types.
I would suggest either only persisting the ID's of those objects or using the Html.Serialize helper function from the MVC Futures project to serialize your account and insured objects, then deserialize them in your controller.
http://weblogs.asp.net/shijuvarghese/archive/2010/03/06/persisting-model-state-in-asp-net-mvc-using-html-serialize.aspx
Persisting the ID's should just be the InsuredID, from which you can re-get the Insured and it's address on postback. The same with Agency.

Related

Razor - Passing DropDownList selected value to Controller via Ajax.BeginForm

I am trying to pass the dropdownlist value to my controller so I can add the values to a list of paycodes but I keep getting a "Value cannot be null." error. My paycode list has many items in it. Not sure what is null or wrong here...
INNER EXCEPTION
Value cannot be null.
Parameter name: source
VIEW
<!-- products input-->
<div class="control-group col-lg-6">
<label class="control-label">Product</label>
<div class="controls">
#using (Ajax.BeginForm("AddPayCode", "Referral",
new AjaxOptions()
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "PayCodes",
Url = Url.Action("AddPayCode", "Referral")
}))
{
#Html.ValidationSummary()
#Html.DropDownListFor(model => model.SelectedPayCode, new SelectList(Model.PayCodes.ToList(), "Id", "Description"), "- Select -")
<input type="submit" value="Add" />
}
</div>
</div>
CONTROLLER
[HttpPost]
public void AddPayCode(ReferralModel model)
{
var test = model.SelectedPayCode;
//TODO: Add to model.Referral.PayCodes list and return list of selected items
}
MODEL
public class ReferralModel
{
public Customer Customer { get; set; }
public Employee Employee { get; set; }
public List<PayCode> PayCodes { get; set; }
public int SelectedPayCode { get; set; }
public Referral Referral { get; set; }
}
DOMAIN OBJECT
public class Referral
{
[Key]
public int Id { get; set; }
public int CustomerId { get; set; }
public int EmployeeId { get; set; }
public decimal Total { get; set; }
public virtual List<PayCode> PayCodes { get; set; }
public virtual Customer Customer { get; set; }
public virtual Employee Employee { get; set; }
}
Objective :
User chooses a paycode from the dropdown and clicks "Add" Paycode is
added to the referral PayCodes list
Controller returns the list of paycodes selected to the view (not
yet implemented)
The issue was the controller is missing the property of the dropdown list.
[HttpPost]
public void AddPayCode(ReferralModel model, ** string SelectedPayCode ** <-- missing)
{
var test = SelectedPayCode;
//TODO: Add to model.Referral.PayCodes list and return list of selected items
}

Populating a dropdownlist with values

Hello I'm trying to make my dropdownlist to work but it seems harder than expected. I have 3 domain classes Member, Rental and Movie. My Idea was to make a dropdownlist that will show a specific users rented movies and when I select a movie in the dropdownlist and submit it I will get back the selected movie and I can set bool IsInStock to true.
So I made a viewmodel and a controller action but would like some help how to go forward with this. Now I get a dropdownlist with the users "Jan" rented movies but when I klick submit I would like to get the values back in order to set the IsInStock to true. I know I will need method to handle the POST values but I'm trying to make this work first.
public class Member
{
public virtual int MemberId { get; set; }
public virtual string LastName { get; set; }
public virtual List<Rental> Rentals { get; set; }
}
public class Rental
{
public virtual int RentalId { get; set; }
public virtual int MovieId { get; set; }
public virtual Movie Movie { get; set; }
public virtual int MemberId { get; set; }
public virtual Member Member { get; set; }
}
public class Movie
{
public virtual int MovieId { get; set; }
public virtual string Name { get; set; }
public virtual bool IsInStock { get; set; }
}
public class RentalsViewModel
{
// Need something here.
public IEnumerable<SelectListItem> RentedMovies { get; set; }
}
public ActionResult ReturnMovie()
{
var rentedmovies = db.Rentals.Where(r => r.Member.Name == "Jan").ToList();
var model = new RentalsViewModel()
{
RentedMovies = rentedmovies.Select(x => new SelectListItem
{
Value = x.MovieId.ToString(),
Text = x.Movie.Name
})
};
return View(model);
}
// In the View
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.RentedMovies, //Something here);
<p>
<input type="submit" value="Save" />
</p>
}
First parameter here:
#Html.DropDownListFor(x => x.RentedMovies, //Something here);
must be int, because you will send this value to the server:
public virtual int MovieId { get; set; }
So, your example could look like:
#Html.DropDownListFor(x => x.RentedMovieId, Model.RentedMovies);
(Add property RentedMovieId to RentalsViewModel)

DropDownList Result not being Returned

I am trying to create an Item that has an ItemType coming from another table. I am unable to get back the actual Type object from the Create page. This is the code I have tried:
Models:
public class ItemType {
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Item> Item{ get; set; }
}
public class Item {
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ItemType ItemType { get; set; }
}
In the ItemController, this is my create code:
public ActionResult Create() {
var itemTypeRepo = new ItemTypeRepository(context);
ViewBag.ItemTypes = new SelectList(itemTypeRepo.GetAll(), "ID", "Name");
return View();
}
[HttpPost]
public ActionResult Create(Item item) {
if (ModelState.IsValid) {
context.Items.Add(item);
context.SaveChanges();
return RedirectToAction("Index");
}
return View(item);
}
In my Create.cshtml view I have tried:
<div class="editor-field">
#Html.DropDownList("ItemType", String.Empty)
#Html.ValidationMessageFor(model => model.ItemType)
</div>
This returns no value at all and throws an error "The value 'X' is invalid." Where X is the ID of the ItemType I selected.
And
<div class="editor-field">
#Html.DropDownListFor(x => x.ItemType.Id, (SelectList)ViewBag.ItemType)
#Html.ValidationMessageFor(model => model.ItemType)
</div>
This creates a stub ItemType object with the correct ID but does not insert it into the database since the object is not fully loaded. If I look at ModelState object, I find that there is an error that the Name field is missing from ItemType object.
I also attempted to solve the problem using the second .cshtml code and adding this code:
public ActionResult Create(Item item) {
item.ItemType = context.ItemTypes.Find(item.ItemType.Id);
if (ModelState.IsValid)
This does not change the value of ModelState.IsValid from false even through it should.
What do I need to do to get this to work?
You should add a property ItemTypeId to your Item entity so that it acts as a foreign key.
public class Item
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public int ItemTypeId { get; set; }
[ForeignKey("ItemTypeId")]
public virtual ItemType ItemType { get; set; }
}
You can then use that property for the dropdownlist:
<div class="editor-field">
#Html.DropDownListFor(x => x.ItemTypeId, (SelectList)ViewBag.ItemType)
#Html.ValidationMessageFor(model => model.ItemType)
</div>

MVC 3 Selectlist/DropDownList Is Not Updating My Model

I hope someone can help with this one. I have three Model classes like this:
public class Provider
{
public Guid ProviderId { get; set; }
public string Name { get; set; }
public Guid LocationId { get; set; }
public virtual Location Location { get; set; }
}
public class Location
{
public Guid LocationId { get; set; }
public string NameOrCode { get; set; }
public string Description { get; set; }
public string StreetNumber { get; set; }
public string StreetAddress1 { get; set; }
public string StreetAddress2 { get; set; }
public string City { get; set; }
public int? StateId { get; set; }
public string Zip { get; set; }
public string ContactPhone { get; set; }
public virtual State State { get; set; }
}
public class State
{
public int StateId { get; set; }
public string Name { get; set; }
public string Abbreviation { get; set; }
}
As you can see, a Provider has a Location (separate class for reuse elsewhere), and a Location has a State (which is null until selected).
My Controller looks like this for my Create methods:
public class ProviderController : BaseController
{
private SetupContext db = new SetupContext();
// other CRUD methods ...
//
// GET: /Provider/Create
public ActionResult Create()
{
Location location = new Location()
{
LocationId = Guid.NewGuid(),
NameOrCode = Resources.BillingLocation,
Description = Resources.BillingLocationDescription
};
Provider provider = new Provider()
{
ProviderId = Guid.NewGuid(),
LocationId = location.LocationId,
Location = location
};
ViewBag.StateId = new SelectList(db.States, "StateId", "Name", provider.Location.StateId);
return View(provider);
}
//
// POST: /Provider/Create
[HttpPost]
public ActionResult Create(Provider provider)
{
if (ModelState.IsValid)
{
db.Locations.Add(provider.Location);
db.Providers.Add(provider);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.StateId = new SelectList(db.States, "StateId", "Name", provider.Location.StateId);
return View(provider);
}
// other CRUD methods ...
}
Finally, my View looks like this:
<div class="editor-label">
#Html.LabelFor(model => model.Location.StateId, #Resources.Location_State_Display_Name)
</div>
<div class="editor-field">
#Html.DropDownList("StateId", #Resources.ChooseFromSelectPrompt)
#Html.ValidationMessageFor(model => model.Location.StateId)
</div>
My problem is that the state the user selects in the DropDownList never gets set on my Model on the Create POST. I have similar code in my Edit View and the state is populated correctly in that View (that is, the state associated with an existing Provider.Location shows selected in the DropDownList for the user to edit if desire), but in both the Create and the Edit Views the selection made by the user is never registered in my Model (specifically the Provider.Location.StateId) coming in from the POST.
Looking at the HTML produced I see this:
<div class="editor-label">
<label for="Location_StateId">State/Territory</label>
</div>
<div class="editor-field">
<select id="StateId" name="StateId"><option value="">[Choose]</option>
<option value="1">Alabama</option>
<option value="2">Alaska</option>
<!-- more options ... -->
</select>
<span class="field-validation-valid" data-valmsg-for="Location.StateId" data-valmsg-replace="true"></span>
</div>
I suspect I need to somehow convey the Location.StateId relationship instead of just StateId as I see above but I can't figure out the correct syntax to do that. I've tried changing my ViewBag dynamic property to Location_StateId like this:
ViewBag.Location_StateId = new SelectList(db.States, "StateId", "Name", provider.Location.StateId);
And the DropDownList in my View like this:
#Html.DropDownList("Location_StateId", #Resources.ChooseFromSelectPrompt)
I figured then perhaps that notation would work because the label beside my DropDownList was rendered as:
<div class="editor-label">
<label for="Location_StateId">State/Territory</label>
</div>
This attempt did not work. Can you help me out?
Thanks in advance.
#Html.DropDownList("Location.StateId", #Resources.ChooseFromSelectPrompt)
Also the following line doesn't do anything useful:
ViewBag.StateId = new SelectList(db.States, "StateId", "Name", provider.Location.StateId);
You are assigning a SelectList to something that is supposed to be a scalar property. You probably wanted to pass the collection as ViewBag:
ViewBag.States = new SelectList(db.States, "StateId", "Name", provider.Location.StateId);
and then in the view:
#Html.DropDownList("Location.StateId", (SelectList)ViewBag.States)

When using ASP.NET MVC 3 with EF 4.1 Code First, I can only edit the main table, what am I doing wrong?

A brief description of what I am doing. I am creating a rather crude IS Asset tracking database using ASP MVC 3 and EF Code First approach. I can create a new asset. I can view the details on an asset. I can even display the edit view and edit the AssetTag. However the record will not update any of the other fields. If I edit the LocationName for instance. It will act like it is posting and return me to the Index view, but the record never actually posts the change.
I have created the Model below
public class AssetModel
{
public int id { get; set; }
public string AssetTag { get; set; }
public virtual Location Location { get; set; }
public virtual Hardware Hardware { get; set; }
public virtual Software Software { get; set; }
public virtual User User { get; set; }
}
public class Location
{
public int LocationId { get; set; }
public string LocationName { get; set; }
}
public class Hardware
{
public int HardwareId { get; set; }
public string Manufacturer { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
public class Software
{
public int SoftwareId { get; set; }
public string PublisherName { get; set; }
public string SoftwarePackageName { get; set; }
public string SoftwarePackageVersion { get; set; }
public string SerialNumber { get; set; }
public bool IsVolumeLicense { get; set; } // as in "Yes this is a Vol. Lic. Agreement"
public LicenseAgreement LicenseAgreement { get; set; }
}
public class LicenseAgreement
{
public int LicId { get; set; }
public string VolumeLicenseAgreementCompany { get; set; }
public string AgreementIdentifier { get; set; }
public DateTime VolumeLicenseStartDate { get; set; }
public DateTime VolumeLicenseExpirationDate { get; set; }
public Int16 NumberOfLicenses { get; set; }
}
public class User
{
// may remove this at some time and pull from Active Directory.
// for now we take the easy route.
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
I have this DbDataSet that uses the AssetModel above:
public class AssetContext : DbContext
{
public DbSet<AssetModel> Assets { get; set; }
}
In my AssetController I have this for Edit:
public ActionResult Edit(int id)
{
AssetModel assetmodel = db.Assets.Find(id);
return View(assetmodel);
}
//
// POST: /Asset/Edit/5
[HttpPost]
public ActionResult Edit(AssetModel assetmodel)
{
if (ModelState.IsValid)
{
db.Entry(assetmodel).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(assetmodel);
}
And here is the Edit.cshtml
#model ISHelpDesk.Models.AssetModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm("Edit", "Asset")) {
#Html.ValidationSummary(true)
<fieldset>
<legend>AssetModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.AssetTag)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AssetTag)
#Html.ValidationMessageFor(model => model.AssetTag)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Location.LocationName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Location.LocationName)
#Html.ValidationMessageFor(model => model.Location.LocationName)
</div>
</fieldset>
<p><input type="submit" value="Save"</p>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
your AssetContext should be
public class AssetContext : DbContext
{
public DbSet<AssetModel> Assets { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<Hardware> Hardwares { get; set; }
public DbSet<Software> Softwares { get; set; }
public DbSet<LicenseAgreement> LicenseAgreements { get; set; }
public DbSet<User> Users { get; set; }
}
this is registering each of your classes as a table in the DbContext, what you had before showed your DbContext consists only of AssetModel
Update: The issue may be that when you get to the post method of the edit, the Asset is no longer associated with the database Asset it was originally loaded from, have a go at changing it to
public ActionResult Edit(int id)
{
AssetModel assetmodel = db.Assets.Find(id);
return View(assetmodel);
}
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
AssetModel assetmodel = db.Assets.Find(id);
if (TryUpdateModel(assetmodel))
{
db.SaveChanges();
return RedirectToAction("Index");
}
return View(assetmodel);
}
Obviously this may not be the behaviour you want I'm just trying to see if you can get some changes persisted and go from there
Your Model classes should extend DbContext:
public class AssetModel :DbContext{}

Resources