I have a View for creating a new account in my application. This view starts with Html.BeginForm() and hits the right controller (Create) no problems.
I decided to add an Ajax.BeginForm() so that I could make sure an account with the same name doesn't already exist in my application.
When I click the submit using either button it goes to the same controller (Create). To try and differentiate which submit button was clicked, I put in a check to see if the request is Ajax then try to run a different code path. But Request.IsAjaxRequest() doesn't fire. What is my best bet to implement this functionality in an existing form with MS Ajax?
<% using (Html.BeginForm()) {%>
..............
<% using(Ajax.BeginForm("Echo",
new AjaxOptions() { UpdateTargetId = "EchoTarget" }))
{ %>
Echo the following text:
<%=Html.TextBox("echo", null, new { size = 40 })%>
<input type="submit" value="Echo" />
<% } %>
<div id="EchoTarget">
controller code:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(User newUser)
{
if (Request.IsAjaxRequest())
{
return Content("*you hit the ajax button");
}
else
{ //regular create code here.....
}
</div>
If you insist on multiple form usage..use Javascript in a some function like this
<SCRIPT>
function InnerFormSubmitter(dataForm, actionForm) {
actionForm.innerHTML = dataForm.innerHTML;
actionForm.submit();
}
</SCRIPT>
<form name="yourButton" action="ValidateSomething" method="post"></form>
<form name="mainForm" action="SavedData" method="post">
<input type="textbox" name="text1">
<input type="textbox" name="text2">
<button name="validateUserButton" id="FormButton" onChange=
"InnerFormSubmitter (this.form, document.getElementById('yourButton'))">
</button>
</form>
Hope this helps!
Addendum on jQuery usage for your scenario:
Since you wanted a link:
Check Availability
function isValidUser(userId) {
var url = "<CONTROLLER>/<ACTION>/" + userId;
$.post(url, function(data) {
if (data) {
// callback to show valid user
} else {
// callback to show error/permission
}
});
}
And you controller should have:
[AcceptVerbs("POST")]
public bool IsValidUser(int id) {
// check availability
bool allow = CheckUser();
// if allow then insert
if (allow) {
//insert user
return true;
} else {
return false;
}
}
Further Update on jQuery:
Instead of
document.getElementById('UserIdent').value
you can use
$('#UserIdent').val();
Update on JSON usage
The JsonResult class should be used in the Controller and $.getJson function in the View.
$(function() {
$('#yourLinkOrButton').click(function() {
$.getJSON("<CONTROLLER>/GetUserAvailability/", null, function(data) {
$("#yourDivorLablel").<yourFunctionToUpdateDiv>(data);
});
});
public JsonResult GetUserAvailability()
{
//do all validation and retrieval
//return JsonResult type
}
You cannot nest forms, ever, in any HTML page, no matter how you generate the form. It isn't valid HTML, and browsers may not handle it properly. You must make the forms siblings rather than children.
Related
I am trying to find a way to send the id of the clicked button to the backend. The problem is that I am creating lots of buttons with one method but the id is different.
#foreach (var item in Model.showManager.GetMovies())
{
i++;
#if (Model.user.IsAdmin == true)
{
<input class="btn_confirm" type="submit" id=i value="Delete"/>
}
}
The point is that every button is created with different id and I want to send that id to the backend.
Update
My demo is a MVC project, I have a DynamicButtonController and a Index view:
DynamicButtonController:
public class DynamicButtonController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Index(int id)
{
return View();
}
}
Index view :
#for (var i = 0; i < 5;i++ )
{
<input class="btn_confirm" type="submit" id=#i value="Delete" />
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
$(".btn_confirm").click(function()
{
var data = (this).id;
$.ajax({
type: "POST",
url: '/DynamicButton/Index/',
data: { id: data }
});
});
</script>
result:
If you use Razor pages, you can refer to the below demo,use asp-route-id="#i"
ButtonIdModel:
public class ButtonIdModel : PageModel
{
public void OnGet()
{
}
public void OnPost(string id)
{
}
}
ButtonId.cshtml:
#page
#model yourproject.Pages.ButtonIdModel
<form method="post">
#for (var i = 0; i < 5;i++ )
{
<input class="btn_confirm" type="submit" id=#i value="Delete" asp-route-id="#i" />
}
</form>
The point is that every button is created with different id and I want
to send that id to the backend.
Well, based on your issue, you want to bind all the button ids then want to pass those Ids in your backend.
However, another answer has guided you how to pass id to your controller. Nonetheless, it doesn't resolve your main concern that is how to pass the list of ids on button submit.
Algorithm:
As said earlier, first you have to get the list of button ids which has been generated from your foreach loop and you have to push them in an array, finally need to pass those in your controller (backend). Here, importantly you have to keep in mind, it doesn't matter how the button been generated, for loop or foreach loop the fact is your button should have class name of same type and the ids for instance: class="myBtnClass btn btn-danger" and id="btnId:#i"
Solution:
View:
#{
ViewData["Title"] = "ViewGetDynamicButtonsID";
}
<div>
#for (var i = 1; i < 5; i++)
{
<input class="myBtnClass btn btn-danger" id="btnId:#i" value="Delete:#i" style="margin-bottom:2px" /> <br />
}
<input type="submit" id="btnSubmit" class="btn btn-success" value="Submit" />
</div>
#section scripts {
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"></script>
<script>
$(document).ready(function () {
$("#btnSubmit").on("click", function () {
var ids = [];
$(".myBtnClass").each(function () {
//Getting All Button Ids and Pusing in array
ids.push($(this).attr("id"));
});
$.ajax({
type: "POST",
url: 'http://localhost:5094/stuff/GetAllButtonId',
datatype: "json",
data: { buttonids: ids },
success: function (res) {
console.log(res);
alert("It works")
},
error: function () {
alert("It failed");
}
});
return false;
});
});
</script>
}
Controller:
public IActionResult CreateDynamicButton()// This is for loading the view
{
return View();
}
[HttpPost]
public IActionResult GetAllButtonId(List<string> buttonids) // This for submit the button request.
{
return Ok(buttonids);
}
Note: I have defined Button Ids as List<string> thus you can do it as your convenient type
Output:
I currently have the following code:
[HttpPost]
public ActionResult Index(IList<LocalPageModel> postPages,
IEnumerable<HttpPostedFileBase> files)
{
if (ModelState.IsValid)
{
foreach (HttpPostedFileBase file in files)
{
if ((file != null) && (file.ContentLength > 0))
{
var fileName = Path.GetFileName(file.FileName);
var path = Path.Combine(Server.MapPath("~/App_Data/"),
fileName);
file.SaveAs(path);
}
}
}
else
{
ManagePagesModel mod = new ManagePagesModel
{
PostPages = postPages
};
return View("Index", mod);
}
return RedirectToAction("Index");
}
In my view, I have a JavaScript button which will add a div so that the user can post another page such as:
$("#add-page").click(function () {
$("#page").append('<div id="page"> #Html.TextBoxFor(u => u.PostPages[0].Title) </div>');
});
How do I make it so that when the user clicks on the JavaScript button, the new text will be appended to the page and u.PostPages[x] will be incremented?
If you want to do it all on the client (no AJAX), maybe don't use the MVC helpers at all, and do it manually instead - you know the HTML that will be rendered, so just do that:
var i = 0;
$("#add-page").click(function () {
$("#page").append('<input type="text" name="PostPages[' + (i++) + '].Title">');
});
Maybe clean the code up a bit so the quotes don't get too confusing, but you get the idea...
You didn't past your view, but I assume you have the following at the top:
#model = ManagePagesModel
If that's the case, you can then use the following #foreach to loop through the page models:
$("#add-page).click(function() {
#foreach(var pageModel in Model.PostPages){
$("#page").append('<div id="page"> #Html.TextBoxFor(u => pageModel.Title) </div>');
});
To increment u.PostPages[x] you may use following code:
<script>
var i = 0;
$("#add-page").click(function () {
i++
$("#page").append('<div id="page"> #Html.TextBoxFor(u => u.PostPages['+i+'].Title') </div>');
});
</script>
Here is small working example: jsfiddle
Basically I want to implement simple search functionality, whenever user enters some keyword in the text box on view and clicks submit button I want to make ASYNC calls to predefined website urls using TPL Async mechanism. When I do the same with console application it works like a charm but not with ASP.NET MVC3.
I couldn't find the reason
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
return View();
}
[HttpPost]
public ActionResult Index(string text)
{
string[] url = { "http://www.msnbc.com", "http://www.yahoo.com",
"http://www.nytimes.com", "http://www.washingtonpost.com",
"http://www.latimes.com", "http://www.newsday.com" };
Task<string[]> webTask = this.GetWordCounts(url, text);
string[] results = null;
try
{
results = webTask.Result;
}
catch (AggregateException e)
{
}
return View("Index", results);
}
//Taken from MSDN
Task<string[]> GetWordCounts(string[] urls, string name)
{
TaskCompletionSource<string[]> tcs = new TaskCompletionSource<string[]>();
WebClient[] webClients = new WebClient[urls.Length];
object m_lock = new object();
int count = 0;
List<string> results = new List<string>();
for (int i = 0; i < urls.Length; i++)
{
webClients[i] = new WebClient();
#region callback
// Specify the callback for the DownloadStringCompleted
// event that will be raised by this WebClient instance.
webClients[i].DownloadStringCompleted += (obj, args) =>
{
if (args.Cancelled == true)
{
tcs.TrySetCanceled();
return;
}
else if (args.Error != null)
{
// Pass through to the underlying Task
// any exceptions thrown by the WebClient
// during the asynchronous operation.
tcs.TrySetException(args.Error);
return;
}
else
{
// Split the string into an array of words,
// then count the number of elements that match
// the search term.
string[] words = null;
words = args.Result.Split(' ');
string NAME = name.ToUpper();
int nameCount = (from word in words.AsParallel()
where word.ToUpper().Contains(NAME)
select word)
.Count();
// Associate the results with the url, and add new string to the array that
// the underlying Task object will return in its Result property.
results.Add(String.Format("{0} has {1} instances of {2}", args.UserState, nameCount, name));
}
// If this is the last async operation to complete,
// then set the Result property on the underlying Task.
lock (m_lock)
{
count++;
if (count == urls.Length)
{
tcs.TrySetResult(results.ToArray());
}
}
};
#endregion
// Call DownloadStringAsync for each URL.
Uri address = null;
try
{
address = new Uri(urls[i]);
// Pass the address, and also use it for the userToken
// to identify the page when the delegate is invoked.
webClients[i].DownloadStringAsync(address, address);
}
catch (UriFormatException ex)
{
// Abandon the entire operation if one url is malformed.
// Other actions are possible here.
tcs.TrySetException(ex);
return tcs.Task;
}
}
// Return the underlying Task. The client code
// waits on the Result property, and handles exceptions
// in the try-catch block there.
return tcs.Task;
}
this is my view - for now I have hard coded keyword as microsoft
#using (Html.BeginForm("Index", "Home", new { text = "Microsoft" }))
{
<input type="submit" />
}
Update: It stays forever and inside the try block of Index Post method
I would recommend you using an AsyncController for this task to avoid jeopardizing ASP.NET worker threads which is one the worst thing that might happen to an ASP.NET application => running out of worker threads. It's like running out of fuel in the middle of the desert. You most certainly die.
So let's start by writing an extension method that will allow us converting the legacy WebClient event based pattern into the new task based pattern:
public static class TaskExtensions
{
public static Task<string> DownloadStringAsTask(this string url)
{
var tcs = new TaskCompletionSource<string>(url);
var client = new WebClient();
client.DownloadStringCompleted += (sender, args) =>
{
if (args.Error != null)
{
tcs.SetException(args.Error);
}
else
{
tcs.SetResult(args.Result);
}
};
client.DownloadStringAsync(new Uri(url));
return tcs.Task;
}
}
Armed with this extension method in hand we could now define a view model that will basically reflect the requirements of our view:
public class DownloadResultViewModel
{
public string Url { get; set; }
public int WordCount { get; set; }
public string Error { get; set; }
}
Then we move on to an asyncrhonous controller that will contain 2 actions: a standard synchronous Index action that will render the search form and an asynchronous Search action that will perform the actual work:
public class HomeController : AsyncController
{
public ActionResult Index()
{
return View();
}
[AsyncTimeout(600000)]
[HttpPost]
public void SearchAsync(string searchText)
{
AsyncManager.Parameters["searchText"] = searchText;
string[] urls =
{
"http://www.msnbc.com",
"http://www.yahoo.com",
"http://www.nytimes.com",
"http://www.washingtonpost.com",
"http://www.latimes.com",
"http://www.unexistentdomainthatwillcrash.com",
"http://www.newsday.com"
};
var tasks = urls.Select(url => url.DownloadStringAsTask());
AsyncManager.OutstandingOperations.Increment(urls.Length);
Task.Factory.ContinueWhenAll(tasks.ToArray(), allTasks =>
{
var results =
from task in allTasks
let error = task.IsFaulted ? task.Exception.Message : null
let result = !task.IsFaulted ? task.Result : string.Empty
select new DownloadResultViewModel
{
Url = (string)task.AsyncState,
Error = error,
WordCount = result.Split(' ')
.Where(x => string.Equals(x, searchText, StringComparison.OrdinalIgnoreCase))
.Count()
};
AsyncManager.Parameters["results"] = results;
AsyncManager.OutstandingOperations.Decrement(urls.Length);
});
}
public ActionResult SearchCompleted(IEnumerable<DownloadResultViewModel> results)
{
return View("index", results);
}
}
Now we define an ~/Views/Home/Index.cshtml view that will contain the search logic as well as the results:
#model IEnumerable<DownloadResultViewModel>
#using (Html.BeginForm("search", null, new { searchText = "politics" }))
{
<button type="submit">Search</button>
}
#if (Model != null)
{
<h3>Search results</h3>
<table>
<thead>
<tr>
<th>Url</th>
<th>Word count</th>
</tr>
</thead>
<tbody>
#Html.DisplayForModel()
</tbody>
</table>
}
And of course the corresponding display template that will be rendered automatically for each element of our model (~/Views/Shared/DisplayTemplates/DownloadResultViewModel.cshtml):
#model DownloadResultViewModel
<tr>
<td>#Html.DisplayFor(x => x.Url)</td>
<td>
#if (Model.Error != null)
{
#Html.DisplayFor(x => x.Error)
}
else
{
#Html.DisplayFor(x => x.WordCount)
}
</td>
</tr>
Now, since the search operation could take quite a long time your users could quickly get bored without being able to use some of the other hundredths of functionalities that your webpage has to offer them.
In this case it is absolutely trivial to invoke the Search controller action using an AJAX request and showing a spinner to inform the users that their search is in progress but without freezing the webpage allowing them to do other things (without navigating away from the page obviously).
So let's do that, shall we?
We start by externalizing the results into a partial (~/Views/Home/_Results.cshtml) without touching at the display template:
#model IEnumerable<DownloadResultViewModel>
#if (Model != null)
{
<h3>Search results</h3>
<table>
<thead>
<tr>
<th>Url</th>
<th>Word count</th>
</tr>
</thead>
<tbody>
#Html.DisplayForModel()
</tbody>
</table>
}
and we adapt our ~/Views/Home/Index.cshtml view to use this partial:
#model IEnumerable<DownloadResultViewModel>
#using (Html.BeginForm("search", null, new { searchText = "politics" }))
{
<button type="submit">Search</button>
}
<div id="results">
#Html.Partial("_Results")
</div>
and of course the SearchCompleted controller action that must now return only the partial result:
public ActionResult SearchCompleted(IEnumerable<DownloadResultViewModel> results)
{
return PartialView("_Results", results);
}
Now all that's left is to write a simple javascript that will AJAXify our search form. So this could happen into a separate js that will reference in our layout:
$(function () {
$('form').submit(function () {
$.ajax({
url: this.action,
type: this.method,
success: function (results) {
$('#results').html(results);
}
});
return false;
});
});
Depending on whether you referenced this script in the <head> section or at the end of the body you might not need to wrap it in a document.ready. If the script is at the end you could remove the wrapping document.ready function from my example.
And the last part is to give some visual indication to the user that the site is actually performing a search. This could be done using a global ajax event handler that we might subscribe to:
$(function () {
$(document).ajaxStart(function () {
$('#results').html('searching ...');
});
});
I am attempting to dynamically update a select list based on a selection in another select list. The jQuery code
<script type="text/javascript">
// Respond to Category selection
$('#selectCategory').change(function () {
// Ajax call to 'Specialties' action method with parameter searchType
$.get('#Url.Action("Products", "Product")',
{ "parameter1": $(this).val(), "parameter2": null },
function (data) {
$('#products').html(data);
});
});
</script>
the view
<div id="products">
#Html.Partial("_Products")
</div>
the partial view
#model MyMVC3App.Models.MyViewModel
<select mid="productSelect" name="#ClarusConstants.PropertyNames.Specialty" >
<option value="0">#Resources.LabelText_SelectASpecialty</option>
foreach (Product p in Model.Products)
{
<option value="#Product.ID" name="#product.Description</option>
}
</select>
the action method
public ActionResult Specialties(string parameter1, string parameter2)
{
List<Product> products = myDB.Products.Where(p => p.Category == parameter1).ToList();
ViewModel myViewModel = new ViewModel();
viewModel.Products = products;
if (Request.IsAjaxRequest())
{
return PartialView("_Products", myViewModel);
}
else
{
return View(myViewModel);
}
}
The callback is hitting the right action method and the ajax request is being detected. Unfortunately the content (InnnerHTML) of the products div is not being replaced so it is all for nothing.
Thank you in advance for your help.
I am fairly new to MVC and just trying to achieve something which I think shouldn't be too complicated to achieve. Just want to know what the best approach for that is. I have an Event-RSVP application (NerdDinner kind) where you go to view details of the event and then click on an AJAX link that will RSVP you for the event.
<%
if (Model.HasRSVP(Context.User.Identity.Name))
{
%>
<p>
You are registered for this event!
<%:
Ajax.ActionLink("Click here if you can't make it!", "CancelRegistration", "RSVP", new { id = Model.RSVPs.FirstOrDefault(r => r.AttendeeName.ToLower() == User.Identity.Name.ToLower()).RSVPID }, new AjaxOptions { UpdateTargetId = "QuickRegister"})
%>
</p>
<%
}
else
{
%>
<p>
<%:
Ajax.ActionLink("RSVP for this event", "Register", "RSVP", new { id=Model.EventID }, new AjaxOptions { UpdateTargetId="QuickRegister" }) %>
</p>
<%
}
%>
Now corresponding to these two links, my functions in RSVP Controller look like these.
[Authorize, HttpPost]
public ActionResult Register(int id)
{
Event event = eventRepository.GetEvent(id);
if (event == null)
return Content("Event not found");
if (!event.IsUserRegistered(User.Identity.Name))
{
RSVP rsvp = new RSVP();
rsvp.AttendeeName = User.Identity.Name;
event.RSVPs.Add(rsvp);
eventRepository.Save();
}
return Content("Thanks, you are registered.");
}
[Authorize, HttpPost]
public ActionResult CancelRegistration(int id)
{
RSVP rsvp = eventRepository.GetRSVP(id);
if (rsvp == null)
return Content("RSVP not found");
if (rsvp.Event.IsUserRegistered(User.Identity.Name))
{
eventRepository.DeleteRSVP(rsvp);
eventRepository.Save();
}
return Content("Sorry, we won't be seeing you there!");
}
Both of these seem to work without any issues. Now I want to make it a little fancier by doing either of these two:
1) Return an AJAX link from controller so that when you register, you get cancel registration link shown to you without page refresh.
2) Somehow make the view rendering refreshed when the controller method has finished executing so the first code block in my question gets executed after the click of any of the AJAX links. So clicking on register will register you and show you cancel link and clicking on cancel will cancel your registration and show you register link.
Any help would be greatly appreciated.
Thanks.
You could by using jQuery show and hide these links.
I never use the Ajax.ActionLink, I do my AJAX without the helper but I think it should look loke this :
Ajax.ActionLink("Click here if you can't make it!", "CancelRegistration", "RSVP", new { id = Model.RSVPs.FirstOrDefault(r => r.AttendeeName.ToLower() == User.Identity.Name.ToLower()).RSVPID }, new AjaxOptions { UpdateTargetId = "QuickRegister", OnSuccess = "ShowHideLinks" }, new { id = "cancel-link", #style = "display:none"})
Ajax.ActionLink("RSVP for this event", "Register", "RSVP", new { id=Model.EventID }, new AjaxOptions { UpdateTargetId="QuickRegister", OnSuccess = "ShowHideLinks" }, new { id = "register-link"})
And some javascript/jQuery to initialize the current display link :
function ShowHideLinks() {
$('#register-link').toggle();
$('#cancel-link').toggle();
}
<%: if(Model.HasRSVP(Context.User.Identity.Name)) { %>
ShowHideLinks();
<%: } %>
Hope this help!
You can set the links using jQuery. I see you are passing Content from both the action methods... You can use $.post() in jQuery to execute these action and in the success eventhandler compare the returned content (you would need to send something which is more easy to compare than the current strings) and set the links appropriately.