I successfully created a self-hosted WCF mono-service on raspberry jessie, and it works (almost).
Versions
The problem is that Chrome web apps can't consume it, since it doesn't respond to any OPTIONS request. The request stays in 'pending' state until I kill the service.
I really tried a lot of solutions related to CORS, but i think the problem might be a bit deeper. I think that way, because a OPTIONS request does not reach the CorsDispatchMessageInspector (3rd code fragment below).
The setup I currently have.
app.config
<startup>
<supportedRuntime version="v4.5" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<services>
<service name="AspaDeviceControlCenter.Fp550Module.Fp550Module">
<endpoint address="http://localhost:8111/json/fp550/" binding="webHttpBinding" behaviorConfiguration="jsonEndpointBehaviour" contract="AspaDeviceControlCenter.Fp550Module.IFp550Module"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="jsonEndpointBehaviour">
<webHttp/>
<corsEndpointBehaviorExtension/>
</behavior>
<behavior name="webscriptBehavior">
<enableWebScript/>
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<directoryBrowse enabled="true"/>
</system.webServer>
<extensions>
<behaviorExtensions>
<add name="corsEndpointBehaviorExtension" type="AspaDeviceControlCenter.Service.Hosting.CorsEndpointBehaviorExtension, AspaDeviceControlCenter.Service"/>
</behaviorExtensions>
</extensions>
I used EndpoinBehaviorExtension solution from enable-cors.org to add required 'alow' headers to any OPTIONS request, but a breakpoint there does not fire for the OPTIONS requests, only for GET and POST
The extension actual part
public class CorsEndpointBehaviorExtension : BehaviorExtensionElement, IEndpointBehavior
{
/*some empty methods*/
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
var requiredHeaders = new Dictionary<string, string>();
//requiredHeaders.Add("Access-Control-Allow-Origin", "*");
requiredHeaders.Add("Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS");
requiredHeaders.Add("Access-Control-Allow-Headers", "Accept,Origin,Authorization,X-Requested-With,Content-Type,X-Tenant");
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new CorsDispatchMessageInspector(requiredHeaders));
}
Now the part that should help and where i think that the problem there is a deeper one, maybe a OS configuration one, but I'm not a Linux expert.
public class CorsDispatchMessageInspector: IDispatchMessageInspector
{
Dictionary<string, string> requiredHeaders;
public CorsDispatchMessageInspector(Dictionary<string, string> headers)
{
requiredHeaders = headers ?? new Dictionary<string, string>();
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
return null;
}
public void BeforeSendReply(ref Message reply,object correlationState)
{
var httpHeader = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
foreach (var item in requiredHeaders)
{
httpHeader.Headers.Add(item.Key, item.Value);
}
}
}
Both AfterReceiveRequest and BeforeSendReply has breakpoints, and they break when, I send a POST / GET, but not when the request method is OPTIONS
The service contract interface. I have lots of different operation contracts defined to test what I have found on the internet so far. None of those responded to OPTIONS request.
[ServiceContract]
public interface IFp550Module : IControlCenterModule
{
[OperationContract]
bool Init();
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped)]
string Open(/*int port, string settings*/);
[OperationContract]
[WebInvoke(Method = "*", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped)]
string Version();
[OperationContract]
[WebInvoke(Method = "OPTIONS", UriTemplate="*", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped)]
bool GetOptions();
}
No error callback breaks during runtime.
var host = new ServiceHost(module);
_serviceHosts.Add(host);
host.Faulted += OnFaulted;
host.UnknownMessageReceived += OnUnknownMessageReceived;
host.Opening += module.ServiceStarting;
host.Closing += module.ServiceClosing;
host.Open();
I also tried my own CustomTextMessageEncoder : MessageEncoder, and public override Message ReadMessage method breakpoint was hit only for GET/POST. How deep I must go hit that OPTIONS request breakpoint? Or maybe is it just some flag somewhere in mono-service.
Also when I stop my service all pending options requests returns network error.
And when I start the request and then send a any request (including OPTIONS) there is a "Thread started: #16" message in the application output, so it seems that the OPTIONS request reaches 'the application level'.
The next step deeper would be my own ServiceHost class and channel listener, but that is a ton of work and it could be that the problem is not even in application level anyway.
Let me know if I miss something. I use this question as my last resort.
OK, I tired searching for 'mono custom request methods', and I found that it is due to bug documented in mono-bug-tracker. I'll update this answer if I find a workaround.
I'm looking for a way how to re-implement the bugged part.
EDIT: I fixed my problem. I failed to recompile the entire mono with its dependencies, so I only recompiled the System.ServiceModel.dll and replaced the original with the new one in the mono GAC directory, System.ServiceModel folder. New dll was loaded and changes were accepted.
The change was in HttpReplyChannel.cs
if (ctxi.Request.HttpMethod == "POST" || ctxi.Request.HttpMethod == "PUT" )
msg = CreatePostMessage (ctxi);
else if (ctxi.Request.HttpMethod == "GET" || ctxi.Request.HttpMethod == "DELETE" || ctxi.Request.HttpMethod == "OPTIONS")
msg = Message.CreateMessage (MessageVersion.None, null);
I added additional methods, before the change, there was only GET and POST.
Now my service was able to receive options requests and add required headers to it with MessageInspector.