I am having an issue getting data in my model on my MakePayment.cshmtl view.
The AccountScreen.cshtml is calling the MakePayment.cshtml view:
#model SuburbanCustPortal.SuburbanService.CustomerData
#{
ViewBag.Title = "Account Screen";
}
<h2>AccountScreen</h2>
<div class="leftdiv">
<fieldset>
<legend>customer info</legend>
#Html.Partial("CustomerInfoPartialView", Model)
</fieldset>
<fieldset>
<legend>delivery address</legend>
#Html.Partial("DeliveryAddressPartialView", Model)
</fieldset>
<fieldset>
<legend>delivery info</legend>
#Html.Partial("DeliveryInfoPartialView", Model)
</fieldset>
</div>
<div class="rightdiv">
<fieldset>
<legend>balance</legend>
<div>
#Html.Partial("BalancePartialView", Model)
</div>
</fieldset>
<fieldset>
<legend>payment</legend>
<div>
#Html.Partial("MakePayment", Model)
</div>
</fieldset>
<fieldset>
<legend>billing info</legend>
<div>
#Html.Partial("BillingInfoPartialView", Model)
</div>
</fieldset>
</div>
My MakePayment.cshtml view:
#model SuburbanCustPortal.SuburbanService.CustomerData
#using (Html.BeginForm("MakePayment2", "Customer", FormMethod.Post))
{
<div style="text-align:center;">
<input class="makePaymentInput" type="submit" value="Make a Payment" />
</div>
}
My CustomerController:
public ActionResult AccountScreen(LogOnModel model)
{
return ShowCustomer(model.AccountNumber);
}
public ActionResult MakePayment(CustomerData model)
{
return View("MakePayment", model);
}
[HttpPost]
public ActionResult MakePayment2(CustomerData model)
{
//CustomerData model = new CustomerData();
var newmodel = new PaymentModel.SendToGateway();
newmodel.AccountBalance = model.TotalBalance;
newmodel.Amount = model.TotalBalance;
return RedirectToAction("PrePayment", "Payment", newmodel);
}
The public ActionResult MakePayment(CustomerData model) is never being reached.
My problem: The [HttpPost] public ActionResult MakePayment2(CustomerData model) is being reached but the model has nulls in it.
I know the data initial model from the AccountScreen is being populated since the other views that are being rendered is showing data.
Anyone see what I am doing wrong?
The problem is there's nothing inside your form except a submit button. You need to make sure input fields are there (either text boxes, select lists, or hidden fields), as those are what post data back to the controller.
You could try using EditorForModel inside your partial view:
#using (Html.BeginForm("MakePayment2", "Customer", FormMethod.Post))
{
#Html.EditorForModel()
<div style="text-align:center;">
<input class="makePaymentInput" type="submit" value="Make a Payment" />
</div>
}
Edit based on comments
Razor doesn't include an Html.HiddenForModel() method, for whatever reason. Possible workarounds:
List out each property of the model using Html.HiddenFor(model => model.Property)
Annotate the model properties with \[HiddenInput\]
Use EditorForModel() but wrap it in <div style="display: none;"></div> (NOTE that a malicious user can still modify the properties as if they were visible.)
Use only Html.HiddenFor(model => model.id) and fetch the model in the controller.
Use the serialization method in the MVC Futures assembly
Related quesion here:
Is there some way to use #Html.HiddenFor for complete model?
The problem is, you are creating a form containing nothing else than a submit button.
When you submit it, it posts nothing back to the server, thus your function receives an empty model.
#using (Html.BeginForm("MakePayment2", "Customer", FormMethod.Post))
{
<div style="text-align:center;">
<input class="makePaymentInput" type="submit" value="Make a Payment" />
</div>
}
This translates as :
<form method="POST" action="{url}">
<div style="text-align:center;">
<input class="makePaymentInput" type="submit" value="Make a Payment" />
</div>
</form>
More details :
Since in the logic you then redirect to a new page to collect payment information, you don't want to give the user the opportunity to mess with your model, thus you should query your customer data from your Context instead of trusting what is submitted in the POST.
Thus all you really need to add if this :
#using (Html.BeginForm("MakePayment2", "Customer", FormMethod.Post))
{
#Html.HiddenFor(model => model.{ID Field})
<div style="text-align:center;">
<input class="makePaymentInput" type="submit" value="Make a Payment" />
</div>
}
This way, you will be able to get your model back in the server side code.
Basically, your form submits nothing as there are no input fields inside the form scope. Try to wrap all your html in AccountScreen.cshtml within #using (Html.BeginForm( statement (and throw it out from MakePayment.cshtml).
Related
I have a small tool that downloads reports based on the specified options. The download works well. And now, I want to also upload a file to the folder and then further use it.
The problem is that I already have one submit button on the form that is used for the download and when I am adding another button for the upload, only download is triggered.
I tried to resolve it using an #Html.ActionLink(), but no success. Is there any proper way to resolve the issue? I know that there is a possibility to capture the submit value and then check in one main ActionResult in the Controller and redirect to the respective ActionResult, but I don't want to do it, since there are too many POST Actions in one controller.
Here is my View - download.cshtml:
#using (Html.BeginForm())
{
<fieldset>
<div class="title">Click to download report</div>
<div class="field">
<input id="downloadBtn" type="submit" class="button" value="Download" />
</div>
</fieldset>
<fieldset id="Option_ClientInfo">
<div class="title">
Image
</div>
<div class="field">
<input type="file" name="ImageUpload" accept="image/jpeg" />
<p>#Html.ActionLink("Upload", "UploadImage", new { controller = "Home", enctype = "multipart/form-data"}, new { #class = "button" })</p>
</div>
</fieldset>
}
And the controller - HomeController.cs:
public partial class HomeController : Controller
{
// some functions
// ....
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UploadImage(HttpPostedFileBase imageFile)
{
string path = Path.Combine(this.GetImageFolder, Path.GetFileName(imageFile.FileName));
imageFile.SaveAs(path);
return null;
}
// additional POST functions for other forms
// ....
[HttpPost]
public ActionResult Download(Info downloadInfo)
{
// perform checks and calculations
return new reportDownloadPDF(downloadInfo);
}
}
Any suggestion in appreciated.
The solution is just separate upload and download functionalities using two forms so it wont conflict while submitting.
#using (Html.BeginForm())
{
<fieldset>
<div class="title">Click to download report</div>
<div class="field">
<input id="downloadBtn" type="submit" class="button" value="Download" />
</div>
</fieldset>
<fieldset id="Option_ClientInfo">
<div class="title">
Image
</div>
</fieldset>
}
#using (Html.BeginForm("UploadImage", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<fieldset>
<div class="field">
<input type="file" name="ImageUpload" accept="image/jpeg" />
<p>
<input id="uploadBtn" type="submit" class="button" value="Upload" />
</p>
</div>
</fieldset>
}
There is another issue as well. Image control name and Post Action method parameter name should be same.
So your upload image Post Action method will be:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UploadImage(HttpPostedFileBase imageUpload)
{
string path = Path.Combine(this.GetBasePath + "/img/tmp/", Path.GetFileName(imageFile.FileName));
imageFile.SaveAs(path);
return null;
}
I have the view:
<div>
<form method="post"action='#Url.Action("ImportProviders")'>
#(Html.Kendo().Upload()
.Name("files")
.HtmlAttributes(new { aria_label = "files" })
.Validation(validation => validation.AllowedExtensions(new string[] { ".csv" }))
)
<p class ="text"> This File Browser only Accepts .CSV File.</p>
<input type="hidden" id="clientNumber" name="Clientid" value="" />
<p class="submit-btn">
<input type="submit" value="Upload Providers" onclick="submit" id="UploadClick" class="k-button k-primary" />
<i class="fa fa-spinner fa-pulse fa-3x fa-fw" id="makeMeDisappear"></i>
<span class="sr-only">Loading...</span>
</p>
</form>
</div>
<br />
<div>
#Html.Partial("ImportedProviderView");
</div>
I have the c# Controller:
public PartialViewResult ImportProviders(String ClientId, IEnumerable<HttpPostedFileBase> files) {
...
ImportProvidersView(result,FaultyRowsTable,"FAULTY ROW");
ImportProvidersView(result, InsertedRows, "INSERTED ROW");
ImportProvidersView(result, UpdatedRowsTable, "UPDATED ROW");
return PartialView("ImportedProviderView", result.OrderByDescending(x=>x.Createdon));
}
But everytime I run this page it takes me to the partial view only, it doesn't inject it into the main view above.
MAIN QUESTION
How do I make it display inside the main view above?
The #Html.Partial method is only going to render the partial view. If you need to execute the controller action that returns the view you will need to use the #Html.Action method
<div>
#Html.Action("ImportProviders", "ControllerName", new { ClientId = <>, files = <> })
</div>
#Html.Partial renders the partial view on the server as part of the main view, which is returned to the browser. If you need something dynamic when the user does something in the browser then you'll probably want to GET/POST to a controller action rendering a new view - or you'll want to call a controller action rendering a partial view using ajax.
I have this index view:
#model LeadManager.Models.ProspectingApprovalViewModel
#{
ViewBag.Title = "Index";
}
<h2>Approve Or Reject</h2>
<div class="form-horizontal">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-group">
<div>
<input type="submit" name="ApprovalAction" value="Approve" class="btn btn-default" />
<input type="submit" name="ApprovalAction" value="Reject" class="btn btn-default" />
</div>
</div>
}
</div>
//model details are below form
I am trying to read the model inside controller like so:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(ProspectingApprovalViewModel prospectingApprovalViewModel, string ApprovalAction)
{
//access model
}
I can read ApprovalAction but the prospectingApprovalViewModel is null on the postback.
Why is the model not being attached?
Your helper must be inside the form.
When you use two class in parameter in name of html helper must be
name="className.propetyName".
When you want to post two classes you must use ViewModel.
In my MedicalProductController, I am trying to make my Edit action able to edit multiple objects on one page. To do that, I plan on my HTTPPOST edit action method receiving an IEnumerable<MedicalProduct> instead of the MedicalProduct that the scaffolding set up for me.
When I click save to submit some changes, I get an ArguementNullException unhandled on the line: _db.Entry(productList).State = EntityState.Modified; and I don't understand why it is null.
MedicalProductController:
public class MedicalProductController : Controller
{
private MvcMedicalStoreDb _db = new MvcMedicalStoreDb();
// some code omitted for brevity
public ActionResult Edit(int id = 0)
{
MedicalProduct product = _db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
var productList = new List<MedicalProduct> { product };
var viewModel = GetMedicalProductViewModelList(productList);
return View(viewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(IEnumerable<MedicalProduct> productList)
{
if (ModelState.IsValid)
{
_db.Entry(productList).State = EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("Index");
}
//var productList = new List<MedicalProduct> { product };
var viewModel = GetMedicalProductViewModelList(productList);
return View(viewModel);
}
}
Edit.cshtml:
#model IEnumerable<MvcMedicalStore.Models.MedicalProductViewModel>
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>MedicalProduct</legend>
#foreach (var modelItem in Model)
{
#Html.HiddenFor(item => modelItem.ID)
<div class="editor-label">
#Html.LabelFor(item => modelItem.Name)
</div>
<div class="editor-field">
#Html.EditorFor(item => modelItem.Name)
#Html.ValidationMessageFor(item => modelItem.Name)
</div>
<div class="editor-label">
#Html.LabelFor(item => modelItem.Price)
</div>
<div class="editor-field">
#Html.EditorFor(item => modelItem.Price)
#Html.ValidationMessageFor(item => modelItem.Price)
</div>
<div class="editor-label">
#Html.LabelFor(item => modelItem.BrandName)
</div>
<div class="editor-field">
#Html.DropDownListFor(item => modelItem.BrandName, modelItem.BrandSelectListItem)
#Html.ValidationMessageFor(item => modelItem.BrandName)
</div>
}
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
It looks to me like the model binder isn't able to bind to your collection, which would cause it to be null. The reason it's doing that is because you're not specifying an index for each of the elements. That means MVC has no way to determine how to bind them correctly.
Edit
I've figured out why the last revision of this answer didn't work. Firstly, IEnumerable<T> doesn't have a direct indexer. Instead you would use Model.ElementAt(i).ID to access the ID property. However, this actually wouldn't solve the problem with the model binding issue as, for some reason, this doesn't generate the proper indices on the name attributes for the generated <input> fields. (More on this below.)
There are two ways to fix that. The first way would be to pass a List to the view, instead of IEnumerable, then accessing the fields as I showed earlier. However, the better way would be to create an EditorTemplate instead. This will be easier because it saves you having to change your existing methods which are generating your view model. So you'll need to follow these steps:
Create an EditorTemplates folder inside your view's current folder (e.g. if your view is Home\Index.cshtml, create the folder Home\EditorTemplates).
Create a strongly-typed view in that directory with the name that matches your model (e.g in this case the view would be called MedicalProductViewModel).
Move the bulk of your original view into that new template.
You'll end up with the following:
#model MedicalProductViewModel
#Html.HiddenFor(item => Model.ID)
<div class="editor-label">
#Html.LabelFor(item => Model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(item => Model.Name)
#Html.ValidationMessageFor(item => Model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(item => Model.Price)
</div>
<div class="editor-field">
#Html.EditorFor(item => Model.Price)
#Html.ValidationMessageFor(item => Model.Price)
</div>
<div class="editor-label">
#Html.LabelFor(item => Model.BrandName)
</div>
<div class="editor-field">
#Html.DropDownListFor(item => Model.BrandName, Model.BrandSelectListItem)
#Html.ValidationMessageFor(item => Model.BrandName)
</div>
Notice how we're no longer using any indexing notation to access the model properties.
Now in your Edit.cshtml view, you'd be left with this:
#model IEnumerable<MvcMedicalStore.Models.MedicalProductViewModel>
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>MedicalProduct</legend>
#Html.EditorFor(m => m)
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Although I gave a brief explanation at the start, I should really explain what this is actually doing. Your original HTML would have produced output like the following:
<input name="ID" type="text" value="1" />
<input name="Name" type="text" value="Name 1" />
<input name="ID" type="text" value="2" />
<input name="Name" type="text" value="Name 2" />
As you can see, multiple input fields share the same name. That's why the model binder is tripping up, because your action is telling it to bind to a collection and the binder needs to be able to distinguish between each element in the collection. EditorTemplates are smart enough to figure out when you're working with a collection and will apply indices to your input fields automatically. What the code above will do is generate output like this instead:
<input name="[0].ID" type="text" value="1" />
<input name="[0].Name" type="text" value="Name 1" />
<input name="[1].ID" type="text" value="2" />
<input name="[1].Name" type="text" value="Name 2" />
As you can see, the fields now have an index associated with them. That gives the model binder all the information it needs to be able to add all of the items to the collection. Now that's out of the way, we can get back to fixing your product saving code.
What Gert said is still right about the way you're trying to save productList. You need to be setting the EntityState.Modified flag on each individual item in that collection:
if (ModelState.IsValid)
{
foreach (var product in productList)
_db.Entry(product).State = EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("Index");
}
See if that works.
my action method:
[HttpPost, ActionName("Delete")]
[RequiredPermissions(RequiredPermissionName, Operation.Delete)]
public ActionResult DeleteConfirmed(User userWhoGone)
{
_db.Users.Remove(userWhoGone);
_db.SaveChanges();
this.TempData["msg"] = "Deleted User Id " + userWhoGone.Id;
return RedirectToAction("Index");
}
Delete.cshtml
#model NewsMonitoringWeb.Database.Models.User
#{
ViewBag.Title = "User Delete";
<h2>User Delete</h2>
<h3>Are you sure you want to delete this user?</h3>
#Html.Partial("_PartialUser")
#using (Html.BeginForm()) {
<p>
<input type="submit" value="Delete" /> |
#Html.ActionLink("Back to List", "Index")
</p>
}
_PartialUser.cshtml
#model NewsMonitoringWeb.Database.Models.User
<fieldset>
<legend>User</legend>
<div class="display-label">User Role</div>
<div class="display-field">
#Html.DisplayFor(model => model.UserRole.Name)
</div>
<div class="display-label">FirstName</div>
<div class="display-field">
#Html.DisplayFor(model => model.FirstName)
</div>
<div class="display-label">LastName</div>
<div class="display-field">
#Html.DisplayFor(model => model.LastName)
</div>
<div class="display-label">Email</div>
<div class="display-field">
#Html.DisplayFor(model => model.Email)
</div>
<div class="display-label">Is Contributor</div>
<div class="display-field">
#Html.DisplayFor(model => model.IsContributor)
</div>
</fieldset>
i use chrome to check the posted data, and i found that this method post nothing to server.
any suggestion?
Your form has no values to post back. You need to either put some hidden input values in your form to build the user or add a hidden user id to the form and change the post action method.
The later would be my preferred method as follows:
[HttpPost, ActionName("Delete")]
[RequiredPermissions(RequiredPermissionName, Operation.Delete)]
public ActionResult DeleteConfirmed(int userIdWhoGone)
{
_db.Users.RemoveById(userIdWhoGone); // You may need to create this.
// Atlernatively, get the user by id then call 'Remove()
var user = _db.User.Find(userIdWhoGone);
_db.Users.Remove(user);
_db.SaveChanges();
this.TempData["msg"] = "Deleted User Id " + userIdWhoGone;
return RedirectToAction("Index");
}
Delete.cshtml
#model NewsMonitoringWeb.Database.Models.User
#{
ViewBag.Title = "User Delete";
<h2>User Delete</h2>
<h3>Are you sure you want to delete this user?</h3>
#Html.Partial("_PartialUser")
#using (Html.BeginForm()) {
<p>
#Html.HiddenFor(m => m.Id) #* <--- New input *#
<input type="submit" value="Delete" /> |
#Html.ActionLink("Back to List", "Index")
</p>
}
You are not currently posting anything back at all. Your user partial is outside the form.
The only thing in the form is your button and a link.
You need to move your partial so that it is inside the form.
So it will now look like this:
#model NewsMonitoringWeb.Database.Models.User
#{
ViewBag.Title = "User Delete";
<h2>User Delete</h2>
<h3>Are you sure you want to delete this user?</h3>
#using (Html.BeginForm()) {
#Html.Partial("_PartialUser")
<p>
<input type="submit" value="Delete" /> |
#Html.ActionLink("Back to List", "Index")
</p>
}
EDIT
You will need to either add a hidden input to the partial containing the User ID or leave the partial where it was and just put a hidden input within the form.
You would then need change the action to accept the userid and do a lookup on the database.