Consider the following html with three submit buttons:
#using (Html.BeginForm("Process", "Part4", FormMethod.Post, new { id = "submitForm" }))
{
<select class="form-control" name="lbToolingID" id="lbToolingID">
#foreach (var toolingID in toolingIDList)
{
<option value="#toolingID">#toolingID</option>
}
<input class="btn btn-primary" type="submit" name="btnAction" value="Add Tooling" />
#* Selected tool will be added here on the [Add Tooling] button click *#
<table>
#if (Model != null && Model.M_PartTooling_List != null && Model.M_PartTooling_List.Count > 0)
{
for (int i = 0; i < Model.M_PartTooling_List.Count; i++)
{
<tr>
<td nowrap>
#Html.ActionLink("Remove Location", "Remove", new { #id = Model.M_PartTooling_List[#i].ID }, new { #class = "btn-sm btn-danger" })
</td>
<td nowrap colspan="2">
#Html.HiddenFor(model => model.M_PartTooling_List[i].ToolingID)
#(Model.M_PartTooling_List[i].ToolingID)
</td>
</tr>
}
}
</table>
#* Once done these buttons will add the records into database *#
<input class="btn btn-success" type="submit" name="btnAction" value="Save & Return To List" />
<input class="btn btn-success" type="submit" name="btnAction" value="Save & Continue Create" />
}
...
...
#section Scripts
{
#Scripts.Render("~/bundles/jqueryval")
}
The following is the action :
[HttpPost]
public ActionResult Process(M_PartViewModels m_PartViewModels, string btnAction, string lbToolingID)
{
if (btnAction == "Add Tooling")
{
AddPartTooling(m_PartViewModels, lbToolingID);
return View("Create", m_PartViewModels);
}
CreatePart(m_PartViewModels);
if (btnAction == "Save & Return To List")
{
return RedirectToAction("Index");
}
return RedirectToAction("Create");
}
On the first time clicking the Add Tooling button, it is working fine, we can see that on breakpoint the value of btnAction is indeed Add Tooling :
But on the second time clicking the Add Tooling button or any other two buttons, somehow the value of btnAction became null :
We have already inspect the said button in the browser and we can see that value of the button still intact :
Can somebody point out what was the cause of this and how to work around this?
I know we can work around this using jQuery but we wanted try this with minimal jQuery coding if possible.
UPDATE
Removing the reference to this script got the page to work as intended but it disables any other (remote) validations that we had:
#section Scripts
{
#Scripts.Render("~/bundles/jqueryval")
}
Will look more into this. Possibly if we can disable the call to remote validation temporarily on Add Location button click.
UPDATE 2017-06-22 0017 WITH OK SOLUTION - jquery.validate* is the culprit?
This may not be the solution I would like it to be but as long as it is working I am OK with this.
Since we already knew that removing the
#Scripts.Render("~/bundles/jqueryval")
made the page work as intended.
This is the bundle definition by the way :
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include("~/Scripts/jquery.validate*"));
I decided to ditch the remote validation
[Remote("CheckPartID", "Part", AdditionalFields = "PartID_Ori", ErrorMessage = "Part ID already in use!")]
defined in my model annotation and port the validation into my action instead :
[HttpPost]
public ActionResult CreateProcess(M_PartViewModels m_PartViewModels, string btnAction, string lbToolingID)
{
ViewBag.VMainMenu = GetMenu();
ViewBag.ToolingIDList = GetToolingID();
CheckPartID(m_PartViewModels);
CheckPartDesc(m_PartViewModels);
if (btnAction == "Add Tooling")
{
AddPartTooling(m_PartViewModels, lbToolingID);
return View("Create", m_PartViewModels);
}
if (ModelState.IsValid)
{
// Do some DB insert stuff
if (btnAction == "Save & Return To List")
{
return RedirectToAction("Index");
}
else
{
return RedirectToAction("Create");
}
}
return View("Create", m_PartViewModels);
}
public void CheckPartID(M_PartViewModels m_PartViewModels)
{
if (m_PartViewModels.M_Part.PartID != m_PartViewModels.M_Part.PartID_Ori)
{
var routingID = db.M_Part.Where(u => u.PartID == m_PartViewModels.M_Part.PartID).FirstOrDefault();
if (routingID != null)
{
ModelState.AddModelError("M_Part.PartID", "Part ID already exists.");
}
}
}
public void CheckPartDesc(M_PartViewModels m_PartViewModels)
{
if (m_PartViewModels.M_Part.PartDesc != m_PartViewModels.M_Part.PartDesc_Ori)
{
var routingID = db.M_Part.Where(u => u.PartDesc == m_PartViewModels.M_Part.PartDesc).FirstOrDefault();
if (routingID != null)
{
ModelState.AddModelError("M_Part.PartDesc", "Part Description already exists.");
}
}
}
While I felt not entirely satisfied with this solution (where the validation only fires on submit instead on keyup) but since it is working as intended will use this for now.
Will update this once found better way.
Thank you for your kind attention guys. I really appreciate it. :)
Related
Ok, so now I'm trying to learn .net core mcv and I'm having a problem mapping data from MySQL back to my form. When I make the form as a single text box with a single button, and the other fields outside the form (but on the same page), the mapping works fine. If I include all the fields within the form, the data is obtained but not displayed on the page. I have even gone so far as to code one of the multiple submit buttons as an update of the data. I use the first text box to get the item from the database, which it does (but does not map to the text-boxes), then in the second text box (which should have the existing data, but is empty) I put the information to update in the database, click on the submit button for that text box, and the database is updated (but the text boxes in the view remain blank).
My model:
using System;
namespace DbTest.Models
{
public class ProductInventory
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
public int Field4 { get; set; }
}
}
my controller:
using System;
using Microsoft.AspNetCore.Mvc;
using MySql.Data.MySqlClient;
using Microsoft.AspNetCore.Authorization;
using DbTest.Models;
namespace DbTest.Controllers
{
public class InventoryController : Controller
{
// [Authorize]
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult ProcessForm(string button, ProductInventory p)
{
IActionResult toDo = null;
if (button == "Button1")
{
toDo = GetItem(p);
}
if (button == "Button2")
{
toDo = UpdateField2(p);
}
if (button == "Button3")
{
toDo = UpdateField3(p);
}
if (button == "Button4")
{
toDo = UpdateField4(p);
}
return toDo;
}
// [HttpPost]
public IActionResult GetItem(ProductInventory p)
{
//CODE SNIP - DATABASE QUERY, IT ALL WORKS, SO WHY BOTHER YOU WITH THE DETAILS?
return View("Index", p);
}
public IActionResult UpdateField2(ProductInventory p)
{
//CODE SNIP - DATABASE UPDATE, ALL WORKS, NOTHING TO SEE HERE
return View("Index", p);
}
}
}
And finally, my view:
#model DbTest.Models.ProductInventory
#{
ViewData["Title"] = "Inventory Page";
}
#using (Html.BeginForm("ProcessForm", "Inventory", FormMethod.Post))
{
<div>
Search Item (Field 1):
#Html.TextBoxFor(model => model.Field1)
<input type="submit" name="button" value="Button1" />
</div>
<div>
Field 2:
#Html.TextBoxFor(model => model.Field2)
<input type="submit" name="button" value="Button2" />
</div>
<div>
Field 3:
#Html.TextBoxFor(model => model.Field3)
<input type="submit" name="button" value="Button3" />
</div>
<div>
Field 4:
#Html.TextBoxFor(model => model.Field4)
<input type="submit" name="button" value="Button4" />
</div>
}
To reiterate, if I close the form after Button1:
#using (Html.BeginForm("ProcessForm", "Inventory", FormMethod.Post))
{
<div>
Search Item (Field 1):
#Html.TextBoxFor(model => model.Field1)
<input type="submit" name="button" value="Button1" />
</div>
}
<div>
Field 2:
//etc.
the mapping works, but only the first field and button of the form work. With the form around all four fields and buttons, the mapping doesn't work, but the coding of the second button DOES update the database on clicking Button2.
Can someone explain what I've done wrong here?
Thanks!
At first, don't use html helpers in ASP.NET Core.They work but it is not best practice. Instead use tag helpers wherever possible. Furthermore, don't use your db models as view models.
Regarding your Index action: You forgot to pass a view model to your view.
In your ProcessForm action you instantiate IActionResult and then assign it with a (action) function. Don't do that. Instead use return RedirectToAction("ActionName");.
In your case I would handle the DB updates inside the ProcessForm action or in a function, which doesn't return IActionResult.
In conclusion, I can only recommend you to read the ASP.NET Core documentation and then ask again if you still don't get it to work. I recommend you to start with reading this.
I'm searching my database correctly and returning the records that I have when I click my search button. I'm having a bit of a problem trying to add code to my submit button functionality. If I can't find a record in my DB, I want to just return text on the page like "Record Not Found" and display button options to add a record or cancel. I have the add record and cancel functionalities. I'm just stuck on my submit button displaying if I i cant find a record and if i can't display the buttons for add or cancel. Any help is greatly appreciated. Thank You.
Here is my Controller:
public ActionResult TakeInventory(int? AssetNum, string owners)
{
var records = from s in db.Assets select s;
if (AssetNum != 0)
{
records = records.Where(c => c.AssetKey == AssetNum);
}
if (!String.IsNullOrEmpty(owners))
{
records = records.Where(x => x.InventoryOwner.Equals(owners));
}else
{
return View(records);
}
return View(records);
Here is my View:
#model IEnumerable<CTS_Inventory.Models.Asset>
#using (Html.BeginForm("TakeInventory","InventoryManagement",FormMethod.Get))
{
<p>
Item: #Html.TextBox("AssetNum", "Enter An Asset Tag" )
Inventory Owner: #Html.TextBox("owners", "Enter Owner")
<input type="submit" value="Search" />
</p>
}
yBrowser: IE9
Technologies: MVC5
I am mainly using Angular for everything on my page. (Single Page App).
But because I am working with IE9, I can't use FileAPI.. So, I decided to go with MVC's Form Actions to get HttpPostedFileBase in my controller methods to handle fileupload.
Html Code: (Is present in a modal)
#using (Html.BeginForm("UploadTempFileToServer", "Attachment", FormMethod.Post, new { enctype = "multipart/form-data", id = "attachmentForm" }))
{
<div>
<span id="addFiles" class="btn btn-success fileinput-button" ng-class="{disabled: disabled}" onclick="$('#fileUpload').click();">
<span>Add files...</span>
</span>
<input id="fileUpload" type="file" name="files" class="fileInput" onchange="angular.element(this).scope().fileAdded(this)" />
</div>
<div>
<span class="control-label bold">{{currentFilePath}}</span>
<input name="fileUniqueName" value="{{fileUniqueName}}" />
<input id="attachmentSubmit" type="submit" value="Upload File" />
</div>
}
MVC Controller:
public void UploadTempFileToServer(IEnumerable<HttpPostedFileBase> files, string fileUniqueName)
{
var folderPath = fileStorageFolder;
foreach (var file in files)
{
if (file.ContentLength > 0)
{
file.SaveAs(folderPath + fileUniqueName);
}
}
}
Question #1: Does anyone know of a way to send the HttpPostedFileBase data to the controller, without using form's submit action?
I don't mind using Jquery if need be. I have tried hijacking the form's submit action and that didn't work.
I tried sending the file control's data using non submit button event, but no luck there either.
If not:
Question #2 How do I prevent the page from going to /Attachment/UploadTempFileToServer after the execution of submit is completed?
To answer #2 (and assuming you're using jQuery):
$(document).on('submit', '#attachmentForm', function(event){
event.preventDefault();
// everything else you want to do on submit
});
For #1, unfortunately, unless a browser supports XMLHttpRequest2 objects (which I don't believe IE9 does), you can't send file data via ajax. There are plugins that let you submit the form to a hidden iframe, though. I think Mike Alsup's Form plugin has that ability: http://malsup.com/jquery/form/#file-upload
So, after much research and attempts. This is my solution:
Using https://github.com/blueimp/jQuery-File-Upload/wiki
HTML:
Earlier I was using a hidden file upload control and triggering its click via a span. But because of security issues a file input which is opened by javascript can't be submitted by javascript too.
<div class="col-md-7">
<div class="fileupload-buttonbar">
<label class="upload-button">
<span class="btn btn-success btnHover">
<i class="glyphicon glyphicon-plus"></i>
<span>Add files...</span>
<input id="fileUpload" type="file" name="files"/>
</span>
</label>
</div>
</div>
Javascript:
$('#fileUpload').fileupload({
autoUpload: true,
url: '/Attachment/UploadTempFileToServer/',
dataType: 'json',
add: function (e, data) {
var fileName = data.files[0].name;
var ext = fileName.substr(fileName.lastIndexOf('.'), fileName.length);
var attachment = {
AttachmentName: fileName,
Extension: ext
}
var fileUniqueName = id + ext;
//Sending the custom attribute to C#
data.formData = {
fileUniqueName: fileUniqueName
}
data.submit().success(function (submitData, jqXhr) {
attachment.Path = submitData.path;
//Add the attachment to the list of attached files to show in the table.
$scope.attachmentControl.files.push(attachment);
//Since this is not a direct angular event.. Apply needs to be called for this to be bound to the view.
$scope.$apply();
}).error(function (errorData, textStatus, errorThrown) {
});
},
fail: function (data, textStatus, errorThrown) {
}
});
C#:
public virtual ActionResult UploadTempFileToServer(string fileUniqueName)
{
//Getting these values from the web.config.
var folderPath = fileStorageServer + fileStorageFolder + "\\" + tempFileFolder + "\\";
var httpPostedFileBase = this.Request.Files[0];
if (httpPostedFileBase != null)
{
httpPostedFileBase.SaveAs(folderPath + fileUniqueName);
}
return Json(new
{
path = folderPath + fileUniqueName
},
"text/html"
);
}
I've tried implementing the option explained in this article.
public class HttpParamActionAttribute : ActionNameSelectorAttribute
{
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
if (actionName.Equals(methodInfo.Name, StringComparison.InvariantCultureIgnoreCase))
return true;
if (!actionName.Equals("Action", StringComparison.InvariantCultureIgnoreCase))
return false;
var request = controllerContext.RequestContext.HttpContext.Request;
return request[methodInfo.Name] != null;
}
}
My controller actions:
[HttpParamAction]
[HttpPost]
public virtual ActionResult EditAccouncement(_AccouncementPostViewModel m)
[HttpParamAction]
[HttpPost]
public virtual PartialViewResult DeleteAnnouncement(int id)
My form:
#using (Ajax.BeginForm("Action", ajaxOptions: new AjaxOptions()
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "announcement" + #Model.id
}))
{
//form values omitted
<button type="submit" class="submitbutton" name="edit">Change details</button>
<button type="submit" class="submitbutton" name="delete">Delete</button>
}
However the controller action being called is still just the Action method (which doesn't exist). Am I missing something?
Your problem is caused by mismatch between your action names and the button name attributes.
The value of the name attribute on your buttons needs to match the action names, like so:
<button type="submit" name="EditAccouncement">Change details</button>
<button type="submit" name="DeleteAnnouncement">Delete</button>
Update: I would suggest a different approach all together. This solution seems counter-intuitive to me, and not particularly easy to follow.
You could just as easily use JavaScript (e.g. jquery) to handle the form submits 'manually', by hooking up two different event handlers to your buttons. One event would make a POST to the EditAccouncement (typo!) action and one would make a POST to the DeleteAnnouncement action.
I made a mockup on jsfiddle which demonstrates the code: http://jsfiddle.net/wmWNj/3/
Update 2: fixed typos in jsfiddle
I am trying to have two submit buttons in my form - one accepts meetings; the other declines them. They will both have different behaviours. How can I do is in my C# code?
The functionality I want is essentially
if(isPost) {
if(//accept button pressed(Request.Form[???]))
{
}
else
{
}
}
Here is my HTML :
<button name="accept" type="submit">Accept</button>
<div class="spacer"></div>
<button name="decline" type="submit">Decline</button>
<div class="spacer"></div>
Simple enough, but I cannot find a test for this on the Internet or in any documentation. Does anyone know what I would have for this ?
Give each button element the same name (in this example, 'SubmitButton') but a different value, then do a check for the different values in your server code, ie:
<button type="submit" name="SubmitButton" value="Accept">Accept</button>
<button type="submit" name="SubmitButton" value="Decline">Decline</button>
Then in your server code:
string buttonClicked = Request.Form["SubmitButton"]
if(buttonClicked == "Accept")
{
// Accept code
}
else if(buttonClicked == "Decline")
{
// Decline code
}
Be aware that this won't work on some earlier versions of IE though, and you may have to do a little javascript on the client prior to the post request being made.
As far as I remember, you have a controller action looking like this:
public ActionResult MyAction(string accept, string decline)
{
if (!string.IsNullOrEmpty(accept))
{
//do something
}
else
{
//do something else
}
}
Presuming you are using MVC3 you would do something like
#using (Html.BeginForm("Accept","Meeting"))
{
<input type="submit" value="Accept" />
}
#using (Html.BeginForm("Decline","Meeting"))
{
<input type="submit" value="Decline" />
}
You would then just have your accept and decline code in your Accept and Decline actions of your meeting controller respectively. No need for an if statement at all.
Example controller
public class MeetingController : Controller
{
[HttpPost]
public ActionResult Accept()
{
//Do accept stuff
return View();
}
[HttpPost]
public ActionResult Decline()
{
//Do decline stuff
return View();
}
}
Generally in the codebehind in c# you'd be looking for which button was clicked. You'd have an event handler for each button, and you code would react according to which button was clicked.
this.btnRejectAppointment.Click += new System.EventHandler(this.btnRejectAppointment_Click);
And then your method
private void btnRejectAppointment_Click(object sender, System.EventArgs e)
{
//code to reject appt.
}
And you'd have the SAME set for the other button.
ASP.NET Button control has OnClick event, so add different handler to every your button:
<asp:Button OnClick="MyHandler1" ... />
in code behind:
protected void MyHandler1(object sender, EventArgs args)
{ /* do stuff */ }
One of the possible solutions - use two buttons of type LinkButton but specify slightly different PostBackUrl:
<!-- Accept meeting button -->
<asp:LinkButton ID="acceptMeeting" runat="server"
PostBackUrl="MeetingsManager.aspx?Mode=Accept" />
<!-- Decline meeting button -->
<asp:LinkButton ID="declineMeeting" runat="server"
PostBackUrl="MeetingsManager.aspx?Mode=Decline" />
And in code behind:
public enum Mode
{
Unknown,
Accept,
Decline
}
protected void Page_Load(object sender, EventArgs args)
{
Mode currentMode = Mode.Unknown;
var rawMode = Request.QueryString["Mode"];
if (rawMode != null)
{
currentMode = (Mode)Enum.Parse(
typeof(Mode),
rawMode.ToString())
}
}