Ajax, jQuery, and JSONP - ajax

It seems like I can get JSON data from a different domain using jQuery's getJSON method (see: http://docs.jquery.com/Getjson). However, this works only for HTTP GET.
What if I needed to POST something and get the JSON response? How would I do that in jQuery/Ajax?

It is not possible to POST requests to a remote server from the client using jQuery alone as of version 1.6.1 in all browsers. If you attempt to make an XHttpRequest of any sort to a server in a different domain than the document, some browsers will simply fail to complete it. The JSONP requests to remote servers are handled by creating a script tag, the src for which is the API url with the query parameters added, including a callback method name. Because scripts can be loaded from any domain, this works, but it limits you to GET requests. The remote host returns the body of the script which is the callback invoked on the resulting javascript object. jQuery typically creates the callback function for you and from it calls the anonymous callback function you supply in the getJSON method parameters.
There are emerging standards, CORS and UMP (see also the comparison), that some browsers support but not in standardized ways (read IE does it differently). There are plugins to provide partial support for those browsers that do support CORS. No idea how well they work and they won't work unless the browser supports it.

The other answers aren't entirely true. This is possible if you have control over the server.
See:
W3C - Cross-Origin Resource Sharing
http://www.w3.org/TR/cors/
Essentially, the client sends a "pre-flight" OPTIONS HTTP request, and, if the correct response is received from the server, it continues with it's regular operations. (There are plenty of examples online... Unless you need me to, I won't get into the details).
I understand this may not work in all scenarios (for example, I'm not sure if IE5/5.5 supports this or not... but I believe IE6 does)... but if you're working on an HTML5 app, and you have control over the server, this could be a possibility for you.
NOTE: Just an aside - Given the option I'd prefer JSONP, of course. Less to go wrong.
EDIT: There seems to be a lot of confusion here, so let me give an example of how one might do this using .NET / WCF (I think some of this came from an article somewhere, and other parts of it were developed in house... so if some of it came from somewhere else, I apologize in advance for not giving the due credit):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace YourNamespaceHere
{
using System;
using System.Web;
using System.Collections;
public class CrossOriginModule : IHttpModule {
public String ModuleName {
get { return "CrossOriginModule"; }
}
public void Init(HttpApplication application) {
application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
}
private void Application_BeginRequest(Object source, EventArgs e) {
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
CrossOriginHandler.SetAllowCrossSiteRequestOrigin(context);
}
public void Dispose()
{
}
}
public class CrossOriginHandler : IHttpHandler
{
#region IHttpHandler Members
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
//Clear the response (just in case)
ClearResponse(context);
//Checking the method
switch (context.Request.HttpMethod.ToUpper())
{
//Cross-Origin preflight request
case "OPTIONS":
//Set allowed method and headers
SetAllowCrossSiteRequestHeaders(context);
//Set allowed origin
//This happens for us with our module:
SetAllowCrossSiteRequestOrigin(context);
//End
context.Response.End();
break;
default:
context.Response.Headers.Add("Allow", "OPTIONS");
context.Response.StatusCode = 405;
break;
}
context.ApplicationInstance.CompleteRequest();
}
#endregion
#region Methods
protected void ClearResponse(HttpContext context)
{
context.Response.ClearHeaders();
context.Response.ClearContent();
context.Response.Clear();
}
protected void SetNoCacheHeaders(HttpContext context)
{
context.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
context.Response.Cache.SetValidUntilExpires(false);
context.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
context.Response.Cache.SetNoStore();
}
#endregion
public static void SetAllowCrossSiteRequestHeaders(HttpContext context)
{
string requestMethod = context.Request.Headers["Access-Control-Request-Method"];
context.Response.AppendHeader("Access-Control-Allow-Methods", "GET,POST");
//We allow any custom headers
string requestHeaders = context.Request.Headers["Access-Control-Request-Headers"];
if (!String.IsNullOrEmpty(requestHeaders))
context.Response.AppendHeader("Access-Control-Allow-Headers", requestHeaders);
}
public static void SetAllowCrossSiteRequestOrigin(HttpContext context)
{
string origin = context.Request.Headers["Origin"];
if (!String.IsNullOrEmpty(origin))
context.Response.AppendHeader("Access-Control-Allow-Origin", origin);
else
//This is necessary for Chrome/Safari actual request
context.Response.AppendHeader("Access-Control-Allow-Origin", "*");
}
}
}
And in the Web.config:
...
<system.webServer>
...
<modules runAllManagedModulesForAllRequests="true">
...
<add name="CrossOriginModule" preCondition="managedHandler" type="YOURNANMESPACEHERE.CrossOriginModule, ASSEMBLYNAME" />
</modules>
<handlers>
<add name="CrossOrigin" verb="OPTIONS" path="*" type="YOURNAMESPACEHERE.CrossOriginHandler, ASSEMBLYNAME" />
</handlers>
</system.webServer>

in short: JsonP is a cross-domain technique limited to GET request.

test.php
<?php fire query
$row = array();
while($queryresults){
$row['id'] = '$queryresults['idfield']';
$row['name'] = '$queryresults['namefield']';
$row['marks'] = '$queryresults['marksfield']';
$output[] = $row;
}
echo json_encode( $output ); //json array
?>
document ready
$.getJSON('test.php?query=query,function(data) {
$.each(enq_data, function(i,data){
$('.anydiv').append('<div class="row">'+data.id+data.name+data.marks+'</div>');
});
});

Related

Exception Handler for normal AND ajax request for logging purpose

Can someone give me a working example of an ExceptionHandler which extends ExceptionHandlerWrapper for both normal and ajax request. I don't want to use a filter. I want all my exception handling to be in one entry place for logging purpose.
In Omnifaces we have the FullAjaxExceptionHandler but only for ajax request. How can I refactor this class in order to take into account both types of request?
In order to render the error page inside my CustomExceptionHandler, I use inside a utility method such a statement which identify the type of request (normal or ajax)
if (context.getPartialViewContext().isAjaxRequest()) {
ViewHandler viewHandler = context.getApplication().getViewHandler();
UIViewRoot viewRoot = viewHandler.createView(context, errorPageLocation);
context.setViewRoot(viewRoot);
context.getPartialViewContext().setRenderAll(true);
context.renderResponse();
} else {
NavigationHandler nav = context.getApplication().getNavigationHandler();
nav.handleNavigation(context, null, errorPageLocation);
context.renderResponse();
}
By this way I can handle both request effectively in my CustomExceptionHandler
In the Wrapper you can use, if you want to detect the type of request:
context == null || !context.getPartialViewContext().isAjaxRequest()
Also recommend you to integrate the WrapperHandler with Deltaspike Exception Control
And finally a common way to redirect (working for both types of requests):
public static void redirect(FacesContext fc, String view) {
ExternalContext ec = fc.getExternalContext();
String path = ec.encodeResourceURL(fc.getApplication().getViewHandler().getActionURL(fc, view));
try {
ec.redirect(path);
} catch(Exception e) {
throw new RuntimeException();
}
}

Restricting auto Help Page contents when using Attribute Routing in Web API 2

I'm currently implementing a Web API using Web API 2's attribute routing (http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2). I am also using the Help Pages module in order to automatically generate documentation from XML comments (http://www.asp.net/web-api/overview/creating-web-apis/creating-api-help-pages).
For this API I am providing support for optional return format extensions, so that every API method has a pair of routes defined on it like so:
[HttpGet]
[Route("Path/Foo")]
[Route("Path/Foo.{ext}")]
public HttpResponseMessage DoFoo()
{
// Some API function.
}
This allows a user to hit any of these and get a result:
www.example.com/api/Controller/Path/Foo
www.example.com/api/Controller/Path/Foo.json
www.example.com/api/Controller/Path/Foo.xml
My issue is that when Help Pages uses MapHttpAttributeRoutes() to generate documentation, it is picking up both routes for each method. So right now I see help for:
api/Controller/Foo
api/Controller/Foo.{ext}
But I want to only see:
api/Controller/Foo.{ext}
I would prefer to hide the non-extension route on each method, so that every method only shows a single Help Page entry.
Has anyone else tried something similar? Is there a work around that I am missing?
My question would be is that, would consumers of your api figure out easily that the {ext} is optional?...personally, I would prefer the default behavior...but anyways following are some workarounds that I can think of:
A quick and dirty workaround. Split the DoFoo into 2 actions like DoFoo() and DoFooWithExt maybe. Notice that I am using an attribute called ApiExplorerSettings, which is for HelpPage purposes. Example below:
[HttpGet]
[Route("Path/Foo")]
[ApiExplorerSettings(IgnoreApi=true)]
public HttpResponseMessage DoFoo()
{
return DoFooHelper();
}
[HttpGet]
[Route("Path/Foo.{ext}")]
public HttpResponseMessage DoFooWithExt()
{
return DoFooHelper();
}
private HttpResponseMessage DoFooHelper()
{
//do something
}
Create a custom ApiExplorer (which HelpPage feature uses internally) and check for specific routes like the following and can decide whether to show the action or not for that particular route.
// update the config with this custom implementation
config.Services.Replace(typeof(IApiExplorer), new CustomApiExplorer(config));
public class CustomApiExplorer : ApiExplorer
{
public CustomApiExplorer(HttpConfiguration config) : base(config)
{
}
public override bool ShouldExploreAction(string actionVariableValue, HttpActionDescriptor actionDescriptor, IHttpRoute route)
{
if (route.RouteTemplate.EndsWith("Path/Foo", StringComparison.OrdinalIgnoreCase))
{
return false;
}
return base.ShouldExploreAction(actionVariableValue, actionDescriptor, route);
}
}
Get list of all ApiDescription from the default ApiExplorer and then filter out the descriptions which you do not like. Example:
Configuration.Services.GetApiExplorer().ApiDescriptions.Where((apiDesc) => !apiDesc.RelativePath.EndsWith("Path/Foo", StringComparison.OrdinalIgnoreCase))

HttpRoutes - how do they work?

I´m struggling with URLs for ajax-reader/JSON. Each time I think I understand it, it seems that I haven´t.
Please, can anybody explain the logic behind this???
I got this Controller:
public class ServiceController : DnnApiController
{
[AllowAnonymous]
[HttpGet]
public HttpResponseMessage GetAllItems(int moduleId)
{
MyProjectController controller = new MyProjectController();
IEnumerable<ItemInfo> items = controller.GetAllItems(moduleId);
return Request.CreateResponse(HttpStatusCode.OK, items);
}
}
I got this Routemapper:
public class RouteMapper : IServiceRouteMapper
{
public void RegisterRoutes(IMapRoute mapRouteManager)
{
mapRouteManager.MapHttpRoute("MyProject",
"default",
"{controller}/{action}",
new[] { "MyCompany.MyProject.Services" });
}
}
At what URL can I read the data with $.ajax() and what is the URL showing me the data in a browser?
Thanx in Advance!
Asle :)
This is how I do it (Note: this will only work with DNN6.2 and above);
In the View.ascx.cs add
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
ServicesFramework.Instance.RequestAjaxScriptSupport();
ServicesFramework.Instance.RequestAjaxAntiForgerySupport();
jQuery.RequestDnnPluginsRegistration();
}
This ensures that jquery and the required DNN ajax plugins are added.
Initiate the services framework jquery plugin in the View.ascx like this inside javascript script tags (S.O. wouldn't allow me to include them)
var modId = <%=ModuleId %>;
var sf = $.ServicesFramework(modId);
Now in a separate javascript file or in the view.ascx control add the ajax function
function getAllItems(){
$.ajax({
type:"GET",
url:sf.getServiceRoot("MyProject")+"Service/GetAllItems",
beforeSend:sf.setModuleHeaders,
data:{moduleId:modId},
cache:false
}).done(function(data){
alert("Success!");
}).fail(function(){
alert("Crashed!");
}).always(function(){
//something you want done whether passed or failed
//like hide progress bar, ajax spinner etc.
});
}
The DNN jquery plugin will build the url which will look similar to this (Note: 142 is just for illustration purpose and will be replace with actual module id)
/DesktopModules/MyProject/API/Service/GetAllItems?moduleId=142
The URL will be something like
/desktopmodules/SlidePresentation/API/SlidePresetnation.ashx/ListOfSlides
I have examples at
https://slidepresentation.codeplex.com/SourceControl/latest
but they were for DNN6, they might require a few updates due to the API changes for DNN 7
you can see a DNN7 module that has a service layer at https://dnnsimplearticle.codeplex.com/SourceControl/latest#cs/services/

Adding profile values for auto-generated user

I'm creating a ASP.NET MVC 3.0 website, and have a couple of different database initializations based on whether the site is intended for development, testing, or production. I'm stuck on the testing initialization, as I'm trying to get a test user created. I can get the user to create just fine, however when I try to add some profile values, I get: System.Web.HttpException: Request is not available in this context. Is there a way to add Profile values in a situation where the request isn't going to be available?
Following code is what is being run:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
if (ApplicationServices.GetInitialCatalog() != "tasktracker")
{
Database.SetInitializer(new TaskTrackerDropCreateDatabaseIfModelChanges());
}
else
{
Database.SetInitializer(new TaskTrackerCreateDatabaseIfNotExists());
}
using (var db = new TaskTrackerContext())
{
db.Database.Initialize(false);
}
}
public class TaskTrackerDropCreateDatabaseIfModelChanges : DropCreateDatabaseIfModelChanges<TaskTrackerContext>
{
protected override void Seed(TaskTrackerContext context)
{
// Set up the membership, roles, and profile systems.
ApplicationServices.InstallServices(SqlFeatures.Membership | SqlFeatures.Profile | SqlFeatures.RoleManager);
// Create the default accounts and roles.
if (ApplicationServices.GetInitialCatalog() == "tasktracker_testing")
{
if (Membership.GetUser("testuser", false) == null)
{
Membership.CreateUser("testuser", "password", "testuser#test.com");
MembershipUser user = Membership.GetUser("testuser", false);
user.IsApproved = true;
var profile = ProfileBase.Create("testuser");
profile.SetPropertyValue("FirstName", "test");
profile.SetPropertyValue("LastName", "user");
profile.SetPropertyValue("TimeZone", "US Mountain Standard Time");
profile.Save();
}
}
}
}
Interesting question. Have you looked at using the new Universal Providers? Dunno if you will run into the same httpcontext issue but may be worth a look: http://www.hanselman.com/blog/IntroducingSystemWebProvidersASPNETUniversalProvidersForSessionMembershipRolesAndUserProfileOnSQLCompactAndSQLAzure.aspx
Did you try to do a call of "Initialize()" :
profile.Initialize(username, true)
after your create action to see if the context should be Initialized.
By using Reflector i saw the ProfileBase of Initialize (see below) creates this kind of context from the settings:
public void Initialize(string username, bool isAuthenticated)
{
if (username != null)
{
this._UserName = username.Trim();
}
else
{
this._UserName = username;
}
SettingsContext context = new SettingsContext();
context.Add("UserName", this._UserName);
context.Add("IsAuthenticated", isAuthenticated);
this._IsAuthenticated = isAuthenticated;
base.Initialize(context, s_Properties, ProfileManager.Providers);
}
It seems working here, the SettingsContext() seems taking account of my custom properties declared in the web.config.
Regards,
I come back again because the solution I added with the "Initialize()" function in fact not run really after an other test. So in fact I found a way which runs correctly.
The problem of "request is not available in this context" in application_start in your case could be due to the application mode "Integrated" which is new from II7 instead of the Classic mode.
To see a good explain you ca go on the Mike Volodarsky's blog IIS7 Integrated mode: Request is not available in this context exception in Application_Start .
I copy/paste an extract which could indicate the main reason:
" *This error is due to a design change in the IIS7 Integrated pipeline that makes the request context unavailable in Application_Start event. When using the Classic mode (the only mode when running on previous versions of IIS), the request context used to be available, even though the Application_Start event has always been intended as a global and request-agnostic event in the application lifetime. Despite this, because ASP.NET applications were always started by the first request to the app, it used to be possible to get to the request context through the static HttpContext.Current field.* "
To solve this you can use a workaround that moves your first-request initialization from Application_Start to BeginRequest and performs the request-specific initialization on the first request.
A good example of code is done in his blog :
void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
HttpContext context = app.Context;
// Attempt to peform first request initialization
FirstRequestInitialization.Initialize(context);
}
class FirstRequestInitialization
{
private static bool s_InitializedAlready = false;
private static Object s_lock = new Object();
// Initialize only on the first request
public static void Initialize(HttpContext context)
{
if (s_InitializedAlready)
{
return;
}
lock (s_lock)
{
if (s_InitializedAlready)
{
return;
}
// Perform first-request initialization here
//
// You can use your create profile code here....
//---
s_InitializedAlready = true;
}
}
}

Execute multiple webrequests in WP7?

I have a list of addresses that i want to visit using httpWebRequest.
All i need is the statuscode returned by the server.
I have tried to foreach through them and begin a httpWebRequest on each of them, but then i only receive the callback from the last one.
It seems like only one webrequest is allowed at a time.
I'm having quite a hard time understanding how to do this without the GetResponse, which is not allowed in silverlight.
The code is running in a backgroundworker.
And i am using Mango - WP7.1
How do i solve that?
foreach (var current in Addresses)
{
var request = HttpWebRequest.Create(current);
request.BeginGetResponse(r =>
{
try
{
var response = (HttpWebResponse)request.EndGetResponse(r);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
//BOOM RECEIVED
});
}
catch (Exception)
{
Debug.WriteLine("Error in EndGetResponse");
}
}, null);
}
Thanks in advance =)
Your problem of a single response is most likely being caused by your use of anonymous methods and the the way scoping works when you put these inside loops. You are throwing away the earlier request references on each step through the loop.
See my blogpost on the topic here http://csainty.blogspot.com/2010/10/windows-phone-7asynchronous-programming.html
The simplest way to illustrate this is to rewrite your code with full methods, this forces you to consider the scope instead of just blindly referening external variables in your delegates.
foreach (var current in Addresses)
{
var request = HttpWebRequest.Create(current);
request.BeginGetResponse(EndGetResponse, new RequestState { Request = request, Address = current });
}
private void EndGetResponse(IAsyncResult result) {
try {
var state = (RequestState)result.AsyncState;
var response = (HttpWebResponse)state.Request.EndGetResponse(result);
Deployment.Current.Dispatcher.BeginInvoke(GotResponse, state.Address, response.StatusCode);
} catch (Exception) {
Debug.WriteLine("Error in EndGetResponse");
}
}
private void GotResponse(Address address, HttpStatusCode code) {
//BOOM RECEIVED
}
public class RequestState {
HttpWebRequest Request { get; set; }
Address Address { get; set; }
}
Once you solve the scoping issues you can rewrite back into anonymos methods for stylistic reasons if you like.
This will only solve your first problem of getting all the responses back however, I assume you also need to run some code when all the requests are complete to check the status of the whole batch?
That is a different problem altogether.
You can not use WaitOne() or anything like that, it will lock your thread and stop the requests from actually running at all. You will probably want to call off to another method in you BOOM code that stores away the result and checks if all the results are in yet.

Resources