I have a view model object that contains a List of another model object type. When the user does a query on the page, if the List returned contains more than 300 records we want to use paging to keep the loading time down (some search results can return more than 14k records). The paging plugin we're using can be found here.
Once the results have been displayed on the page, the user has the ability to click a check box next to specific results, type in some information in an input text box, hit submit, and have the selected records edited with the information from the text box.
Since we needed to use an IPagedList<> in order to enable paging, however, when you hit submit (and before the page even hits the controller) we get the following error:
Cannot create an instance of an interface.
View Model
These are the two list objects that we use for paging. The zipCodeTerritory object holds the results of the query. The pagedTerritoryList is used to display only the results on the specific page the user is on.
//Paging List objects
public IPagedList<ZipCodeTerritory> pagedTerritoryList { get; set; }
public List<ZipCodeTerritory> zipCodeTerritory { get; set; }
public IPagedList PagingMetaData { get; set; }
Controller
This is our basic search. The .ToPagedList method is used to specify what range of results we want to display and place them in the pagedTerritoryList object.
//set Paged List counter variables
int pageNumber = page ?? 1;
int pageSize = 300;
//Determine if Territory present?
if (string.IsNullOrWhiteSpace(search.searchTerritory))
{
//State Code ONLY search
search.zipCodeTerritory = (from z in db.ZipCodeTerritory
where z.StateCode.Equals(search.searchState)
select z).ToList();
}
else if(string.IsNullOrWhiteSpace(search.searchState))
{
//Territory ONLY search
search.zipCodeTerritory = (from z in db.ZipCodeTerritory
where z.IndDistrnId.Equals(search.searchTerritory)
select z).ToList();
}
else
{
//Territory AND state search
search.zipCodeTerritory = (from z in db.ZipCodeTerritory
where z.IndDistrnId.Equals(search.searchTerritory) &&
z.StateCode.Equals(search.searchState)
select z).ToList();
}
//Convert list to IPagedList for pagining on Index
search.pagedTerritoryList = search.zipCodeTerritory.ToPagedList(pageNumber, pageSize);
//Set Paged List objects
search.PagingMetaData = new StaticPagedList<ZipCodeTerritory>(search.zipCodeTerritory, pageNumber, pageSize,
search.zipCodeTerritory.Count).GetMetaData();
return View(search);
View
This is the form that displays the search results. If a user checks the check box, then hits either clone or delete buttons, the results are supposed to be posted back to the controller's Update method and appropriate edits or deletes performed. The information the user wants to overlay in an edit are input into the newTerritory/Description/etc fields in the form (above the table).
Regarding the #Html.PagedListPager I found I had to pass back to the index method the same search criteria from the page, thus the excessive amount of parameters in the RouteValueDictionary.
#if (Model.zipCodeTerritory.Count > 0)
{
using (Html.BeginForm("Update", "ZipCodeTerritory", FormMethod.Post))
{
#Html.HiddenFor(model => model.searchZip)
#Html.HiddenFor(model => model.searchDate)
#Html.HiddenFor(model => model.searchState)
<div id="cloneBox">
<div id="rw1">
#Html.LabelFor(model => model.newTerritory)
#Html.TextBoxFor(model => model.newTerritory, new { style = "width: 30px;padding-left:10px;", maxLength = 3 })
#Html.LabelFor(model => model.newDescription)
#Html.TextBoxFor(model => model.newDescription, new { style = "width: 250px;padding-left:10px;", maxLength = 30 })
#Html.LabelFor(model => model.newEffectiveDate)
#Html.TextBoxFor(model => model.newEffectiveDate, new { style = "width: 80px;padding-left:10px;" })
<div id="rw2" style="padding-top: 10px;">
#Html.LabelFor(model => model.newChannelCode)
#Html.DropDownListFor(model => model.newChannelCode, Model.ChannelCodes, " ")
#Html.LabelFor(model => model.newStateCode)
#Html.DropDownListFor(model => model.newStateCode, Model.StateCodes, " ")
</div>
</div>
</div>
<br/>
<div id="buttonDiv">
<button type="submit" id="CloneButton" name="button" value="clone">Apply New Data</button>
<button type="submit" id="deleteButton" name="button" value="delete">Delete Selected Items</button>
</div>
#*Display paging only if necessary*#
if (Model.pagedTerritoryList.Count >= 300)
{
<div id="pagingDiv">
#Html.PagedListPager(new StaticPagedList<Monet.Models.ZipCodeTerritory>(Model.zipCodeTerritory, Model.PagingMetaData) ,
Page => Url.Action("Index", new RouteValueDictionary()
{
{ "Page", Page},
{ "searchZip", Model.searchZip },
{ "searchActiveOnly", Model.searchActiveOnly },
{ "searchDate", Model.searchDate },
{ "searchState", Model.searchState },
{ "searchTerritory", Model.searchTerritory },
{ "searchChannel" , Model.searchChannelCode }
}), PagedListRenderOptions.DefaultPlusFirstAndLast)
</div>
}
<table id="thetable" class="tablesorter" >
<thead>
<th>#Html.CheckBox("SelectAll")</th>
<th>State</th>
<th>Territory</th>
<th>Zip</th>
<th>Description</th>
<th>Effective</th>
<th>End Date</th>
<th>Last Update Date</th>
<th>Channel</th>
<th></th>
</thead>
<tbody id="tableBody">
#for (int i = 0; i < Model.pagedTerritoryList.Count; i++)
{
<tr id="#(Model.lastEditedId == Model.pagedTerritoryList[i].Id ? "lastEdit" : "")">
<td>
#Html.CheckBoxFor(model => model.pagedTerritoryList[i].Update)
#Html.HiddenFor(model => model.pagedTerritoryList[i].Update)
</td>
<td>
#Html.DisplayFor(model => model.pagedTerritoryList[i].StateCode)
#Html.HiddenFor(model => model.pagedTerritoryList[i].StateCode)
</td>
<td>
#Html.DisplayFor(model => model.pagedTerritoryList[i].IndDistrnId)
#Html.HiddenFor(model => model.pagedTerritoryList[i].IndDistrnId)
</td>
<td>
#Html.DisplayFor(model => model.pagedTerritoryList[i].ZipCode)
#Html.HiddenFor(model => model.zipCodeTerritory[i].ZipCode)
</td>
<td>
#Html.DisplayFor(model => model.pagedTerritoryList[i].DrmTerrDesc)
#Html.HiddenFor(model => model.pagedTerritoryList[i].DrmTerrDesc)
</td>
<td>
#Html.DisplayFor(model => model.pagedTerritoryList[i].EffectiveDate)
#Html.HiddenFor(model => model.pagedTerritoryList[i].EffectiveDate)
</td>
<td>
#if (Model.pagedTerritoryList[i].EndDate.Date != DateTime.MaxValue.Date)
{
#Html.DisplayFor(model => model.pagedTerritoryList[i].EndDate)
#Html.HiddenFor(model => model.pagedTerritoryList[i].EndDate)
}
</td>
<td>
#Html.DisplayFor(model => model.pagedTerritoryList[i].LastUpdateDate)
#Html.HiddenFor(model => model.pagedTerritoryList[i].LastUpdateDate)
</td>
<td>
#Html.DisplayFor(model => model.pagedTerritoryList[i].ChannelCode)
#Html.HiddenFor(model => model.pagedTerritoryList[i].ChannelCode)
</td>
#if (ViewBag.SecurityLevel >= 4)
{
<td>
#Html.ActionLink("Edit", "Edit", new
{
id = Model.zipCodeTerritory[i].Id,
searchZip = Model.searchZip,
searchActiveOnly = Model.searchActiveOnly,
searchDate = Model.searchDate,
searchState = Model.searchState,
searchTerritory = Model.searchTerritory,
searchChannelCode = Model.searchChannelCode
})
#Html.HiddenFor(model => model.zipCodeTerritory[i].Id)
</td>
}
</tr>
}
</tbody>
</table>
}
}
EDIT
Per the comment below, here is the signature for the method the form is posting to. It contains an instance of the ZipCodeIndex that gets loaded on the page originally, plus the text from the button to determine whether we're doing a clone or delete
[HttpPost]
public ActionResult Update(ZipCodeIndex updateZip, string button)
{
Second Edit
Tried the method from this question but still receiving the original error message ("cannot create instance of an interface").
I was able to completely hack my way out of this, however I don't think it's the best solution. Would love it if someone could provide a better answer however I'll post this up here in the meantime.
Since the IPagedList object was built to hold a specific range of the List<> I just made a display property on my view model and used this on my view. This list, not the IPagedList gets posted back to the controller for the updates, so no interface weirdness occurs.
View Model
//Paging List objects
public IPagedList<ZipCodeTerritory> pagedTerritoryList { get; set; }
public List<ZipCodeTerritory> zipCodeTerritory { get; set; }
public List<ZipCodeTerritory> displayForPaging { get; set; }
Controller
//Convert list to IPagedList for pagining on Index
search.pagedTerritoryList = search.zipCodeTerritory.ToPagedList(pageNumber, pageSize);
search.displayForPaging = search.pagedTerritoryList.ToList();
View
<td>
#Html.CheckBoxFor(model => model.displayForPaging[i].Update)
#Html.HiddenFor(model => model.displayForPaging[i].Update)
</td>
<td>
.
.
I've found that models with interfaces as properties will need to have a ModelBinder created to determine which implementation to use.
ASP.Net MVC Custom Model Binding explanation
Basically, your binder will parse your model for you, determining which type of IPagedList<> to implement.
Related
I want to update only a single value from a row on click of the update button against a specific ID, the id is available at controller but the updated value field is not available so when I press the update button controller updated the database but store a null value in DB.
//View Code
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(m => item.Id)
</td>
<td>
#Html.DisplayFor(m => item.TagName)
</td>
<td>
#Html.DisplayFor(m => item.TagCategory)
</td>
<td>
#Html.TextBoxFor(m => item.TagValue, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
#Html.ActionLink("Update", "Update", new { id = item.Id}, new { htmlAttributes = new { #class = "form-control" } })
</td>
</tr>
}
</tbody>
//Controller Code
public ActionResult Update([Bind(Include = "Id,TagValue") ]Tag tags)
{
var data = db.Tags.Where(x => x.Id == tags.Id).FirstOrDefault();
if (data!=null)
{
data.TagValue = tags.TagValue;
db.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
So right now your update button makes a GET request. The database is storing null because when you create the ActionLink you are not passing in the TagValue as a parameter because with GET requests the variables need to be passed in the url. You could add the TagValue as a parameter like this
#Html.ActionLink("Update", "Update", new { id = item.Id, TagValue = item.TagValue }, new { htmlAttributes = new { #class = "form-control" } })
I don't think this will work the way you want though because I am assuming you want the user to be able to change the value of TagValue and then hit update to save the value. What this solution does is it just grabs the original value of whatever TagValue is.
If you want to be able to edit the TagValue and then submit you could change your html to look like this
<tbody>
#foreach (var item in Model)
{
<tr>
<form action="#Url.Action("Update")" method="post">
<td>
#Html.HiddenFor(m => item.Id, new { #Name = "Id" })
#Html.DisplayFor(m => item.Id)
</td>
<td>
#Html.DisplayFor(m => item.TagName)
</td>
<td>
#Html.DisplayFor(m => item.TagCategory)
</td>
<td>
#Html.TextBoxFor(m => item.TagValue, new { #class = "form-control", #Name="TagValue" })
</td>
<td>
<input type="submit" value="Update" />
</td>
</form>
</tr>
}
This will create a form for every Tag you have and then generate a POST request when you hit submit.
vikscool's comment is also another good solution you could use.
You just need to Bind your View with The table from which you are sending and receiving data and you can do it with Html Begin Forms
I've seen a few very similar posts on here regarding iterating through a list of models and creating a form within each iteration, but nothing has led me to successfully POST back a populated model.
My goal is to present an inline form for each iteration of the model and allow users to make changes and save the edits for that specific model when the associated form gets submitted. I'm still fairly new to ASP.NET MVC so if there is a better way to go about this, please feel free to offer suggestions.
Thanks in advance for any help with this!
View
#model List<POSGuys.Option>
#{
var options = Model.OrderBy(i => i.OptionEndOfLife).ToList();
}
#for (int i = 0; i < options.Count(); i++)
{
using (Html.BeginForm("Save", "Option", FormMethod.Post ))
{
<tr style="#(options[i].OptionEndOfLife ? "color:#777" : "")">
#Html.HiddenFor(model => options[i].OptionID)
<td>#options[i].ItemNumber</td>
<td width="100"><img #Html.Raw(POSGuys.Controllers.Shims.Resize("/content/images/catalog/" + options[i].image, 200, 200, rescale: 2)) /></td>
<td>#Html.EditorFor(model => options[i].OptionName)</td>
<td>#Html.EditorFor(model => options[i].PGPrice)</td>
<td>#Html.EditorFor(model => options[i].OptionsMSRP)</td>
<td>#Html.EditorFor(model => options[i].Cost)</td>
<td>#Html.EditorFor(model => options[i].Description)</td>
<td>#Html.EditorFor(model => options[i].Rank)</td>
<td>#Html.EditorFor(model => options[i].Standard)</td>
<td><input type="submit" value="Save" class="btn btn-warning" /></td>
</tr>
}
}
Controller
[HttpPost]
public ActionResult Save(Option option)
{
var opt = StoreDatabase.Options.Find(option.OptionID);
if (opt != null)
{
StoreDatabase.Entry(opt).CurrentValues.SetValues(option);
StoreDatabase.SaveChanges();
}
return RedirectToAction("EditList", option.ProductID);
}
You just need to make sure that your code is generating the input field's with name values same as the property names of the Option class and model binding will work.
#for (int i = 0; i < options.Count(); i++)
{
using (Html.BeginForm("Save", "Option", FormMethod.Post))
{
#Html.EditorFor(model => options[i].OptionName,null,"OptionName")
#Html.EditorFor(model => options[i].PGPrice,null, "PGPrice")
<input type="submit" />
<br />
}
}
Now when you submit the form, the Default model binder will be able to map the form field values to the properties of the Option class object.
Much easier solution - create partial view.
If you have your main view as List your partial should be just Model and everything will work like it should.
I have a UserManager page where an admin is able to get all the accounts that have currently been registered, delete accounts, update accounts, and register new accounts all from the same page.
I have a controller which reutrns all the users in db.users in a list.
public ActionResult UserManager()
{
if (User.IsInRole("1"))
{
var db = new ReviewLogsTestDBEntities();
return View(db.Users.ToList());
}
else
{
return RedirectToAction("Index", "Home");
}
}
That's working fine, and I have the delete working.
The problem comes when I want to get a registration form on the same page.
This what my view Looks like right now:
#model IEnumerable<ReviewLogs.User>
#{
ViewBag.Title = "UserManager";
}
<h2>UserManager</h2>
<div id="userDetails">
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.UserName);
</th>
<th>
#Html.DisplayNameFor(model => model.Role)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.UserName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Role)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
#Html.ActionLink("Delete", "Del", new { id = item.ID}, new { onclick = "return confirm('Are you sure you wish to delete this article?')"});
</td>
</tr>
}
</table>
</div>
As you can see there is a model:
#model IEnumerable<ReviewLogs.User>
But I want my form to use its own RegisterModel which has required fields, regex statements, etc, that need to be filled. When the user presses submit they the UserManager page is reloaded and now shows the newly added user.
I thought I could create a partial view:
#model ReviewLogs.Models.RegisterModel
#{
ViewBag.Title = "Register";
ViewBag.SubHead = "Register";
}
#Html.ValidationSummary(true, "Creation of new Account has failed please check your fields.")
<p id="success">#ViewBag.SuccessMessage</p>
<br />
#using (Html.BeginForm())
{
//The Registration Page the user sees
//The userName label and textbox
<div class="inputFields">
#Html.LabelFor(model => model.userName)
#Html.TextBoxFor(model => model.userName)
#Html.ValidationMessageFor(model => model.userName)
</div>
//The accountType label and radioButton
<div class="radioButtonAccountType">
#Html.LabelFor(model => model.accountType)
#Html.RadioButtonFor(model => model.accountType, "1")<span class="adminLabel">Admin</span>
#Html.RadioButtonFor(model => model.accountType, "2")<span class="userLabel">User</span>
#Html.ValidationMessageFor(model => model.accountType)
</div>
<input class="submitButton" type="submit" value="Create User" style="margin-left:140px;margin-bottom: 20px;" />
}
And In my UserManager View I added:
#Html.Partial("Register");
But then I got this error:
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[ReviewLogs.User]', but this dictionary requires a model item of type 'ReviewLogs.Models.RegisterModel'.
How can I get pass this so I am able to POST my registration?
Look for the #Html.Partial method overload which accepts a model, and call it via something like:
#Html.Partial("Register", Model.RegisterModel)
In order for this to work, probably the best bet would be to create a new Model that you pass into the UserManager view. This UserManagerModel will have two properties: IEnumerable<ReviewLogs.User>, used by the UserManagerView itself, and RegisterModel which you'll pass into the Partial view.
Just new up a copy of register model in your view and pass it to the partial:
#Html.Partial("Register", new Namespace.To.RegisterModel())
Just make sure to make the form for registering post to a separate action. You should not try to have this one action handle multiple different types of posts. You can pass a return URL (basically just Request.RawUrl) in a hidden field in your form, and then if this value is present, you can use it to redirect back to the same page. For example:
#Html.Hidden("returnUrl", Request.RawUrl)
Then,
[HttpPost]
public ActionResult Register(RegisterModel model, string returnUrl)
{
// do the register thing
if (!String.IsNullOrWhiteSpace(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index"); // or whatever should be default
}
I'm new to ASP.NET MVC and have been searching for a solution to this problem for hours. I'm trying to create a site where admins can go in and approve registration user requests and also assign them to a user group. I'm getting data from multiple tables so I created a viewmodel.
I finally have the GET Edit controller working to display the data, but can't figure out how the POST Edit should work. When I was debugging, I realized that the viewmodel I was trying to return had only null values.
I'm not sure if there are many things wrong with this code or just one. On postback, I need to update some values in the Access table. If you need more information from me, just let me know. Thanks!
ViewModel:
using System.Collections.Generic;
using AccessRequests.Models;
using System.ComponentModel.DataAnnotations;
namespace AccessRequests.ViewModels
{
public class UserAccessData
{
public IEnumerable<User> Users { get; set; }
public IEnumerable<Access> Accesses { get; set; }
public IEnumerable<UserGroup> UserGroups { get; set; }
}
}
Controller:
// GET: Accesses/Edit/5
public ActionResult Edit(string brand, string group)
{
var viewModel = new UserAccessData();
viewModel.Users = db.Users
.Include(i => i.Accesses)
.OrderBy(i => i.UserName);
viewModel.UserGroups = db.UserGroups
.Where(i => i.Groups.Contains(group));
if (brand != null)
{
viewModel.Accesses = db.Accesses
.Include(x => x.User)
.Where(x => x.Brand.ToUpper() == brand);
}
return View(viewModel);
}
// POST: Accesses/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Access access, UserAccessData editaccess)
{
//code here
}
View:
#model AccessRequests.ViewModels.UserAccessData
#{
ViewBag.Title = "Edit";
}
<h2>Edit Access</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<table class="table">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Company</th>
<th>Role</th>
<th>Region</th>
<th>User Group</th>
<th>Approve</th>
<th>Deny Reason</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Accesses)
{
#Html.HiddenFor(modelItem => item.UserName)
<tr>
<td>
#Html.DisplayFor(modelItem => item.User.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.Email)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.Company)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.Role)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.Region)
</td>
<td>
#Html.DropDownListFor(m => m.UserGroups, new SelectList(Model.UserGroups, "Groups", "GroupDescription"), "Please select a User Group")
</td>
<td>
#Html.DropDownListFor(modelItem => item.Approved, new SelectList(
new List<Object>{
new { value = 0 , text = "" },
new { value = "YES" , text = "YES" },
new { value = "NO" , text = "NO"}
},"value","text", 2))
</td>
<td>
#Html.EditorFor(modelItem => item.DenyReason, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(modelItem => item.DenyReason, "", new { #class = "text-danger" })
</td>
</tr>
}
</tbody>
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</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>
In order to post to a collection property, your field names need to be in the form of something like: Accesses[0].Approved, Accesses[1].Approved, etc. In order to achieve that, you need to use a for loop rather than foreach. You'll also need to change your property's type from IEnumerable<Access> to List<Access>.
#for (var i = 0; i < Model.Accesses.Count(); i++)
{
...
#Html.DropDownListFor(m => Model.Accesses[i].Approved, ...)
}
Also, bear in mind that only those properties which have actual HTML fields that participate in the the post will have values in your post action. Everything else will either be null or whatever default value the property may have. If you save an entity with properties that have been nulled out because they weren't posted, you will overwrite those properties in your database. You need to take care to either make sure all the necessary data comes through in the post or that you repopulate said data from the the database before attempting to save anything.
I am trying to use one view in which I display current results which has the ability to add a new record. I looked at this post and also this post and pieced together something I think should work but it will not save to the database. Here is my view model:
public class LabIndexViewModel
{
public Lab Lab { get; set; }
public IEnumerable<Lab> Labs { get; set; }
}
And in my controller I have this in my index:
public ActionResult Index(int patid = 0, Lab lab = null)
{
ViewBag.Finalize = PatientSubmitted(patid);
ViewBag.DispPatientId = patid;
ViewBag.CheckButtonStatus = ButtonSubmitted(patid);
var labs = db.Labs.Where(l => l.PatientId == patid && l.Active);
LabIndexViewModel model = new LabIndexViewModel();
model.Labs = labs.ToList();
model.Lab = lab;
SetViewBagLists();
return View(model);
}
Then in my post where it will not save:
[HttpPost]
public ActionResult Create(LabIndexViewModel labindex)
{
ViewBag.DispPatientId = labindex.Lab.PatientId;
Lab lab = labindex.Lab;
try
{
lab.Active = true;
db.Labs.Add(lab);
db.SaveChanges();
return RedirectToAction("Index", "Lab", new { patid = lab.PatientId });
}
catch
{
ViewBag.Phase = new SelectList(StatusList(), "Text", "Value");
ViewBag.Name = new SelectList(db.LabOptions, "Test", "Value", lab.Name);
return View(lab);
}
}
Here is my partial where I submit the data in my view:
#model PamperWeb.Models.LabIndexViewModel
#using (Html.BeginForm("Create", "Lab")) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Lab</legend>
<tr>
<td>
#Html.DropDownList("Name", String.Empty)
#Html.ValidationMessageFor(model => model.Lab.Name)
</td>
<td>
#Html.EditorFor(model => model.Lab.Value)
#Html.ValidationMessageFor(model => model.Lab.Value)
</td>
<td>
#Html.EditorFor(model => model.Lab.Given)
#Html.ValidationMessageFor(model => model.Lab.Given)
</td>
<td>
#Html.EditorFor(model => model.Lab.TimeGiven)
#Html.ValidationMessageFor(model => model.Lab.TimeGiven)
</td>
<td>
#Html.DropDownList("Phase", String.Empty)
#Html.ValidationMessageFor(model => model.Lab.Phase)
</td>
#Html.HiddenFor(model => model.Lab.PatientId)
<td>
<input type="submit" value="Create" />
</td>
</tr>
</fieldset>
}
Anybody have any idea on how to make this work or have a good example?
I didn't realy understand all the question, but I saw something wrong there:
1) Yours PartialView must post a Lab, so make It strongly typed for Lab, because HTML Helpers will generate HTML that the default ModelBinder cannot process to build the model back in the server using LabIndexViewModel:
#model PamperWeb.Models.Lab
#using (Html.BeginForm("Create", "Lab")) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Lab</legend>
<tr>
<td>
#Html.DropDownList("Name", String.Empty)
#Html.ValidationMessageFor(model => model.Name)
</td>
<td>
#Html.EditorFor(model => model.Value)
#Html.ValidationMessageFor(model => model.Value)
</td>
<td>
#Html.EditorFor(model => model.Given)
#Html.ValidationMessageFor(model => model.Given)
</td>
<td>
#Html.EditorFor(model => model.TimeGiven)
#Html.ValidationMessageFor(model => model.TimeGiven)
</td>
<td>
#Html.DropDownList("Phase", String.Empty)
#Html.ValidationMessageFor(model => model.Phase)
</td>
#Html.HiddenFor(model => model.PatientId)
<td>
<input type="submit" value="Create" />
</td>
</tr>
</fieldset>
}
2) Change the controller Action Create to receive as parameter the posted Lab:
[HttpPost]
public ActionResult Create(Lab lab)
{
ViewBag.DispPatientId = Lab.PatientId;
try
{
lab.Active = true;
db.Labs.Add(lab);
db.SaveChanges();
return RedirectToAction("Index", "Lab", new { patid = lab.PatientId });
}
catch
{
ViewBag.Phase = new SelectList(StatusList(), "Text", "Value");
ViewBag.Name = new SelectList(db.LabOptions, "Test", "Value", lab.Name);
return View(lab);
}
}
3) Use the ViewModel created to display the labs! Thats the ViewModel master purpose, display complex types in the view! Any other opperation requires creation of a custom ModelBinder to interate throught the request and build the model back in the server.
Hopes this help you! I really got this from the question!
With the help of the comments I was able to figure out the issue. When I added the parameters to the html.beginform it no longer sent my url parameters with the patientid. Not sure why? My normal create view had this so my hidden parameter picked up the value. I ended up setting the value in my controller so the hidden parameter in the form was able to pick it up. Here is what my get index is now which resolved the issue:
public ActionResult Index(int patid = 0, Lab lab = null)
{
ViewBag.Finalize = PatientSubmitted(patid);
ViewBag.DispPatientId = patid;
ViewBag.CheckButtonStatus = ButtonSubmitted(patid);
var labs = db.Labs.Where(l => l.PatientId == patid && l.Active);
LabIndexViewModel model = new LabIndexViewModel();
if (lab == null)
lab = new Lab();
lab.PatientId = patid;
model.Labs = labs.ToList();
model.Lab = lab;
SetViewBagLists();
return View(model);
}
I also found this post helpful in that I found out I can specify a model to send to a partial.