I try to pass some hidden data to my controller by using the hiddenFor, I know the value I want gets to the view, but after submiting the form the value stays null when it arrives in the controller. The data in EditorFor is passed correctly to the controller.
// View
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
// Some working editorFor fields. Data from these gets successfully received
// The name is correctly displayed in the paragraph
<p>#Model.name</p>
// This data is not received in the controller
#Html.HiddenFor(x => x.name)
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
// Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Product product, HttpPostedFileBase image)
{
product.name = "a name";
return View(product);
}
I also tried using a normal named hidden, but this also didn't return a value.
Someone an idea what I missed?
You can pass the hidden fields automatically, if you have a form, using for example the razor helper
#using (Html.BeginForm("CreateTable", "Home", FormMethod.Post, null){ #HiddenFor(i => i.PropertyName) }
and the hidden fields must be inside of form, otherwise you will "lost" them.
Update following your updated question: Try remove the HiddenField and change <p>#Model.name</p>
to
#Html.LabelFor(i => i.Name)
I did focus on the incorrect thing, the problem was that I changed the model in the controller after the postback. But this only changes the model en does not changes the ModelState, which the form data uses.
//This is updated after model changes.
<p>#Model.name</p>
//For this you need to update the ModelState
#Html.HiddenFor(x => x.name)
In the controller you need to use ModelState.Remove(property name). (Or clear the complete ModelState)
//After removal of the property the ModelState will update to the new model value.
product.name = "a name";
ModelState.Remove("name");
return View(product);
In this article it's explained, https://weblog.west-wind.com/posts/2012/Apr/20/ASPNET-MVC-Postbacks-and-HtmlHelper-Controls-ignoring-Model-Changes.
Related
Solved by using a view model that has HttpPostedFileBase property bound to the view, so that the form only requires a single post method to upload the image and then add the entity to the database.
Here is the form tag:
#using (Html.BeginForm("Create", "Lessons", FormMethod.Post, new {enctype = "multipart/form-data" }))
and the view control bound to the view model property (happens by default if the names match, in my case the property is HttpPostedFileBase file:
<input type="file" name="file" />
This is far easier than the path I was on below, trying to have two different post actions for a single view (which should be possible from everything I read, but I think my problem was trying to have a form within a form):
My controller for Lesson has two post methods that can be called:
The first is to create a new Lesson:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(Lesson lesson)//, WebImage Photo)
{
if (ModelState.IsValid && Request.Form["Create"]!=null)
//Request.Form check because the image file function is calling back to this handler
//Once that is sorted out the check can be removed and simply use ModelState.IsValid
{
db.Lessons.Add(lesson);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
ViewBag.DivID = new SelectList(db.Divisions, "ID", "DivName", lesson.DivID);
ViewBag.ProjectID = new SelectList(db.Projectinformations, "ID", "Code", lesson.ProjectID);
ViewBag.PeopleID = new SelectList(db.Peoples, "ID", "Firstname", lesson.PeopleID);
return View(lesson);
}
On the page this is the form (shortened) with the submit button that calls the controller.Create() action:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
//. . . for each Lesson property there is a form group with #Html Label
//#Html.EditorFor, etc. and then at the bottom is this:
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" name="Create" class="btn btn-default" />
</div>
</div>
The image path field is part of the main form, and I left the generated code for it's text box etc. This is the part of the form where I'm trying to insert an image upload that calls a different controller action:
<div class="form-group">
<div class="col-md-10">
<form action="UploadImage" name="ImageFileForm" id="imageForm" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="OK" name="OK" />
</form>
</div>
#Html.LabelFor(model => model.Image, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Image, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Image, "", new { #class = "text-danger" })
</div>
</div>
This is the contoller action for UploadImage
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult UploadImage(HttpPostedFileBase file)
{
string imagePath ="";
if (file != null)
{
var newFileName = Path.GetFileName(file.FileName);
imagePath = #"LessonsLearnedImages\" + newFileName;
file.SaveAs(#"~\" + imagePath);
}
ViewBag.DivID = new SelectList(db.Divisions, "ID", "DivName", lesson.DivID);
ViewBag.ProjectID = new SelectList(db.Projectinformations, "ID", "Code", lesson.ProjectID);
ViewBag.MOAPeopleID = new SelectList(db.MOApeoples, "ID", "Firstname", lesson.MOAPeopleID);
ViewBag.Image = imagePath;
return View();
}
When I press the ImageUpload button however, the controller Create (form default) function gets called. I can potentially use the Create action in the controller to do everything with a Request.Form check, but this has failed for me when adding a HttpPostedFileBase parameter to it.
With image upload there are two things that you need to save. The first part is saving the actual image, eg. saving onto a folder on your computer. The second part is saving info about the image, eg. saving its full path in a database. Doing both parts are essential so that you can retrieve the image later.
One way to capture images (or uploaded files in general) is by using HttpPostedFileBase as the type of parameter:
[HttpPost]
public async Task<ActionResult> Upload(HttpPostedFileBase myFile)
{
// myFile contains several properties which you need to be able to save the file
}
The myFile parameter has several properties that you need for saving. Examples are the path from which you are uploading the image and the actual image content which you can extract as a byte[].
The corresponding form on the web page, trimmed down to the essential bits, would be:
<form action="Upload" method="post" enctype="multipart/form-data">
<input type="file" name="myFile" />
<input type="submit" value="Click me to upload" />
</form>
You need to make sure the following line up so that the correct controller action gets called:
The method of the form should match the verb of the controller method (post in the form corresponding to HttpPost in the controller)
The action of the form should match the name of the controller method (Upload in this case)
The name of the input should match the name of the controller method's parameter (myFile in this case)
You also need to make sure that the enctype is multipart/form-data in order for the myFile parameter to actually have a value. If not, you may execute the correct controller method, but the parameter may be null.
After all of that, you should have a proper myFile parameter whose properties you can use to save. For example, after saving the image, you can store its full path in an ImagePath column in your Lessons table.
As you mentioned, the image (and anything else for that matter) gets lost once the controller method finishes executing. But that's okay, because we already have the image and image path saved in persistent storage. To retrieve the image at a later time, you can just look at the associated lesson. For example, assuming you have a Model object that corresponds to a lesson that you got from the database, displaying the image could look like this:
<img src="#Model.ImagePath" />
EDIT
It seems that you want to save multiple fields on the create page in one go, with the image file only being one of the fields. In that case, only a single method is needed on the controller and only a single form element is needed on the page.
To capture the file, add the HttpPostedFileBase as a property in your lesson view model, for example:
public class Lesson
{
public HttpPostedFileBase File { get; set; }
}
I'm trying to enable posting Comments from the view where I display a Forum Post by id. That's the model which my view receives:
#model PostViewModel
the Model has and "Id" property, which I send to a controller with javascript:
<script>
$("#target").click(function () {
$("#formPlace").load('#Url.Action("AddComment","Posts", new { postId = Model.Id })');
});
</script>
That's the controller action I send the "postId" to:
[HttpGet]
public ActionResult AddComment(int postId)
{
var comment = new CommentViewModel();
comment.PostId = postId;
return this.PartialView("_AddComment", comment);
}
This view returns a form in which the user has to fill the comment's content:
#model MvcTemplate.Web.ViewModels.Posts.CommentViewModel
<div>
#using (Html.BeginForm("AddComment", "Posts", FormMethod.Post))
{
<div>Enter your comment here:</div>
<div>
#Html.TextAreaFor(x => Model.Content)
</div>
<input type="submit" name="Submit" />
}
</div>
When the view receives the model its "PostId" is still correct = what I set it with the javascript. However after the user submits the form, the CommentViewModel which is sent to the controller has 0(default int value) for "PostId". This is the controller action:
[HttpPost]
public ActionResult AddComment(CommentViewModel viewModel)
{
// transfer the view model to db model and save;
}
Any idea how I can keep the correct PostId?
When you submit the form to the AddComment action method, the default model binder will try to bind the form field values to the properties of your CommentViewModel object. Your form has an input field for the Content property, but you do not have one for the PostId. So the browser will send the value of only form element with name Content.
If you want PostId, You need to keep the Post id value in the form. Since user does not need to see/edit this, you may keep this in a hidden input field in the form.
You can use the Html.HiddenFor helper method to generate the hidden field with the name and value for the corresponding property.
#using (Html.BeginForm("AddComment", "Posts", FormMethod.Post))
{
<div>Enter your comment here:</div>
<div>
#Html.TextAreaFor(x => Model.Content)
</div>
#Html.HiddenFor(s=>s.PostId)
<input type="submit" name="Submit" />
}
Or just a hidden input element markup (the helper ultimately generate this only)
<input type="hidden" name="PostId" value="#Model.PostId" />
You need to put hidden field with PostId value in your form. You are only posting content. Alternatively your post action AddComment should have url parameter postId, so form's action url will include postId.
You can set your PostId in a TempData and can get in subsequent post action in controller. If you want to get it from view you need to set it in a hidden field as suggested by Shyju.
I am very confused as to why this started happening as I had fixed the problem already. When this problem first occurred I did not have #Html.HiddenFor(model => model.OrganizationID) being passed through the POST action of the form. After I put that in - it worked just fine.
Now, it is not working again. The DbUpdateConcurrencyException is being thrown when I attempt to delete something. My Edit View works just fine.
I followed this tutorial to create a Model first approach.
These are the delete actions in my controller, OrganizationController:
public ActionResult Delete(int id)
{
using (var db = new VAGTCEntities())
{
return View(db.Organizations.Find(id));
}
}
//
// POST: /Organization/Delete/5
[HttpPost]
public ActionResult Delete(int id, Organization organization)
{
try
{
// TODO: Add delete logic here
using (var db = new VAGTCEntities())
{
db.Entry(organization).State = System.Data.EntityState.Deleted;
db.SaveChanges();
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
This is my delete view:
#model VAGTC.Models.Organization
#{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<fieldset>
<legend>Organization</legend>
<div class="display-label">
#Html.DisplayNameFor(model => model.Name)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.Name)
</div>
</fieldset>
#using (Html.BeginForm()) {
#Html.HiddenFor(model => model.OrganizationID)
<p>
<input type="submit" value="Delete" /> |
#Html.ActionLink("Back to List", "Index")
</p>
}
I debugged it to see if the id is being passed and it is indeed being passed through to the POST action. I am unsure of where to go from here. As anything I search for brings up just adding the HiddenFor statement.
This exception indicates that for some reason, no rows were affected by your query (ie. the row was not deleted). Instead of setting the State of the entry, try using the following approach:
db.Remove(organization);
db.SaveChanges();
Edited in response to comments
When you post back the organisation object, it will be populated by the fields contained in the form element of your view. In your case that is only OrganisationId. You change the State of the entity object posted to the controller and save it. If you have set the Concurrency Mode property on any of your EF entities (in the .edmx for example), EF will throw a concurrency exception if the value you pass for that field does not agree with the database value. That could be why it needs the value of the Name field.
A better approach (if you're not concerned about concurrency during deletes) would be to remove the object in the method signature, and rename the Id argument to recieve the OrganisationId. Then read the object directly from the database before setting its state and saving it.
How can I pass back a (binded) model from a view to a controller method that is not an [httppost]?
View:
#Html.EditorFor(model => model.Name)
#Html.DisplayFor(model => model.Model)
// Table displays more properties of model
<input type="button" title="GetStuff" value="Get Stuff" onclick="location.href='#Url.Action("GetStuff")'" />
Controller Method:
public ActionResult GetStuff(ViewModel Model /* Realize this is non-httppost but...how can I get the model back with this type of scenario */)
{
// Get the name from per the model of the main view if the user changed it
string theName = Model.Name;
Model2 m2 = new Model2(){name = theName};
return ("someView", Model2);
}
Instead of writing your own query strings, simply wrap your EditorFor stuff in an using Html.BeginForm() statement, remove the Javascript from your button, and change the button to a submit type.
#using (Html.BeginForm("GetStuff", "Controller", FormMethod.Get)
{
#Html.EditorFor(model => model.Name)
#Html.DisplayFor(model => model.Model)
// Table displays more properties of model
<input type="submit" title="GetStuff" value="Get Stuff"/>
}
The FormMethod.Get will send a query string request of the form elements to the action/controller defined in the Html.BeginForm definition.
Since the way you pass data in GET requests is via the query string, you'd have to put all that data on the query string. Model binding may work, at least for simple models, but you'll have to explicity include all that data in the action. For example:
#Url.Action("GetStuff", new { Name = Model.Name, Model = Model.Model })
But if any of that data is complex, you'll have to specify each and every subproperty yourself... Perhaps you should just post? :)
The issue I am having is when i pass a populated object to a view that doesn't display all of the properties.
public ActionResult Edit(Guid clientID)
{
Property property = PropertyModel.GetPropertyDetails(clientID);
return View("Edit", "Site", property);
}
View:
<%# Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<WebFrontend.ViewModels.Property>" %>
<% using (Html.BeginForm())
{%>
<%: Html.ValidationSummary(true, "Property update was unsuccessful. Please correct the errors and try again.") %>
<fieldset>
<legend>Edit Account: <%: Model.ClientAccountNo %></legend>
<div class="editor-label">
<%: Html.LabelFor(model => model.ClientName)%>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.ClientName)%>
<%: Html.ValidationMessageFor(model => model.ClientName)%>
</div>
<p>
<input type="submit" value="Update Property" />
</p>
</fieldset>
<% } %>
When submitted the Property object is passed to this controller method but all of the properties not used in the view are null or empty including Model.ClientAccountNo which is present on the view before submitting.
[HttpPost]
public ActionResult Edit(Property property)
{
if (ModelState.IsValid)
{
bool success = PropertyModel.UpdateProperty(property);
if (success)
{
// property has been updated, take them to the property details screen.
return RedirectToAction("Details", "Property", new { clientID = property.ClientID });
}
else
{
ModelState.AddModelError("", "Could not update property.");
return View("Activate", "Site", property);
}
}
// If we got this far, something failed, redisplay form
return View("Edit", "Site", property);
}
I can't find anything similar on the web, any explanation for why this is happening and how to fix it would be appreciated.
This is the way MVC works - it tries to construct an object of the action parameter type from the values in the route, form and query string. In your case it can only get the values in the form collection. It does not know anything about the values that you have stored in your database.
If you are only interested in certain properties you would be better off doing a specific view model with just these on - then you can validate for this specific case.
If you store values in hidden fields keep in mind that these can be manipulated - if this is a problem definitely go with a specific view model with only the editable fields on.
Since you are not passing unused properties in URL, then you will have to render hidden fields for them. Use Html.HiddenFor method.