简体   繁体   中英

WCF routing based on parameter type

I'm working on converting an older ASMX Web Service to a WCF Service (I'm not sure if those are even the right terms, the old code has an ASMX file created using the wsdl tool and I'm trying to create the same service using the svcutil tool). Based on the code and experimenting it looks like the old code is routing messages based on the content of that message.

The previous code (simplified) is as follows:

[SoapDocumentService(SoapBindingUse.Literal, SoapParameterStyle.Wrapped, RoutingStyle = SoapServiceRoutingStyle.RequestElement)]
public class Server : WebService, IServer
{
    public Action1Response Action1(Action1Request request)
    {
        //Do stuff here
    }

     public Action2Response Action2(Action2Request request)
    {
        //Do stuff here
    }
}

New code for reference:

[ServiceContract]
public class Server : WebService, IServer
{
    [OperationContract]
    public Action1Response Action1(Action1Request request)
    {
        //Do stuff here
    }

    [OperationContract]
    public Action2Response Action2(Action2Request request)
    {
        //Do stuff here
    }
}

However, I can't figure out how to something similar in WCF. I tried the SoapDocumentService but that didn't work. The only other thing I could think of was to do some routing/filtering but I have no idea how I'd make that work. My guess would be to do something like:

<routing>
     <namespaceTable>
        <add namespace="http://example.org" prefix="ns"/>
     </namespaceTable>
     <filters>
       <filter name="action1" filterType="XPath" filterData="boolean(//ns:Action1Request)"/>
       <filter name="action1" filterType="XPath" filterData="boolean(//ns:Action2Request)"/>
     </filters>
     <filterTables>
       <filterTable name="routingTable">
         <add filterName="action1" endpointName="Action1Service" />
         <add filterName="action2" endpointName="Action2Service" />
       </filterTable>
     </filterTables>
</routing>

<client>
    <!-- Pretty sure this wouldn't work -->
    <endpoint name="Action1Service" address="/Server.svc/Action1" binding="basicHttpBinding" contract="*" />
    <endpoint name="Action1Service" address="/Server.svc/Action2" binding="basicHttpBinding" contract="*" />
 </client>

My question is, how can I make a WCF service to route to different operations based on the content of the message. I have no control over the client, it will only send messages to /Server.svc . I apologize if I'm using the incorrect terminology here, I'm very new to WCF.

After a lot of reading I found that the service does not dispatch messages based on the Action URI but rather based on an XML element in the SOAP body, which is supported in SOAP 1.1. The solution was to create a custom class to dispatch messages based on an element in the body. This Microsoft Document explains everything in detail. The basic code I used is as follows:

Dispatcher

public class DispatchByBodyOperationSelector : IDispatchOperationSelector
{
    public string SelectOperation(ref Message message)
    {
        XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();

        //By accessing the body the message is now marked as read, thus we need to clone the message so it's left in an unread state
        //NOTE: We have to pass in the body reader because if we try to get it again it throws an error
        message = CloneMessage(message, bodyReader);

        //The element in the body has "Request" appended so we need to remove it to match the method
        return bodyReader.LocalName.Replace("Request", "");
    }

    private Message CloneMessage(Message message, XmlDictionaryReader body)
    {
        Message toReturn = Message.CreateMessage(message.Version, message.Headers.Action, body);

        toReturn.Headers.CopyHeaderFrom(message, 0);
        toReturn.Properties.CopyProperties(message.Properties);

        return toReturn;
    }
}

Custom Attribute

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class DispatchByBodyBehaviorAttribute : Attribute, IContractBehavior
{
    public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters){}
    public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime){}
    public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint){}

    public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        //We want the operator selector to be this
        dispatchRuntime.OperationSelector = new DispatchByBodyOperationSelector();
    }
}

Service

[ServiceContract, DispatchByBodyBehavior]
public class Server : IServer
{
    [OperationContract]
    public Action1Response Action1(Action1Request request)
    {
        //Do stuff here
    }

    [OperationContract]
    public Action2Response Action2(Action2Request request)
    {
        //Do stuff here
    }
}

Obviously this is very basic version and requires more validation and edge case handling. The linked Microsoft document goes into further detail.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM