Why is my model not passed as parameter with form post - c#

I'm having trouble understanding why my model is not passed along with its values to my controller when posting a form.
I have a view with a strongly typed model (UnitContract) that is being fetched from a webservice, that holds a set of values. In my action I'm trying to fetch int ID and bool Disabled fields that exists in my model. When debugging, I see that my model being passed from the form doesn't contain any values at all. What am I missing?
My view (UnitContract as strongly typed model):
...
<form class="pull-right" action="~/UnitDetails/EnableDisableUnit" method="POST">
<input type="submit" class="k-button" value="Enable Unit"/>
</form>
My controller action:
[HttpPost]
public ActionResult EnableDisableUnit(UnitContract model)
{
var client = new UnitServiceClient();
if (model.Disabled)
{
client.EnableUnit(model.Id);
}
else
{
client.DisableUnit(model.Id);
}
return RedirectToAction("Index", model.Id);
}

Sounds like you need to add the fields from your model to your form. Assuming your view accepts a UnitContract model, then something like this should work:
<form class="pull-right" action="~/UnitDetails/EnableDisableUnit" method="POST">
#Html.HiddenFor(x => x.Id)
#Html.HiddenFor(x => x.Disabled)
<input type="submit" class="k-button" value="Enable Unit"/>
</form>
Now when you submit the form, it should submit the fields to your model.

The MVC framework will use the data from the form to create the model. As your form is essentially empty, there is no data to create the model from, so you get an object without any data populated.
The only data that is sent from the browser in the request when you post the form, is the data that is inside the form. You have to put the data for the properties in the model as fields in the form, so that there is something to populate the model with.

Look into using #Html.HiddenFor(). Put these in your form, and the data you want to see posted back to your controller should be there. For example, your form would look something like...
<form class="pull-right" action="~/UnitDetails/EnableDisableUnit" method="POST">
#Html.HiddenFor(x => x.Id)
#Html.HiddenFor(x => x.IsDisabled)
<input type="submit" class="k-button" value="Enable Unit"/>
</form>

Let's say you have a model like this:
public class UnitContract
{
public int Id { get; set; }
public DateTime SignedOn { get; set; }
public string UnitName { get; set; }
}
Your view would look something like this:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>UnitContract</legend>
#Html.HiddenFor(model => model.Id)
<div class="editor-label">
#Html.LabelFor(model => model.SignedOn)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.SignedOn)
#Html.ValidationMessageFor(model => model.SignedOn)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.UnitName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UnitName)
#Html.ValidationMessageFor(model => model.UnitName)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
In your controller:
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult Edit(UnitContract unitContract)
{
// do your business here .... unitContract.Id has a value at this point
return View();
}
Hope this is helpful.

Related

How can I pass hidden field value from view to controller ASP.NET MVC 5?

I am trying to pass hidden field value from view to controller by doing the following
#Html.HiddenFor(model => model.Articles.ArticleId)
and also tried
<input type="hidden" id="ArticleId" name="ArticleId" value="#Model.Articles.ArticleId" />
On both instances the value of ArticleId is 0 but when i use TextboxFor i can see the correct ArticleId, please help
Here it is
View
#model ArticlesCommentsViewModel
....
#using (Html.BeginForm("Create", "Comments", FormMethod.Post))
{
<div class="row">
<div class="col-xs-10 col-md-10 col-sm-10">
<div class="form-group">
#Html.LabelFor(model => model.Comments.Comment, new { #class = "control-label" })
#Html.TextAreaFor(m => m.Comments.Comment, new { #class = "ckeditor" })
#Html.ValidationMessageFor(m => m.Comments.Comment, null, new { #class = "text-danger"})
</div>
</div>
</div>
<div class="row">
#*#Html.HiddenFor(model => model.Articles.ArticleId)*#
<input type="hidden" id="ArticleId" name="ArticleId" value="#Model.Articles.ArticleId" />
</div>
<div class="row">
<div class="col-xs-4 col-md-4 col-sm-4">
<div class="form-group">
<input type="submit" value="Post Comment" class="btn btn-primary" />
</div>
</div>
</div>
}
Controller
// POST: Comments/Create
[HttpPost]
public ActionResult Create(CommentsViewModel comments)//, int ArticleId)
{
var comment = new Comments
{
Comment = Server.HtmlEncode(comments.Comment),
ArticleId = comments.ArticleId,
CommentByUserId = User.Identity.GetUserId()
};
}
Model
public class CommentsViewModel
{
[Required(ErrorMessage = "Comment is required")]
[DataType(DataType.MultilineText)]
[Display(Name = "Comment")]
[AllowHtml]
public string Comment { get; set; }
public int ArticleId { get; set; }
}
ViewModel
public class ArticlesCommentsViewModel
{
public Articles Articles { get; set; }
public CommentsViewModel Comments { get; set; }
}
The model in the view is ArticlesCommentsViewModel so therefore the parameter in your POST method must match. Your use of
#Html.HiddenFor(model => model.Articles.ArticleId)
is correct, but you need to change the method to
[HttpPost]
public ActionResult Create(ArticlesCommentsViewModel model)
and the model will be correctly bound.
As a side note, your ArticlesCommentsViewModel should not contain data models, and instead should contain only those properties you need in the view. If typeof Articles contains properties with validation attributes, ModelState would be invalid because your not posting all properties of Article.
However, since CommentsViewModel already contains a property for ArticleId, then you could just use
#Html.HiddenFor(model => model.Comments.ArticleId)
and in the POST method
[HttpPost]
public ActionResult Create([Bind(Prefix="Comments")]CommentsViewModel model)
to effectively strip the "Comments" prefix
In your controller, you need to pass the hidden value with the model,
for example, if you have a userId as a hidden value, in your Page you add:
#Html.HiddenFor(x => x.UserId)
In your model of course you would already have UserId as well.
In your controller, you need the model as a parameter.
public async Task<ActionResult> ControllerMethod(YourViewmodel model) { model.UserId //this should be your HiddenValue
I guess your model have another class called Articles inside CommentsViewModel.Change your controller function for accessing the ArticleId accordingly.
[HttpPost]
public ActionResult Create(CommentsViewModel comments)//, int ArticleId)
{
var comment = new Comments
{
Comment = Server.HtmlEncode(comments.Comment),
ArticleId = comments.Articles.ArticleId, // Since you are using model.Articles.ArticleId in view
CommentByUserId = User.Identity.GetUserId()
};
}
In my case, I didn't put the hidden input in the form section, but out of form, so it's not send to backend. Make sure put hidden input inside the form.
Also make sure name attribute is specified on the hidden field. Element's "id" is often used on client side but "name" on server side.
<input type="hidden" value="#ViewBag.selectedTraining" id="selectedTraining"
name="selectedTraining" />
In my case, I was passing a couple of fields back and forth between controllers and views. So I made use of hidden fields in the views.
Here's part of the view. Note a controller had set "selectedTraining" and "selectedTrainingType" in the ViewBag to pass to the view. So I want these values available to pass on to a controller. On the hidden tag, the critical thing is set to the "name" attribute. "id" won't do it for you.
#using (Html.BeginForm("Index", "ComplianceDashboard"))
{
<input type="hidden" value="#ViewBag.selectedTraining" id="selectedTraining" name="selectedTraining" />
<input type="hidden" value="#ViewBag.selectedTrainingType" id="selectedTrainingType" name="selectedTrainingType" />
if (System.Web.HttpContext.Current.Session["Dashboard"] != null)
{
// Show Export to Excel button only if there are search results
<input type="submit" id="toexcel" name="btnExcel" value="Export To Excel" class="fright" />
}
<div id="mainDiv" class="table">
#Html.Grid(Model).Columns(columns =>
Then back on the controller:
// POST: Dashboard (Index)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(string excel)
{
string selectedTraining, selectedTrainingType;
selectedTraining = Request["selectedTraining"];
selectedTrainingType = Request["selectedTrainingType"];
Or can put the requests as parameters to the method: public ActionResult Index(string excel, string selectedTraining, string selectedTrainingType)

MVC Ajax.BeginForm - Model valid and data added to dbase but constantly triggering OnFailure

I am at the end of my tether with this. I am trying to update a list of Contacts in a page using an Ajax Form in a partial view and I keep hitting this weird error. For the life of me I cannot understand why this is happening since this action is pretty much a clone of another form/action combo in my project and that one works!!
When I submit the form, I've been tracking it with the debugger. The model IsValid and it saves the data to the database successfully. Despite this though, the form is triggering the OnFailure method. Further probing in the Chrome Console reveals that I am getting a 500 Server Error on the action I am calling
I cannot understand how this is happening since I am sending the correct model to the Controller Action; the debugger shows it's valid and my form data is being saved in the database after all.
This is the model
public class ClientContact
{
public int Id { get; set; }
public int ClientId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Position { get; set; }
public string Email { get; set; }
public string Telephone { get; set; }
//public virtual Client Client { get; set; }
}
This is the controller action I am calling
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateContact([Bind(Include = "Id,ClientId,FirstName,LastName,Position,Email,Telephone")] ClientContact clientContact)
{
if (ModelState.IsValid)
{
db.ClientContacts.Add(clientContact);
db.SaveChanges();
return PartialView("AddCanNote", new ClientContact { ClientId = clientContact.ClientId });
}
return new EmptyResult();
}
This is my Partial View
#model _3KB.Entities.Client_Entities.ClientContact
<div id="clientContactTarget">
<div class="row-fluid">
<div class="span6">
#{Html.RenderAction("ContactsForClient", "ClientContacts", new { clientId = Model.ClientId });}
</div>
<div class="span6">
<h5>Add New Contact</h5>
<hr/>
#using (Ajax.BeginForm("CreateContact", "ClientContacts", null, new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "clientContactTarget",
OnSuccess = "SuccessContact",
OnFailure = "FailureContact"
}))
{
#Html.AntiForgeryToken()
<div class="row-fluid">
<div class="span6">
<div class="control-group">
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.ClientId)
#Html.Label("First Name")
#Html.TextBoxFor(m => m.FirstName)
</div>
</div>
<div class="span6">
<div class="control-group">
#Html.Label("Surname")
#Html.TextBoxFor(m => m.LastName)
</div>
</div>
</div>
<div class="row-fluid">
<div class="span4">
<div class="control-group">
#Html.Label("Position")
#Html.TextBoxFor(m => m.Position)
</div>
</div>
<div class="span4">
<div class="control-group">
#Html.Label("Telephone")
#Html.TextBoxFor(m => m.Telephone)
</div>
</div>
<div class="span4">
<div class="control-group">
#Html.Label("Email")
#Html.TextBoxFor(m => m.Email)
</div>
</div>
</div>
<div class="row-fluid">
<div class="span12">
<div class="control-group">
<input type="submit" value="Add Contact" class="btn blue"/>
</div>
</div>
</div>
}
</div>
</div>
And I am calling this in the parent view with
#Html.Partial("AddClientContact", new ClientContact {ClientId = Model.Id })
I've been looking at this all day now. I thought I'd figure it out but I think I've been looking at it too long.
Has anyone experienced a similar issue to this?
Any suggestions or advice would be welcomed
Thanks
First, your action raising an exception doesn't cause anything done previously in the action to be rolled-back. Since the exception isn't raised until view processing (which is one of the last things to happen in the response-pipeline) everything will be saved and such just fine.
Second, OnFailure will be hit if the action returns 500. It doesn't matter that it did all the core stuff it was responsible for (writing to DB, etc.) fine, it's still returning a 500, so it's a failure.
Finally, the exception is pretty explicit. Somewhere, you're passing ClientContact to a view that accepts only CandidateComment. A common source of this is loading a partial within another view, and neglecting to explicitly pass a model. For example, let's say you had a view, Foo.cshtml that has a model declaration of CandidateComment. If you were to do the following in a view whose model is ClientContact:
#Html.Partial("Foo")
You're in fact actually passing the ClientContact instance from the main view to that partial, just as if you were to do:
#Html.Partial("Foo", Model)
Long and short, look at all views that are involved in rendering the view for this action. Find the one that has a model declaration line including CandidateComment and then make sure you pass it a CandidateComment instance when you call it.

Object edited in view delivered by HttpGet Edit() is not received by HttpPost Edit()

I have two edit Action methods, one for HttpGet and one for HttpPost. The HttpGet method takes an id parameter, retrieves the appropriate object, and displays it for editing. The HttpPost method takes a parameter that should be the edited object; however, the ids do not match. Why is that mismatch occurring? I've included the code for my Edit.cshtml view, and for my two Action Methods.
The View:
#model WebApplicationPbiBoard.Models.Sprint
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Sprint</legend>
#Html.HiddenFor(model => model.Id)
<div class="editor-label">
#Html.LabelFor(model => model.Start)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Start)
#Html.ValidationMessageFor(model => model.Start)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.End)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.End)
#Html.ValidationMessageFor(model => model.End)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
The Action Methods:
//
// GET: /Sprint/Edit/5
public ActionResult Edit(int id)
{
var sprint = Db.GetById(id);
return View(sprint);
}
//
// POST: /Sprint/Edit/5
[HttpPost]
public ActionResult Edit(Sprint editedSprint)
{
if (ModelState.IsValid)
{
Db.Save(editedSprint);
return RedirectToAction("Index");
}
else
return View(editedSprint);
}
Here's the GetById method. It's pretty much a wrapper around the NHibernate ISession.GetById method.
public T GetById(int id)
{
return Session.Get<T>(id);
}
Here's my solution. Thanks to everyone who helped me with debugging it.
The problem was that, to adhere to NHibernate best practices, the setter for my id property was private. That meant that the controller couldn't set it in the reconstructed model object, so the id was the default value of 0. To solve the problem, I passed the id in as a parameter to the controller and found the right object in my repository, which I then manually updated. Here's the code:
[HttpPost]
public ActionResult Edit(int id, DateTime start, DateTime end)
{
//get the right model to edit
var sprintToEdit = Db.GetById(id);
//copy the changed fields
sprintToEdit.Start = start;
sprintToEdit.End = end;
if (ModelState.IsValid)
{
Db.Save(sprintToEdit);
return RedirectToAction("Index");
}
else
return View(sprintToEdit);
}

AllowHtml attribute not working

I have a model with this property:
[AllowHtml]
[DisplayName("Widget for Table")]
[StringLength(1000, ErrorMessage = "Maximum chars 1000")]
[DataType(DataType.Html)]
public object TableWidget { get; set; }
And here is the create methods in controller:
//
// GET: /Admin/Table/Create
public ActionResult Create(int id)
{
Season season = _seasonRepository.GetSeason(id);
var table = new Table
{
SeasonId = season.SeasonId
};
return View(table);
}
//
// POST: /Admin/Table/Create
[HttpPost]
public ActionResult Create(Table a)
{
if (ModelState.IsValid)
{
_tableRepository.Add(a);
_tableRepository.Save();
return RedirectToAction("Details", "Season", new { id = a.SeasonId });
}
return View();
}
And last here is my view:
#model Stridh.Data.Models.Table
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Fields</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>
<div class="editor-label">
#Html.LabelFor(model => model.TableURL)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.TableURL) #Html.ValidationMessageFor(model => model.TableURL)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.SortOrder)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.SortOrder) #Html.ValidationMessageFor(model => model.SortOrder)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.TableWidget)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.TableWidget) #Html.ValidationMessageFor(model => model.TableWidget)
</div>
<div class="editor-label invisible">
#Html.LabelFor(model => model.SeasonId)
</div>
<div class="editor-field invisible">
#Html.EditorFor(model => model.SeasonId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
When I add a "normal" message without html everything is saved OK, but when saving it says A potentially dangerous Request.Form...
Another strange thing is that I got this [AllowHtml] to work in another model class. I cant find why this is causing me troubble. Need your help. :-)
The way you are using AllowHtml should work. Make sure that you are not accessing the HttpRequest.Form collection anywhere else in your code (controller, filter, etc) as this will trigger ASP.NET Request Validation and the error you are seeing. If you do want access to that variable then you should access it via the following code.
using System.Web.Helpers;
HttpRequestBase request = .. // the request object
request.Unvalidated().Form;
I get the same problem and i solve it with the help of this post.
If you are on .net 4.0 make sure you add this in your web.config
<httpRuntime requestValidationMode="2.0" />
Inside the <system.web> tags
I had the same problem. My model class is named "GeneralContent" and has the property "Content". In my action method i used attribute like this:
public ActionResult Update(GeneralContent content)
when i renamed content argument to cnt, everything works well. I think MVC is confused when some attribude of model class has the same name as the argument in action method.
I also had this issue. I could not get a model property marked with [AllowHtml] to actually allow HTML, and instead encountered the same error you describe. My solution ended up being to mark the Controller action that accepts the posted model with the [ValidateInput(false)] attribute.
The answer that #marcind put me on the right track but my issue was that I was passing the FormCollection into the Controller method, so changing this...
public ActionResult Edit(MyClass myClass, FormCollection collection)
To this...
public ActionResult Edit(MyClass myClass)
Solved the problem.
Subsequently, I was able to access the heck out of the form collection with code like this without issue.
foreach (var key in Request.Form.AllKeys)
{
...
}
So, it was the passing the form collection parameter that caused the problem, not merely accessing the form collection.

Rebuilding an ICollection after MVC edit?

If my model Product has a member ICollection<ProductOption> how do I rebuild my Product with the member collection in my Controller.Edit(...) method on post back from Edit?
(We can assume we never add or remove an option, only ever edit.)
Razor:
#model Models.Products.Product
#{
ViewBag.Title = "Edit";
Layout = "~/Views/Shared/_GlobalLayout.cshtml";
}
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
#Html.HiddenFor(model => model.Id)
<legend>Product</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
#foreach (ProductOption option in Model.Options)
{
<div style="border:1px solid black; margin: 5px; padding:5px 7px;">
#Html.Partial("_ProductOptionInput", option)
</div>
}
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
controller:
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
var p = _repository.ById(id);
UpdateModel<Product>(p);
_repository.SaveChanges();
return RedirectToAction("Index");
}
partial view:
#modelModels.Products.ProductOption
#Html.ValidationSummary(true)
<fieldset>
<legend>ProductOption</legend>
#Html.HiddenFor(model => model.Id)
<div class="editor-label">
#Html.LabelFor(model => model.Term)
</div>
</fieldset>
UPDATE
My FormCollection in ActionResult Edit(int id, FormCollection collection) is essentially a dictionary, so it has the ProductOption values for one of the updated ProductOptions but not the rest of them as the keys (ie the ProductOptions property names) can't be repeated in the dictionary.
You either need to write your own model binder (either for ICollection<ProductOption> or one that pulls your entity out of the database instead of instantiating a new instance), or you can NOT take the model in as a parameter, and instead, pull it out of the database in your action method, then call TryUpdateModel from the controller.
HTH
I added the following in the Razor view and it works like a charm!
#Html.EditorFor(model => model.Options.ToList()[0], templateName: "ProductOptionInput", htmlFieldName: "Options[0]")
#Html.EditorFor(model => model.Options.ToList()[1], templateName: "ProductOptionInput", htmlFieldName: "Options[1]")

Categories

Resources