Reputation: 734
There are numerus question about the same topic here on SO, but none of them seems to give a complete answer. I have checked most of the questions/answers and tested, tested and tested. So hopefully this question will help me and others struggling.
The question.
How do i set up WCF selfhosted REST service that works over https? This is how I have tried setting up the service and clients. It doesn't work! But i feel that im very close with every change, but im not reaching the goal.
So, can someone help me with a complete example that is working with a REST endpoint, selfhosted WCF over HTTPS and a POST request? I have tried puzzling together bits and pieces from everywhere and i cant get it working! Should i give up? Choose another technology?
So, some code:
[ServiceHost]
Uri uri = new Uri("https://localhost:443");
WebHttpBinding binding = new WebHttpBinding(WebHttpSecurityMode.Transport);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
using (ServiceHost sh = new ServiceHost(typeof(Service1), uri))
{
ServiceEndpoint se = sh.AddServiceEndpoint(typeof(IService1), binding, "");
//se.EndpointBehaviors.Add(new WebHttpBehavior());
// Check to see if the service host already has a ServiceMetadataBehavior
ServiceMetadataBehavior smb = sh.Description.Behaviors.Find<ServiceMetadataBehavior>();
// If not, add one
if (smb == null)
smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = false; //**http**
smb.HttpsGetEnabled = true; //**https**
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
sh.Description.Behaviors.Add(smb);
// Add MEX endpoint
sh.AddServiceEndpoint(
ServiceMetadataBehavior.MexContractName,
MetadataExchangeBindings.CreateMexHttpsBinding(), //**https**
"mex"
);
var behaviour = sh.Description.Behaviors.Find<ServiceBehaviorAttribute>();
behaviour.InstanceContextMode = InstanceContextMode.Single;
Console.WriteLine("service is ready....");
sh.Open();
Console.ReadLine();
sh.Close();
}
[IService]
[ServiceContract]
public interface IService1
{
[WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Xml, RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest, UriTemplate = "Datarows_IN/")]
[OperationContract]
bool Save(BatchOfRows batchOfRows);
}
[Service]
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
public class Service1 : IService1
{
public bool Save(BatchOfRows batchOfRows)
{
Console.WriteLine("Entered Save");
return true;
}
}
[BatchOfRows] - simplified
[DataContract]
public class BatchOfRows
{
[DataMember]
public int ID { get; set; } = -1;
[DataMember]
public string Data { get; set; } = "Hej";
}
This is build upon SO answer after SO answer and Microsoft tutorials. I dont even know where what example started and the others ended. I began from this: https://stackoverflow.com/a/57554374/619791 And that worked very well until i tried enabling https, then everything stopped working.
Here are some client code that i have tried.
[WebClient]
string uri = "https://localhost:443/Datarows_IN";
WebClient client = new WebClient();
client.Headers["Content-type"] = "application/json";
client.Encoding = Encoding.UTF8;
var b = new BatchOfRows();
var settings = new JsonSerializerSettings() { DateFormatHandling = DateFormatHandling.MicrosoftDateFormat };
string str2 = "{\"batchOfRows\":" + JsonConvert.SerializeObject(b, settings) + "}";
string result = client.UploadString(uri, "POST", str2);
[HttpClient]
string str2 = "{\"batchOfRows\":" + JsonConvert.SerializeObject(b, settings) + "}";
var contentData = new StringContent(str2, System.Text.Encoding.UTF8, "application/json");
//string result = client.UploadString(uri, "POST", str2);
//HttpResponseMessage response = client.PostAsJsonAsync("https://localhost:443/Datarows_IN", b).GetAwaiter().GetResult();
HttpResponseMessage response = client.PostAsync("https://localhost:443/Datarows_IN", contentData).GetAwaiter().GetResult();
response.EnsureSuccessStatusCode();
[ChannelFactory]
//var c = new ChannelFactory<IService1>(binding, new EndpointAddress("https://localhost:443/Datarows_IN"));
var c = new ChannelFactory<IService1>(binding, new EndpointAddress("https://localhost:443/"));
((WebHttpBinding)c.Endpoint.Binding).Security.Mode = WebHttpSecurityMode.Transport;
((WebHttpBinding)c.Endpoint.Binding).Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
c.Endpoint.Behaviors.Add(new WebHttpBehavior());
var aw = c.CreateChannel();
var b = new ModuleIntegration.Client.Objects.BatchOfRows();
aw.Save(b);
None of the clients work. If i debug my Service endpoint is never triggered. This is the current error that im getting:
<Fault xmlns="http://schemas.microsoft.com/ws/2005/05/envelope/none">
<Code>
<Value>Sender</Value>
<Subcode>
<Value xmlns:a="http://schemas.microsoft.com/ws/2005/05/addressing/none">a:ActionNotSupported</Value>
</Subcode>
</Code>
<Reason>
<Text xml:lang="sv-SE">The message with Action '' cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver. Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None).</Text>
</Reason>
</Fault>
Please help! Why is this so hard?!?
Upvotes: 1
Views: 1038
Reputation: 7522
Your code snippets seem very cautious, which caused the above error, and namely, we should add the WebHttpBehavior.
However, there is one more thing we should pay attention to.
Ordinarily, the service is required to provide a certificate to encrypt and sign the communication between the server-side and the client-side when we hosting the service over HTTPS in IIS.
Thereby, we should bind a certificate theoretically when we use self-host, or the service should not be work properly.
Why does this service endpoint address work well? The only explanation is that we have bound a certificate to the port somewhere, such as IIS, there is a website with an https binding and use the default port.
If the custom port does not associate with a certificate we should bind a certificate by using the below command.
Netsh http add sslcert ipport=0.0.0.0:portnumber certhash=0000000000003ed9cd0c315bbb6dc1c08da5e6 appid={00112233-4455-6677-8899-AABBCCDDEEFF}
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-configure-a-port-with-an-ssl-certificate
https://learn.microsoft.com/en-us/windows/win32/http/add-sslcert
By default, the certificate could merely be set up when it is stored in the Local Machine instead of the Current User. We could manage the certificate with the below command.
Certlm.msc
Wish you have good luck.
At last, WCF is not aimed at designing the Restful style service. We should consider Asp.net WebAPI.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api
Feel free to let me know if there is anything I can help with.
Upvotes: 0
Reputation: 78568
Your service is missing the WebHttpBehavior
.
Without it, the WebInvoke
attribute does nothing, and the path "Datarows_IN"
is not recognized as an action.
Here's the full (works for me) service host code:
var binding = new WebHttpBinding()
{
Security = {
Mode = WebHttpSecurityMode.Transport
}
};
var baseUri = new Uri("https://localhost:443");
using (ServiceHost sh = new ServiceHost(typeof(Service1), baseUri))
{
var metadata = sh.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (metadata == null) {
metadata = new ServiceMetadataBehavior();
sh.Description.Behaviors.Add(metadata);
}
metadata.HttpsGetEnabled = true;
var endpoint = sh.AddServiceEndpoint(typeof(IService1), binding, "/");
endpoint.EndpointBehaviors.Add(new WebHttpBehavior());
Console.WriteLine("Service is ready....");
sh.Open();
Console.WriteLine("Service started. Press <ENTER> to close.");
Console.ReadLine();
sh.Close();
}
Upvotes: 1