BillRP

Hi,

I was wondering if it was possible to change the default parameter style from Wrapped to Bare for a service contract or operation contract I have a lot of legacy classes, representing messages based on the XmlSerializer. I need to expose WCF operations that accept and reply with these messages, without wrapper elements being added to the message as it would change the schema for the service.

I am hoping I don't have to write a wrapper message class with a MessageContract attribute (with IsWrapped=false) for every single message! With ASP.NET we used to be able to just set ParameterStyle to Bare on the service class. Where has this feature gone !

Please help!

Bill



Re: Windows Communication Foundation (Indigo) Bare parameter style

joe zhou - MSFT

Bare is only supported by MessageContract in WCF. I think if you use /MC to generate client proxy, we'll generated a messagecontract and operation contract as well as a wrapper proxy with parameter style. Could you try it and let me know whether that suite your need

thanks





Re: Windows Communication Foundation (Indigo) Bare parameter style

BillRP

Hi Joe,

Thanks for your reply. Our issue is primarily on the service side rather than the client side. We are upgrading an existing ASP.NET Web service to WCF and need to do so without breaking existing clients.

At the moment, if we have a method on a service contract that accepts a single message parameter (and that message parameter is an XML Serializable message class), WCF will add a wrapper element. So existing clients are enable to communicate with the service because the service contract has effectively changed. We need a way to use our existing message classes on the service side without having to create an extra class for every message, which is then decorated with the MessageContract attribute.

Is there any way to achieve this

Bill





Re: Windows Communication Foundation (Indigo) Bare parameter style

Brian McNamara - MSFT

There is no way to do this within the programming model without using MessageContract, and yes, the programming model for MessageContract is inconvenient in that it introduces an extra class for each message.

You can work around it by doing minor imperative surgery on the description, as in the sample code below. (Try toggling the value of BARE and see the effects.)


//#define BARE
using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;

class MyDMI : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
Console.WriteLine(request.ToString());
return null;
}

public void BeforeSendReply(ref Message reply, object correlationState)
{
}
}

class MyEB : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}

public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MyDMI());
}

public void Validate(ServiceEndpoint endpoint)
{
}
}

[ServiceContract]
public interface IMyContract
{
[OperationContract]
string echo(string s);
}

public class MyService : IMyContract
{
public string echo(string s) { return s; }
}

public class Repro
{
public static void Main()
{
Uri address = new Uri("http://localhost:8004/Service");
Binding binding = new BasicHttpBinding();

ServiceHost service = new ServiceHost(typeof(MyService), address);
ServiceEndpoint e = service.AddServiceEndpoint(typeof(IMyContract), binding, address);
e.Behaviors.Add(new MyEB());
MakeBare(e);
service.Open();

ChannelFactory<IMyContract> cf = new ChannelFactory<IMyContract>(binding, new EndpointAddress(address));
MakeBare(cf.Endpoint);
IMyContract proxy = cf.CreateChannel();
Console.WriteLine(proxy.echo("Hello World"));
((IClientChannel)proxy).Close();
service.Close();
Console.WriteLine("Done, press a key");
Console.ReadKey();
}

static void MakeBare(ServiceEndpoint e)
{
#if BARE
MessageDescription md = e.Contract.Operations[0].Messages[0];
md.Body.WrapperName = null;
#endif
}
}






Re: Windows Communication Foundation (Indigo) Bare parameter style

BillRP

Hi Brian,

Thanks very much for your detailed reply. I tried your solution and unfortunately arrived at a problem. Setting the WrapperName property to null resulted in the service expecting messages in the same namespace as the service contract. So if we have a [ServiceContract( Namespace="urn:ServiceContract" )], and messages that are sent by the service clients in the namespace "urn:Messages", then the service cannot process the incoming messages. Likewise, the outgoing messages are in the "urn:ServiceContract" namespace, rather than the "urn:Messages" namespace as designated by the [XmlRoot] attribute on the message classes.

I also needed to apply the MessageParameter attribute to the message parameters so the message root element names were set correctly. Unfortunately, the MessageParameter attribute doesn't allow me to override the namespace, so I can't solve my problem this way.

I suppose I could apply the message schema namespace to the service contract, but that doesn't seem like a good solution.

Any further information/ideas as how to resolve this issue would be most welcomed.

Bill





Re: Windows Communication Foundation (Indigo) Bare parameter style

BillRP

Expanding on the solution offered by Brian, I modified the MessagePartDescription as well as setting the WrapperName to null. This is achieved by executing the following code for each of the OperationDescriptions in the endpoint contract:

foreach( MessageDescription messageDescription in operationDescription.Messages )

{

// Clear off the message wrapper element

messageDescription.Body.WrapperName = null;

messageDescription.Body.WrapperNamespace = null;

// Get the current message part

MessagePartDescription oldPart = messageDescription.Body.Parts[ 0 ];

string name = oldPart.Name;

string ns = oldPart.Namespace;

// Are we using XmlSerializer

if( operationDescription.Behaviors.Find<XmlSerializerOperationBehavior>() != null )

{

// Try and get the XmlRoot attribute

object[] attributes = oldPart.Type.GetCustomAttributes( typeof( XmlRootAttribute ), false );

if( attributes.Length > 0 )

{

XmlRootAttribute attribute = ( XmlRootAttribute )attributes[ 0 ];

name = attribute.ElementName;

ns = attribute.Namespace;

}

}

// How about the data contract serialiser

else if( operationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>() != null )

{

// Try and get the DataContract attribute

object[] attributes = oldPart.Type.GetCustomAttributes( typeof( DataContractAttribute ), false );

if( attributes.Length > 0 )

{

DataContractAttribute attribute = ( DataContractAttribute )attributes[ 0 ];

name = attribute.Name;

ns = attribute.Namespace;

}

}

// Create the new message part description

MessagePartDescription newPart = new MessagePartDescription( name, ns );

newPart.Index = oldPart.Index;

newPart.MemberInfo = oldPart.MemberInfo;

newPart.Multiple = oldPart.Multiple;

newPart.ProtectionLevel = oldPart.ProtectionLevel;

newPart.Type = oldPart.Type;

// Replace the old part with the new one

messageDescription.Body.Parts[ 0 ] = newPart;

}

The main disadvantage with this approach is that the changes performed by the above code are not seen when executing svcutil.exe over the service assembly, so your contracts come out incorrectly.

As far as I know, there is no way to apply an attribute to a service contract or operation to modify its exposed contract. MSDN documentation for the ServiceBehavior and OperationBehavior classes stipulate we should not make changes to the contract/operation description during their execution.





Re: Windows Communication Foundation (Indigo) Bare parameter style

Brian McNamara - MSFT

If you made the changes you described on the server before calling service.Open(), I think they will show up in the wsdl if you publish metadata, and thus then they'll surface on the other side when you run svcutil. True, they may not show up when running svcutil directly on the assembly. So just run svcutil on the running service, instead.

You're right that you can't make an attribute/behavior that modifies the description.