We have custom XML serialization for our "protocol" here:
[XmlRoot("axf", Namespace = Axf10Namespace)]
public class AxfDocument : IXmlSerializable
{
public const string Axf10Namespace = "http://schemas.***.ru/axf/axf-1.0.0";
// ...
}
and all's fine when using standard .NET XmlSerializer:
<?xml version="1.0" encoding="utf-16"?>
<axf version="1.0.0" createdAt="2011-10-20T13:11:40" xmlns="http://schemas.***.ru/axf/axf-1.0.0">
<itineraries>
<!-- -->
</itineraries>
</axf>
Now that we try to use this class in a bare-bones WCF service:
[OperationContract]
AxfDocument GetItineraries(ItinerariesQuery query);
actual XML document that gets sent to client side is this:
<GetItinerariesResult version="1.0.0" createdAt="2011-10-20T13:17:50" xmlns="http://tempuri.org/">
<itineraries xmlns="http://schemas.***.ru/axf/axf-1.0.0">
<!-- rest is fine, serialization code does work -->
How do I bend WCF to send root element as-is and not to replace it with its own?
By default, the operation responses are wrapped in the operation name. You can, however, use a MessageContract in the operation definition to use an "unwrapped" response, as shown below. If you look at the response body of the request in Fiddler, you'll see that it's exactly as the one from the serialization.
public class StackOverflow_7836645
{
[XmlRoot("axf", Namespace = Axf10Namespace)]
public class AxfDocument : IXmlSerializable
{
public const string Axf10Namespace = "http://schemas.something.ru/axf/axf-1.0.0";
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("intineraries", Axf10Namespace);
writer.WriteElementString("item", Axf10Namespace, "one value");
writer.WriteElementString("item", Axf10Namespace, "another value");
writer.WriteEndElement();
}
}
[MessageContract(IsWrapped = false)]
public class OperationResponse
{
[MessageBodyMember(Name = "axf", Namespace = AxfDocument.Axf10Namespace)]
public AxfDocument axf;
}
[ServiceContract]
public interface ITest
{
[OperationContract]
OperationResponse GetAxf();
}
public class Service : ITest
{
public OperationResponse GetAxf()
{
return new OperationResponse { axf = new AxfDocument() };
}
}
public static void Test()
{
Console.WriteLine("Serialization");
MemoryStream ms = new MemoryStream();
XmlSerializer xs = new XmlSerializer(typeof(AxfDocument));
xs.Serialize(ms, new AxfDocument());
Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray()));
Console.WriteLine();
Console.WriteLine("Service");
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "");
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new BasicHttpBinding(), new EndpointAddress(baseAddress));
ITest proxy = factory.CreateChannel();
Console.WriteLine(proxy.GetAxf());
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
Related
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();
}
}
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();
}
}
I would like to call Sql Azure's REST API to create a SQL Azure server. The method is documented here: http://msdn.microsoft.com/en-us/library/gg715274.aspx
I ran into problem. The response from this method is very simple:
<?xml version="1.0" encoding="utf-8"?><ServerName xmlns="http://schemas.microsoft.com/sqlazure/2010/12/">zpc0fbxur0</ServerName>
How do I define a DataContract class for this response?
If response were something like this:
<?xml version="1.0" encoding="utf-8"?>
<ServerName xmlns="http://schemas.microsoft.com/sqlazure/2010/12/"><Name>zpc0fbxur0</Name></ServerName>
the following class would work:
`
[DataContract(Namespace=SqlAzureConstants.ManagementNS, Name="ServerName")]
public class ServerName : IExtensibleDataObject
{
[DataMember()]
public string Name
{
get;
set;
}
public ExtensionDataObject ExtensionData
{
get;
set;
}
}
`
But I need to specify that property should be mapped to the text of the root element. Any ideas how to do this?
The DataContractSerializer as it's created by default cannot deserialize that XML - but if you use a constructor which sets the rootName and rootNamespace parameters, it can be done.
Another alternative is to use the XmlSerializer, where you can use it directly.
The code below shows both options, and also a WebChannelFactory implementation which uses the XmlSerializer type.
public class StackOverflow_6399085
{
[XmlRoot(ElementName = "ServerName", Namespace = "http://schemas.microsoft.com/sqlazure/2010/12/")]
public class ServerName
{
[XmlText]
public string Name { get; set; }
public override string ToString()
{
return string.Format("ServerName[Name={0}]", this.Name);
}
}
const string XML = "<?xml version=\"1.0\" encoding=\"utf-8\"?><ServerName xmlns=\"http://schemas.microsoft.com/sqlazure/2010/12/\">zpc0fbxur0</ServerName>";
static void RunWithXmlSerializer()
{
XmlSerializer xs = new XmlSerializer(typeof(ServerName));
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(XML));
ServerName obj = (ServerName)xs.Deserialize(ms);
Console.WriteLine("Using XML serializer: {0}", obj);
}
static void RunWithDataContractSerializer()
{
DataContractSerializer dcs = new DataContractSerializer(typeof(string), "ServerName", "http://schemas.microsoft.com/sqlazure/2010/12/");
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(XML));
string name = (string)dcs.ReadObject(ms);
Console.WriteLine("Using DataContractSerializer (different name): {0}", name);
}
[ServiceContract(Namespace = "http://schemas.microsoft.com/sqlazure/2010/12/")]
public class MockSqlAzureRestService
{
[WebGet]
public Stream GetServerName()
{
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(XML));
WebOperationContext.Current.OutgoingResponse.ContentType = "text/xml";
return ms;
}
}
[ServiceContract(Namespace = "http://schemas.microsoft.com/sqlazure/2010/12/")]
public interface IServerNameClient
{
[WebGet(BodyStyle = WebMessageBodyStyle.Bare)]
[XmlSerializerFormat]
ServerName GetServerName();
}
static void RunWithWCFRestClient()
{
// Setting up the mock service
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
WebServiceHost host = new WebServiceHost(typeof(MockSqlAzureRestService), new Uri(baseAddress));
host.Open();
WebChannelFactory<IServerNameClient> factory = new WebChannelFactory<IServerNameClient>(new Uri(baseAddress));
IServerNameClient proxy = factory.CreateChannel();
var name = proxy.GetServerName();
Console.WriteLine("Using WCF REST client: {0}", name);
}
public static void Test()
{
RunWithXmlSerializer();
RunWithDataContractSerializer();
RunWithWCFRestClient();
}
}
I have a WCF proxy generated at runtime with DuplexChannelFactory.
How can I access the binding information given only the service interface returned from DuplexChannelFactory?
I can get most stuff by casting to an IClientChannel, but I can't seem to find binding info on there. The closest I can get is IClientChannel.RemoteAddress which is an endpoint, but that doesn't seem to have binding info either. :-/
You can't (directly). There are a few things which you can get from the channel, such as the message version (channel.GetProperty<MessageVersion>()), and other values. But the binding isn't one of those. The channel is created after the binding is "deconstructed" (i.e., expanded into its binding elements, while each binding element can add one more piece to the channel stack.
If you want to have the binding information in the proxy channel, however, you can add it yourself, using one of the extension properties of the context channel. The code below shows one example of that.
public class StackOverflow_6332575
{
[ServiceContract]
public interface ITest
{
[OperationContract]
int Add(int x, int y);
}
public class Service : ITest
{
public int Add(int x, int y)
{
return x + y;
}
}
static Binding GetBinding()
{
BasicHttpBinding result = new BasicHttpBinding();
return result;
}
class MyExtension : IExtension<IContextChannel>
{
public void Attach(IContextChannel owner)
{
}
public void Detach(IContextChannel owner)
{
}
public Binding Binding { get; set; }
}
static void CallProxy(ITest proxy)
{
Console.WriteLine(proxy.Add(3, 5));
MyExtension extension = ((IClientChannel)proxy).Extensions.Find<MyExtension>();
if (extension != null)
{
Console.WriteLine("Binding: {0}", extension.Binding);
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(ITest), GetBinding(), "");
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(GetBinding(), new EndpointAddress(baseAddress));
ITest proxy = factory.CreateChannel();
((IClientChannel)proxy).Extensions.Add(new MyExtension { Binding = factory.Endpoint.Binding });
CallProxy(proxy);
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
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();