How to handle Ajax JQUERY POST request with WCF self-host - wcf

There are many reasons create a RESTful WCF server (it is easy) and even better if you can avoid ASP and it's security box (if all you are doing is simple requests to return information). See: http://msdn.microsoft.com/en-us/library/ms750530.aspx on how to do this.
What I found is that handling AJAX (JQUERY) GET requests is easy. But dealing with JSON in a POST is tricky.
Here is an example of a simple GET request contract:
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json)]
String Version();
And the implementaion is here (which returns a JSON)
public partial class CatalogService : ICatalogService
{
public String Version()
{
mon.IsActive = true;
this.BypassCrossDomain();
ViewModel.myself.TransactionCount++;
return ViewModel.myself.VersionString;
}
}
Ah, but what if you want to POST some JSON. You will find lots of articles on stack overflow that tell you all you have to do is this:
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
BuildResponse BuildToby(BuildRequest request);
which will receive a JSON message, de-serialize into a Plain .NET object (PONO) and let you work with it. And indeed, this worked fine when I constructed the request in Fiddler.
POST /BuildToby HTTP/1.1
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:4326
Content-Length: 1999
However, when you use the following AJAX in JQUERY 1.8, you will find a SURPRISE:
It by specifying content-type of "application/json" you will find that there is a "preflight" check that is fired off by the browser to see if you are allowed to POST something other than a www-url-encloded post message. (there are notes in stack overflow about this).
var request = JSON.stringify({ FrameList: ExportData.buildList });
var jqxhr = $.ajax({
type: "POST",
url: "http://localhost:4326/BuildToby",
data: request,
contentType: "application/json; charset=utf-8",
dataType: "json"
});
and here is what fiddler reports: (Note it is not a POST message, but a OPTIONS message).
OPTIONS http://localhost:4326/BuildToby HTTP/1.1
Host: localhost:4326
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:19.0) Gecko/20100101 Firefox/19.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: http://ysg4206
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
What has happened is that a browser (in this case Firefox) has to make a extra call to the server, with a OPTIONS HTTP message, to see if a POST is allowed (of this content type).
All the articles about fixing this are about editing GLOBAL.ASAX which is fine if you are in ASP.NET but are useless if you are doing a self-host WCF.
So now you see the question (sorry for being so long winded, but I wanted to make this a complete article so that others can follow the results).

Ok, now there are some real MSDN gurus out there who have written solutions, but I cannot figure them out: http://blogs.msdn.com/b/carlosfigueira/archive/2012/05/15/implementing-cors-support-in-wcf.aspx
But I have come up with a simple solution. At least in WCF 4.5 you can add your own OperationContract for dealing with OPTIONS requests:
[OperationContract]
[WebInvoke(Method = "OPTIONS", UriTemplate = "*")]
void GetOptions();
Note that the method signature is void, and has no arguments. This will get called first, and then the POST message will be called.
The implementation of GetOptions is:
public partial class CatalogService : ICatalogService
{
public void GetOptions()
{
mon.IsActive = true;
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
}
}
and that is really all you have to do.
You might also want to add this attribute to your service class, so that you can serialize large JSON:
//This defines the base behavior of the CatalogService. All other files are partial classes that extend the service
[ServiceBehavior(MaxItemsInObjectGraph = 2147483647)] // Allows serialization of very large json structures
public partial class CatalogService : ICatalogService
{
PgSqlMonitor mon = new PgSqlMonitor();
private void BypassCrossDomain()
{
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
}
}
Note I have a little helper method called BypassCrossDomain() which I call on all my POST and GET methods, so that I can deal with cross domain calls.
I spent a lot of research time here (in MSDN forums, stack overflow, blogs) and I hope this will help others trying to do these sorts of projects.

One other addition to Dr.YSG's answer, if you need to support the OPTIONS method on endpoints which take POSTS to individual IDs, you will have to implement multiple GetOptions methods:
[WebInvoke(Method = "OPTIONS", UriTemplate = "")]
void GetOptions();
[WebInvoke(Method = "OPTIONS", UriTemplate = "{id}")]
void GetOptions(string id);
Its really disappointing that WCF/Microsoft can't automatically generation the proper OPTIONS response based on the signature of the endpoint automatically, but at least it can be handled manually.

In addition to what Dr.YSG listed as his answer, I found that I was getting a Firefox notice that a redirect was occurring, and a "405 Method Not Allowed" error. Adding
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Max-Age", "1728000");
to the GetOptions class seems to have addressed the issue.

After many days searching about it and reading many posts and proposed solutions, i think this question by Dr. YSG was the best resource to understand and solve my problem with angular/wcf/post/CORS etc.
But what really worked for me was this:
protected void Application_BeginRequest(object sender, EventArgs e)
{
if (Request.HttpMethod == "OPTIONS")
{
Response.End();
}
}
I understand it is not a full (nor beautiful) solution, since i am using global.asax and there are so many possible scenarios, but i just want to share this alternative, since it may help somebody else eventually.
(Besides adding the headers)

Related

How to disable JSON OK responses (but not errors) in ASP.NET Core 3.1

In a ASP.NET Core 3.1 API I need to be strict with content negotiation. If actions are executed OK, the response will have a application/pdf content-type, but if there are any errors (eg a 400, 406, etc) the content-type will be application/problem+json.
The relevant part of my Startup class is:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.ReturnHttpNotAcceptable = true;
options.RespectBrowserAcceptHeader = true;
options.OutputFormatters.Insert(0, new PdfOutputFormatter());
});
In my controller:
namespace MyApplication
{
[ApiController]
[Consumes("multipart/form-data")]
public abstract class PdfController : ControllerBase
{
The requests with an Accept: application/json and Accept: text/json returns a 200 OK but a 406 Not Accepted is expected.
I tried removing the JSON output formatter with:
options.OutputFormatters.RemoveType(typeof(SystemTextJsonOutputFormatter));
But this makes all errors (for example a 400 Bad Request with a application/problem+json content type) return a 406 Not Accepted as there is no output formatter for JSON responses.
How can I remove the ability to send JSON responses (ie Accept: application/json → 406) without breaking the ability to send application/problem+json responses for errors?

WCF SOAP Service Ignore Content-Type charset

I have a new WCF service that some existing clients need to be able to communicate with.
There are some clients that are incorrectly sending the SOAP request with a Content-Type header of 'text/xml; charset=us-ascii'. At the moment, the clients themselves can't be changed.
When they send a request, they get the error message:
HTTP/1.1 415 Cannot process the message because the content type
'text/xml; charset=us-ascii' was not the expected type 'text/xml;
charset=utf-8'
Is there any way to instruct the WCF service to ignore the charset of the Content-Type and assume utf-8?
This may be helpful for you, if not I will delete the answer. I think this can be used in your context, but am not quite sure since there are no other details.
A WebContentTypeMapper can be used to override the content type being sent. This is snipped of the code used in the WCF Sample sets. In this example it is taking in whatever content type that is sent in and mapping it to json, but you could adjust that to your needs.
If you don't have it already you can download the samples from WCF Samples
This sample is located in ..WF_WCF_Samples\WCF\Extensibility\Ajax\WebContentTypeMapper\CS
using System.ServiceModel.Channels;
namespace Microsoft.Samples.WebContentTypeMapper
{
public class JsonContentTypeMapper : System.ServiceModel.Channels.WebContentTypeMapper
{
public override WebContentFormat GetMessageFormatForContentType(string contentType)
{
if (contentType == "text/javascript")
{
return WebContentFormat.Json;
}
else
{
return WebContentFormat.Default;
}
}
}
}

Accessing API on different domain with CORS

I am working on a self-hosted WCF service with some GET and POST methods,
For Example
[OperationContract]
[WebInvoke(Method = "GET", BodyStyle = WebMessageBodyStyle.Wrapped)]
public string HelloWorld(){
....
}
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped)]
public string GetMessage(string username){
....
}
when accessing those services on the client side which is hosted on different domain, I have got the following error
No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:57805' is therefore not allowed
access. The response had HTTP status code 400.
After researching on the Internet, I figured that the error can be resolved by putting the following codes in each WCF function
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
if (WebOperationContext.Current.IncomingRequest.Method == "OPTIONS")
{
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Header", "Content-Type, Accept, SOAPAction");
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Credentials", "false");
}
Now this block of code is working great for a GET method, so I can see there is "Access-Control-Allow-Origin" added into the response header. However it does not work for a POST method somehow as the response header has no change at all.
Does anyone have an idea why it is not working for a POST method?
I have changed the GetMessage to be a GET method, the response header worked immediately but not when I changed it back to POST
The problem is resolved!!
The code works great for both GET and POST, however the web service throw the following error for my POST function, therefore it causes a failure.
The OperationFormatter could not deserialize any information from the
Message because the Message is empty (IsEmpty = true).
By changing the BodyStyle for the function to WrappedResponse, the error gone and the function can be accessed outside the domain
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedResponse)]
Booking CreateNewBooking(int divisionId);

WCF - posting JSON to an endpoint - what should the request content body look like?

I've just spent a couple of hours debugging and looking through questions like POSTing JSON to WCF REST Endpoint and Generic WCF JSON Deserialization, but currently I think my code and/or debugging is failing at quite a basic level...
I've set up a WCF service like:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, IncludeExceptionDetailInFaults = true)]
public class AutomationService : IAutomationService
{
[WebInvoke(Method = "POST", UriTemplate = "getNextCommand")]
public CommandBase GetNextCommand(int timeoutInMilliseconds)
{
// stuff
}
}
where IAutomationService is:
[ServiceContract]
public interface IAutomationService
{
[OperationContract]
[ServiceKnownType("GetKnownTypes", typeof(KnownTypeProvider))]
CommandBase GetNextCommand(int timeoutInMilliseconds);
}
and I've now got this service successfully setup with SOAP and JSON endpoints.
However... I can't seem to work out how to call the service using variables passed in the ContentBody from Fiddler.
For example, I can call the service with a POST on the Uri - e.g.
POST http://localhost:8085/phoneAutomation/jsonAutomate/getNextCommand?timeoutInMilliseconds=10000
However, if I try to put the content in the body, then I get an exception. e.g.
POST http://localhost:8085/phoneAutomation/jsonAutomate/getNextCommand
Host: localhost:8085
Connection: keep-alive
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1
Accept: application/json,text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: Orchrd-=%7B%22Exp-N42-Settings%22%3A%22open%22%2C%22Exp-N42-New%22%3A%22open%22%7D
Content-Length: 31
Content-Type: application/json
{"timeoutInMilliseconds":10000}
fails with:
The server encountered an error processing the request. The exception
message is 'There was an error deserializing the object of type
System.Int32. The value '' cannot be parsed as the type 'Int32'.'. See
server logs for more details. The exception stack trace is:
at
System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator
reader, Boolean verifyObjectName, DataContractResolver
dataContractResolver) at
System.Runtime.Serialization.Json.DataContractJsonSerializer.ReadObject(XmlDictionaryReader
reader, Boolean verifyObjectName) at
...
Anyone got any ideas what I'm doing wrong (other than using WCF!) - I'm just not sure what shape the JSON {"timeoutInMilliseconds":10000} is supposed to be.
By default the "body style" of a WCF REST service is "Bare", meaning that for operations with a single input, the value of the operation should go "as is" without any object wrapping it. That means in your case that this will work:
POST http://localhost:8085/phoneAutomation/jsonAutomate/getNextCommand
Host: localhost:8085
Connection: keep-alive
Cache-Control: max-age=0
...
Content-Length: 5
Content-Type: application/json
10000
One more thing, not directly related to your question: if you define a service contract in the interface, you should also add any "contract-related" attributes (such as WebInvoke) in the interface as well. That would make your code look like this:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, IncludeExceptionDetailInFaults = true)]
public class AutomationService : IAutomationService
{
public CommandBase GetNextCommand(int timeoutInMilliseconds)
{
// stuff
}
}
[ServiceContract]
public interface IAutomationService
{
[OperationContract]
[ServiceKnownType("GetKnownTypes", typeof(KnownTypeProvider))]
[WebInvoke(Method = "POST", UriTemplate = "getNextCommand")]
CommandBase GetNextCommand(int timeoutInMilliseconds);
}
And another info: if you wanted to send the request the same way as you had originally ({"tiemoutInMilliseconds":10000}), you can set the BodyStyle property in the [WebInvoke] attribute to Wrapped (or WrappedRequest):
[ServiceContract]
public interface IAutomationService
{
[OperationContract]
[ServiceKnownType("GetKnownTypes", typeof(KnownTypeProvider))]
[WebInvoke(Method = "POST",
UriTemplate = "getNextCommand",
BodyStyle = WebMessageBodyStyle.WrappedRequest)]
CommandBase GetNextCommand(int timeoutInMilliseconds);
}

How do I construct a request for a WCF http post call?

I have a really simple service that I'm messing about with defined by:
[OperationContract]
[WebInvoke(UriTemplate = "Review/{val}", RequestFormat = WebMessageFormat.Xml, Method = "POST", BodyStyle=WebMessageBodyStyle.Bare)]
void SubmitReview(string val, UserReview review);
UserReview is, at the moment, a class with no properties. All very basic. When I try and test this in Fiddler I get a bad request status (400) message.
I'm trying to call the service using the details:
POST http://127.0.0.1:85/Service.svc/Review/hello
Headers
User-Agent: Fiddler
Content-Type: application/xml
Host: 127.0.0.1:85
Content-Length: 25
Body
<UserReview></UserReview>
I would think i'm missing something fairly obvious. Any pointers?
By adding the XmlSerializerFormatAttribute to the method caused this to start working as expected
[OperationContract]
[XmlSerializerFormat]
[WebInvoke(UriTemplate = "Review/{val}", RequestFormat = WebMessageFormat.Xml, Method = "POST", BodyStyle=WebMessageBodyStyle.Bare)]
void SubmitReview(string val, UserReview review);