tags:

views:

313

answers:

3

I'm using Framework 3.5 and would like to have a ServiceContract that can take in different types of Request / Response objects, is it possible?

A: 

Yes, if you really want to, you can deal with a generic Message type as parameter and/or return value.

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    Message GetData();

    [OperationContract]
    void PutData(Message m);
}

See the details here in the MSDN documentation.

However, this means you'll have to manually do a lot of XML manipulation voodoo and handle lots of stuff yourself, that would be handled for free, if you used strongly typed [DataContract] types.

Can you do it? Yes. Should you do it? Well, depends on how much you really want it! :-)

Marc

marc_s
A: 

As Marc says you have a service contract which takes Message parameters. But you don't have to build the XML by hand, instead you could share the interface and the message contracts in a shared DLL which is common to both the server and the client.

For example I have a piece of software which takes Messages because the spec says it had to. I have a common assembly which contains the interface, all the potential request and response messages and public static strings for the namespaces. So, for one of the operations it looks like the following

[ServiceContract(
    Namespace = Constants.Service.Namespace.Location, 
    Name = "ServiceMonitorContract")]
public interface IMonitor
{
    [OperationContract(
        Action = Constants.Service.Actions.GetTasksRequest,
        ReplyAction = Constants.Service.Actions.GetTasksResponse)]
    Message GetTasks(Message request);
}

and I have message contracts that look like

[MessageContract(IsWrapped = true, 
                 WrapperNamespace = Constants.Messages.Namespace.Location)]
public sealed class GetTasksRequest 
{
    ....
}

To get a connection to the service I do the following

private static IMonitor GetChannelToWebService()
{
    EndpointAddress endpoint = new EndpointAddress("http://example/service.svc");
    ChannelFactory<IMonitor> channelFactory = 
        new ChannelFactory<IMonitor>(new BasicHttpBinding(), endpoint);

    return channelFactory.CreateChannel();
}

And then I can do the following to use it, with the shared message contracts

IMonitor channel = GetChannelToWebService();

// Create the GetTasksRequest message
GetTasksRequest getTasksRequest = new GetTasksRequest();
// Set the various properties on the message

// Convert it to a strongly type message
TypedMessageConverter requestMessageConverter = TypedMessageConverter.Create(
    typeof(GetTasksRequest),
    Constants.Service.Actions.GetTasksRequest,
    Constants.Service.Namespace.Location);
Message request = requestMessageConverter.ToMessage(
                      getTasksRequest, 
                      MessageVersion.Soap11);

// Send it and get the response.
Message response = channel.GetTasks(request);

// Check for SOAP faults
if (response.IsFault)
{
    MessageFault fault = MessageFault.CreateFault(response, int.MaxValue);
    // React accordingly        
}

TypedMessageConverter responseMessageConverter = TypedMessageConverter.Create(
    typeof(GetTasksResponse),
    Constants.Service.Actions.GetTasksResponse,
    Constants.Service.Namespace.Location);
GetTasksResponse getTasksResponse = 
    responseMessageConverter.FromMessage(response) as GetTasksResponse;

((IClientChannel)channel).Close();

The one thing to be aware of is that faults will not get thrown client side, you must check the Message object when it arrives back as a response manually and act accordingly as you can see from the sample.

Server side I do much the same thing with a TypedMessageConvertor

// Convert the inbound message to a GetTasksRequest.
TypedMessageConverter getTasksMessageConverter = TypedMessageConverter.Create(
    typeof(GetTasksRequest),
    Constants.Service.Actions.GetTasksRequest,
    Constants.Service.Namespace.Location);
GetTasksRequest getTasksMessage = 
    getTasksMessageConverter.FromMessage(request) as GetTasksRequest;

// Validate the message is the correct type.
if (getTasksMessage == null)
{
    throw FaultHelper.UnknownMessageTypeFault();
}

// Do my thing

GetTasksResponse responseMessage = new GetTasksResponse();

// Set appropriate response bits in the responseMessage

TypedMessageConverter responseConverter = TypedMessageConverter.Create(
    typeof(GetTasksResponse),
    Constants.Service.Actions.GetTasksResponse,
    Constants.Service.Namespace.Location);
Message response = responseConverter.ToMessage(responseMessage, request.Version);
response.Headers.RelatesTo = request.Headers.MessageId;

return response;

Just don't forget to set the RelatesTo header to be the MessageId from the request headers

blowdart
Interesting approach - thanks for sharing! However, I don't know if that'll work in the OP's scenario where we wants to pass it "anything" basically..... if you pass in anything, you might have to resort to manually futzing around the XML......
marc_s
Well yes true, but anything often isn't what people want, even when they think it is to begin with. The above is also how you start to write a message router (although that will be built into 4.0/Dublin)
blowdart
A: 

You could also pass an XElement. This gives the request object the flexibility to contain whatever content you choose.

You would ideally specify an XSD which contains a number of 'choice' elements, each of which specify one of the different request types.

<xs:element name="request">
  <xs:complexType>
    <xs:choice>
      <xs:element name="requestType1"/>
          ....
      </xs:element>
      <xs:element name="requestType2"/>
          ....
      </xs:element>
    </xs:choice>
  </xs:complexType>
</xs:element>

Your service side code simple needs to determine which of the 'choice' objects are present in order to determine what to do with the parameter.

Kirk Broadhurst