Related
I have an ASP.NET MVC page that uses a lot of AJAX. At the most basic level, it contains a list of questions, with each question having a list of answers inside it. My view models (vastly simplified for the sake of this question):
View Models:
public class QuestionViewModel
{
....
public string QuestionText { get; set; }
public List<AnswerViewModel> AnswerList { get; set; }
public bool EditMode { get; set; }
....
}
public class AnswerViewModel
{
....
public string Answer { get; set; }
....
}
_View.cshtml:
#model QuestionViewModel
#if(Model.EditMode)
{
#Html.EditorFor(x => x)
}
else
{
...
}
QuestionViewModel.cshtml (Editor Template):
#model Project.Models.QuestionViewModel
....
#Html.EditorFor(x => x.AnswerList)
....
AnswerViewModel.cshtml (Editor Template):
#model KSUAdvising.Models.AnswerViewModel
....
#Html.TextBoxFor(x => x.Answer)
....
^^^This EditorFor call renders my answer list fine (along with the appropriate indices so the model binder will bind back to the list appropriately. i.e.:
<input id="AnswerList_0__Answer" name="AnswerList[0].Answer" type="text" value="Yes">
<input id="AnswerList_1__Answer" name="AnswerList[1].Answer" type="text" value="No">
However, on the client side I need to add items to this list when the user clicks a button. This needs to happen client side so no changes are made to the database until the user saves the question.
I see a few direction to go to solve this problem:
Make an AJAX call to the server to render back a new blank answer and add it to the DOM. However, with this being out of the context of a question it doesn't render the proper name (index) and it doesn't get picked up by the model binder.
Render a hidden HTML "template" to the client including the proper input names, then clone this template and modify the name indices to satisfy the model binder. This seems a bit hacky, and is hard to properly render this template.
This seems like it would be a common scenario, but I'm having trouble coming up with a good solution to this. What is the recommended approach for this case?
I vote for option 2.
Render a hidden HTML Item Template. Then on a client button click, clone the template and modify the index.
In the cshtml page, after the foreach, add a new hidden div for the template. Give it an index place holder (I use _ x0x_). Create a new empty item and then render it the same way as you did in the foreach. (You could also have the item render as a partial view and then call it inside the foreach and outside.)
Here is sample cshtml page:
#foreach (var role in roles)
{
int roleIndex = roles.IndexOf(role);
string rolePrefix = "CasePartyRoles[" + roleIndex + "].";
<div id="CasePartyRoleIndex_#roleIndex" class="row brdr-bttm mrgn-bttm-sm">
<div class="col-md-10 mrgn-bttm-sm">
#Html.Hidden(rolePrefix + "SequenceNo", role.SequenceNo)
#Html.Hidden(rolePrefix + "RowVersion", role.RowVersion)
#Html.DropDownListFormGroupFor(modelItem => role.PartyRoleCode, (SelectList)ViewBag.PartyRoleSelectList, null, "col-md-3", "col-md-9", null, role.PartyRoleCode, rolePrefix + "PartyRoleCode")
#Html.DropDownListFormGroupFor(modelItem => role.PartyStatusCode, (SelectList)ViewBag.PartyStatusSelectList, null, "col-md-3", "col-md-9", null, role.PartyStatusCode, rolePrefix + "PartyStatusCode")
#Html.EditorFormGroupFor(modelItem => role.SubFileNo, "col-md-3", "col-md-9", null, null, rolePrefix + "SubFileNo")
#Html.EditorFormGroupFor(modelItem => role.PartyRank, "col-md-3", "col-md-9", null, null, rolePrefix + "PartyRank")
</div>
</div>
}
<div id="CasePartyRoleTemplate" class="hidden">
#*Template for new Role*#
#{
var newRole = new CasePartyRole();
string newRolePrefix = "CasePartyRoles[_x0x_].";
}
<div id="CasePartyRoleIndex__x0x_" class="row brdr-bttm mrgn-bttm-sm">
<div class="col-md-10 mrgn-bttm-sm">
#Html.Hidden(newRolePrefix + "SequenceNo", newRole.SequenceNo)
#Html.Hidden(newRolePrefix + "RowVersion", newRole.RowVersion)
#Html.DropDownListFormGroupFor(modelItem => newRole.PartyRoleCode, (SelectList)ViewBag.PartyRoleSelectList, null, "col-md-3", "col-md-9", null, newRole.PartyRoleCode, newRolePrefix + "PartyRoleCode")
#Html.DropDownListFormGroupFor(modelItem => newRole.PartyStatusCode, (SelectList)ViewBag.PartyStatusSelectList, null, "col-md-3", "col-md-9", null, newRole.PartyStatusCode, newRolePrefix + "PartyStatusCode")
#Html.EditorFormGroupFor(modelItem => newRole.SubFileNo, "col-md-3", "col-md-9", null, null, newRolePrefix + "SubFileNo")
#Html.EditorFormGroupFor(modelItem => newRole.PartyRank, "col-md-3", "col-md-9", null, null, newRolePrefix + "PartyRank")
</div>
</div>
</div>
Here is a little jQuery function to add the item based on the template:
function createItemFromTemplate(templateId, indexNo, insertBeforeId) {
// Copy the template Element, replaces all the _x0x_ with the index no and add the new element
$(insertBeforeId).before($.parseHTML($(templateId).clone().prop('outerHTML').replace(/_x0x_/g, indexNo)));
}
It depends... For the sake of performance I wouldn't go with the first option. This slight delay to load up a new item via Ajax could be annoying.
Personally most times I would just build an HTML element completely on client side. I know it complicates maintanance as you have to remember to change your client side logic any time you make changes to your model collection but it's fast and straightforward.
Also have an idea how to improve your second option. Basicly you don't need to have a real object to build it.
#Html.TextBoxFor(model => model.Items[int.MaxValue], new { style= "display:none" })
the input rendered
<input name="Items[2147483647]" id="Items_2147483647_" style="display: none;" type="text" value="">
Please note the Items property is null in my case but it doesn't matter if you just want to build a template as xxxFor html helpers just use expressions to build html. Also it's very easy then to replace int.MaxValue while cloning the template with a real index. It's safe to use int.MaxValue as a placeholder, it's very unlickely you will have that much items on your page.
Hope it helps!
For example I have the following model which I am passing a list of to the view:
public class ExampleViewModel
{
public int id { get; set; }
public string name { get; set; }
}
In my view I have the following:
#model List<ExampleViewModel>
<table>
<tr>
<th>Name</th>
<th>Action</th>
</tr>
#foreach (var x in Model)
{
<tr>
<td>
#Html.DisplayFor(m => x.name)
</td>
<td>
<input type="button" onclick="edit();" value="Edit">
</td>
</tr>
}
</table>
<script type="text/javascript">
function edit(x) {
window.location = "/Home/Edit?id=" + x
}
</script>
What I am having trouble with is passing x.id to the edit() function. I expected:
<input type="button" onclick="edit(#x.id);" value="Edit">
to work but it did not.
Thanks
Try this
<input type="button" onclick="edit(#x.id);" value="Edit">
I'd recommend you to use data slash atributes, and use something like jQuery to handle events, and use the data slash attributes.
A data slash attribute is simply an attribute whose name starts with "data-". You can define as many of theses attributes as you need, on any element, an all browsers will support them.
<input type="button" onclick="edit" data-id="#x.id" value="Edit">
When the edit method is executed, you can access the element (using this), and them with jQuery you can get the attributes values lie this:
var id = $(this).attr('data-id');
You can even go further, and remove the "onclick=edit" part. Then use jQuery to susbscribe the click event to all the elements with the required attribute, like this
$(document).ready(function() {
// this happens when all the page has been loaded in the browser (ready)
$('input[data-id]').on('click', function() {
// find the input elements with the "data-id" attr, and, when 'click'ed...
var id = $(this).attr('data-id');
//get the attribute value and do whatever you want with it...
});
});
*NOTE: you can use var id = $(this).data('id'); as an alternative.
This technique is known as "unobtrusive JavaScript". To do this, of course, you need to include jQuery in your page. Please, start using jQuery (or any other library) tha will make things easier for you. if you use it, I'd recommend you to use "namespaced" names for the attributes to avoid collisions. I.e, something like "data-mynamespace-id", using whatever makes sense as your namespace.
Try this:
<input type="button" onclick="edit('#(x.id)');" value="Edit">
Note that if you want to pass variable from your ViewModel to javascript you should use qoutes like this:
<script type="text/javascript">
var x = '#Model[0].x';
</script>
Also you can try declaration of edit function before table.
I am in the process of converting several working asp.net mvc 4 (razor) webpages over to jquery mobile and I am seeing some things I don't understand.
The #Html.DropDownListFor don't always populate.....I have inspected the html using firebug and SOMETIMES when the webpage comes up its dropdown has no values...but it is sporadic...I hate sporadic problems....it is driving me up the wall...
Here is the controller...I have checked when the problem is happening...it is always generating the data and sending it....
public ActionResult SelectWorkCenter()
{
SelectWorkCenterInputModel model = new SelectWorkCenterInputModel();
model.WorkCenterList = wcService.GetWorkCenters().OrderBy(p=>p.WorkCenterName).ToList();
return View(model);
}
Here is the web page with the dropdown that is sporadically working:
#using TBS.Etracs.Web.Main.Areas.WorkCenter.Models
#model SelectWorkCenterInputModel
#{
ViewBag.Title = "Select Work Center";
}
#using (Html.BeginForm("SelectWorkOrder", "Mobile", new {Model.SelectedWorkCenterID }))
{
<div class="divTable">
<div class="divTableRow">
<div class="divTableCell"></div>
<div class="divTableCell">[WorkCenterName (#Vehicles)]</div>
</div>
<div class="divTableRow">
<div class="divTableCell"><label><strong> WorkCenter</strong></label></div>
<div class="divTableCell">
#Html.DropDownListFor(p => p.SelectedWorkCenterID,
new SelectList(Model.WorkCenterList, "WorkCenterID", "WorkCenterName"),
"Select a Work Center",
new { Class = "dropdownstyle",
onchange = "this.form.submit();",
style = "width:220px; height: 40px;font-size: 1.2em;font-weight:bold"
})
</div>
</div>
</div>
<input type="submit" value="Back" onclick="history.back(); return false;" style="margin-right: 20px" />
}
I am in the process of converting the from a divTable over to data-role....and the wierd thing is sometimes this after I convert to data-role things improve and other times it makes the problems worse....
I don't understand why converting to jquery mobile would have any impact on this.....
The other problem I am having is that my buttons don't always fire events back to my controllers now that I have converted over to jquery mobile.... I don't know if this is a related problem or not...
Any ideas what I have missed?
More details.....Now that I know more of what is going on I am seeing that this question has been asked before .... I still have not solved the problem, but it does not seem to be the most likey problem that I have seen in other post .... Here is my model which .... this should be correct (as it was working)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using TBS.Etracs.Web.Main.Models;
namespace TBS.Etracs.Web.Main.Areas.WorkCenter.Models
{
public class SelectWorkCenterInputModel
{
public int SelectedWorkCenterID { get; set; }
public List<AssignedWorkCenter> WorkCenterList { get; set; }
}
}
and
AssignedWorkCenter is an EF generated type with values of
System.Int32 WorkCenterID
System.String WorkCenterName
Ok...I don't understand it , but here is the solution....
The problem was with the specifiction of the Style inside the dropDownListFor....By eliminating the Style line everything works...
#Html.DropDownListFor(p => p.SelectedWorkOrderCode,
new SelectList (Model.WorkOrders, "WorkCenterID", "WorkOrderCodeDesc"),
"Select a Work Order",
new { id = "WorkCenterID",
Class = "dropdownstyle" })
Following Brad Wilson's excellent series on using and customizing editor templates, I tried adding an Object.cshtml to the Shared\EditorTemplates folder. The template renders, but the [HiddenInput(DisplayValue = false)] on a model property doesn't render a hidden <input type="hidden" ... /> as expected. Using [HiddenInput(DisplayValue = true)] renders both the hidden and visible elements as expected.
I have verified that the default template for Object works fine and renders the hidden inputs. It's only a problem when building a custom template based on Brad's series above.
Looks like something has changed. Inspecting the MVC 3 source, I found that prop.HideSurroundingHtml is used to determine when to print the surrounding HTML, not to print only the hidden element. The following template allows several levels of rendering an editor for an object graph:
#if (ViewData.TemplateInfo.TemplateDepth > 2)
{
#(ViewData.ModelMetadata.Model != null ?
ViewData.ModelMetadata.SimpleDisplayText :
ViewData.ModelMetadata.NullDisplayText)
}
else
{
foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm)))
{
if (!prop.HideSurroundingHtml)
{
if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString()))
{
<div class="editor-label">#Html.Label(prop.PropertyName)</div>
}
#Html.Raw("<div class=\"editor-field\">")
}
#Html.Editor(prop.PropertyName)
if (!prop.HideSurroundingHtml)
{
#Html.ValidationMessage(prop.PropertyName, "*")
#Html.Raw("</div>")
}
}
}
I tidied my version of that up a bit for anyone who cares:
#foreach (var modelMetadata in ViewData.ModelMetadata.Properties)
{
if (modelMetadata.HideSurroundingHtml == false)
{
if (!string.IsNullOrEmpty(Html.Label(modelMetadata.PropertyName).ToHtmlString()))
{
<div class="editor-label">
#Html.Label(modelMetadata.PropertyName)
</div>
}
<div class="editor-field">
#Html.Editor(modelMetadata.PropertyName)
</div>
}
}
Can anyone tell me how can I submit values to Controller using ActionLink and POST method?
I don't want to use buttons.
I guess it has something with jquery.
If you're using ASP MVC3 you could use an Ajax.ActionLink(), that allows you to specify a HTTP Method which you could set to "POST".
You can't use an ActionLink because that just renders an anchor <a> tag.
You can use a jQuery AJAX post.
Or just call the form's submit method with or without jQuery (which would be non-AJAX), perhaps in the onclick event of whatever control takes your fancy.
You can use jQuery to do a POST for all your buttons. Just give them the same CssClass name.
Use "return false;" at the end of your onclick javascript event if you want to do a server side RedirectToAction after the post otherwise just return the view.
Razor Code
#using (Html.BeginForm())
{
#Html.HiddenFor(model => model.ID)
#Html.ActionLink("Save", "SaveAction", "MainController", null, new { #class = "saveButton", onclick = "return false;" })
}
JQuery Code
$(document).ready(function () {
$('.saveButton').click(function () {
$(this).closest('form')[0].submit();
});
});
C#
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult SaveAction(SaveViewModel model)
{
// Save code here...
return RedirectToAction("Index");
//return View(model);
}
#Aidos had the right answer just wanted to make it clear since it is hidden inside a comment on his post made by #CodingWithSpike.
#Ajax.ActionLink("Delete", "Delete", new { id = item.ApkModelId }, new AjaxOptions { HttpMethod = "POST" })
Here was an answer baked into the default ASP.NET MVC 5 project I believe that accomplishes my styling goals nicely in the UI. Form submit using pure javascript to some containing form.
#using (Html.BeginForm("Logout", "Account", FormMethod.Post, new { id = "logoutForm", #class = "navbar-right" }))
{
<a href="javascript:document.getElementById('logoutForm').submit()">
<span>Sign out</span>
</a>
}
The fully shown use case is a logout dropdown in the navigation bar of a web app.
#using (Html.BeginForm("Logout", "Account", FormMethod.Post, new { id = "logoutForm", #class = "navbar-right" }))
{
#Html.AntiForgeryToken()
<div class="dropdown">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span class="ma-nav-text ma-account-name">#User.Identity.Name</span>
<i class="material-icons md-36 text-inverse">person</i>
</button>
<ul class="dropdown-menu dropdown-menu-right ma-dropdown-tray">
<li>
<a href="javascript:document.getElementById('logoutForm').submit()">
<i class="material-icons">system_update_alt</i>
<span>Sign out</span>
</a>
</li>
</ul>
</div>
}
ActionLink will never fire post. It always trigger GET request.
Use the following the Call the Action Link:
<%= Html.ActionLink("Click Here" , "ActionName","ContorllerName" )%>
For submitting the form values use:
<% using (Html.BeginForm("CustomerSearchResults", "Customer"))
{ %>
<input type="text" id="Name" />
<input type="submit" class="dASButton" value="Submit" />
<% } %>
It will submit the Data to Customer Controller and CustomerSearchResults Action.
This is taken from the MVC sample project
#if (ViewBag.ShowRemoveButton)
{
using (Html.BeginForm("RemoveLogin", "Manage"))
{
#Html.AntiForgeryToken()
<div>
#Html.Hidden("company_name", account)
#Html.Hidden("returnUrl", Model.returnUrl)
<input type="submit" class="btn btn-default" value="Remove" title="Remove your email address from #account" />
</div>
}
}
Use this link inside Ajax.BeginForm
#Html.ActionLink(
"Save",
"SaveAction",
null,
null,
onclick = "$(this).parents('form').attr('action', $(this).attr('href'));$(this).parents('form').submit();return false;" })
;)
My Solution to this issue is a fairly simple one. I have a page that does a customer search one by the whole email and the other by a partial, the partial pulls and displays a list the list has an action link that points to a actionresult called GetByID and passes in the id
the GetByID pulls the data for the selected customer then returns
return View("Index", model);
which is the post method
This has been a difficult problem for me to solve. How can I build a dynamic link in razor and html that can call an action method and pass a value or values to a specific action method? I considered several options including a custom html helper. I just came up with a simple and elegant solution.
The view
#model IEnumerable<MyMvcApp.Models.Product>
#using (Html.BeginForm()) {
<table>
<thead>
<tr>
<td>Name</td>
<td>Price</td>
<td>Quantity</td>
</tr>
</thead>
#foreach (Product p in Model.Products)
{
<tr>
<td>#p.Name</td>
<td>#p.Price.ToString()</td>
<td>#p.Quantity.ToString()</td>
</tr>
}
</table>
}
The action method
public ViewResult Edit(Product prod)
{
ContextDB contextDB = new ContextDB();
Product product = contextDB.Products.Single(p => p.ProductID == prod.ProductId);
product = prod;
contextDB.SaveChanges();
return View("Edit");
}
The point here is that Url.Action does not care whether the action method is a GET or a POST. It will access either type of method. You can pass your data to the action method using
#Url.Action(string actionName, string controllerName, object routeValues)
the routeValues object. I have tried this and it works. No, you are not technically doing a post or submitting the form but if the routeValues object contains your data, it doesnt matter if its a post or a get. You can use a particular action method signature to select the right method.
I have done the same issue using following code:
#using (Html.BeginForm("Delete", "Admin"))
{
#Html.Hidden("ProductID", item.ProductID)
<input type="submit" value="Delete" />
}
This is my solution for the problem.
This is controller with 2 action methods
public class FeedbackController : Controller
{
public ActionResult Index()
{
var feedbacks =dataFromSomeSource.getData;
return View(feedbacks);
}
[System.Web.Mvc.HttpDelete]
[System.Web.Mvc.Authorize(Roles = "admin")]
public ActionResult Delete([FromBody]int id)
{
return RedirectToAction("Index");
}
}
In View I render construct following structure.
<html>
..
<script src="~/Scripts/bootbox.min.js"></script>
<script>
function confirmDelete(id) {
bootbox.confirm('#Resources.Resource.AreYouSure', function(result) {
if (result) {
document.getElementById('idField').value = id;
document.getElementById('myForm').submit();
}
}.bind(this));
}
</script>
#using (Html.BeginForm("Delete", "Feedback", FormMethod.Post, new { id = "myForm" }))
{
#Html.HttpMethodOverride(HttpVerbs.Delete)
#Html.Hidden("id",null,new{id="idField"})
foreach (var feedback in #Model)
{
if (User.Identity.IsAuthenticated && User.IsInRole("admin"))
{
#Html.ActionLink("Delete Item", "", new { id = #feedback.Id }, new { onClick = "confirmDelete("+feedback.Id+");return false;" })
}
}
...
</html>
Point of interest in Razor View:
JavaScript function confirmDelete(id) which is called when the link generated with #Html.ActionLink is clicked;
confirmDelete() function required id of item being clicked. This item is passed from onClick handler confirmDelete("+feedback.Id+");return false; Pay attention handler returns false to prevent default action - which is get request to target. OnClick event for buttons could be attached with jQuery for all buttons in the list as alternative (probably it will be even better, as it will be less text in the HTML page and data could be passed via data- attribute).
Form has id=myForm, in order to find it in confirmDelete().
Form includes #Html.HttpMethodOverride(HttpVerbs.Delete) in order to use the HttpDelete verb, as action marked with the HttpDeleteAttribute.
In the JS function I do use action confirmation (with help of external plugin, but standard confirm works fine too. Don't forget to use bind() in call back or var that=this (whatever you prefer).
Form has a hidden element with id='idField' and name='id'. So before the form is submitted after confirmation (result==true), the value of the hidden element is set to value passed argument and browser will submit data to controller like this:
Request URL:http://localhost:38874/Feedback/Delete
Request Method:POST Status Code:302 Found
Response Headers
Location:/Feedback
Host:localhost:38874
Form Data X-HTTP-Method-Override:DELETE id:5
As you see it is POST request with X-HTTP-Method-Override:DELETE and data in body set to "id:5". Response has 302 code which redirect to Index action, by this you refresh your screen after delete.
I would recommend staying pure to REST principles and using an HTTP delete for your deletes. Unfortunately HTML Specs only has HTTP Get & Post. A tag only can a HTTP Get. A form tag can either do a HTTP Get or Post. Fortunately if you use ajax you can do a HTTP Delete and this is what i recommend. See the following post for details: Http Deletes
Calling $.post() won't work as it is Ajax based. So a hybrid method needs to be used for this purpose.
Following is the solution which is working for me.
Steps:
1. Create URL for href which calls the a method with url and parameter
2. Call normal POST using JavaScript method
Solution:
In .cshtml:
View
Note: the anonymous method should be wrapped in (....)()
i.e.
(function() {
//code...
})();
postGo is defined as below in JavaScript.
Rest are simple..
#Url.Action("View") creates url for the call
{ 'id': #receipt.ReceiptId } creates parameters as object which is in-turn converted to POST fields in postGo method. This can be any parameter as you require
In JavaScript:
(function ($) {
$.extend({
getGo: function (url, params) {
document.location = url + '?' + $.param(params);
},
postGo: function (url, params) {
var $form = $("<form>")
.attr("method", "post")
.attr("action", url);
$.each(params, function (name, value) {
$("<input type='hidden'>")
.attr("name", name)
.attr("value", value)
.appendTo($form);
});
$form.appendTo("body");
$form.submit();
}
});
})(jQuery);
Reference URLs which I have used for postGo
Non-ajax GET/POST using jQuery (plugin?)
http://nuonical.com/jquery-postgo-plugin/
jQuery.post() will work if you have custom data. If you want to post existing form, it's easier to use ajaxSubmit().
And you don't have to setup this code in the ActionLink itself, since you can attach link handler in the document.ready() event (which is a preferred method anyway), for example using $(function(){ ... }) jQuery trick.
Came across this needing to POST from a Search (Index) page to the Result page. I did not need as much as #Vitaliy stated but it pointed me in the right direction. All I had to do was this:
#using (Html.BeginForm("Result", "Search", FormMethod.Post)) {
<div class="row">
<div class="col-md-4">
<div class="field">Search Term:</div>
<input id="k" name="k" type="text" placeholder="Search" />
</div>
</div>
<br />
<div class="row">
<div class="col-md-12">
<button type="submit" class="btn btn-default">Search</button>
</div>
</div>
}
My Controller had the following signature method:
[HttpPost]
public async Task<ActionResult> Result(string k)