Model passed to a partial view is null upon form submit - asp.net-mvc

I have the following model:
#model SmartSEOModel
public class SmartSEOModel
{
public SmartSEOSettingsModel SmartSEOSettingsModel { get; set; }
public SEOTemplateModel SEOTemplateModel { get; set; }
}
In my view I have a partial view which I call like this:
#using (Html.BeginForm())
{
some razor code here
<div id="pnlSmartSEO">
#Html.Partial(ViewNames.SmartSEOController_SEOTemplate, Model.SEOTemplateModel)
</div>
}
In the partial view there are some form fields bound to the SEOTemplateModel.
The problem is that when I receive the SmartSEOModel in my HttpPost action, the SEOTemplateModel is null. As if the SEOTemplateModel has been passed by copying it to the partial view.
Please advise why this is and how to workaround it.
Many thanks
My partial view looks like this:
#Html.Telerik().TabStrip().Name("SmartSEO").Items(x =>
{
x.Add().Text(T("Admin.SmartSEO").Text).Content(GetSmartSEOUI().ToHtmlString()).Selected(true);
})
#helper GetSmartSEOUI()
{
#(Html.LocalizedEditor<SEOTemplateModel, SEOTemplateLocalizedModel>("SmartSEO-Localized",
#<table class="adminContent">
<tr>
<td class="adminTitle">
#Html.NopLabelFor(model => model.Locales[item].CategoryTitleSEOTemplate):
</td>
<td class="adminData">
#Html.EditorFor(model => model.Locales[item].CategoryTitleSEOTemplate)
</td>
</tr>
</table>,
#<table class="adminContent">
<tr>
<td class="adminTitle">
#Html.NopLabelFor(model => model.CategoryTitleSEOTemplate):
</td>
<td class="adminData">
#Html.EditorFor(model => model.CategoryTitleSEOTemplate)
</td>
</tr>
</table>
))
}
My HttpPost action looks like this:
[HttpPost]
public ActionResult Configure(SmartSEOModel smartSEOModel)
{
var seoTemplate = SEOTemplateService.GetSEOTemplateById(smartSEOModel.SEOTemplateModel.Id);
if(seoTemplate == null)
{
throw new ArgumentException(String.Format("No SEOTemplate found with Id {0}", smartSEOModel.SEOTemplateModel.Id));
}
if (!ModelState.IsValid)
{
RedirectToAction("Configure");
}
SettingService.SaveSetting(smartSEOModel.SmartSEOSettingsModel.ToEntity());
seoTemplate = smartSEOModel.SEOTemplateModel.ToEntity(seoTemplate);
SEOTemplateService.UpdateSEOTemplate(seoTemplate);
UpdateLocales(seoTemplate, smartSEOModel.SEOTemplateModel);
//activity log
CustomerActivityService.InsertActivity("EditSEOTemplate", LocalizationService.GetResource("ActivityLog.EditSEOTemplate"));
SuccessNotification(LocalizationService.GetResource("SevenSpikes.NopSmartSEO.Admin.SEOTemplate.Notifications.SEOTemplateEdited"));
return View("SevenSpikes.Nop.Plugins.SmartSEO.Views.Configure", smartSEOModel);
}

Becuase you don't have a form within your partial view, it will not persist the data. Try using #Html.EditorFor instead of #Html.Partial.
So your main view would look like
#using (Html.BeginForm())
{
some razor code here
<div id="pnlSmartSEO">
#Html.EditorFor(model => model.SEOTemplateModel)
</div>
}
You would then need to move your partial view into a template. Rename your partial view to EditorTemplates\SEOTemplateModel.cshtml and place it in the same location where your main view is.
You will also need to make your template strongly typed: #model [namespace].SEOTemplateModel

Related

MVC model data that is rendered in the view is null when posted back

I have seen similar questions to this and followed the routine answer which is to ensure all model data is rendered in the HTML.
I have done that and the model is rendered in the view with #Html.HiddenFor() but when the posting back to the controller there are no items in the list ?
The view will happily render multiple items in the list, but List<Item> Items in the posted data is always an empty list (not null)
Model
public class ItemCollection
{
public List<string> AvailiableActions { get; set; }
public List<Item> Items { get; set; }
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string SelectedAction { get; set; }
}
View
#model ItemCollection
#using (Html.BeginForm("myAction", "myController", FormMethod.Post))
{
<fieldset>
<div>
#Html.HiddenFor(m => Model.Items)
#Html.DisplayNameFor(x => x.AvailiableActions)
<table>
#{
foreach (var item in Model.Items)
{
#Html.HiddenFor(m => item)
#Html.HiddenFor(s => item.Id)
<tr>
<td>#item.Name</td>
<td>#Html.DropDownList(item.SelectedAction, new SelectList(Model.AvailiableActions))</td>
</tr>
}
}
</table>
</div>
</fieldset>
}
Controller
[HttpPost]
private ActionResult myAction(ItemCollection model)
{
if (model.Items.Count() == 0)
{
// this is true.. something is wrong......
}
}
You cannot use a foreach loop to render controls for a collection. It renders duplicate id and name attributes without the necessary indexers to bind to a collection. Use a for loop
for (int i = 0; i < Model.Items.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(m => m.Items[i].Id)
#Html.DisplayFor(m => m.Items[i].Name)
</td>
<td>#Html.DropDownList(m => m.Items[i].SelectedAction, new SelectList(Model.AvailiableActions))</td>
</tr>
}
Note your view also included #Html.HiddenFor(m => Model.Items) and #Html.HiddenFor(m => item) which would also have failed because item is a complex object and you can only bind to value types. You need to remove both.
Instead of iterating over all items to make sure the index is added to the generated output, you may consider using EditorTemplates (an example on an other site).
EditorTemplates allow you to specify a template for a single Item in \Views\Shared\EditorTemplates\Item.cshtml:
#model Item
#{
var options= (List<string>)ViewData["Options"];
}
<tr>
<td>
#Html.HiddenFor(m => m.Id)
#Html.DisplayFor(m => m.Name)
</td>
<td>#Html.DropDownList(m => m.SelectedAction, new SelectList(options))</td>
</tr>
Then you may simply change your view to:
#model ItemCollection
#using (Html.BeginForm("myAction", "myController", FormMethod.Post))
{
<fieldset>
<div>
<table>
#Html.EditorFor(m => m.Items, new {Options = Model.AvailiableActions })
</table>
</div>
</fieldset>
}

Postback on partially loaded view brings null model

My application have page with few search boxes. When user clicks on respective search button a result grid is expected to load. That grid have few editable controls in which user can modifications and hit save button if needed to save the data.
We have implemented the functionality using AjaxForm and partial view.
Search area containing search boxes and search button is created using Ajax.BeginForm, on submit of which a postback Search method is called from controller. SearchModel is passed to this method.
Partial View is created to show results and Ajax form in step 1 loads it successfully from controller postback method. SearchModel.Results property is passed to view as its model.
This partial view showing results have a Save button (again a Ajax form) which invokes another method in controller but gets model null in controller.
have tried lot of tricks but was unsuccessful. Any working example demonstrated anywhere or suggestion to make this working ? There are lot of examples on web where usage of AjaxForm to load data is explained but didn’t found any for multiple (or nested?)
Thanks in advance.
Edit - Feb 24
Here is the sample I created using Visual Studio default MVC template, which is similar to the actual criteria explained above and have the same problem on Submit of Partial page ..
Views:
Index.cshtml
#using MvcApplication1.Models
#model SearchModel
#{
ViewBag.Title = "Home Page";
}
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" ></script>
<h2>#ViewBag.Message</h2>
<p>
To learn more about ASP.NET MVC visit http://asp.net/mvc.
</p>
#Ajax.BeginForm("Search", "Home", new AjaxOptions { HttpMethod = "Post",
InsertionMode = InsertionMode.Replace,UpdateTargetId = "SearchResults"})
{
<table>
<tr>
<td>
First Name:
</td>
<td>
#Html.TextBoxFor(m => m.SearchString)
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Submit" />
</td>
</tr>
</table>
}
<div id="SearchResults" style="color: Green;"></div>
Partial View - _SearchResult.cshtml
#using MvcApplication1.Models
#model MvcApplication1.Models.SearchModel
#{
ViewBag.Title = "Result Partial";
}
<h2>
testPartial</h2>
#Ajax.BeginForm("SearchResult", "Home", new AjaxOptions
{
HttpMethod = "Post"
})
{
<table>
#foreach (ResultModel item in Model.Result)
{
<tr>
<td>
Name:
</td>
<td>
#Html.DisplayFor(m => item.Name)
</td>
</tr>
<tr>
<td>
Address:
</td>
<td>
#Html.TextAreaFor(m => item.Address)
</td>
</tr>
}
<tr>
<td colspan="2">
<input type="submit" value="Submit" />
</td>
</tr>
</table>
}
Models:
SearchModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication1.Models
{
public class SearchModel
{
public string SearchString { get; set; }
public List<ResultModel> Result { get; set; }
}
}
ResultModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication1.Models
{
public class ResultModel
{
public string Name { get; set; }
public string Address { get; set; }
}
}
Controller:
HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
return View();
}
[HttpPost]
public ActionResult Search(SearchModel model)
{
//dummy search for result
List<ResultModel> result = new List<ResultModel>();
ResultModel res1 = new ResultModel();
res1.Name = model.SearchString + " 1";
res1.Address = "Dummy address";
result.Add(res1);
ResultModel res2 = new ResultModel();
res2.Name = model.SearchString + " 2";
res2.Address = "Rummy address";
result.Add(res2);
//assign seach results to model
model.Result = result;
return PartialView("_SearchResult", model);
}
[HttpPost]
public ActionResult SearchResult(SearchModel model)
{
//do something with results
List<ResultModel> res = model.Result; // null here !!
return RedirectToAction("Index");
}
}
}
SO, above sample will
Show you a Search box on home page - Index.cshtml
When you click on submit it will load a partial view displaying results below search
When you edit search results and hit submit on result form, please have a breakpoint set up there, you will see model being returned is null.
Hope, this explain my problem.
Now that you have shown your actual code with a complete example your question can be answered.
The reason you are getting null is because you are not respecting the standard naming convention for your input fields that the default model binder expects. Please read the following article from Phil Haack to familiarize yourself with those conventions.
The problem with your code is the fact that you used a foreach loop inside your partial to render the results instead of using an editor template. So replace your code inside _SearchResult.cshtml with the following:
#using MvcApplication1.Models
#model SearchModel
#{
ViewBag.Title = "Result Partial";
}
<h2>testPartial</h2>
#using(Ajax.BeginForm("SearchResult", "Home", new AjaxOptions()))
{
<table>
#Html.EditorFor(x => x.Result)
<tr>
<td colspan="2">
<input type="submit" value="Submit" />
</td>
</tr>
</table>
}
and then define a custom editor template for the ResultModel type (~/Views/Shared/EditorTemplates/ResultModel.cshtml - warning, the name and location of your editor template is important because it works by convention):
#using MvcApplication1.Models
#model ResultModel
<tr>
<td>
Name:
</td>
<td>
#Html.DisplayFor(m => m.Name)
#Html.HiddenFor(m => m.Name)
</td>
</tr>
<tr>
<td>
Address:
</td>
<td>
#Html.TextAreaFor(m => m.Address)
</td>
</tr>
Things to notice:
I have wrapped the Ajax.BeginForm helper in a using statement. You should do the same inside your Index.cshtml view
I have added a hidden field for the Name property inside the custom editor template (#Html.HiddenFor(m => m.Name)) in order to send this value to the server when the form is submitted because you only had a TextArea for the Address field meaning that the Name would never have been sent to your server.

Get Value of Property (List<long>) in Post Action in ASP.NET MVC3

This is My model:
public class MyModel
{
public List<long> NeededIds { get; set; }
public string Name { get; set; }
}
My Controllers:
public ActionResult Create()
{
MyModel model = new MyModel();
model.NeededIds = new List<long> { 1, 2, 3, 4 };
return View(model);
}
[HttpPost]
public ActionResult Create(MyModel model)
{
string name = model.Name;
List<long> ids = model.NeededIds;
return RedirectToAction("Index");
}
And View:
#model TestMVC.Models.MyModel
#using(Html.BeginForm()) {
<table>
<thead>
<tr>
<th>
Id
</th>
</tr>
</thead>
<tbody>
#foreach(long id in Model.NeededIds) {
<tr>
<td>
#id
</td>
</tr>
}
</tbody>
</table>
#Html.ValidationSummary(true)
<fieldset>
<legend>MyModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
I set NeededIds in Get action and in the view I can see NeededIds. I also need it in Post action, but in post action the NeededIds is always null. How can I get the property value in post action when I set it in get action? What is your suggestion?
You are not posting your NeededIds back to the server. In order to get this working you can add them as hidden fields in a for loop inside the form:
#for (int i = 0; i < Model.NeededIds.Count(); i++) {
#Html.HiddenFor(model => model.NeededIds[i])
}
if you are using layout page than simply remove the form tag from the layout page.
in addition to the answer by Yakimych
you have kept the ids as constant.. this means two things
1. you can use arrays in place of list
2.you can just save the ids list/array in TempData and retrive it back from there when POST happens
you can do this like this
in your GET handler
TempData.Add("ids",idArray);
in your POST handler
var idArray = (long[])TempData["ids"];

How do I return List or Collection to Controller from View in MVC 3?

Someone please help me return this list properly from my view. I don't see why I'm returning null for my fieldModelList I try to pass to the controller...
Here is my view:
#model List<Regions.SOA.UI.CopyBookSchemaCreator.Models.FieldModel>
<script type="text/javascript" src="~/Scripts/jquery-ui-1.8.11.min.js"></script>
#using (Html.BeginForm("GetResponse", "TestMethods", FormMethod.Post))
{
<table id="tblMethods">
<tr>
<th>
Property Name
</th>
<th>
Request
</th>
</tr>
#foreach (FieldModel fieldModel in Model)
{
<tr>
<td>
#Html.DisplayFor(m => fieldModel.PropertyName)
</td>
<td>
#Html.TextBoxFor(m => fieldModel.PropertyValue)
</td>
</tr>
}
</table>
<div>
<input type="submit"/>
</div>
and here is my controller:
[HttpPost]
public ActionResult GetResponse(List<FieldModel> fieldModelList)
{
return GetResponse(fieldModelList);
}
I am hitting the HttpPost method but if I place a breakpoint just inside it, I am returning null for the fieldModelList right off the bat, which I was hoping would be a list of the values I entered into the texboxes on the view that is of model FieldModel...
I think something is wrong with my logic versus my syntax, or as maybe as well as my syntax, but basically what I want to do is return back a list of type FieldModel with each corresponding PropertyName and PropertyValue to the controller. I noticed I am not passing any kind of id parameter in my BeginForm statement in the view. Do I need one here?
Just in case, here is my model class for FieldModel:
namespace Regions.SOA.UI.CopyBookSchemaCreator.Models
{
public class FieldModel
{
[Display(Name = "Property")]
public string PropertyName { get; set; }
[Display(Name = "Value")]
public string PropertyValue { get; set; }
}
}
Phil Haack wrote an article some time ago explaining how to bind collections (ICollection) to view models. It goes into additional detail about creating an editor template, which you could certainly do as well.
Basically, you need to prefix the HTML elements' name attributes with an index.
<input type="text" name="[0].PropertyName" value="Curious George" />
<input type="text" name="[0].PropertyValue" value="H.A. Rey" />
<input type="text" name="[1].PropertyName" value="Ender's Game" />
<input type="text" name="[1].PropertyValue" value="Orson Scott Card" />
Then, your controller could bind the collection of FieldModel
[HttpPost]
public ActionResult GetResponse(List<FieldModel> fieldModelList)
{
return GetResponse(fieldModelList);
}
I'm not 100% sure the following would name the attributes correctly (I'd recommend using the editor template) but you could easily use the htmlAttributes argument and give it a name using the index.
#for(int i = 0;i < Model.Count;i++)
{
<tr>
<td>
#Html.DisplayFor(m => m[i].PropertyName)
</td>
<td>
#Html.TextBoxFor(m => m[i].PropertyValue)
</td>
</tr>
}
Editor Template
If you wanted to go as far as adding an editor template, add a partial view named FieldModel.ascx to /Views/Shared that is strongly typed to a FieldModel
#model Regions.SOA.UI.CopyBookSchemaCreator.Models.FieldModel
#Html.TextBoxFor(m => m.PropertyName) #* This might be a label? *#
#Html.TextBoxFor(m => m.PropertyValue)
And, then the part of your view responsible for rendering the collection would look like:
#for (int i = 0; i < Model.Count; i++) {
#Html.EditorFor(m => m[i]);
}

Asp.net MVC Razor more than one form on a page

Yo
I have a registration page on my site - at the top of the page is a login form for existing users. In the main area there is the registration form.
The login are is a partial view with #model ViewModels.LoginViewModel
The registration are is also a partial with #model ViewModels.RegViewModel
The main page which houses these partials is a view with #model ViewModels.RegPageViewModel
This viewmodel looks like:
public class RegViewModel
{
public RegisterVm RegisterVm { get; set; }
public LoginVm LoginVm { get; set; }
}
When I submit the registration part of the page (it's action is register/capture - the receiving action expects a RegisterVm) to it's controller it complains about being passed the wrong viewmodel
What's the deal with subviews and their viewmodel? Is there a standard approach to dealing with this?
Should I have one submit URL for this page which figures out if it's a login request or a register request and then handles the post accordingly? That seems messy to me though...
http://monobin.com/__d33cf45a4 - RegisterVm.cs (LoginVm.cs is pretty much the same as this)
http://monobin.com/__m69132f76 - RegPageVm.cs
Register.cshtml:
#model xxxx.ViewModels.RegPageVm
#{
View.Title = "Register";
Layout = "~/Views/Shared/_BareBones.cshtml";
}
<link rel="stylesheet" href="#Url.Content("~/Public/Css/signup.css")" type="text/css" />
<div id="sign-up-container">
<div id="sign-up-box">
<div id="sign-up-box-left">
<img src="#Url.Content("~/Public/Images/Signup_176x81.png")" />
</div>
<div id="sign-up-box-right">
#Html.Partial("_Register")
</div>
</div>
</div>
<div class="clear">
</div>
_Register.cshtml:
#model xxxx.ViewModels.RegisterVm
#using (Html.BeginForm("Capture", "Register", FormMethod.Post))
{
<table class="sign-up-box-inner">
<tr>
<td class="label-area">
#Html.LabelFor(x => x.Email)
</td>
<td class="field-area">
#Html.TextBoxFor(x => x.Email, new { #class = "login-input", title = "Enter Name" })
</td>
</tr>
<tr>
<td class="label-area">
#Html.LabelFor(x => x.Password)
</td>
<td class="field-area">
#Html.PasswordFor(x => x.Password, new { #class = "login-input", title = "Enter Name" })
</td>
</tr>
<tr>
<td class="label-area">
#Html.LabelFor(x => x.UserName)
</td>
<td class="field-area">
#Html.TextBoxFor(x => x.UserName, new { #class = "login-input", title = "Enter Name" })
</td>
</tr>
<tr>
<td colspan="2">
<input type="image" src="../../Public/Images/Submit_150x47.png" class="submit-button" />
</td>
</tr>
</table>
#Html.AntiForgeryToken()
}
And finally RegisterController.cs:
public class RegisterController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Capture(RegisterVm registerVm)
{
if (!ModelState.IsValid)
{
return View("index", new RegPageVm()
{
LoginVm = new LoginVm(),
RegisterVm = registerVm
});
}
return RedirectToAction("index", "Event");
}
}
w://
You need to ensure that the form elements (like the textbox etc) should have the same id as the RegisterVM and LoginVM properties. Your theory is right but I think you might be making a mistake in the naming convention of MVC.
If you can share your view code + the VM classes, then we'll be able to help better.
EDIT:
Looking at your code I think you should be passing the view model to your partial view. Like for example the following line believe should be like this >
#Html.Partial("_Register", Model.RegisterVm)
According to your answer to nEEbz:
You are using:
Html.TextBoxFor(x=>x.LoginVM.Email) // i guess
this would turn into <input name="LoginVM.Email" ...>
Notice the LoginVM. part
Your login action probably looks like:
public ActionResult Login(LoginVM model) { }
so it expect field names like Email and Password, not LoginVM.Email and LoginVM.Password.
So you could could use Html.Textbox instead (so that the field name doesn't get autocreated).

Resources