Passing selected button value from view to controller - c#

I would like to pass the selected button value to the controller. See my code below.
In my controller I am passing through the ProductId which I will then use to set up my product value inside my controller.
Controller:
public ActionResult PlaceOrder(int ProductId, string OrderType)
{
// Do something
}
Inside my view I have a foreach loop which will create radio like buttons and I have also got a hiddenfor(SelectedProductId)
View:
<div class="panel panel-primary">
<div class="panel-heading">Panel Name</div>
<div class="panel-body">
<div class="row">
<div class="form-group">
#Html.HiddenFor(m => m.SelectedProductId)
#if (Model.Products != null && Model.Products.Count > 0)
{
<div class="btn-group" data-toggle="buttons">
#foreach (var product in Model.Products)
{
<label class="btn btn-default productButton">
<div class="labelProduct">#Product.Name</div>
<input type="radio" name="ProductGMX" id="#("product" + #product.Id)" autocomplete="off" checked data-id="#product.Id">
</label>
}
</div>
I will want to pass the Product Id in the ActionLink which will then pass it to the controller but I am not sure how this can be achieved
Button Click:
#Html.ActionLink("Order with standard delivery", "PlaceOrder", "Standard", new { ProductId = ?, OrderType = "Standard delivery" }, new { area = "Standard" })
#Html.ActionLink("Order with Next day Delivery", "PlaceOrder", "Elevated", new { ProductId = ?, OrderType = "NextDay" }, new { area = "Elevated", })

You either need to use JavaScript to update the ActionLink's url whenever the product changes, using the data-id from the radio button.
Or
Use submit buttons instead of ActionLinks, and set the value on the radio button to the product id. You'll need to put some logic in your controller to handle the two different buttons.

Those aren't buttons. They're links, which don't participate in the form submission.
Use real buttons, i.e. <button></button> and give them a name. Then you can see which was clicked by inspecting the Request object:
<button name="_StandardDelivery">Order with standard delivery</button>
Then in your action:
if (Request["_StandardDelivery"] != null) {
// user chose standard delivery
}

Related

How to submit multiple identical forms with one button

I'm currently building and application in ASP.NET Core MVC and I have ran into a problem which I cannot solve.
I have a form for something and that form should contain multiple identical fields which are added dynamically (1-10). I have managed to do that by creating a ViewComponent which contains those form fields and I make an Ajax call to invoke the view component into a tab if a user chooses to add another segment of those fields.
function CallViewComponent(num_tabs) {
var data = { id: num_tabs };
$.ajax({
type: 'POST',
url: '/Create/CreateActivityForm',
cache: false,
data: data
}).done(function (result) {
var container = "#activity-" + num_tabs;
$(container).html(result);
});
}
The problem arises because each of those fields in that view component shares a name with the other fields so each time I invoke another view component the radio buttons are shared between all identical fields.
Here is a snippet of the ViewComponent:
#model CreateActivityViewModel
<div class="activity-tab">
<div class="form-group">
<label asp-for="La.OrdinalNumber">Redni broj aktivnosti</label><br />
<select asp-for="La.OrdinalNumber" class="ordinals" style="width:50%">
#foreach (var on in Model.OrdinalNumbers)
{
<option value="#on.Value">#on.Text</option>
}
</select>
</div>
<div class="form-group">
<label asp-for="La.Latype">Tip aktivnosti</label><br />
<select asp-for="La.Latype" class="activity-type" style="width:50%">
#foreach (var lt in Model.LaTypes)
{
<option value="#lt">#lt.LatypeName</option>
}
</select>
</div>
<div class="form-group">
<label asp-for="La.Laname">Naziv aktivnosti</label>
<input asp-for="La.Laname" type="text" name="La.Laname" placeholder="Unesite naziv aktivnosti" class="f1-activity-name form-control" id="f1-activity-name">
</div>
Here is my controller which returns the ViewComponent:
[HttpPost]
public IActionResult CreateActivityForm(int id)
{
return ViewComponent("ActivityTab", id);
}
Here is the Invoke method from the ViewComponent:
public IViewComponentResult Invoke(int id)
{
var latypes = _laTypeRepository.GetAllLaType.ToList();
var ordinals = new List<SelectListItem>();
var laperformances = _laPerformanceRepository.GetAllLaPerformance.ToList();
var teachingAids = _teachingAidRepository.GetAllTeachingAid.ToList();
var strategyMethods = _strategyMethodRepository.GetAllStrategyMethod.ToList();
var laCollaboration = _laCollaborationRepository.GetAllLaCollaboration.ToList();
for (int i = 1; i <= 100; i++)
{
ordinals.Add(new SelectListItem($"{ i }. aktivnost", i.ToString()));
}
return View( new CreateActivityViewModel
{
FormId = id,
LaTypes = latypes,
OrdinalNumbers = ordinals,
LaPerformances = laperformances,
StrategyMethods = strategyMethods,
Lacollaborations = laCollaboration,
TeachingAids = teachingAids,
TeachingAidUser = new List<TeachingAid>(),
TeachingAidStudent = new List<TeachingAid>()
});
}
And finally this is where the ViewComponent gets invoked. It is inside another form because I need to submit the main form and all the ViewComponents at once:
<fieldset>
<h4>Aktivnosti</h4>
<!-- Activity Tabs -->
<div id='activity-tabs'>
<!-- Activity Links -->
<ol id="#activity-links">
<li><a href='#activity-1'>#1</a></li>
<li id="add-activity"><button type="button" id='add-activity'><i class="fa fa-plus"></i></button></li>
</ol>
<!-- Activity Content -->
<div id='activity-1'>
<h3>Aktivnost #1</h3>
#await Component.InvokeAsync("ActivityTab")
</div>
</div>
<!-- Navigation Buttons -->
<div class="f1-buttons">
<button type="button" class="btn btn-previous">Prethodna</button>
<button type="submit" class="btn btn-submit">Kreiraj scenarij</button>
</div>
</fieldset>
My question is how do I separate those identical forms and be able to submit them and store every single one of those forms into an array of objects which I can then store into a database.
I am open to all ideas and will change the entire code if necessary.
Thank you!
If you have an array of objects you need to render the components using a FOR loop rather than a FOR-EACH. I like to push common code into a shared view but you can code direct in the view. You will need to set your asp-for attributes in order to bind values to the model
#for (int index = 0; index < Model.Resources.Count; index++)
{
<partial name="_ResourceHidden" for="#Model.Resources[index]" />
Direct render
#for (int index = 0; index < Model.Resources.Count; index++)
{
<tr>
<td>
#Model.Resources[index].ResourceName
</td>

Partial within a partial null exception

I have a MVC form which is more complex than all of my others, utilising three models.
Company -> Base_IP -> RequestedIP which goes ViewModel -> Partial1 -> Partial2
I am using BeginCollectionItem for this has each model has a property list of the the model down from it. IE - Company has a property called baseIps, the BaseIp class has a property called requestedIps, it is requestedIps that is coming back null, the count is there on page render, but is not on submit.
When submitting to the database in the post Create(), I get nulls on the 'requestedIps' property, why is this?
I've added the offending controller and partial code samples below, not the entire thing as it's massive/redundant - any questions, please let me know.
Controller - [HttpGet]Create()
public ActionResult Create()
{
var cmp = new Company
{
contacts = new List<Contact>
{
new Contact { email = "", name = "", telephone = "" }
}, pa_ipv4s = new List<Pa_Ipv4>
{
new Pa_Ipv4
{
ipType = "Pa_IPv4", registedAddress = false, existingNotes = "", numberOfAddresses = 0, returnedAddressSpace = false, additionalInformation = "",
requestedIps = new List<IpAllocation>
{
new IpAllocation { allocationType = "Requested", cidr = "", mask = "", subnet = "" }
}
}
}
};
return View(cmp);
}
Controller - [HttpPost]Create()
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Company cmp) // does not contain properties assigned/added to in view render
{
if (ModelState.IsValid)
{
db.companys.Add(cmp);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(cmp);
}
Create View
#model Company
#using (Html.BeginForm())
{
<div id="editorRowsAsn">
#foreach (var ip in Model.pa_ipv4s)
{
#Html.Partial("Pa_IPv4View", ip)
}
</div>
<br />
<div data-role="main" class="ui-content">
<div data-role="controlgroup" data-type="horizontal">
<input type="submit" class="ui-btn" value="Create" />
</div>
</div>
}
Pa_Ipv4 View
#model Pa_Ipv4
#using (Html.BeginCollectionItem("pa_ipv4s"))
{
#Html.AntiForgeryToken()
<div id="editorRowsRIpM">
#foreach (var item in Model.requestedIps)
{
#Html.Partial("RequestedIpView", item)
}
</div>
#Html.ActionLink("Add", "RequestedManager", null, new { id = "addItemRIpM", #class = "button" }
}
RequestedIpView
#model IpAllocation
<div class="editorRow">
#using (Html.BeginCollectionItem("requestedIps"))
{
<div class="ui-grid-c ui-responsive">
<div class="ui-block-a">
<span>
#Html.TextBoxFor(m => m.subnet, new { #class = "checkFiller" })
</span>
</div>
<div class="ui-block-b">
<span>
#Html.TextBoxFor(m => m.cidr, new { #class = "checkFiller" })
</span>
</div>
<div class="ui-block-c">
<span>
#Html.TextBoxFor(m => m.mask, new { #class = "checkFiller" })
<span class="dltBtn">
<img src="~/Images/DeleteRed.png" style="width: 15px; height: 15px;" />
</span>
</span>
</div>
</div>
}
</div>
You first (outer) partial will be generating correct name attributes that relate to your model (your code does not show any controls in the Pa_Ipv4.cshtml view but I assume you do have some), for example
<input name="pa_ipv4s[xxx-xxx].someProperty ...>
however the inner partial will not because #using (Html.BeginCollectionItem("requestedIps")) will generate
<input name="requestedIps[xxx-xxx].subnet ...>
<input name="requestedIps[xxx-xxx].cidr ...>
where they should be
<input name="pa_ipv4s[xxx-xxx].requestedIps[yyy-yyy].subnet ...>
<input name="pa_ipv4s[xxx-xxx].requestedIps[yyy-yyy].cidr ...>
Normally you can pass the prefix to the partial using additional view data (refer this answer for an example), but unfortunately, you do not have access to the Guid generated by the BeginCollectionItem helper so its not possible to correctly prefix the name attribute.
The articles here and here discuss creating your own helper for handling nested collections.
Other options include using nested for loops and including hidden inputs for the collection indexer which will allow you to delete items from the collection and still be able to bind to your model when you submit the form.
for (int i = 0; i < Model.pa_ipv4s.Count; i++)
{
for(int j = 0; j < Model.pa_ipv4s[i].requestedIps.Count; j++)
{
var name = String.Format("pa_ipv4s[{0}].requestedIps.Index", i);
#Html.TextBoxFor(m => m.pa_ipv4s[i].requestedIps[j].subnet)
#Html.TextBoxFor(m => m.pa_ipv4s[i].requestedIps[j].cidr)
...
<input type="hidden" name="#name" value="#j" />
}
}
However if you also need to dynamically add new items you would need to use javascript to generate the html (refer examples here and here)
If you look at your final markup you will probably have inputs with names like
input name="subnet"
input name="cidr"
input name="mask"
This is how the form collection will appear when the form gets posted. Unfortunately this will not bind to your Company model.
Your fields will need to look like this instead
input name="Company.pa_ipv4s[0].subnet"
input name="Company.pa_ipv4s[0].cidr"
input name="Company.pa_ipv4s[0].mask"
input name="Company.pa_ipv4s[1].subnet"
input name="Company.pa_ipv4s[1].cidr"
input name="Company.pa_ipv4s[1].mask"
There are multiple ways to "fix" this, and each has its own caveats.
One approach is to setup "Editor" views (typically in ~/Views/Shared/EditorTemplates/ClassName.cshtml), and then use #Html.EditorFor(x => x.SomeEnumerable). This will not work well in a scenario in which you need to be able to delete arbitrary items from the middle of a collection; although you can still handle those cases by means of an extra property like ItemIsDeleted that you set (e.g. via javascript).
Setting up a complete example here would be lengthy, but you can also reference this tutorial: http://coding-in.net/asp-net-mvc-3-how-to-use-editortemplates/
As a start, you would create a simple template like
~/Views/Share/EditorTemplates/Contact.cshtml:
#model yournamespace.Contact
<div>
#Html.LabelFor(c => c.Name)
#Html.TextBoxFor(c => c.Name)
#Html.ValidationMessageFor(c => c.Name)
</div>
<div>
#Html.LabelFor(c => c.Email)
#Html.TextBoxFor(c => c.Email)
#Html.ValidationMessageFor(c => c.Email)
</div>
... other simple non-enumerable properties of `Contact` ...
#Html.EditorFor(c => c.pa_ipv4s) #* uses ~/Views/Shared/EditorTemplates/pa_ipv4s.cshtml *#
In your view to edit/create a Company, you would invoke this as
#Html.EditorFor(company => company.Contacts)
(Just like the EditorTemplate for Company invokes the EditorFor pa_ipv4s.)
When you use EditorFor in this way, MVC will handle the indexing automatically for you. (How you handle adding a new contact/IPv4/etc. here is a little more advanced, but this should get you started.)
MVCContrib also has some helper methods you can use for this, but it's not particularly simple from what I recall, and may tie you down to a particular MVC version.

ListBoxFor not unselecting between posts

I have following code in View:
<div class="row form-group">
#Html.LabelFor(x => x.GroupCriteria.Name, Translation.Name, new { #class = "col-sm-4 control-label" })
<div class="col-sm-4">
#Html.ListBoxFor(m => m.GroupCriteria.Name, Model.NameList, new { #class = "multi-select-custom" })
</div>
</div>
...
<button type="submit" name="removeCriterion" value="Name">X</button>Name: #(string.Join(",", Model.GroupCriteria.Name))</button>
...
<button type="submit">#Translation.Update</button>
And in controller:
public ActionResult GetUsers(GroupCriteriaModel groupCriteria, string removeCriterion)
{
if("Name".Equals(removeCriterion)) groupCriteria.Name.Clear();
...
return View(modelWithCriterias);
}
Now what is happening:
When I select/unselect some values by clicking in select then click "Update", everything works fine: selection in returned page is same as what was posted.
Now i click "X" to clear "Name". "removeCriterion" is equal to "Name", so Name list is cleared (in debugger I see it is empty). Debugger in View: "Model.GroupCriteria.Name" is still empty, none of "Model.NameList" is "Selected" (set true). But returned HTML contains same selection as before (same items have attribute 'selected="selected"').
Is this some kind of caching done by MVC? How to avoid this?
Most likely it is being binded from ModelState, so you should clear it:
if("Name".Equals(removeCriterion))
{
groupCriteria.Name.Clear();
ModelState.Clear();
}
If you want to perform the operation in more specific way, you can use ModelState.Remove() in order to remove only the state related to your ListBox.

Form Collection issue

I have an html begin form in mvc
#using (Html.BeginForm("Search", "Reports",FormMethod.Post, new { enctype = "multipart/form-data", #class = "form-inline" }))
{
<div class="form-group">
<input type="text" class="form-control input-sm" placeholder="Value" name="SearchValue">
<input type="text" class="form-control input-sm second-value" placeholder="Value" style="display:none;" name="SearchValue1">
<button type="button" class="btn btn-default btn-Add">+</button>
</div>
<div id="othersearch"></div>
<input type="submit" value="Search" class="btn btn-primary" />
}
I want to post this form item in one controller
public ActionResult Search(FormCollection collection)
{
string searchvalue = collection.Get("SearchValue");
return View();
}
my problem is that some times second text box is not visible.. that time i dont want to collect the values.And when i press button add generate the same type of input field in the form with same name (i can add many input box). Then how can i collect all these in my controller . please help me..
You can have all the text boxes with same name "SeachValue" in your case.
string searchvalue = collection.Get("SearchValue");
This will return all text box values as comma saperated string which you can split and use further.
Check out the screen shot
the html
and the results
You can get value of all textboxes having same name using following code:
var results = ((String[])formcollection.GetValue("mytxt").RawValue).ToList();
foreach (var item in results)
{
//string name = item;
}
When ever you add a element dynamically make sure you also set it a name. so when you add a new input element it must be
<input type="text" name="NewTextBox" class="form-control input-sm" placeholder="Value" name="searchvalue">
So like this no matter how many text boxes you add, All will have same name. Once you post the form. In your controller do this.
[HTTPPOST]
public ActionResult Search(MyModel newModel,string[] NewTextBox)
{
// here as you had dynamic textbox with name = NewTextBox you
//will get all its value binded to the above string[]
}
OR
You can retrive them using Request.form["NewTextBox"] as
[HTTPPOST]
public ActionResult Search(MyModel newModel)
{
var values = Request.Form[NewTextBox];
}
But I would recommend you the first approach where you use the MVC Model Binder to take care of all the things. You will just have array of values to play with.
NOTE: Always make sure you get the names right, and use the name right while playing with MVC. As all the binding depends on the naming itself.

MVC 4 Clicking a Button to get a Partial Page

I am creating a page with a search property. The user will enter a number in the search field and hit the submit button. I need to be able to call a controller method in order to run the search code.
For now I am just trying to hit a partial page to just get some functionality in there. Below is the code I have so far. As of now nothing happens when I click the button. I hear Ajax was something to use so I have been playing with that a little. I am still learning the framework so bear with me.
<div class="span6 roundedCorners">
<div class="row-fluid Title">
<div class="span6">
Search Area
</div>
</div>
<div class="row-fluid individual">
<div class="row-fluid">
<div class="span4"><span class="pull-right fieldDescription">Number</span></div>
<div class="span8"><input /></div>
</div>
<div class="row">
<div class="span12" id="adminSubmitButton">
#using (Ajax.BeginForm(new AjaxOptions { UpdateTargetId = "_adminDetails" }))
{
<button type="submit" class="btn btn-inverse">Submit</button>
}
</div>
</div>
</div>
Your form is empty, it only has a submit button. You will need to move your search button inside the form. Something like this:
Model
public class SearchModel
{
[Required]
public string Query { get; set; }
}
View
#model SearchModel
...
#using (Ajax.BeginForm(new AjaxOptions { UpdateTargetId = "_adminDetails" }))
{
#Html.EditorFor(m => m.Query)
#Html.ValidationMessageFor(m => m.Query)
<button type="submit" class="btn btn-inverse">Submit</button>
}
<div id="_adminDetails"></di>
Note: Make sure you have an element with id _adminDetails
Your controller will have to take the model and perform the search. Example:
Controller
[HttpPost]
public ActionResult Index(SearchModel model)
{
//do something with your model (perform search)
return View(results);
}
Alright, I have it partially working now. When i click the button I am able to call the controller method using:
function adminButton()
{
$("#adminDetails").load("#Url.Action("ControllerMethod", "ControllerName")");
}
The #Url.Action helper allows me to call the method which returns a partial view. This is a step in the right direction. But from what I am reading I should be able to use the following Url helper instead of the #Url.Action:
"#Url("ControllerMethod", "ControllerName")/" + submitButton.Value
I have tried some variations and I am still doing some reading to figure out a solution. Is there a helper that would let me do this?
Thanks for the help guys. I did end up getting my problem solved friday evening. Basically what I did was load the partial page when the button was clicked through the help of JS/jQuery and the Razor #Url.Action() to call the controller method that returns a PartialView:
<script type="text/javascript">
function adminButton()
{
var link = "#Url.Action("ControllerMethod", "ControllerClass", new {
number = -1})";
var num = parseInt($("#number").val());
link = link.replace(-1, num);
$("#details").load(link);
}
</script>

Categories

Resources