Strange Behavior when using jQuery/asp.net WebApi - asp.net

I have 2 Web API Projects:
Api1 is a testing-Environment for the JavaScript Front-End, but has a API
Back-end(the default ValuesController), also for testing.
Api2 is the "true" Back-end, from which the Experimental JavaScript UI schould pull Data. For Testing, i use the default ValuesController here too, because, i want to have the same Output.
Status Quo
The Api1-UI can query the Data from the ValuesController of the own API
The Api2 returns the Correct Data(tested in Firefox and with Fiddler)
The Code
JavaScript Client:
var _load = function (url) {
$.ajax({
url: url,
method: 'GET',
accepts: "application/json; charset=utf-8",
success: function (data) {
alert("Success: " + data);
},
error: function (data) {
alert("Error :" + data);
}
});
};
WebApi Controller method:
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Problem
The JavaScript UI of the experimental Front-End is not able to display, or even receive, the data from the API 2, which is, according to Fiddler, sent correct.
My first thought was, I am using the wrong Method, but i tried $.getJSON and $.ajax. But i always end up with an error. It just says statusText= "Error"
I don't get, why it can display Data from the own ApiController, but not from the "External"...
Thanks for any Help/Suggestions

You seem to be accessing data from X from a different domain Y using ajax. This seems to be a classic cross domain access issue.
You need to set Access-Control-Allow-Origin to value " * " in your response header.
Response.Headers.Add("Access-Control-Allow-Origin", "*")
There various ways you can solve this
defining this header in IIS
using a actionfilter attribute like below
FilterAttribute
public class AllowCrossSiteJsonAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (actionExecutedContext.Response != null)
actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");
base.OnActionExecuted(actionExecutedContext);
}
}
Using Attribute on Controller Action
[AllowCrossSiteJson]
public Result Get(int id)
{
//return appropriate result
}

Related

Angular 2 HTTP post no HTTP-resource found

I am using Angular 2 in order to send http requests to the server.
The server is running with ASP.Net.
My API:
public class LagerController : ApiController
{
public IHttpActionResult RepHistorie(string vnr, string lagerort)
{
...
}
}
So I would call the API with
http://123.456.7.89/api/lager?vnr=W17291092373&lagerort=0382691395
This works fine when using a tool called postman with wich you can test API's.
But when making a post request with Angular 2, it doesn't work.
It says that the HTTP-resource is not found.
Angular 2:
submit() {
var body = 'vnr=W17291092373&lagerort=0382741400';
var link = 'http://123.456.7.89/api/lager';
this.http.post(link, body)
.subscribe(data => {
console.log(data);
}, error => {
console.log("Oooops!");
});
}
It seems like the parameters aren't added correctly.
Edit: Changed tag
This needs clarification, since the API above seems to be a GET Request.
In case it is a POST Request , then you should use the whole URL while running the API
In case you want to submit an object , you should use [FromBody] as a parameter
Example
[HttpPost]
public IHttpActionResult( [FromBody]YourObject item ) {
}
====
Client-side
var postUrl = 'http://123.456.7.89/api/lager?vnr=W17291092373&lagerort=0382691395';
var postObject = {};
http.post(postUrl,postObject)
For GET request where you would like to use QueryString
[HttpGet]
public IHttpActionResult RepHistorie([FromQuery]string vnr,[FromQuery]string lagerort){
...
}
====
// Query string can be built using RequestOptionsArgs as well
var builtUrl = 'http://123.456.7.89/api/lager?vnr=W17291092373&lagerort=0382691395';
http.get(builtUrl)
Alternative way is to
var getUrl = 'http://webapi/api/lager';
var requestOptions = {};
http.get(getUrl,requestOptions);
Reference:
Angular HTTP: https://angular.io/api/http/Http , check get() and post() methods
RequestOptionsArgs : https://angular.io/api/http/RequestOptionsArgs

Alternative way to provide CSRF Token instead using #Html.AntiForgeryToken() in ASP.Net MVC

I'm working on an angular 2 app using a ASP.Net MVC backend. For this, the index.cshtml has included #Html.AntiForgeryToken(); so i get back a hidden input element with the Token as its value. In my frontend i can grab this token and use it for my requests. So far so good.
Now since user information is used to generate the token in the backend, i have to switch out the token on some occassions. The problem is described here in this question.
Now since i don't want to do a full page reload in my app i have to use a workaround. For this i created a partial view in the backend which just hands out this input using #Html.AntiForgeryToken(); as ActionResult with a simple method like this:
public ActionResult GetAntiForgery()
{
return PartialView("~/Views/Home/AntiForgery.cshtml");
}
After login / logout i call this function from my frontend and replace the value of my existing AntiForgery input element like this:
getAntiForgery(url:string) {
return this._http.get(url)
.map((response:any) => {
let originalXsrfElement = <HTMLInputElement>document.querySelector('[name=__RequestVerificationToken]');
let body = response._body;
if(body) {
let helperElem = document.createElement('div');
helperElem.innerHTML = body;
let helperInputElem = <HTMLInputElement>helperElem.firstChild;
originalXsrfElement.value = helperInputElem.value;
}
return response;
});
}
I can't even tell you what bugs me the most. I hate to make an extra request (but lets not dive into this here) but way more terrible for me is that i have to request something, get an html string back and have to extract the token string out of it.
If i were the backend guy, i would kill the frontend guy (me..) for even thinking about creating an extra partial view, creating an extra method and always doing two requests on login/logout instead of one.
Is there a better way? For example i would like to call a method which just hands out a JSON with the proper token instead of an HTML snippet. Even better, on existing JsonResult methods in the backend, i would like to add the new CSRF Token as a property.
I'm not a backend architect so there might be some stuff wrong in general how i do this, but my backend colleagues don't mind what i'm doing there so it shouldn't be so far off.
Any hints are appreciated.
You can use AntiForgery.GetToken in your action to set some property on the JSON being returned:
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
json.RequestVerificationToken = cookieToken + ":" + formToken;
Then, pass it back in the header of your next AJAX request:
$.ajax("/my/awesome/url", {
type: "post",
contentType: "application/json",
data: { ... },
dataType: "json",
headers: {
'RequestVerificationToken': requestVerificationToken
}
});
Since, it's no longer being handled by cookies, you can't just use the standard ValidateAntiForgeryToken attribute. You'll need to manually check for the header and validate it. However, you should be able to create a custom attribute you can use instead:
AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class ValidateAntiForgeryTokenFromHeaderAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var request = filterContext.HttpContext.Request;
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
}
Then, just decorate your actions with [ValidateAntiForgeryTokenFromHeader] instead.
[Code adapted from http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks]

use json format in an URL

I am building a rest API with asp.net my problem is that when I try to add a student to my database like that :
http://localhost:50001/api/Students?&FirstName=cc&LastName=cc&Email=student10#gmail.com&DropOut=false&Live=false&ClassId=1&ImageId=1
I get "the value variable is null",
this is my code to add a student:
// Get All Students
[Route("api/Students")]
public IEnumerable<Student> Get()
{
return _StudentService.Queryable().ToList();
}
// Insert Student
[Route("api/Students/")]
public IEnumerable<Student> Post(Student value)
{
cc.Students.Add(value);
cc.SaveChanges();
return Get();
}
I have used "Fiddler web Debugger" to test my URLs an it works only in this way:
now If I have an angularJS client that tries to add a new student to the database,how can I send data as a json format in an URL
this is how I add a new student from my client angularJS:
$http({method: 'POST', url: 'http://localhost:50001/api/Students?&FirstName=cc&LastName=cc&Email=student10#gmail.com&DropOut=false&Live=false&ClassId=1&ImageId=1})
.success(function (data) {
console.log("success");
}).error(function (data, status, headers, config) {
console.log("data error ...");
});
thanks a lot for help
If you are saying you want a true Rest API you should continue to use the POST verb as it is more semantically right for creating a new student.
Passing a new student on the URL is possible but not in the configuration you have provided.
Your API method expects a POST request and that the new student be located in the HTTP body.
Just configure your angular call to use jsonData and post it to your API.

MVC 4 PartialViewResult caching on jQuery $.post

I have a jQuery $.post back to a MVC 4 Controller that will return back a PartialViewResult with rendered using the data sent in the POST. When debugging the Partial View and Controller, the correct data is being received and sent to the Partial View as the View Model. The issue is, when analyzing the HTML sent back in the AJAX result it is containing seemingly "cached" data from the original page refresh.
I have seen a good amount of posts on here that are similar, but none that were the same as my issue.
I am aware that HTTP Post requests do not cache in the browser, so that is not the issue. I also have set the set the OutputCache attribute to NoStore = true, etc.
Controller
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public partial class MyController : Controller
{
...
[HttpPost]
public virtual ActionResult UpdatePartial(MyViewModel myVm)
{
return this.PartialView("My/_Partial", myVm);
}
}
JS
$('.someButton').click(function () {
$.post(myAjaxUrl, $('form').serialize(), function (data) {
$('#myContent').html(data);
});
});
I'm able to work around this by adding ModelState.Clear prior to doing any operations on the model.
[HttpPost]
public virtual ActionResult UpdatePartial(PersonViewModel model)
{
ModelState.Clear();
model.FirstName += "1";
model.LastName += "1";
model.Age += 1;
return this.PartialView("../My/_Partial", model);
}
This question has an answer by Tim Scott with more info an links.
By default JQuery will cache $.ajax XMLHttpRequests (unless the data type is script or jsonp). Since $.post is implemented via $.ajax, JQuery itself has cached your request. The following JS should work.
JS
$('.someButton').click(function () {
$.ajax{(
url: myAjaxUrl,
data: myAjaxUrl,
success: function (data) {
$('#myContent').html(data);
},
cache: false
});
});
You might also find it worthwhile to handle the error event in case the post doesn't succeed.

Response.Write() in WebService

I want to return JSON data back to the client, in my web service method. One way is to create SoapExtension and use it as attribute on my web method, etc. Another way is to simply add [ScriptService] attribute to the web service, and let .NET framework return the result as {"d": "something"} JSON, back to the user (d here being something out of my control). However, I want to return something like:
{"message": "action was successful!"}
The simplest approach could be writing a web method like:
[WebMethod]
public static void StopSite(int siteId)
{
HttpResponse response = HttpContext.Current.Response;
try
{
// Doing something here
response.Write("{{\"message\": \"action was successful!\"}}");
}
catch (Exception ex)
{
response.StatusCode = 500;
response.Write("{{\"message\": \"action failed!\"}}");
}
}
This way, what I'll get at client is:
{ "message": "action was successful!"} { "d": null}
Which means that ASP.NET is appending its success result to my JSON result. If on the other hand I flush the response after writing the success message, (like response.Flush();), the exception happens that says:
Server cannot clear headers after HTTP headers have been sent.
So, what to do to just get my JSON result, without changing the approach?
This works for me:
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public void ReturnExactValueFromWebMethod(string AuthCode)
{
string r = "return my exact response without ASP.NET added junk";
HttpContext.Current.Response.BufferOutput = true;
HttpContext.Current.Response.Write(r);
HttpContext.Current.Response.Flush();
}
Why don't you return an object and then in your client you can call as response.d?
I don't know how you are calling your Web Service but I made an example making some assumptions:
I made this example using jquery ajax
function Test(a) {
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "TestRW.asmx/HelloWorld",
data: "{'id':" + a + "}",
dataType: "json",
success: function (response) {
alert(JSON.stringify(response.d));
}
});
}
And your code could be like this (you need to allow the web service to be called from script first: '[System.Web.Script.Services.ScriptService]'):
[WebMethod]
public object HelloWorld(int id)
{
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.Add("message","success");
return dic;
}
In this example I used dictionary but you could use any object with a field "message" for example.
I'm sorry if I missunderstood what you meant but I don't really understand why you want to do a 'response.write' thing.
Hope I've helped at least. :)

Resources