MVC multiple different submit buttons? what is the best approach? - c#

UPDATE:
Here is my .html page looks like and withing the form tag I have couple of buttons (page size, save to db, remote items)
<div class="col-lg-12">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">Records</h3>
</div>
#using (Html.BeginForm("Index", "MyController", FormMethod.Post, new { #id = "form_header" }))
{
<div class="panel-body">
<div class="row">
<div class="col-xs-4">
<div class="form-group">
<label for="" class="col-xs-5 control-label">Rows per page:</label>
<div class="col-xs-4">
#Html.DropDownListFor(m => m.PageSize,
new List<SelectListItem>
{
new SelectListItem() { Text = "10", Value = "10" },
new SelectListItem() { Text = "25", Value = "25" },
new SelectListItem() { Text = "50", Value = "50" },
new SelectListItem() { Text = "ALL", Value = "ALL" }
}, new { #class = "form-control", Name = "pageSizeDDL" })
</div>
</div>
</div>
<div class="row text-right">
<div class="col-xs-12">
<button class="btn btn-success" type="submit" id="btnSavetoDB" name="saveToDB" value="Save to DB">
<span> <i class="fa fa-save"></i> Save to Database </span>
</button>
<button class="btn btn-danger" type="submit" id="btnRemoveFromGrid" name="removeItems" value="Remove Item From Grid">
<span> <i class="fa fa-remove"></i> Remove </span>
</button>
</div>
</div>
</div>
<div class="col-lg-14">
<div class="panel-body">
<div class="row">
<div class="test" style="overflow: scroll; width: 100%">
<div style="width: 1000px">
#if (Model != null)
{
//grid
}
</div>
</div>
</div>
</div>
</div>
</div>
}
</div>
I have seen all sorts of approach but still not convinced the best way to handle and in my situation I have multiple buttons or events that fire based on the user action: for an example in my case:
in the form:
I have page size dropdown where user can select number of rows to display
I have a import button to import the doc
I have save button - save to db
I have a remove button - remove from grid
currently this is how I have laid out:
[HttpPost]
public ActionResult Index(HttpPostedFileBase file, FormCollection formCollection, string pageSizeDDL, string saveToDB, string removeItems, string PageList)
{
if (file upload) {....}
if (formCollection) { .... }
if (pageSizeDDl {....}
..........
}
my question is:
What if i need the form values for all buttons or perhaps is that right approach the way I'm doing?
I have seen wrapping each button in it's own form in view which I things its not the correct approach and the reason is if I have 10 button in a single page does it mean I have to have 10 form?

Related

Making your form input text control have right size fit?

I have a view and im creating a form, but the input text is too big and want a normal input text when user uses his or her title field within a form. Below is my logic but it fits the whole layout and does not look good and im using bootstrap class. Please assist to have normal class for my input text allow user insert normal input text.
<div class="register-box">
<div class="register-logo">
</div>
</div>
<div class="card">
<div class="card-body register-card-body">
<center>
<img src="~/Images/eNtsa.png" />
</center>
<center>
<p><b>Training Course Registration:Motion MasterClass</b></p>
</center>
#using (Html.BeginForm("editRegForm", "Home", FormMethod.Post, new { #role = "form" }))
{
#Html.AntiForgeryToken()
<div class="input-group-mb-2">
#Html.TextBoxFor(m=>m.Title, new {#class = "form-control", type = "text", id = "title", autofocus = "autofocus", placeholder = "Title", required = "required"})
<div class="input-group-text">
</div>
</div>
}
</div>
</div>
My work around to this is given as below, basically i wanted to find a way where my form will be on the center including text input of my form.
#using eNtsaRegistrationTraining.Models
#model EditTrainingRegFormViewModel
#{
ViewBag.Ttile = "Dashboard";
Layout = "~/Views/Shared/_HomeLayout.cshtml";
}
<center>
<div class="register-box ">
<div class="register-logo">
</div>
<div class="card">
<div class="card-body register-card-body">
<center>
<img src="~/Images/eNtsa.png" />
</center>
<center>
<p><b>Training Course Registration:Motion MasterClass</b></p>
</center>
#using (Html.BeginForm("editRegForm", "Home", FormMethod.Post, new { #role = "form" }))
{
#Html.AntiForgeryToken()
<div class="input-group mb-3">
#Html.TextBoxFor(m => m.Title, new { #class = "form-control", type = "text", id = "title", autofocus = "autofocus", placeholder = "Title", required = "required" })
<div class="input-group-append">
<div class="input-group-text">
</div>
</div>
</div>
}
</div>
</div>
</center>
</div>

ASP .NET MVC dynamically created fields not bind to FormCollection

I created a Partial Razor view with dynamically created fields:
for (int i = 0 ; i < atr.Count; i++)
{
<div class="col-md-12 panel panel-default sx-box-shadow-on-hover">
<div class="panel-heading">
<h2 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="##string.Format("{0}{1}", "collapse", #atr[i].Id)" aria-expanded="true">
#atr[i].Label
<span class="caret"></span>
</a>
</h2>
</div>
<div id=#string.Format("{0}{1}", "collapse", #atr[i].Id) class="col-md-12 panel-collapse collapse" aria-expanded="true">
<form>
<div class="form-group">
#switch (atr[i].AtrType)
{
case "TXT":
case "NUM":
<input id=#atr[i].Id class="form-control margin5" name=#atr[i].Name type="text" />
break;
case "CHB":
<input class="form-control margin5" name=#atr[i].Name type="checkbox" />
break;
case "DTP":
#(Html.Kendo().DatePicker()
.Name(#atr[i].Name)
.Value(DateTime.Now)
.HtmlAttributes(new { style = "margin-top: 5px; width:100% !important", title = #atr[i].Name })
)
break;
case "WTXT":
<textarea name=#atr[i].Name , rows="3"></textarea>
break;
}
</div>
</form>
</div>
</div>
}
Partial view is placed in other view with static fields and button. When I make POST action using button, in FormCollection I have only values from static fields. I can't bind this field's to Model because user can add or delete them in any moment. Every dynamic field created in partial view has it's own Id and Name. What am I missing here?
Post Action:
[HttpPost]
[Authorize]
public ActionResult Search(FormCollection form)
{
//
}
Main Razor Page:
#using (Html.BeginForm("Search", "Home", FormMethod.Post))
{
<div class="col-md-12 panel panel-default sx-box-shadow-on-hover">
<div class="panel-heading">
<h2 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#searchCollapse" aria-expanded="true">
Search options
<span class="caret"></span>
</a>
</h2>
</div>
<div id="searchCollapse" class="col-md-12 panel-collapse collapse in" aria-expanded="true">
<div class="form-group">
<label class="col-md-12 fontBold margin5">Document type:</label>
#(Html.Kendo().DropDownList()
.Name("DocumentTypesDropDownList")
.DataTextField("Name")
.DataValueField("Id")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetDocTypes", "Home");
});
})
.Events(e =>
{
e.Change("docTypeOnChange");
})
.HtmlAttributes(new { style = "margin-top: 5px; width:100% !important", title = "DocumentTypesDropDownList" })
)
<button id="SearchButton" class="btn btn-primary form-control margin5">Search</button>
<div id="searchPanelDiv">
#{
Html.RenderPartial("SearchPanel");
}
</div>
</div>
</div>
</div>
}
Request body looks like this:
DocumentTypesDropDownList=1&Identificator=1234
Parameters from dynamic fields are missing.
I think if you place this:
[HttpPost]
[Authorize]
public ActionResult Search(FormCollection form)
{
var fields = new List<(string, string)>();
foreach (var item in Request.Form)
{
fields.Add((item.Key, item.Value));
}
// do some other stuff
}
in your controller, you are able to read all the fields located in your body request, no matter if they were added dynamically or not.
Nested forms are unsupported in HTML.
If you remove the <form> and </form> tags from the partial view you should be able to capture the dynamic inputs from the FormCollection that is created on the main page by the #using (Html.BeginForm.

Submitting a form from a partial view

I'm trying to submit a form from a modal generated in a partial view. And I don't know how to get back the submitted form.
Here is the view:
#model Myproject.ViewModels.GetMonitorFromDeviceViewModel
#{
ViewBag.Title = "GetMonitorFromDevice";
Layout = "~/Views/Shared/ManagementPage.cshtml";
}
<div id="Accordion">
#{
foreach (var type in Model.AvailableTypesAvailableMonitors)
{
<div class="card">
<div class="card-header">
<a class="card-link" data-toggle="collapse" data-parent="#accordion" href="##type">
#type
</a>
</div>
<div id="#type" class="collapse show">
<div class="card-body">
#{
foreach (var monitor in Model.ActiveMonitors)
{
if (monitor.Type == #type)
{
<p>
#monitor.Name
<span class="btn btn-xs btn-primary btnEdit" onclick="createModal('#Url.Action("NameMonitor", "DeviceManager" , new { idDevice = monitor.DeviceOwner.ID, monitorName = monitor.Name })')">Details</span>
</p>
}
}
}
</div>
</div>
</div>
}
}
</div>
And here is my modal at the bottom of the page:
<div class="modal fade" id="myModal" role="dialog" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog">
<div class="modal-content" id="modelContent">
</div>
</div>
</div>
<script>
function createModal(url) {
$('#modelContent').load(url);
$('#myModal').modal('show');
}
</script>
And finally, here is my partial view that is displayed as a modal:
#model MyProject.ViewModels.NameMonitorModal
#using (Html.BeginForm("NameMonitor", "DeviceManager", FormMethod.Post))
{
<div class="modal-body">
<div class="form-group">
#Html.LabelFor(model => model.NewName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.NewName, new { htmlAttributes = new { #class = "form-control", #Value = Model.TrueName } })
#Html.ValidationMessageFor(model => model.NewName, "", new { #class = "text-danger" })
</div>
</div>
#Html.HiddenFor(model => model.TrueName)
#Html.HiddenFor(model => model.IdDevice)
</div>
<div class="modal-footer">
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save name" class="btn btn-default" data-dismiss="modal" />
</div>
</div>
</div>
}
In my controller, I have an action for my partial view called ActionResult NameMonitor.
In order to catch the submited form, I tried to add another action with the [HttpPost] tag with the same name but doesn't work. I also tried to use the main page action with the [HttpPost] tag but it doesn't work either. As you can see, I have specified the action and controller in the form itself but its still not working.
Now, I'm a little bit out of idea of how I can get the information from my modal back.
data-dismiss="modal" will close the modal without submitting the form, see Dismiss and submit form Bootstrap 3
You can change the submit button to call a JavaScript function to submit the form, then close the modal.
function submitModal() {
$('#myFormId').submit();
$('#myModal').modal('hide');
}

Partial View Not Rendering Inside View

I have a partial view inside another partial view which, when first running the application loads as expected, but when you click to reload the view to push a model into it, it then renders as it's own completely separate view as if it weren't a partial.
I'm calling it inside an Ajax Form like so (On the Action link click, the _GetSearchModal method):
<div id="modelSearch">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<i class="fa fa-search"></i> Search by Model / Manufacturer
</h3>
</div>
<div class="panel-body">
#using (Ajax.BeginForm("_GetSearch", "Home", new AjaxOptions() {UpdateTargetId = "modelSearch"}))
{
#Html.AntiForgeryToken()
<div class="input-group">
#Html.TextBox("search", null, new {id = "name", #class = "form-control", placeholder = "Please enter a manufacturer or model"})
<span class="input-group-btn">
<button id="search" class="btn btn-default" type="submit"><i class="fa fa-search"></i></button>
</span>
</div>
if (Model != null)
{
<div id="searchResults" class="fade-in two">
#foreach (var s in Model)
{
<div class="result">
#switch (s.ResultType)
{
case "Man":
#s.Manufacturer
break;
case "Mod":
#Html.ActionLink(s.Manufacturer + Html.Raw(s.Model), "_GetSearchModal", "Home", new {id = s.MachineId}, new {toggle = "modal", data_target = "#MachineModal"})
<img src="~/Images/General/Tier/#(s.TierId).png" alt="Tier #s.TierId"/>
break;
}
</div>
}
</div>
}
}
</div>
</div>
</div>
<!-- Product Modal -->
<div class="modal fade" id="MachineModal" tabindex="-1" role="dialog" aria-labelledby="MachineModalLabel">
#Html.Partial("_SearchModal", new MachineModal())
</div>
And the view itself should load a different view model (MachineModal):
#model SpecCheck.Portals.Web.UI.ViewModels.MachineModal
#if (Model != null)
{
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="MachineModalLabel">#Model.Manufacturer #Model.Model</h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<img src="~/Images/#Model.Manufacturer/logo.png" alt="#Model.Manufacturer" /><br />
Wiki
</div>
<div class="col-md-6">
#Model.Catagory1 | #Model.Category2<br /><br />
<span class="modal-em">Region: </span> #Model.Region<br />
<span class="modal-em">Status: </span>#Model.Status<br />
<span class="modal-em">Spec Date: </span>#Model.SpecDate
</div>
</div>
</div>
<div class="modal-footer">
View
Quick Compare
Compare
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
}
And the action to do this in the "Home Controller" is:
public ActionResult _GetSearchModal(string machineId)
{
using (var db = new SpecCheckDbContext())
{
MachineModal machine = new MachineModal();
var searchHelper = new SearchHelper(db);
//Get Machine Details
var dbResults = searchHelper.SearchModal(Convert.ToInt32(machineId));
machine.Model = dbResults.Model;
machine.Catagory1 = dbResults.Catagory1;
machine.Category2 = dbResults.Category2;
machine.Manufacturer = dbResults.Manufacturer;
machine.Region = dbResults.Region;
machine.SpecDate = dbResults.SpecDate;
machine.Status = dbResults.Status;
machine.MachineId = dbResults.MachineId;
machine.ManufacturerId = dbResults.ManufacturerId;
var model = machine;
return PartialView("_SearchModal", model);
}
}
First thing I checked was the scripts, they're all in place when the layout page loads so it's not a script issue. Not sure what to change to even try at this point so any suggestions welcome.
In the ajax form:
_GetSearch => _GetSearchModal(name of the action)
Try to return machine to the partial view? Maybe see in the View hierarchy, is there is second _SearchModal partial view, that gets returned?

TagBuilder not generating unqiue id attribute values across ajax requests

I have an issue where I'm using the same template to render some content on a page and the same template is used to render additional content on the page using an AJAX request.
The following code renders the partial razor view:
#model SearchBoxViewModel
<div class="smart-search">
#using (Html.BeginForm("Index", "Search", FormMethod.Get, new { #class = "form-horizontal", role = "form" }))
{
<div class="form-group">
<div class="hidden-xs col-sm-1 col-md-1 col-lg-1 text-right">
#Html.LabelFor(m => m.SearchPhrase, new { #class = "control-label heading" })
</div>
<div class="col-xs-8 col-md-9 col-lg-10">
#Html.TextBoxFor(m => m.SearchPhrase, new {#class = "form-control", placeholder = "for products, companies, or therapy areas"})
</div>
<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1">
<input type="submit" value="Search" class="btn btn-default"/>
</div>
<div class="what-to-search hidden-11 col-sm-11 col-sm-offset-1">
<div class="checkbox">
<label>
#Html.CheckBoxFor(m => m.WhatToSearch, null, false)
#Html.DisplayNameFor(m => m.WhatToSearch)
</label>
</div>
</div>
</div>
}
</div> <!-- /.smart-search -->
The following code makes the AJAX request:
$.ajax(anchor.attr("data-overlay-url-action"))
.done(function (data) {
$("div.overlay").addClass("old-overlay");
$("div.navbar").after(data);
$("div.overlay:not(.old-overlay)")
.attr("data-overlay-url-action", anchor.attr("data-overlay-url-action"))
.hide()
.fadeIn();
$("div.old-overlay")
.fadeOut()
.removeClass("old-overlay");
anchor.addClass("overlay-exists");
});
So what you get is the same partial razor view output on the page twice, once during the page request and once during the AJAX request.
The problem is that TextBoxFor, CheckBoxFor, etc. all make use of TagBuilder.GenerateId to generate the id attribute value but it doesn't account for generating id's across multiple requests where AJAX might be involved. This results in the same id value being output on the page, causing JavaScript to break.
The following is the HTML that is output twice (once during the request and then added in a separate part of the page during an AJAX request):
<div class="smart-search">
<form role="form" method="get" class="form-horizontal" action="/PharmaDotnet/ux/WebReport/Search"> <div class="form-group">
<div class="hidden-xs col-sm-1 col-md-1 col-lg-1 text-right">
<label for="SearchPhrase" class="control-label heading">Search</label>
</div>
<div class="col-xs-8 col-md-9 col-lg-10">
<input type="text" value="" placeholder="for products, companies, or therapy areas" name="SearchPhrase" id="SearchPhrase" class="form-control">
</div>
<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1">
<input type="submit" class="btn btn-default" value="Search">
</div>
<div class="what-to-search hidden-11 col-sm-11 col-sm-offset-1">
<div class="checkbox">
<label>
<input type="checkbox" value="true" name="WhatToSearch" id="WhatToSearch">
NewsManager Search Only
</label>
</div>
</div>
</div>
</form></div>
So the SearchPhrase and WhatToSearch id's are duplicated.
Is there any way to work around this, or is there a better way to render the form elements to avoid this issue?
You could specify your own ID for the items, and then you wouldn't have this ID collision problem. Have you tried setting the id manually: Html.TextBoxFor( ... , new { id = "AnythingHere" }), where the id could be a freshly generated Guid? (Note that you'd probably need to add a prefix, because Guids can start with a number.
So you can use the following. The Guid doesn't look good, and is unnecessarily long. You might want to go with something shorter, like short guid, DateTime.Ticks, ...
#{
var id = "chk_" + Guid.NewGuid().ToString();
}
#Html.CheckBoxFor(..., new { id = id })
#Html.LabelFor(..., new { #for = id })

Categories