I am working on a POC using a very old asp.net application that runs winforms, and adding a REST API to it. I have so far been successful up until this one endpoint, and it is not giving me my data back in my controller.
I have more experience in an MVC framework and web API then the current implementation I am working in. What I need help with is how I can get my data sent back to the server.
Here is my code:
this is the equivalent of my global asax file
class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
// Configure Web API for self-host.
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
appBuilder.UseCors(CorsOptions.AllowAll);
appBuilder.UseWebApi(config);
}
}
Javascript
var setPatient = function () {
$.ajax({
url: "http://localhost:9000/api/context/readPtnt?contextData=&type=context",
type: "POST",
dataType: "json",
contentType:"application/json; charset=utf-8",
data: {
jsonData: JSON.stringify({
firstName: $('#fName').val(),
lastName: $('#lName').val(),
ID: $('#ID').val(),
DOB: $('#DOB').val(),
SSN: $('#SSN').val(),
MRN: $('#MRN').val()
})
}
})
.done(successPatientSet)
.fail(errorFn)
.always(function (data, textStatus, jqXHR) {
});
}
//click
$("#btnSetPatientData").on("click", setPatient);
and my controller code:
[HttpPost]
[Route("/api/context/readPtnt")]
public string ReadPtnt(string contextData, string type)
{
try
{
var patientInfo = PatientContext.Create(contextData);
I can successfully hit the back end, but I get nothing in the contextData variable, when I try to change that variable name I get 404's. I have tried all sorts of variations from different iterations of my JQuery, as well as pure javascript Ajax calls. The REST middleware that we are using is OWIN. I am stumped, any input would be helpful. Thanks.
You need enable CORS support
To enable CORS support, add the Microsoft.AspNet.WebApi.Cors NuGet package to your project.
Add this code to your configuration:
public static void Register(HttpConfiguration config)
{
// New code
config.EnableCors();
}
To enable cross-origin requests, add the [EnableCors] attribute to your Web API controller or controller method:
[EnableCors(origins: "http://example.com", headers: "*", methods: "*")]
public class TestController : ApiController
{
// Controller methods not shown...
}
Enabling Globally
The method described above can also be used to enable CORS across the API without annotating each controller:
public static void Register(HttpConfiguration config)
{
var corsAttr = new EnableCorsAttribute("http://example.com", "*", "*");
config.EnableCors(corsAttr);
}
For more information, see the official Web API documentation.
Related
I have created a plugin for brain-tree payment integration that gets load on run time therefore i have generated the client token on the server side c# class and i am trying to use that in my angular controller but unable to do so.
Is there any way that i can get my session variable or some thing like that in my angular controller from c# class? so that i will be able to create drop-in UI in angular ready function.
I want to acheive something like this:
angular.element(document).ready(function () {
var clientToken = "From_Server_Side";
braintree.setup(clientToken , 'dropin', {
container: 'dropin-container',
paypal: {
singleUse: true,
amount: 10.00,
currency: 'GBP'
}
});
});
You have to make a JsonResult like this in your controller
[HttpGet]
public JsonResult client_token()
{
var gateway = config.GetGateway();
var clientToken = gateway.ClientToken.Generate();
return Json(clientToken, JsonRequestBehavior.AllowGet);
}
and then in your angularjs you have to do
$scope.get_token = function () {
$http({
method: 'GET',
url: '/home/client_token'
}).then(function (data) {
$scope.client_token_string = data.data;
})
}
$scope.get_token();
I put home hypothetically assuming that your doing it in home controller
I have some problem with $http.get method in AngularJS. It's little strange because everything works great when I use IE, but when I use Firefox get method returns null exception without any info why it's not work.
My code:
$scope.getData = function () {
var surveyApiUrl = config.api.host + config.api.paths.survey;
debugger;
mainService.httpMethods.get(surveyApiUrl).success(function(data) {
debugger;
$scope.surveyData = data;
}).error(function (data, status, headers, config) {
debugger;
alert(e);
});
}
Some web api:
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Result with IE:
IE result
Result with Firefox:
Firefox result
Resolved
Problem was with cors configuration on server side. I added only attribute to web api controller but there is required enable cors in configuration file too.
Solution which work for me:
In webapi config:
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.EnableCors();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
In webapi controller (there is required Microsoft Asp.NET Cross-Origin Support package which is available in NuGet):
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class SurveyController : BaseController
I'm making an ASP.NET Web API web service, and an HTML/javascript page to test it. The issue I'm having is with passing a complex data parameter and having it come through properly in the web API controller.
I know there are numerous similar questions and I've read them and tried the solutions and haven't solved it. I have also read some JQuery documentation.
Here's my controller:
public class TitleEstimateController : ApiController
{
public IHttpActionResult GetTitleEstimate([FromUri] EstimateQuery query)
{
// All the values in "query" are null or zero
// Do some stuff with query if there were anything to do
}
}
public class EstimateQuery
{
// Various fields
}
The route mapping in WebApiConfig.cs:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{query}"
);
And the javascript:
var uri = 'api/titleEstimate/';
var query = {
"username": $("#user").val(),
// other fields
};
$.getJSON(uri,query)
.done(function (data) {
$('#product').text("OK");
})
.fail(function (jqXHR, textStatus, err) {
$('#product').text('Error: ' + err);
});
Currently I'm getting a 404. I tried $.getJSON(uri + '/' + query) but that didn't work either. Before I was passing this object I was calling it successfully so I think the routing is generally OK. I tried a type converter, but that didn't help, still a 404. Does anyone see what I'm missing/doing wrong?
Answer
You are using the wrong uri. You need api/titleEstimate/getTitleEstimate. That explains and will resolve your 404.
Answer to Follow Up Question
Everything else you're doing almost works. After you resolve the 404, you'll find that you aren't receiving the value FromUri, and you'll have a follow up question. So, you need to change your route config to this:
config.Routes.MapHttpRoute(
name: "Default",
routeTemplate: "api/{controller}/{action}"
);
Then you'll not only resolve your 404 but also receive the FromUri value that you're sending as query string parameters.
Demo
Here is a fiddle for evidence, which is calling into this controller, which is hosted LIVE here. The only thing I changed in the working version are the uri to resolve the 404 and the route config to make sure you receive the FromUri value. That's all.
HTML
<label>Username:
<input id="user" />
</label>
<button id="submit">Submit</button>
<button id="submit-fail">Submit Fail</button>
<div id="product"></div>
JavaScript
This will succeed.
Note, we only need domain because we doing cross-site scripting here for the demo purpose.
var domain = "https://cors-webapi.azurewebsites.net";
$("#submit").click(function () {
var uri = domain + '/api/titleEstimate/getTitleEstimate';
var query = {
"username": $("#user").val(),
// other fields
};
$.getJSON(uri, query)
.done(function (data) {
$('#product').text(data);
})
.fail(function (jqXHR, textStatus, err) {
$('#product').text('Error: ' + err);
});
});
This will 404.
$("#submit-fail").click(function () {
var uri = domain + '/api/titleEstimate';
var query = {
"username": $("#user").val(),
// other fields
};
$.getJSON(uri, query)
.done(function (data) {
$('#product').text(data);
})
.fail(function (jqXHR, textStatus, err) {
$('#product').text('Error: ' + err);
});
});
Controller
public class TitleEstimateController : ApiController
{
public class EstimateQuery
{
public string username { get; set; }
}
public IHttpActionResult GetTitleEstimate([FromUri] EstimateQuery query)
{
// All the values in "query" are null or zero
// Do some stuff with query if there were anything to do
if(query != null && query.username != null)
{
return Ok(query.username);
}
else
{
return Ok("Add a username!");
}
}
}
You can read more details about WebApi routing here. From reading it you can probably come up with an alternative solution within your route config. There are also lots of terrific examples in this blog post.
First, I would try to use the attribute routing feature of web.api like this:
[RoutePrefix("api/titleestimate")]
public class TitleEstimateController : ApiController
{
[HttpGet]
public IHttpActionResult GetTitleEstimate([FromUri] EstimateQuery query)
{
// All the values in "query" are null or zero
// Do some stuff with query if there were anything to do
}
}
It would also be helpful to see the request in your dev tools. I disagree with Colin that you should make this a POST, because HTTP POST is supposed to be used to create new items. You are trying to get information so HTTP GET makes sense.
I think Web.Api assumes methods are GETs by default, but declarating it with the HttpGet attribute will for sure take care of that.
For complex objects, I usually send them in the message body rather than the URL.
Would you have any objection to an approach similar to the answer of this question?
How to pass json POST data to Web API method as object
It seems like the more straightforward/natural approach.
Something like (untested, but should be close):
[RoutePrefix("api/titleestimate")]
public class TitleEstimateController : ApiController
{
[HttpGet]
public IHttpActionResult GetTitleEstimate([FromBody] EstimateQuery query)
{
// All the values in "query" are null or zero
// Do some stuff with query if there were anything to do
}
}
public class EstimateQuery
{
public string QueryName{ get; set; }
public string Whatever{ get; set; }
}
$(function () {
var query = {QueryName:"My Query",Whatever:"Blah"};
$.ajax({
type: "GET",
data :JSON.stringify(query),
url: "api/titleEstimate",
contentType: "application/json"
})
.done(function (data) {
$('#product').text("OK");
})
.fail(function (jqXHR, textStatus, err) {
$('#product').text('Error: ' + err);
});
});
I am developing angular js with web api.
I have this cotroller: Controller/MasterController, and thiis in my WebApi Config:
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I call this function from Global.asax, Application_Start event.
I call my web api from service.js like that:
var service = function ($http) {
var _$http = $http;
self = this;
self.getMenuItems = function () {
var promise = _$http({
method: "GET",
url: 'api/Master'
}).success(function (data, status, headers, config) {
}).error(function (data, status, headers, config) {
});
return promise;
};
In debug mode, I saw that I reach this area.
I get this error in chrome console:
"Failed to load resource: the server responded with a status of 404"
and this is what he was trying to get to: http://localhost:12345/api/Master
Also, I tried to get to the web api controller directly through the browser, and I couldn't find it.
Thank You
Just to be sure your API is working try the following:
Global.asax.cs:
public class WebApiApplication : HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
MasterController:
using System.Web.Http;
namespace WebApi.Controllers
{
public class MasterController : ApiController
{
public string Get()
{
return "Hello world";
}
}
}
Calling http:// localhost:[SomePort]/api/Master in your browser should result in this:
"Hello world"
The configurations above are all standard when creating a new WebApi.
After trying some new directions, I received new error details.
The final error I received + its solution you can find
here
I am creating a REST API and I have been playing with the idea of allowing bundling of requests from clients. By bundling I mean they can send one request, containing multiple "real" requests, and they get delivered to the client together. Typically javascript ajax requests. Something like this:
POST /bundlerequest
["/person/3243", "/person/3243/friends", "/comments/3243?pagesize=10&page=1", "/products", "/product/categories" ]
(The bundled request can only be GET requests, as of now at least)
This is intended to return something like this
{
"success" : ["/person/3243", "/person/3243/friends", "/comments/3243?pagesize=10&page=1", "/products", "/product/categories" ],
"error" : [],
"completiontime" : 94,
other relevant metadata...
"responses" : [
{"key" : "/person/3243" , "data" : {"name" : "John", ...} },
{"key" : "/person/3243/friends" , "data" : [{"name": "Peter", "commonfriends" : 5, ...}] },
etc...
]
}
The benefits of this bundling is that it reduces the number of requests and that is especially important on mobile devices for instance.
So my first question is, is my approach to this a good one? Does anyone have experience with doing something like this?
AFAIK the common way of solving this is to write server side code to return combined data, that I believe is relevant for the client(s). (The twitter user stream for instance does this, combining person info, latest tweets, latest personal messages etc.) But this makes the API very opinionated and when the client needs changes the server might need to change to accomodate to optimize.
And the second question is how to implement this?
My backend is ASP.NET MVC 3 and IIS 7. Should I implement it in the application, having an bundlerequest action that internally calls the other actions specified in the request?
Could it be implemented in IIS 7 directly? Writing a module that transparently intercepts requests to /bundlerequest and then calls all the corresponding sub requests, making the application totally unaware of the bundling that happens? This would also allow me to implement this in an application-agnostic way.
You could use an asynchronous controller to aggregate those requests on the server. Let's first start by defining a view model that will be returned by the controller:
public class BundleRequest
{
public string[] Urls { get; set; }
}
public class BundleResponse
{
public IList<string> Success { get; set; }
public IList<string> Error { get; set; }
public IList<Response> Responses { get; set; }
}
public class Response
{
public string Key { get; set; }
public object Data { get; set; }
}
then the controller:
public class BundleController : AsyncController
{
public void IndexAsync(BundleRequest request)
{
AsyncManager.OutstandingOperations.Increment();
var tasks = request.Urls.Select(url =>
{
var r = WebRequest.Create(url);
return Task.Factory.FromAsync<WebResponse>(r.BeginGetResponse, r.EndGetResponse, url);
}).ToArray();
Task.Factory.ContinueWhenAll(tasks, completedTasks =>
{
var bundleResponse = new BundleResponse
{
Success = new List<string>(),
Error = new List<string>(),
Responses = new List<Response>()
};
foreach (var task in completedTasks)
{
var url = task.AsyncState as string;
if (task.Exception == null)
{
using (var response = task.Result)
using (var stream = response.GetResponseStream())
using (var reader = new StreamReader(stream))
{
bundleResponse.Success.Add(url);
bundleResponse.Responses.Add(new Response
{
Key = url,
Data = new JavaScriptSerializer().DeserializeObject(reader.ReadToEnd())
});
}
}
else
{
bundleResponse.Error.Add(url);
}
}
AsyncManager.Parameters["response"] = bundleResponse;
AsyncManager.OutstandingOperations.Decrement();
});
}
public ActionResult IndexCompleted(BundleResponse response)
{
return Json(response, JsonRequestBehavior.AllowGet);
}
}
and now we can invoke it:
var urls = [
'#Url.Action("index", "person", new { id = 3243 }, Request.Url.Scheme, Request.Url.Host)',
'#Url.Action("friends", "person", new { id = 3243 }, Request.Url.Scheme, Request.Url.Host)',
'#Url.Action("index", "comments", new { id = 3243, pagesize = 10, page = 1 }, Request.Url.Scheme, Request.Url.Host)',
'#Url.Action("index", "products", null, Request.Url.Scheme, Request.Url.Host)',
'#Url.Action("categories", "product", null, Request.Url.Scheme, Request.Url.Host)'
];
$.ajax({
url: '#Url.Action("Index", "Bundle")',
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(urls),
success: function(bundleResponse) {
// TODO: do something with the response
}
});
Of course some tweaking might be necessary to adapt this to your specific needs. For example you mentioned sending AJAX requests with session expired which might redirect to the Logon page and thus not capturing the error. That's indeed a PITA in ASP.NET. Phil Haack blogged a possible way to circumvent this undesired behavior in a RESTful manner. You just need to add a custom header to the requests.
I suggest looking wcf web api