I have a method that follows:
public static void UpdateAll()
{
// Updates database
}
and I have a MVC Application, the view is as follows (removed lines for clarity):
<button id ="btn">Update</button>
//...
<script>
$('#btn').click(function () {
#{
project.models.settings.UpdateAll();
}
});
</script>
The project.models.settings.UpdateAll() method fires as soon as the page loads. Is there a way of making this method load, only once the user has clicked the button?
To test I did replace the method with a simple console.log and that worked as intended.
Any pointers, examples are much appreciated.
Kind regards
I think you're missing a fundamental understanding of how web applications work.
When you put the following into your view you are declaring a chunk of server-side code that will be executed while the view is being processed on the server-side.
#{
project.models.settings.UpdateAll();
}
The reason that it works when you replace it with a console.log is that console.log is client-side code and it executes on the client (i.e. in the browser).
The only way you are going to call server-side code (your UpdateAll method) from client-side code (your button click event handler) is by making an AJAX call.
The simplest thing would be to first expose your UpdateAll method as a controller action. Generally for actions that respond to AJAX calls I like to create a separate controller but that's not absolutely necessary.
public ServiceController : Controller
{
public ActionResult UpdateAll()
{
}
}
Now from your client-side code make an AJAX call to that action.
the way you are doing will not work.
the method will be called when view loads as it is server side code and it will be executed on view rendering time.
so call an action using ajax and in that action call this method:
$('#btn').click(function () {
$.ajax({
type: "GET",
url: '#Url.Action("MyAction","My")',
success: function(data) {
},
error: function(result) {
alert("Error");
}
});
});
write an action in controller:
public void MyAction()
{
project.models.settings.UpdateAll();
}
Related
I'm trying to figure out using AJAX with Razor Pages.
I've been searching the Web but each example I've found does something different, and most are incomplete or not for Razor Pages.
So far, I've been focusing on variations of something like this:
$.post('/?handler=Delete', 5, function (x) {
alert(x);
});
And then my page model looks like this:
public void OnPostDelete(int id)
{
}
I've tried variations on this but, so far, my C# code is not getting called.
Questions:
Can someone show me what I'm missing?
Can anyone offer some good references for this? (I need to perform other AJAX tasks as well.)
Some examples I found had special handling related to anti-forgery tokens. Do I need to code for that as well?
UPDATE:
So I've been working with this and this is what I have now:
$.ajax({
url: '?handler=Delete',
data: {
id: $(this).data('id')
}
})
.fail(function (e) {
// Error
alert(e.responseText); // Way too much info
})
.done(function () {
// Success
})
.always(function () {
// Always
});
And my handler:
public void OnGetDelete(int id)
{
}
This is in fact calling my handler and I finally got it to pass the id argument.
Since I have a bounty, here's what I'd like to see in an answer:
If I set the AJAX call to use POST and rename my handler to OnPostDelete(), the handler is not called. How would I do a post?
Any other suggestions or criticisms with the code above? I know there are many ways to do this. I'm just looking for the simplest way and trying to refine it.
You could pass F12 to check the request in Network tab , you may see 400 bad request error.
Razor Pages are designed to be automatically protected from cross-site request forgery (CSRF/XSRF) attacks. You don’t have to write any additional code. Antiforgery token generation and validation is automatically included in Razor Pages. Here the request fails, there is no AntiForgeryToken present on the page.
For the question , you could add explicitly using #Html.AntiForgeryToken()
To add AntiForgeryToken, we can use any of the approaches. Both the approaches add an input type hidden with name __RequestVerificationToken. The Ajax request should send the anti-forgery token in request header to the server. So, the modified Ajax request looks like,
#Html.AntiForgeryToken()
#section Scripts
{
<script>
$.ajax({
type: "POST",
url: "/?handler=Delete&id="+5,
beforeSend: function (xhr) {
xhr.setRequestHeader("XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
success: function (x) {
alert(x);
},
failure: function (response) {
alert(response);
}
});
</script>
}
Since the script sends the token in a header called X-CSRF-TOKEN, configure the antiforgery service to look for the X-CSRF-TOKEN header:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
}
Reference:https://www.talkingdotnet.com/handle-ajax-requests-in-asp-net-core-razor-pages/
This is how I like to do it:
Configure your Javascript
//If you're returning a object, configure it
var yourObject = {
field1: "value1",
field2: "value2"
};
//setup ajax
$.ajax({
data: yourObject,
type: "POST",
url: "urltoyourhandler/delete" //you can add other paramters here as well by doing ?parm=value
success: function(data){
//do success stuff here
//based off my handler code below:
if(data.success){
console.log("Success!");
}
else{
console.log("Failed for some reason!");
}
}
error: function(){
//do error stuff here
//gets called if there is a issue contacting the URL or if there is a server error like 500
}
});
Configuring your handler. For my CRUD operations, I like making a CRUD controller to handle everything
[BindProperty]
public YourClass Name { get; set; }
//set handler to only accept POST and set a URL for it. URL should be to the same folder you're in
//the 'delete' in route doesn't have to match the function name but it's less confusing if it does
[HttpPost, Route("RouteToThisHandler/delete)]
public async Task<IActionResult> Delete()
{
//verify data and the do something with it
//I like returning a JsonResult. Add whatever data you want. I like returning success
//with true or false and some other data if needed
return new JsonResult(new { success: true, importantInfo: "This is important" });
}
Ajax has more configuration options to give you more information about any server errors that occur
As for the anti-forgery token, Microsoft says:
Antiforgery middleware is added to the Dependency injection container
when one of the following APIs is called in Startup.ConfigureServices:
AddMvc
MapRazorPages
MapControllerRoute
MapBlazorHub
Here is a Microsoft link about the anti-forgery token: https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-3.1
I investigated a few things, what happens when you pass a string from AJAX instead of an integer and how the different routes affect the call (see interesting note below if you've got time) But mostly, all I found is that Razor Pages are pretty forgiving and everything seemed to just work. Like I mentioned, even passing a string where an integer type id still hit the handler method (it just gave me a default(int))
I created this repo just for exploring: https://github.com/EntityAdam/RazorPageHandlers
The major blocker, as #Yan pointed out is the Anti-Forgery token. That is really the only thing that caused a handler not to hit a breakpoint. As was suggested, check your network tab for failed requests or the console for bum JavaScript code.
From your snippet, to change it to a OnPostDelete you would need to use the POST type in the AJAX call AND include the Anti-Forgery Token.
$.ajax({
type: 'POST',
headers: { "RequestVerificationToken": $('input[name="__RequestVerificationToken"]').val() },
url: '?handler=Delete',
data: {
id: $(this).data('id')
}
})
.fail(function (e) {
// Error
alert(e.responseText); // Way too much info
})
.done(function () {
// Success
})
.always(function () {
// Always
});
Another topic that I didn't see discussed is where is this token coming from? It's generated by Razor automagically whenever there is a <form> element. If you do not have a <form> element, you can generate tokens when you need them, check out the #functions {} block in this MSDN Article. There are also scenarios where CSRF is useless or not required and you could also turn anti-forgery off if you don't need it (That's an entirely different discussion).
My criticisms of the approach are opinions so take 'em or leave 'em.
Don't use jQuery if you can avoid it based on the premise that improvements to JavaScript have arguably obsoleted jQuery. Use plain vanilla JS code.
Ajax is showing it's age as well. If you only need to support modern browsers consider the Fetch API
Don't write any JS at all! Blazor is new and shiny. There are still some growing pains, but I'd rather my pain be in C# than in JavaScript =)
An interesting thing I came across..
For this demo, I used the following #page directive
#page "{id?}"
In the HTML part of the forms like:
<form method="post" asp-page-handler="Delete">
<input type="hidden" name="id" value="1" />
<button class="btn btn-danger">Delete</button>
</form>
I'm using the asp-page-handler tag helper to assist in generating the correct URL. For the Create handler, with that #page directive the tag helper comes up with a form target of /?handler=Create
If you swap out that #page directive with #page "{handler?}/{id:int?}", the tag helper figures out the route which is now /Delete. But guess what? The AJAX calls work with either #page directive, even though the URL in the AJAX is hard coded to ?handler=Delete'
Two suggestion:
1- add the page path in front of the url. (I am not sure if it is mentioned in your words).
$.post('/{page_path}/?handler=Delete', 5, function (x) {
alert(x);
});
2- Follow the route map rule. For example, there is the 'id' in your function. The page may have configured like #page "{id:int}". So the url should be something like
$.post('/{page_path}/{id}/?handler=Delete', 5, function (x) {
alert(x);
});
I went through many posts but none exactly targeted a Web API Controller in the MVC Framework so I had to write a post regarding this.
I am using C# and my controller action looks something like this:
//[HttpGet]/[HttpPost]/etc.. anything can be here
public IEnumerable<something> Customers()
{
//Code
return List;
}
And my script in my cshtml view file looks something like below:
#section scripts
{
<script type="text/javascript">
</script>
}
Now if I want to call a jquery function and/or pass some data to it as well on the client side from my C# code, what would my action and jquery code look like?
Your MVC controller should look like similar to this...
public class ExampleController
{
[HttpGet]
public ActionResult Customers(string nameParameter)
{
//Code
return Json(nameParameter);
}
}
You need to define the accepted parameters in the functions constructor.
Your Ajax call on the other hand...
$.ajax({
url: "/Example/Customers?nameParameter=Asbah",
success: function(html){
// Returned value
}
});
The parameters need to match the names you defined in your function constructor. Note in the URL /Example/ it refers to ExampleController.
My javascript to invoke the method in my c# class, the ajax call does get into success method but it does hit the c# method when I put in the break point. I tried changing the shoppingcart.aspx to shoppingcart.aspx.cs but still doesn’t hit the c# method.
<script type="text/javascript">
$('.removeCart').click(function (e) {
$.ajax({
type:"POST",
url: "ShoppingCart.aspx/deleteSelectedProduct/",
success: function () {
console.log("ITS GOING THROUH",e.target.dataset.removename);
},
error: function () {
}
});
});
</script>
my c# code
public void deleteSelectedProduct()
{
}
To troubleshoot
You should remove method name and put url of page as following
url: "ShoppingCart.aspx",
and put a break point on Page_load event if it hits the break point that means your url is fine now you can put complete url with method name.
url: "ShoppingCart.aspx/deleteSelectedProduct/",
Now you can check whats wrong with your method following are possible solutions
Your method deleteSelectedProduct should be static method
You'll need [WebMethod] decoration above your function deleteSelectedProduct
You'll need [WebMethod] decoration above your function in the aspx page.
I think you are missing starting / only. Correct the url like below:
url: "/ShoppingCart.aspx/deleteSelectedProduct/",
Give it try and let me know if it doesn't work.
The method you are trying to access using ajax call should be decorated with WebMethod attribute to enable ajax calling(#Dominic already suggested that, I am just describing it as a solution). It should be something like:
[System.Web.Services.WebMethod]
public void deleteSelectedProduct()
{
//implementation code
}
Or Include System.Web.Services as namespaces on top of the page and used directly WebMethod.
Cheers!!
You have to decorate the C# function with the WebMethod which will be in System.Web.Services.WebMethod.
I'm in a bit of a bind here, I am trying to submit a form using ajax, all goes well and dandy until after the server has done it's thing, it decides to load to the action onto a new page, which is exactly what I do not want.
Here is my controller
[HttpPost]
public ActionResult Submit(int id, string trainingProgramId, FormCollection form, User user)
{
//Do Some action
return null;
}
In a different controller I have it set up to do $return PartialView(model);
this is the JQuery code that I am using.
$(function () {
$("form").submit(function (e) {
if (nextquestion()) { //check if posting can proceed
$.ajax({
type: "POST",
data: $(this).serialize(),
url: $(this).attr("action"),
success: function (response) {
alert(response);
$('#cases:last-child').insertAfter(response);
},
error: function (data) { }
});
}
e.preventDefault();
});
});
In all cases I am being redirected to a different url after performing the operation. Which is not the intention, especially if my intention might be to load more stuff into the page.
I've been tring everything, a bunch of solutions, but once my controller does a return statement the page automatically reloads to $/Submit
I have added $ #Scripts.Render("https://ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.unobtrusive-ajax.min.js"); to my scripts
From any other example I am finding this should work as I want it to, by remaining on the same page, however I am finding it difficult to understand who is responsible for changing the page either way. When I return my PartialView, a completely new page loads with the model as I've had it set up for the Partial View to be inserted originally.
Anyone care to explain what is going on or why my code is wrong?
I can't find it after a whole day of trying to debug.
I am trying to figure out why my HttpRuntime.Cache collection is always empty when controller action is invoked by $.ajax? In actions invoked normally everything works perfectly and I can get my data from Cache. Maybe actions invoked by AJAX have some different lifecycle and Cache collection is not ready yet? This is my example code:
action:
[HttpPost]
public PartialViewResult SomeAjaxAction()
{
// when receive ajax request HttpRuntime.Cache is always empty, but it shouldn't be
SomeType item = HttpRuntime.Cache.Get("cache_key") as SomeType;
return PartialView("SomeAjaxActionView", item);
}
invocation:
$.ajax({
type: 'POST',
url: '/controller/SomeAjaxAction/',
success: function (response) {
alert(response);
}
});
Is there any way to fix it e.g by custom action filter?
ANSWER
Ok, I found answer here: Access to session when making ajax call to Asp.net MVC action So I imporoved my code with base.HttpRuntime.Cache.Get("key") and it works as expected!
Ok, I found answer here: Access to session when making ajax call to Asp.net MVC action So I imporoved my code with base.HttpRuntime.Cache.Get("key") and it works as expected!