AJAX POST request returns 404 .NET 6 - javascript

I have this issue:
I am using .NET 6 with MVC and I am making use of FullcalendarIo. I have a controller for making free slots in the calendar. This is the code in the controller:
[Authorize(Roles = DoctorRoleName)]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < ActionResult < AppointmentSlotInputModel >> GenerateSlots(AppointmentSlotInputModel model) {
//await this.appointmentService.GenerateSlots(model.Start, model.End, model.SlotDurationMinutes);
return Json("Hello");
}
And this is my JS Code that makes the POST request
const params = {
start: startDate,
end: endDate,
slotDurationMinutes: scale
};
const response = await fetch('/Appointment/GenerateSlots', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'RequestVerificationToken': document.getElementById("RequestVerificationToken").value
},
body: JSON.stringify(params)
});
In the network tab the request to /Appointment/GenerateSlots I get first code 302 - Redirect and then 404 Not Found. The request url seems correct - https://localhost:44376/Appointment/GenerateSlots.
f I change the method to GET and put the [HttpGet] attribute above the action in the controller I get the JSON result. In the Startup.cs I am using these:
services.AddAntiforgery(options => {
options.HeaderName = "X-CSRF-TOKEN";
});
services.Configure < CookiePolicyOptions > (options => {
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
But event if I comment them out the result is the same - first 302 then 404. I have tried a lot of the code that is written here on the site for people with similar issues, but it hasn't helped. Where am I mistaking?
I am using standard routing:
app.UseEndpoints(endpoints => {
endpoints.MapControllerRoute(
name: "areas",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Edit: I have found a similar thread here - ASP.NET controller, AJAX GET works, but POST does not (404)
But this doesn't seem to be the case for me.

So I found a my solution. In my Startup.cs I have this code:
services.AddAntiforgery(options =>
{
options.HeaderName = "X-CSRF-TOKEN";
});
And the BadRequest from the Controller came because of the AntiForgeryToken validation. In my AJAX call my header for the AF Token was wrong: It was 'RequestVerificationToken', but it should have been 'X-CSRF-TOKEN' or the other way around. But still now I have the proper result and no more BadRequest.

Related

Trouble with fetch request in FireFox

We've got this rather large'ish and old'ish asp web application, that started to behave poorly because of (as i understand) XHR requests in beforeunload event handler. There is quite a logic tied to this, so i cannot just throw it away.
The code in beforeunload was like this:
$(window).on('beforeunload', function () {
$.ajax('/site/$hdr/unload', { async: true, cache: false });
});
and in server side something like this:
private class HttpModule : IHttpModule
{
private static void PostAcquireRequestState(object sender, EventArgs e)
{
var context = HttpContext.Current;
var path = context.Request.AppRelativeCurrentExecutionFilePath;
if (path.StartsWith("~/$hdr/unload"))
{
//do things
}
}
I'm trying to use the fetch api instead of synchronous ajax request used before. In chrome everything works fine, but FireFox 89.0 behaves strangely.
Code in beforeunload is like this:
let i;
let data = new Headers();
if (window.FMS && window.FMS.HttpHeaders) {
for (i in window.FMS.HttpHeaders) {
if (FMS.HttpHeaders.hasOwnProperty(i)) {
data.append('X-FMS-' + i, FMS.HttpHeaders[i]);
}
}
}
fetch(FMS.UnloadUrl + '?_=' + jQuery.now(), {
method: 'GET',
keepalive: true,
cache: 'no-cache',
headers: data }
).then(response => response.ok)
.then(IsOk => console.debug(IsOk.toString()))
.catch((error) => {console.error('Error:', error)});
If my understanding is correct - the server side now have to receive OPTIONS preflight request first? I never receive this. Sometimes i receive the GET request, but usually in the FireFox console i can see TypeError: NetworkError when attempting to fetch resource error or notification that the XHR request has been blocked by DevTools.

Paypal Checkout client integration - Problem with returning order id promise

i have a problem integrating paypals payment gateway. I am using javascript for the client, python for the backend and the checkouts v2 api.
Creating a order on the backend works without trouble, but while waiting for my servers response the createOrder function raises a error:
unhandled_error
Object { err: "Expected an order id to be passed\nLe/</<#https://www.sandbox.paypal.com/smart/buttons?style.layout=vertical&style.color=blue&style.shape=rect&style.tagline=false&components.0=buttons&locale.country=NO&locale.lang=no&sdkMeta=eyJ1cmwiOiJodHRwczovL3d3dy5wYXlwYWwuY29tL3Nkay9qcz9jbGllbnQtaWQ9QWJmSjNNSG5oMkFIU1ZwdXl4eW5lLXBCbHdJZkNsLXpyVXc1dzFiX29TVUloZU01LXNMaDNfSWhuTnZkNUhYSW5wcXVFdm5MZG1LN0xOZ1gmZGlzYWJsZS1mdW5kaW5nPWNyZWRpdCxjYXJkIiwiYXR0cnMiOnt9fQ&clientID=AbfJ3MHnh2AHSVpuyxyne-pBlwIfCl-zrUw5w1b_oSUIheM5-sLh3_IhnNvd5HXInpquEvnLdmK7LNgX&sessionID=e2ea737589_mtc6mtu6mdi&buttonSessionID=de4bfb3626_mtc6mjm6mtk&env=sandbox&fundingEligibility=eyJwYXlwYWwiOnsiZWxpZ2libGUiOnRydWV9LCJjYXJkIjp7ImVsaWdpYmxlIjpmYWxzZSwiYnJhbmRlZCI6dHJ1ZSwidmVuZG9ycyI6eyJ2aXNhIjp7ImVsaWdpYmxlIjp0cnVlfSwibWFzdGVyY2FyZCI6eyJlbGlnaWJsZSI6dHJ1ZX0sImFtZXgiOnsiZWxpZ2libGUiOnRydWV9LCJkaXNjb3ZlciI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJoaXBlciI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJlbG8iOnsiZWxpZ2libGUiOmZhbHNlfSwiamNiIjp7ImVsaWdpYmxlIjpmYWxzZX19…", timestamp: "1593537805136", referer: "www.sandbox.paypal.com", sessionID: "e2ea737589_mtc6mtu6mdi", env: "sandbox", buttonSessionID: "de4bfb3626_mtc6mjm6mtk" }
Error: Expected an order id to be passed
Error: Expected an order id to be passed
12V21085461823829 // ticks in a few seconds later
Console screenshot
The problem seems to be that createOrder does not wait for the promise before raising the error, or that the promise is not given in the correct way. Something like that. Anyways here is the client side code:
paypal.Buttons({
// button styling removed for clarity
createOrder: function() {
// purchase information
var data = {
'track_id': vm.selectedTrack.id,
'lease_id': vm.selectedLease.id,
}
// post req to api with lease and track ids
// create payment on server side
fetch('http://localhost:5000/api/paypal/create-purchase', {
method: 'post',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(data),
}).then(function(res) {
return res.json();
}).then(function(data) {
console.log(data.order_id)
return data.order_id
})
}
// conatiner element to render buttons in
}).render('#paypal-button');
And the server side:
#app.route('/api/paypal/create-purchase', methods=['POST'])
def paypal_create_purchase():
# cart validation removed for clarity
# create paypal purchase
environment = SandboxEnvironment(client_id=app.config['PAYPAL_PUBLIC'], client_secret=app.config['PAYPAL_PRIVATE'])
client = PayPalHttpClient(environment)
paypal_request = OrdersCreateRequest()
paypal_request.prefer('return=representation')
paypal_request.request_body (
{
"intent": "CAPTURE",
"purchase_units": [
{
"amount": {
"currency_code": "USD",
"value": lease.price
}
}
]
}
)
try:
# Call API with your client and get a response for your call
response = client.execute(paypal_request)
order = response.result
print(order.id)
except IOError as ioe:
print (ioe)
if isinstance(ioe, HttpError):
# Something went wrong server-side
print(ioe.status_code)
# note that it is the same key as on the client
return jsonify(success=True,order_id=order.id)
I found this similar thread, but i dont consider the origin of the error to be the same as in that thread (incorrect json key on client)
Also see this relevant page in the docs which supplies this code:
createOrder: function() {
return fetch('/my-server/create-paypal-transaction', {
method: 'post',
headers: {
'content-type': 'application/json'
}
}).then(function(res) {
return res.json();
}).then(function(data) {
return data.orderID; // Use the same key name for order ID on the client and server
});
}
Damn, just as i was typing out the last part of the post i noticed the error. A missing return before my fetch call. Will leave this up for other people with the same mistake.

How to Login to MediaWiki (Wikipedia) API in Node.js

I'm trying the Wikipedia client login flow depicted in the API:Login docs, but something wrong happens:
1) I correctly get a token raised with the HTTP GET https://en.wikipedia.org/w/api.php?action=query&meta=tokens&type=login&format=json
and I get a valid logintoken string.
2.1) I then try the clientlogin like:
HTTP POST /w/api.php?action=clientlogin&format=json&lgname=xxxx&lgtoken=xxxx%2B%5C
and the POST BODY was
{
"lgpassword" : "xxxxx",
"lgtoken" : "xxxxx"
}
But I get an error:
{
"error": {
"code": "notoken",
"info": "The \"token\" parameter must be set."
},
"servedby": "mw1228"
}
If I try to change lgtoken to token I get the same result.
2.2) I have then tried the old method i.e. action=login and passing the body, but it does not work, since it gives me back another login token: HTTP POST https://en.wikipedia.org/w/api.php?action=login&format=json&lgname=xxxx
and the same POST BODY
I then get
{
"warnings": {}
},
"login": {
"result": "NeedToken",
"token": "xxxxx+\\"
}
where the docs here states that
NeedToken if the lgtoken parameter was not provided or no session was active (e.g. your cookie handling is broken).
but I have passed the lgtoken in the json body as showed.
I'm using Node.js and the built-in http module, that is supposed to pass and keep session Cookies in the right way (with other api it works ok).
I have found a similar issue on a the LrMediaWiki client here.
[UPDATE]
This is my current implementation:
Wikipedia.prototype.loginUser = function (username, password) {
var self = this;
return new Promise((resolve, reject) => {
var cookies = self.cookies({});
var headers = {
'Cookie': cookies.join(';'),
'Accept': '*/*',
'User-Agent': self.browser.userAgent()
};
// fetch login token
self.api.RequestGetP('/w/api.php', headers, {
action: 'query',
meta: 'tokens',
type: 'login',
format: 'json'
})
.then(response => { // success
if (response.query && response.query.tokens && response.query.tokens['logintoken']) {
self.login.logintoken = response.query.tokens['logintoken'];
self.logger.info("Wikipedia.login token:%s", self.login);
return self.api.RequestPostP('/w/api.php', headers, {
action: 'login',
format: 'json',
lgname: username
},
{
lgpassword: password,
lgtoken: self.login.logintoken
});
} else {
var error = new Error('no logintoken');
return reject(error);
}
})
.then(response => { // success
return resolve(response);
})
.catch(error => { // error
self.logger.error("Wikipedia.login error%s\n%#", error.message, error.stack);
return reject(error);
});
});
}//loginUser
where this.api is a simple wrapper of the Node.js http, the source code is available here and the api signatures are like:
Promise:API.RequestGetP(url,headers,querystring)
Promise:API.RequestPostP(url,headers,querystring,body)
If the currently accepted answer isn't working for someone, the following method will definitely work. I've used the axios library to send requests. Any library can be used but the key lies in formatting the body and headers correctly.
let url = "https://test.wikipedia.org/w/api.php";
let params = {
action: "query",
meta: "tokens",
type: "login",
format: "json"
};
axios.get(url, { params: params }).then(resp => {
let loginToken = resp.data.query.tokens.logintoken
let cookie = resp.headers["set-cookie"].join(';');
let body = {
action: 'login',
lgname: 'user_name',
lgpassword: 'password',
lgtoken: loginToken,
format: 'json'
}
let bodyData = new URLSearchParams(body).toString();
axios.post(url, bodyData, {
headers: {
Cookie: cookie,
}
}).then(resp => {
// You're now logged in!
// You'll have to add the following cookie in the headers again for any further requests that you might make
let cookie = resp.headers["set-cookie"].join(';')
console.log(resp.data)
})
})
And you should be seeing a response like
{
login: { result: 'Success', lguserid: 0000000, lgusername: 'Username' }
}
The second post request was where I got stuck for several hours, trying to figure out what was wrong. You need to send the data in an encoded form by using an API like URLSearchParams, or by just typing up the body as a string manually yourself.
I think from what you are saying you have lgtoken and lgname in the URL you are using, and then lgpassword and lgtoken (again!) in a JSON-encoded POST body.
This is not how the Mediawiki API works.
You submit it all as POST parameters. JSON is never involved, except when you ask for the result to come back in that format. I can't help you fix your code as you don't provide it, but that's what you need to do. (If you edit your question with your code, I'll do my best to help you.)
After seeing your code, I'll presume (without knowing the detail of your code) that you want something like this:
return self.api.RequestPostP('/w/api.php', headers, {
action: 'login',
format: 'json',
lgname: username,
lgpassword: password,
lgtoken: self.login.logintoken
});

Specify url parameters in ngResource

I have a service using ngResource that I use to access comments for specific news posts in my webapp.
My problem is that when I want to query comments for a specific news post, like this article.comments = commentService.query()
The get request is made to /api/news/comments, insteaf of /api/news/:id/comments. How can I specify the :id so that the get request is sent to the right url(/api/news/:id/comments)?
commentService
.factory('commentService', function($resource){
return $resource('/api/news/:id/comments/:comment', {id: '#news', comment: '#comment'}, {
update: {
method: 'PUT'
}
}, {
stripTrailingSlashes: false
}
)})
mehtod for fetching comments on ng-click
$scope.getComments = function(article) {
article.comments = commentService.query()
}
I solved it like this
$scope.getComments = function(article) {
return commentService.query({id:article._id})
}

Best way to send a file and additional params from Angular client and handle them on the server

I have a service with the method which gets three parameters which should be sent to the server.
setMainPhotoFor: function(file, petName, petId) {
...
}
I have the following solution:
Client side
services.js
setMainPhotoFor: function(file, pet) {
var baseServerApiUrl = configuration.ServerApi;
var data = new FormData();
data.append("image", file);
data.append("petName", pet.Name);
data.append("petId", pet.ID);
$http.post(baseServerApiUrl + '/pictures/main-picture/add', data, {
headers: { "Content-Type": undefined }
});
}
Server side
PicturesApiController
[HttpPost]
[Route("main-picture/add")]
public async Task<HttpResponseMessage> SetMainPicture()
{
if (!Request.Content.IsMimeMultipartContent())
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
MemoryStream mainPicture = new MemoryStream(await provider.Contents[0].ReadAsByteArrayAsync());
string petName = await provider.Contents[1].ReadAsStringAsync();
int petId;
if (!int.TryParse(await provider.Contents[2].ReadAsStringAsync(), out petId))
{
//...
}
//...
But in my opinion it doesn't look good. Can anybody suggest a right and more elegant solution for this task?
to send multipart form with angular add th options like this:
$http.post(baseServerApiUrl + '/pictures/main-picture/add', data, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
});
I have answered this before here with server side sample code.
Basically you should stream the content and put your dto in a header. I've tried many ways and this is the best way in my opinion.

Categories