ActionMethod param is 0 every time - asp.net-mvc-3

I've got a form (here's some of its code):
#model Discussion
#{
ViewBag.Title = "Edit Discussion";
Layout = "~/Views/Shared/_App.cshtml";
}
#using (Html.BeginForm("Update", "Discussion", FormMethod.Post, new { id = Model.discussionId, #class = "update" }))
{
... rest of the view code
}
My related controller's update method has:
[HttpPost]
[ValidateInput(false)]
public ActionResult Update(FormCollection col, int id = 0)
{
... rest of code
}
when the form is submitted, I keep getting an id of 0 instead of the ID I'm seeing from the model that when I debug is clearly there inside being used during the form rendering. IT's just when I submit, it's not passing the id correctly.
Am I doing something wrong in terms of syntax here? the new{} in the Html.BeginForm I guess I don't understand how the new anonymous type is matching up to this id when the form is submitted when it's a collection of id and a class here as you can see.

When you do the new {id = Model.discussionId ...} you are setting the id attribute of the form element. To get the id to come across properly you would have to make it an input of the form. So inside the form you would put a hidden input element with something like this: #Html.Hidden("id", Model.discussionId)
So your current form is rendering something like this:
<form id="theDiscussionId" class="update" action="/Discussion/Update">
... rest of the view code
</form>
And you need it to be like this:
<form class="update" action="/Discussion/Update">
<input type="hidden" name="id" value="theDiscussionId" />
... rest of the view code
</form>

Related

passing data from view to controller on asp.net mvc 3

i have problem to pass data from view to controller , i have view that is strongly typed with my viewmodel "TimeLineModel", in the first i passed to this view my viewmodel from action on my controller
public ActionResult confirmation(long socialbuzzCompaignId)
{
return View(new TimeLineModel() { socialBuzzCompaignId = socialbuzzCompaignId, BuzzMessages = model });
}
with this i can get info from my action and display it on view , but i have other action POST which i won't get my view model to do some traitement
[HttpPost]
public ActionResult confirmation(TimeLineModel model)
{
}
i can get some propretie of the model but in others no , for example i can get the properti "socialBuzzCompaignId" of model , but other propertie like "IEnumerable BuzzMessages" i can't get it , i dont now why !!
this is the content of my view
#model Maya.Web.Models.TimeLineModel
#{
ViewBag.Title = "confirmation";
}
#using (Html.BeginForm())
{
<h2>confirmation</h2>
<fieldset>
#foreach (var msg in Model.BuzzMessages)
{
<div class="editor-label">
#msg.LongMessage
</div>
<br />
}
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
You need to include BuzzMessages properties within a form element. Since it's not editable, you'd probably want to use hiddens. There are two ways to do this. Easiest is instead of doing a foreach loop, do a for loop and insert them by index.
#for (int i =0; i<Model.BuzzMessages.Count(); i++v)
{
<div class="editor-label">
#Model.BuzzMessages[i].LongMessage
#Html.HiddenFor(m => m.BuzzMessages[i].LongMessage);
</div>
<br />
}
but to do this you'd need to use an IList instead of an IEnumerable in your view model to access by index.
Alternatively, you could create an Editor Template named after your BuzzMessages class (whatever its name is).
#model BuzzMessagesClass
#Html.HiddenFor(m => m.LongMessages)
<!-- Include other properties here if any -->
and then in your main page
#Html.EditorFor(m => m.BuzzMessages)
Check out http://coding-in.net/asp-net-mvc-3-how-to-use-editortemplates/ or search stack overflow if the details of editor templates confuse you.
Just like any HTML POST method, you have to get the data back to the Controller somehow. Just simply "showing" the data on the page doesn't rebind it.
You have to put the data in an input (or a control that will post back) to the appropriate model property name.
So, if you have a model property with name FirstName and you want this data to be rebound to the model on POST, you have to supply it back to the model by placing an "input hidden" (or similar control that postbacks) with the ID of FirstName will rebind that property to the model on POST.
Hope that explains it.
#foreach (var msg in Model.BuzzMessages)
{
<div class="editor-label">
#msg.LongMessage
<input type="hidden" name="BuzzMessages.LongMessage" value="#msg.LongMessage" />
</div>
}
It will post array of LongMessages. Get values like this:
[HttpPost]
public ActionResult confirmation(TimeLineModel model, FormCollection collection)
{
var longMessages = collection["BuzzMessages.LongMessage"];
}

MVC3 Ajax.BeginForm with PartialView and persistent routedata issue

I have a main view and the URL for this view has a Action/Controller/Area and id value, something like:
http://localhost:56513/Incident/IncidentHome/Index/8c02a647-a883-4d69-91be-7ac5f7b28ab7
I have a partialview in this main view, one that calls methods in the controller via Ajax. This partial view needs to know the ID value of the url for the parent page. I found how to do this is through 'ParentActionViewContent'. Something like:
using (Ajax.BeginForm("UpdatePersonalStatusPanel", "Status", new { area = "Tools" , id = ViewContext.ParentActionViewContext.RouteData.Values["id"].ToString() }, new AjaxOptions { UpdateTargetId = "divPersStatus" }))
{
<p style="text-align: center;">
<span class="editor-label">#Html.LabelFor(m => m.StatusText)</span> <span class="editor-field">#Html.EditorFor(m => m.StatusText)</span>
<input type="submit" value="Change Current Status" />
</p>
}
Now, this works fantastic for calling the controller method. The ID is passed correctly so that the controller can then see it in the routedata. I use the id to perform a database call, and then return the partialview again. The problem is on the return. I get a 'Object reference not set to an instance of an object' on the ViewContext.ParentActionViewContext.RouteData.Values["id"].ToString() bit in the ajax.beginform , and my targetid doesn't refresh.
Clearly I must be doing something wrong. Does someone else have a better way to see the parent view's routedata through Ajax?
If I'm understanding you correctly, this partial view calls itself. So ParentActionViewContext works the first time because the first time your main view calls an action using this partial view. However, later an ajax call directly returns this partial view. When the partial view is invoked directly there is no Parent View action hence the null reference on ParentActionViewContext.
Rather than deal with with route data I recommend including the id in the model of your partial view.
new { area = "Tools" , id = Model.Id }

MVC 3 POST data and the Id field

I have a strongly typed razor view for a model in my MVC 3 project. Basically its for editing the model.
The model contains an Id field for the database key and some other string fields (Its a viewModel and all but thats not the point of the question).
In the view I just have a form and a submit button and nothing else. When the View is posted to the controller the model in the controller has all fields empty EXCEPT for the Id field which seems to have been auto-magically filled up.
How and where does the Id field gets populated in the model without there being a corresponding 'input' element for it in the view.
This is probably a dumb question but I would appreciate even just a link to what I should read up on. Thanks.
I bet it comes from the url as route parameter.
For example you have the following controller:
public class HomeController: Controller
{
public ActionResult Index(int id)
{
vqr model = GetModel(id);
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// the model.Id property will be automatically populated here
// because the request was POST /home/index/123
...
}
}
and the following view:
#model MyViewModel
#using (Html.BeginForm())
{
<button type="submit">OK</button>
}
Now you navigate to GET /home/index/123 and you get the following markup:
<form action="/home/index/123" method="post">
<button type="submit">OK</button>
</form>
Notice the action attribute of the form? That's where the id comes from. Basically the Html.BeginForm() helper uses the current url when generating the action attribute, and since the current url is /home/index/123 it is what gets used.
And because if you have left the default routes in your Global.asax, the {id} route token is used at the end of the url, the default model binder successfully binds it to the Id property of your view model.
You are probably hitting a URL similar to the following: /MyObject/Edit/15
This is then returning the page that you have your blank form on.
What happens next is you have an HTML.BeginForm() which is posting BACK to /MyObject/Edit/15
Now because of the post back having the same format your routing rules are picking up the '15' and binding it back to your id.
Have you added the ID field as a hidden field?
e.g.
#Html.HiddenFor(x=> x.ID)

MVC3 - Pass back a model from RenderPartial

I have a page in MVC3 with a model of "pageModel".
In this page I have:
#{ Html.RenderPartial("_subPage", Model.subModel); } (Pagemodel.submodel)
In my controller I am doing:
[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Results(pagemodel model, string frmAction)
{
}
The page loads fine the first time, but when I postback into the httpPost action, model.submodel is always null.
My question is, how do I return an updated model from the RenderPartial (if at all). I can get my model INTO the partial, but not back!
The problem with partials is that they do not preserve the navigational context. This means that any input fields that you might have put inside this partial will have incorrect names and the default model binder will not be able to retrieve the values back when you POST. Your HTML will look like this:
<input type="text" name="Prop1" value="property 1 value" />
<input type="text" name="Prop2" value="property 2 value" />
whereas the correct is:
<input type="text" name="subModel.Prop1" value="property 1 value" />
<input type="text" name="subModel.Prop2" value="property 2 value" />
In order to achieve this correct markup I would recommend you using editor templates.
So you replace:
#{ Html.RenderPartial("_subPage", Model.subModel); }
with:
#Html.EditorFor(x => x.subModel)
and then you move your _subPage.cshtml partial into ~/Views/Shared/EditorTemplates/SubModelType.cshtml where SubModelType is the type of the subModel property:
#model SubModelType
#Html.EditorFor(x => x.Prop1)
#Html.EditorFor(x => x.Prop2)
Now when you look at the generated HTML the corresponding input field names should be prefixed with subModel and inside the POST controller action the model.subModel property will this time be properly initialized and populated from the values that were entered by the user in the input fields.
you'll need to change your partialview to accept the top level model, i.e:
#{ Html.RenderPartial("_subPage", Model); }
which would then render your properties in the partialview with the correct property names i.e. :
<input type="text" name="subModel.MyProperty" value="somevalue" />
It would also mean that your returned model in the HttpPost action will have to correct navigational relationship intact.
this is just one of those caveats related to viewmodels and hierarchies. Oh, btw, in mvc3, you don't need the verbose [AcceptVerbs(HttpVerbs.Post)] for posts. You can simply use [HttpPost]
You can also perform the following.
#Html.RenderPartial(
"_subPage",
Model.subModel,
new ViewDataDictionary
{
TemplateInfo = new TemplateInfo
{
HtmlFieldPrefix = "subModel"
}
});
Your partial view will remain as is, using the #model SubModel

MVC3 Razor "For" model - contents duplicated

It has been intriguing that my MVC3 razor form renders duplicated values inside a foreach code block in spite of correctly receiving the data from the server. Here is my simple form in MVC3 Razor...
-- sample of my .cshtml page
#model List<Category>
#using (#Html.BeginForm("Save", "Categories", FormMethod.Post))
{
foreach (Category cat in Model)
{
<span>Test: #cat.CategoryName</span>
<span>Actual: #Html.TextBoxFor(model => cat.CategoryName)</span>
#Html.HiddenFor(model => cat.ID)
<p>---</p>
}
<input type="submit" value="Save" name="btnSaveCategory" id="btnSaveCategory" />
}
My controller action looks something like this -
[HttpPost]
public ActionResult Save(ViewModel.CategoryForm cat)
{
... save the data based on posted "cat" values (I correctly receive them here)
List<Category> cL = ... populate category list here
return View(cL);
}
The save action above returns the model with correct data.
After submitting the form above, I expect to see values for categories similar to the following upon completing the action...
Test: Category1, Actual:Category1
Test: Category2, Actual:Category2
Test: Category3, Actual:Category3
Test: Category4, Actual:Category4
However #Html.TextBoxFor duplicates the first value from the list. After posting the form, I see the response something like below. The "Actual" values are repeated even though I get the correct data from the server.
Test: Category1, Actual:Category1
Test: Category2, Actual:Category1
Test: Category3, Actual:Category1
Test: Category4, Actual:Category1
What am I doing wrong? Any help will be appreciated.
The helper methods like TextBoxFor are meant to be used with a ViewModel that represent the single object, not a collection of objects.
A normal use would be:
#Html.TextBoxFor(c => c.Name)
Where c gets mapped, inside the method, to ViewData.Model.
You are doing something different:
#Html.TextBoxFor(c => iterationItem.Name)
The method internall will still try to use the ViewData.Model as base object for the rendering, but you intend to use it on the iteration item. That syntax, while valid for the compiler, nets you this problem.
A workaround is to make a partial view that operates on a single item: inside that view you can use html helpers with correct syntax (first sample), and then call it inside the foreach, passing the iteration item as parameter. That should work correctly.
A better way to do this would be to use EditorTemplates.
In your form you would do this:
#model List<Category>
#using (#Html.BeginForm("Save", "Categories", FormMethod.Post))
{
#Html.EditorForModel()
<input type="submit" value="Save" name="btnSaveCategory" id="btnSaveCategory" />
}
Then, you would create a folder called EditorTemplates, either in the ~/Views/Shared folder or in your Controllers View folder (depending on whether you want to share the template with the whole app or just this controller), and in the EditorTemplates folder, create a Category.cshtml file which looks like this:
#model Category
<span>Test: #Model.CategoryName</span>
<span>Actual: #Html.TextBoxFor(model => model.CategoryName)</span>
#Html.HiddenFor(model => model.ID)
<p>---</p>
MVC will automatically iterate over the collection and call your template for each item in it.
I've noticed that using foreach loops within Views causes the name attributes of text boxes to be rendered the same for every item in the collection. For your example, every text box will be rendered with the following ID and Name attributes:
<input id="cat_CategoryName" name="cat.CategoryName" value="Category1" type="text">
When your controller receives the form data collection, it won't be able reconstruct the collection as different values.
The solution
A good pattern I've adopted is to bind your View to the same class you want to post back. In the example, model is being bound to List<Category> but the controller Save method receives a model ViewModel.CategoryForm. I would make them both the same.
Use a for loop instead of a foreach. The name/id attributes will be unique and the model binder will be able to distinguish the values.
My final code:
View
#model CategoryForm
#using TestMvc3.Models
#using (#Html.BeginForm("Save", "Categories", FormMethod.Post))
{
for (int i = 0; i < Model.Categories.Count; i++)
{
<span>Test: #Model.Categories[i].CategoryName</span>
<span>Actual: #Html.TextBoxFor(model => Model.Categories[i].CategoryName)</span>
#Html.HiddenFor(model => Model.Categories[i].ID)
<p>---</p>
}
<input type="submit" value="Save" name="btnSaveCategory" id="btnSaveCategory" />
}
Controller
public ActionResult Index()
{
// create the view model with some test data
CategoryForm form = new CategoryForm()
{
Categories = new List<Category>()
};
form.Categories.Add(new Category() { ID = 1, CategoryName = "Category1" });
form.Categories.Add(new Category() { ID = 2, CategoryName = "Category2" });
form.Categories.Add(new Category() { ID = 3, CategoryName = "Category3" });
form.Categories.Add(new Category() { ID = 4, CategoryName = "Category4" });
// pass the CategoryForm view model
return View(form);
}
[HttpPost]
public ActionResult Save(CategoryForm cat)
{
// the view model will now have the correct categories
List<Category> cl = new List<Category>(cat.Categories);
return View("Index", cat);
}

Resources