Restful service consuming WCF self hosted service - wcf

I am currently working with an application that is using a restful service. There is another application that has a self hosted WCF service running. I would like to consume the self hosted service from the restful service but I'm running into an issue. I am getting a (405) Method Not Allowed.
Here is how the self hosted service is created and hosted
ServiceHost host = new ServiceHost(typeof(LiveService));
host.Open();
Here is how I am trying to consume the function in the restful service
BinaryMessageEncodingBindingElement binaryMessageEncoding = new BinaryMessageEncodingBindingElement();
HttpTransportBindingElement httpTransport = new HttpTransportBindingElement() { MaxBufferSize = int.MaxValue, MaxReceivedMessageSize = int.MaxValue };
CustomBinding ServiceCustomBinding = new CustomBinding(binaryMessageEncoding, httpTransport);
EndpointAddress ServiceEndpointAddress = new EndpointAddress(string.Format("http://{0}/LiveService", host));
LiveWebServiceClient client = new LiveWebServiceClient(ServiceCustomBinding, ServiceEndpointAddress);
Here is an example of the service
[ServiceContract]
public interface ILiveService
{
[OperationContract]
string Hello();
}
public string Hello()
{
return "Hello";
}
I did some research and I'm guessing its because I'm calling from a restful service. I have tried using [WebGet()] and [WebInvoke(Method="GET")] but it didnt seem to make a difference. Not sure what I am missing.

I have tried to simulate your scenario (from whatever I could comprehend from the description) and it worked fine -
Self hosted service code
namespace SelfHostedService
{
[ServiceContract]
internal interface ILiveService
{
[OperationContract]
string Hello();
}
public class LiveService:ILiveService
{
public string Hello()
{
return "Hello";
}
}
}
static void Main(string[] args)
{
var binaryMessageEncoding = new TextMessageEncodingBindingElement();
var httpTransport = new HttpTransportBindingElement() { MaxBufferSize = int.MaxValue, MaxReceivedMessageSize = int.MaxValue };
var ServiceCustomBinding = new CustomBinding(binaryMessageEncoding, httpTransport);
ServiceHost host = new ServiceHost(typeof(LiveService), new Uri("http://localhost:3239/LiveService"));
host.AddServiceEndpoint(typeof (ILiveService), ServiceCustomBinding, "");
var smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
host.Description.Behaviors.Add(smb);
host.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
host.Open();
Console.ReadLine();
}
Restful Service call to the self hosted service after adding the reference to the self hosted service -
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebInvoke(Method = "GET", RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped)]
}
public string ReturnFromSelfHostService()
{
var binaryMessageEncoding = new TextMessageEncodingBindingElement();
var httpTransport = new HttpTransportBindingElement() { MaxBufferSize = int.MaxValue, MaxReceivedMessageSize = int.MaxValue };
var ServiceCustomBinding = new CustomBinding(binaryMessageEncoding, httpTransport);
var ServiceEndpointAddress = new EndpointAddress(string.Format("http://{0}/LiveService", "localhost:3239"));
var client = new LiveServiceClient(ServiceCustomBinding, ServiceEndpointAddress);
return client.Hello();
}
string ReturnFromSelfHostService();
}
It returns me
<ReturnFromSelfHostServiceResponse xmlns="http://tempuri.org/">
<ReturnFromSelfHostServiceResult>Hello</ReturnFromSelfHostServiceResult>
</ReturnFromSelfHostServiceResponse>

Related

Provide exceptions for wcf webhttpbinding

I have to change a binding for wcf webservices from tcpbinding to webhttpbinding with basic authentication and ssl.
Webservices are self hosted in a console application and in a windows service for production version. Some of local services are with named pipe binding, just if a service call another service.
All works perfectly but not the global error manager (a class that implement IErrorHandler interface)
Some of DAL or business methods throw an exception with a custom message and this message was correctly provide to client (unit test for a while). But since I change binding, exceptions caught in unit test are always a 500 error, internal server error and custom messages are not in exception object.
Server code :
// Création de l'URI
var baseAddress = new Uri($"https://localhost/blablabla/{typeof(TBusiness).Name}");
// Création du Host avec le type de la classe Business
var host = new ServiceHost(typeof(TBusiness), baseAddress);
// Liaison WebHttpBinding sécurité transport
var binding = new WebHttpBinding
{
MaxBufferSize = 2147483647,
MaxReceivedMessageSize = 2147483647,
Security = new WebHttpSecurity
{
Mode = WebHttpSecurityMode.Transport
},
};
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
// Permet de renvoyer du xml et du json
var webBehavior = new WebHttpBehavior
{
AutomaticFormatSelectionEnabled = true
};
var ep = host.AddServiceEndpoint(typeof(TContracts), binding, "");
ep.Behaviors.Add(webBehavior);
var sdb = host.Description.Behaviors.Find<ServiceDebugBehavior>();
sdb.HttpHelpPageEnabled = false;
// Activation https
var smb = new ServiceMetadataBehavior
{
HttpGetEnabled = false,
HttpsGetEnabled = true,
};
host.Description.Behaviors.Add(smb);
// Ajout de l'authentification
var customAuthenticationBehavior = new ServiceCredentials();
customAuthenticationBehavior.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
customAuthenticationBehavior.UserNameAuthentication.CustomUserNamePasswordValidator = new SessionAuthentication();
host.Description.Behaviors.Add(customAuthenticationBehavior);
// Démarrage du host
host.Open();
Business method that throw exception :
public TOUser GetUserByLogin(string login)
{
using (var service = new ServiceProviderNamedPipe<IBFSessionManager, BSSessionManager>())
{
// Récupération de la DALUsers
var dal = service.Channel.GetDALUsers(OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name);
var user = dal.GetUserByLogin(login);
if (user == null) throw new FaultException(Errors.DALUsers_Err001);
return BMToolsEntitiesToTO.UserToTOUser(user);
}
}
Error global manager :
public class GlobalErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
// Empèche la propagation de l'erreur
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
var msg = error.Message;
// Création de l'exception de retour
var newEx = new FaultException(msg);
var msgFault = newEx.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, newEx.Action);
}
}
Unit test :
public void GetUserByLoginWithUnknownLoginTest()
{
TOUser user = null;
using (var service = new ServiceProviderHTTP<IBFUsers, BSUsers>(_user))
{
try
{
user = service.Channel.GetUserByLogin("1234");
}
catch (Exception e)
{
// e.message always provide "Internal server error instead of custom message (Errors.DALUsers_Err001)
Assert.AreEqual(Errors.DALUsers_Err001, e.Message);
}
Assert.IsNull(user);
}
}
All unit tests that catch exception failed since I change binding.
Thank you for your help.
I doubt whether your service is running correctly. Do you bind the certificate to the default port 443 due to transport layer security (using HTTPS)? Please use the below statement to bind a certificate to the 443 port.
netsh http add sslcert ipport=0.0.0.0:443 certhash=c20ed305ea705cc4e36b317af6ce35dc03cfb83d appid={c9670020-5288-47ea-70b3-5a13da258012}
please refer to this link.
https://learn.microsoft.com/en-us/windows/win32/http/add-sslcert
Here is a relevant discussion.
How to disable credentials input for HTTPS call to my WCF hosted in windows service
Besides, I didn’t see you apply the GlobalErrorHandler to the self-hosted service. This is usually implemented by service endpoint behavior.
ServiceEndpoint se = sh.AddServiceEndpoint(typeof(IService),new WebHttpBinding(), "");
MyEndpointBehavior bhv = new MyEndpointBehavior();
se.EndpointBehaviors.Add(bhv);
I wrote an example, wish it is useful to you.
class Program
{
static void Main(string[] args)
{
//I have already bound a certificate to the 21011 port.
var baseAddress = new Uri($"https://localhost:21011");
var host = new ServiceHost(typeof(MyService), baseAddress);
var binding = new WebHttpBinding
{
MaxBufferSize = 2147483647,
MaxReceivedMessageSize = 2147483647,
Security = new WebHttpSecurity
{
Mode = WebHttpSecurityMode.Transport
},
};
//basic authentication use windows login account located on the server-side instead of the below configuration(UserNamePasswordValidationMode.Custom)
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
// Permet de renvoyer du xml et du json
var webBehavior = new WebHttpBehavior
{
AutomaticFormatSelectionEnabled=true
};
var ep = host.AddServiceEndpoint(typeof(IService), binding, "");
ep.Behaviors.Add(webBehavior);
MyEndpointBehavior bhv = new MyEndpointBehavior();
ep.EndpointBehaviors.Add(bhv);
var sdb = host.Description.Behaviors.Find<ServiceDebugBehavior>();
sdb.HttpHelpPageEnabled = false;
// Activation https
var smb = new ServiceMetadataBehavior
{
HttpGetEnabled = true,
HttpsGetEnabled = true,
};
host.Description.Behaviors.Add(smb);
// Ajout de l'authentification
//var customAuthenticationBehavior = new ServiceCredentials();
//customAuthenticationBehavior.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
//customAuthenticationBehavior.UserNameAuthentication.CustomUserNamePasswordValidator = new SessionAuthentication();
//host.Description.Behaviors.Add(customAuthenticationBehavior);
// Démarrage du host
host.Open();
Console.WriteLine("service is running....");
Console.ReadLine();
Console.WriteLine("Closing.....");
host.Close();
}
}
[ServiceContract(ConfigurationName = "isv")]
public interface IService
{
[OperationContract]
[WebGet]
string Delete(int value);
}
[ServiceBehavior(ConfigurationName = "sv")]
public class MyService : IService
{
public string Delete(int value)
{
if (value <= 0)
{
throw new ArgumentException("Parameter should be greater than 0");
}
return "Hello";
}
}
public class MyError
{
public string Details { get; set; }
public string Error { get; set; }
}
public class MyCustomErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
MyError myerror = new MyError()
{
Details = error.Message,
Error = "An error occured"
};
fault = Message.CreateMessage(version, "messsagefault", myerror);
}
}
public class MyEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
return;
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
MyCustomErrorHandler myCustomErrorHandler = new MyCustomErrorHandler();
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(myCustomErrorHandler);
}
public void Validate(ServiceEndpoint endpoint)
{
return;
}
}
Result.
Feel free to let me know if there is anything I can help with.
After several search, I saw that a lot of people have same problem.
Here is my solution :
On server side, always throw a WebFaultException like this with correct HTTP Status code :
throw new WebFaultException<string>(myStringMessage, HttpStatusCode.NotFound);
On client side (only for unit tests or MVC project), cast exception to call GetResponseStream on Response object to get custom message :
var err = (WebException)e;
using (Stream respStream = err.Response.GetResponseStream())
{
using (var reader = new StreamReader(respStream))
{
var serializer = new XmlSerializer(typeof(string));
var response = reader.ReadToEnd();
return response.Substring(response.IndexOf('>') + 1).Replace("</string>", "");
}
}
In ProvideFault method from IErrorHandler, I just add code to write errors in a file but not create a message with Message.CreateMessage method.
It works correctly but generate an EndPointNotFoundException after ProvideFault, in some other posts I saw that a ProtocolException could be thrown.
Thank you for your remarks.

How to read WCF message headers in duplex callback?

In a normal WCF request/reply contract, you can read the message headers using something like:
OperationContract.Current.IncomingMessageHeaders
What I can't figure out is how to do this on the callback side of a duplex contract. Inside the callback implementation OperationContext.Current is null.
Edit 4/5/2013:
I'm using a custom binding based on net.tcp, but with a lot of customizations. For example, using protocol buffers message encoding rather than Xml. Also there is some custom security.
What binding are you using? In the SSCCE below the context is not null on the callback implementation.
public class StackOverflow_15769719
{
[ServiceContract(CallbackContract = typeof(ICallback))]
public interface ITest
{
[OperationContract]
string Hello(string text);
}
[ServiceContract]
public interface ICallback
{
[OperationContract(IsOneWay = true)]
void OnHello(string text);
}
public class Service : ITest
{
public string Hello(string text)
{
ICallback callback = OperationContext.Current.GetCallbackChannel<ICallback>();
ThreadPool.QueueUserWorkItem(delegate
{
callback.OnHello(text);
});
return text;
}
}
class MyCallback : ICallback
{
AutoResetEvent evt;
public MyCallback(AutoResetEvent evt)
{
this.evt = evt;
}
public void OnHello(string text)
{
Console.WriteLine("[callback] Headers: ");
foreach (var header in OperationContext.Current.IncomingMessageHeaders)
{
Console.WriteLine("[callback] {0}", header);
}
Console.WriteLine("[callback] OnHello({0})", text);
evt.Set();
}
}
public static void Test()
{
bool useTcp = false;
string baseAddress = useTcp ?
"net.tcp://" + Environment.MachineName + ":8000/Service" :
"http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
Binding binding = useTcp ?
(Binding)new NetTcpBinding(SecurityMode.None) :
new WSDualHttpBinding(WSDualHttpSecurityMode.None)
{
ClientBaseAddress = new Uri("http://" + Environment.MachineName + ":8888/Client")
};
host.AddServiceEndpoint(typeof(ITest), binding, "");
host.Open();
Console.WriteLine("Host opened");
AutoResetEvent evt = new AutoResetEvent(false);
MyCallback callback = new MyCallback(evt);
DuplexChannelFactory<ITest> factory = new DuplexChannelFactory<ITest>(
new InstanceContext(callback),
binding,
new EndpointAddress(baseAddress));
ITest proxy = factory.CreateChannel();
Console.WriteLine(proxy.Hello("foo bar"));
evt.WaitOne();
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}

WCF REST Self-Hosted 400 Bad Request

I'm having a problem with a self-host WCF REST service.
When I try to issue a GET via browser or Fiddler, I get a 400 Bad Request. Tracing is reporting an inner exception of XmlException "The body of the message cannot be read because it is empty."
I don't have any configuration in app.config (do I need any?). I have tried changing WebServiceHost to ServiceHost, and WSDL is returned, but the operations still return 400.
What am I missing here?
// Add Reference to System.ServiceModel and System.ServiceModel.Web
using System;
using System.Diagnostics;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Web;
namespace WCFRESTTest
{
class Program
{
static void Main(string[] args)
{
var baseAddress = new Uri("http://localhost:8000/");
var host = new WebServiceHost(typeof(RestService), baseAddress);
try
{
host.AddServiceEndpoint(typeof(IRestService), new WSHttpBinding(), "RestService");
var smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
host.Description.Behaviors.Add(smb);
host.Open();
Console.WriteLine("Service Running. Press any key to stop.");
Console.ReadKey();
}
catch(CommunicationException ce)
{
host.Abort();
throw;
}
}
}
[ServiceContract]
public interface IRestService
{
[OperationContract]
[WebGet(UriTemplate = "Test")]
bool Test();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class RestService : IRestService
{
public bool Test()
{
Debug.WriteLine("Test Called.");
return true;
}
}
}
When you use the WebServiceHost, you typically don't need to add a service endpoint - it will add one with all behaviors required to make it a "Web HTTP" (a.k.a. REST) endpoint (i.e., an endpoint which doesn't use SOAP and you can easily consume with a tool such as Fiddler, which seems to be what you want). Also, Web HTTP endpoints aren't exposed in the WSDL, so you don't need to add the ServiceMetadataBehavior either.
Now for why it doesn't work - sending a GET request to http://localhost:8000/Test should work - and in the code below it does. Try running this code, and sending the request you were sending before with Fiddler, to see the difference. That should point out what the issue you have.
public class StackOverflow_15705744
{
[ServiceContract]
public interface IRestService
{
[OperationContract]
[WebGet(UriTemplate = "Test")]
bool Test();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class RestService : IRestService
{
public bool Test()
{
Debug.WriteLine("Test Called.");
return true;
}
}
public static void Test()
{
var baseAddress = new Uri("http://localhost:8000/");
var host = new WebServiceHost(typeof(RestService), baseAddress);
// host.AddServiceEndpoint(typeof(IRestService), new WSHttpBinding(), "RestService");
// var smb = new ServiceMetadataBehavior();
// smb.HttpGetEnabled = true;
// host.Description.Behaviors.Add(smb);
host.Open();
WebClient c = new WebClient();
Console.WriteLine(c.DownloadString(baseAddress.ToString().TrimEnd('/') + "/Test"));
Console.WriteLine("Service Running. Press any key to stop.");
Console.ReadKey();
}
}

call wcf service by HttpWebRequest

When I have that service:
[OperationContract]
ResponseMessage GetData(RequestMessage message);
Where
class RequestMessage
{
public string data
}
class ResponseMessage
{
public string data
}
and call this service
string data2 = ""
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost/Service.svc/GetData");
request.ContentType = "application/json";
request.Method = "POST";
request.KeepAlive = true;
using (Stream requestStream = request.GetRequestStream())
{
var bytes = Encoding.UTF8.GetBytes(data2);
requestStream.Write(bytes, 0, bytes.Length);
requestStream.Close();
}
var response = (HttpWebResponse)request.GetResponse();
var abc = new StreamReader(response.GetResponseStream()).ReadToEnd();
as data2 should I send string "mydata" or should I wrap it in json format : {"message": {"data":"mydata"}}
??
I have problem with understand how should be send data on client side by post to get it properly on service side :/
You didn't mention how the service is defined. Assuming your endpoint uses webHttpBinding, and an endpoint behavior with <webHttp/> with default values, then the default value for the body style is "Bare", which means that the request should contain only the serialized version of the parameter. For this case, you can send the string {"data":"hello world"}.
If you want a quick way to find what's the expected format for a WCF service, you can use a WCF client, using the same contract / binding / behaviors, and send a message to the server (and capture it on fiddler). For example, the code below shows a server similar to yours, and a client which sends a request to it.
public class StackOverflow_7492678
{
public class RequestMessage
{
public string data;
}
public class ResponseMessage
{
public string data;
}
[ServiceContract]
public interface ITest
{
[OperationContract]
ResponseMessage GetData(RequestMessage message);
}
public class Service : ITest
{
public ResponseMessage GetData(RequestMessage message)
{
return new ResponseMessage { data = message.data };
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
var endpoint = host.AddServiceEndpoint(typeof(ITest), new WebHttpBinding(), "");
endpoint.Behaviors.Add(new WebHttpBehavior());
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new WebHttpBinding(), new EndpointAddress(baseAddress));
factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
ITest proxy = factory.CreateChannel();
Console.WriteLine(proxy.GetData(new RequestMessage { data = "mydata" }).data);
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}

Generic Service Contract

I need to have a generic Service contract but if I do that I receive this error:
[ServiceContract]
public interface IService<T> where T : MyClass
{
[OperationContract]
void DoWork();
}
The contract name 'x.y' could not be found in the list of contracts implemented by the service 'z.t'.
As long as you use a closed generic for your interface it does work - see below. What you cannot do is to have an open generic as the contract type.
public class StackOverflow_6216858_751090
{
public class MyClass { }
[ServiceContract]
public interface ITest<T> where T : MyClass
{
[OperationContract]
string Echo(string text);
}
public class Service : ITest<MyClass>
{
public string Echo(string text)
{
return text;
}
}
static Binding GetBinding()
{
BasicHttpBinding result = new BasicHttpBinding();
//Change binding settings here
return result;
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(ITest<MyClass>), GetBinding(), "");
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest<MyClass>> factory = new ChannelFactory<ITest<MyClass>>(GetBinding(), new EndpointAddress(baseAddress));
ITest<MyClass> proxy = factory.CreateChannel();
Console.WriteLine(proxy.Echo("Hello"));
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
Your service contract is not interoperable. It's not possible to expose generics like that via WSDL.
Take a look at this article (link) for a possible workaround.
If you use an servicereference on thee client side generic will fail.
Use the following on client side with generic:
var myBinding = new BasicHttpBinding();
var myEndpoint = new EndpointAddress("");
var myChannelFactory = new ChannelFactory<IService>(myBinding, myEndpoint);
IService gks = myChannelFactory.CreateChannel();