I'm trying to implement an UPnP MediaServer in WCF. I'm slowly getting there, but now I've hit a brick wall. I need to add a prefix to the ServiceContract namespace. Right now I have the following:
[ServiceContract(Namespace = "urn:schemas-upnp-org:service:ContentDirectory:1")]
public interface IContentDirectory
{
[OperationContract(Action = "urn:schemas-upnp-org:service:ContentDirectory:1#Browse")]
void Browse(string ObjectID, string BrowseFlag, string Filter, ulong StartingIndex, ulong RequestedCount, string SortCriteria, out string Result, out ulong NumberReturned, out ulong TotalMatches, out ulong UpdateID);
}
This listens to the correct soap-messages. However, I need the soap-body to begin with
<u:Browse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
and WCF is now listening to:
<Browse xmlns="urn:schemas-upnp-org:service:ContentDirectory:1">
How do I get the prefix there? Does it matter? Or is there another reason why the parameters are not passed into the Browse method?
UPDATE Here's some extra info: The following message is sent by a real UPnP control point. The parameters are not passed into the Browse method.
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<To s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://XXXXX:8731/ContentDirectory</To>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">urn:schemas-upnp-org:service:ContentDirectory:1#Browse</Action>
</s:Header>
<s:Body>
<u:Browse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
<ObjectID>0</ObjectID>
<BrowseFlag>BrowseDirectChildren</BrowseFlag>
<Filter>*</Filter>
<StartingIndex>0</StartingIndex>
<RequestedCount>15</RequestedCount>
<SortCriteria />
</u:Browse>
</s:Body>
</s:Envelope>
This is the request that is generated by the WCF test client. Now the parameters are passed into the Browse method:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<To s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://XXXXXX:8731/ContentDirectory</To>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">urn:schemas-upnp-org:service:ContentDirectory:1#Browse</Action>
</s:Header>
<s:Body>
<Browse xmlns="urn:schemas-upnp-org:service:ContentDirectory:1">
<ObjectID>0</ObjectID>
<BrowseFlag>BrowseMetadata</BrowseFlag>
<Filter>*</Filter>
<StartingIndex>0</StartingIndex>
<RequestedCount>0</RequestedCount>
<SortCriteria i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" />
</Browse>
</s:Body>
</s:Envelope>
The two requests which you showed (one by the UPnP control point and one by the WCF Test Client) are not equivalent. The namespace of the children of the Browse element in the first (UPnP) is the empty ("") namespace, while in the second request (WCF TC) it's "urn:schemas-upnp-org:service:ContentDirectory:1" - the empty prefix is bound to the empty namespace unless it's bound to another namespace, and it's rebound in the WCF request but not on the UPnP one.
Now, if you want the namespace of the parameters of an operation to be different than the namespace of the operation itself, you'll have to use a message contract - that'll get the request sent by WCF to be equivalent (modulo prefixes) to the request sent by the UPnP control point. The code below shows how a message contract would be defined to generate a request equivalent to the one you showed for the control point.
public class StackOverflow_2495195
{
[MessageContract(WrapperName="Browse", WrapperNamespace="urn:schemas-upnp-org:service:ContentDirectory:1", IsWrapped=true)]
public class BrowseRequest
{
[MessageBodyMember(Namespace = "", Order = 0)]
public string ObjectID;
[MessageBodyMember(Namespace = "", Order = 1)]
public string BrowseFlag;
[MessageBodyMember(Namespace = "", Order = 2)]
public string Filter;
[MessageBodyMember(Namespace = "", Order = 3)]
public ulong StartingIndex;
[MessageBodyMember(Namespace = "", Order = 4)]
public ulong RequestedCount;
[MessageBodyMember(Namespace = "", Order = 5)]
public string SortCriteria;
}
[MessageContract(WrapperName = "BrowseResponse", WrapperNamespace = "urn:schemas-upnp-org:service:ContentDirectory:1", IsWrapped = true)]
public class BrowseResponse
{
[MessageBodyMember(Namespace = "", Order = 0)]
public string Result;
[MessageBodyMember(Namespace = "", Order = 1)]
public ulong NumberReturned;
[MessageBodyMember(Namespace = "", Order = 2)]
public ulong TotalMatches;
[MessageBodyMember(Namespace = "", Order = 3)]
public ulong UpdateID;
}
[ServiceContract(Namespace = "urn:schemas-upnp-org:service:ContentDirectory:1")]
public interface IContentDirectory
{
[OperationContract(Action = "urn:schemas-upnp-org:service:ContentDirectory:1#Browse")]
//void Browse(string ObjectID, string BrowseFlag, string Filter, ulong StartingIndex, ulong RequestedCount, string SortCriteria, out string Result, out ulong NumberReturned, out ulong TotalMatches, out ulong UpdateID);
BrowseResponse Browse(BrowseRequest request);
}
public class Service : IContentDirectory
{
//public void Browse(string ObjectID, string BrowseFlag, string Filter, ulong StartingIndex, ulong RequestedCount, string SortCriteria, out string Result, out ulong NumberReturned, out ulong TotalMatches, out ulong UpdateID)
//{
// Result = null;
// NumberReturned = 0;
// TotalMatches = 0;
// UpdateID = 0;
//}
public BrowseResponse Browse(BrowseRequest request)
{
return new BrowseResponse { NumberReturned = 0, Result = null, TotalMatches = 0, UpdateID = 0 };
}
}
static Binding GetBinding()
{
return new CustomBinding(
new TextMessageEncodingBindingElement(MessageVersion.Soap11WSAddressing10, Encoding.UTF8),
new HttpTransportBindingElement());
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(IContentDirectory), GetBinding(), "");
host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<IContentDirectory> factory = new ChannelFactory<IContentDirectory>(GetBinding(), new EndpointAddress(baseAddress));
IContentDirectory proxy = factory.CreateChannel();
//string result;
//ulong ul1, ul2, ul3;
//proxy.Browse(null, null, null, 0, 0, null, out result, out ul1, out ul2, out ul3);
proxy.Browse(new BrowseRequest { BrowseFlag = null, Filter = null, ObjectID = null, RequestedCount = 0, SortCriteria = null, StartingIndex = 0 });
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
Now, the request that is sent by WCF is XML-equivalent, but the prefixes are still different. It As John Saunders mentioned, it shouldn't matter, so there's no simple knob in WCF to let you set the prefix for the elements written in the WCF messages. You can, however, use a custom encoder to do exactly that. I posted about it a while back at http://blogs.msdn.com/b/carlosfigueira/archive/2010/06/13/changing-prefixes-in-xml-responses.aspx - you can use that example to create a custom encoder which will set the prefixes to what you expect them to be.