WCF REST service url routing based on query parame

2019-07-24 01:54发布

问题:

Since WCF routing doesn't support routing for REST services, I created a REST service that has one enpoint which accepts all incoming requests and than redirects those requests based on the query parameters.
I did this by following this article http://blog.tonysneed.com/2012/04/24/roll-your-own-rest-ful-wcf-router/.

This approach works for passing through requests and returning the results. The problem is whenever I get an error, like a 404, from the actual service the message that is returned to the client is a 400 (Bad Request).
What I would like to have is a routing proxy that actually just redirects the calls to the real service based on the query and returns all the errors to the client as they come from the real service.

Is this even the right approach to what I'm trying to accomplish, or are there easier or better solutions?

Any help is appreciated!

In the following I added what my code looks like.
app.config:

<!--
  System.net
-->
<system.net>
<settings>
  <servicePointManager expect100Continue="false" useNagleAlgorithm="false" />
</settings>
<connectionManagement>
  <add address="*" maxconnection="24" />
</connectionManagement>
</system.net>

<!-- 
  System.ServiceModel 
-->
<system.serviceModel>

<!-- 
    Services 
-->
<services>
  <service name="RoutingGateway.RoutingService">
    <endpoint address="/api/routing" binding="webHttpBinding" bindingConfiguration="secureWebHttpBinding" contract="RoutingGateway.IRoutingService" behaviorConfiguration="RESTBehaviour" />
  </service>
</services>

<client>
  <endpoint binding="webHttpBinding" bindingConfiguration="secureWebHttpBinding" contract="RoutingGateway.IRoutingService" name="routingService" behaviorConfiguration="RESTBehaviour" />
</client>

<!-- 
    Bindings
-->
<bindings>
  <webHttpBinding>
    <binding name="secureWebHttpBinding" hostNameComparisonMode="StrongWildcard" maxReceivedMessageSize="2147483647" transferMode="Streamed">
      <security mode="Transport">
        <transport clientCredentialType="None" />
      </security>
    </binding>
  </webHttpBinding>
</bindings>

<!-- 
    Behaviors
-->
<behaviors>
  <endpointBehaviors>
    <behavior name="RESTBehaviour">
      <dispatcherSynchronization asynchronousSendEnabled="true" />
      <webHttp helpEnabled="true" />
    </behavior>
  </endpointBehaviors>

  <serviceBehaviors>
    <behavior>
      <!-- To avoid disclosing metadata information, set the value below to false before deployment -->
      <serviceMetadata httpsGetEnabled="false" />
      <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
      <serviceDebug includeExceptionDetailInFaults="false" />
      <!-- Enable Throttling -->
      <serviceThrottling maxConcurrentCalls="100" maxConcurrentInstances="100" maxConcurrentSessions="100" />
    </behavior>
  </serviceBehaviors>
</behaviors>

<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>

IRoutingService.cs:

[ServiceContract(Namespace = "https://test/api/routing")]
public interface IRoutingService
{
    [OperationContract(Action = "*", ReplyAction = "*")]
    [WebInvoke(UriTemplate = "*", Method = "*")]
    Message ProcessRequest(Message requestMessage);
}

RoutingService.cs:

public Message ProcessRequest(Message requestMessage)
{
    ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true;

    Uri originalRequestUri = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri;

    // Gets the URI depending on the query parameters
    Uri uri = GetUriForRequest(requestMessage);

    // Select rest client endpoint
    string endpoint = "routingService";
    // Create channel factory
    var factory = new ChannelFactory<IRoutingService>(endpoint);

    Uri requestUri = new Uri(uri, originalRequestUri.PathAndQuery);
    factory.Endpoint.Address = new EndpointAddress(requestUri);
    requestMessage.Headers.To = requestUri;

    // Create client channel
    _client = factory.CreateChannel();

    // Begin request
    Message result = _client.ProcessRequest(requestMessage);
    return result;
}

回答1:

I ended up catching all CommunicationExceptions and then rethrowing WebFaultExceptions with the appropriate messages and status codes.

Here is the code:

Message result = null;
try
{
    result = _client.ProcessRequest(requestMessage);
}
catch (CommunicationException ex)
{
    if (ex.InnerException == null ||
        !(ex.InnerException is WebException))
    {
        throw new WebFaultException<string>("An unknown internal Server Error occurred.",
            HttpStatusCode.InternalServerError);
    }
    else
    {
        var webException = ex.InnerException as WebException;
        var webResponse = webException.Response as HttpWebResponse;

        if (webResponse == null)
        {
            throw new WebFaultException<string>(webException.Message, HttpStatusCode.InternalServerError);
        }
        else
        {
            var responseStream = webResponse.GetResponseStream();
            string message = string.Empty;
            if (responseStream != null)
            {
                using (StreamReader sr = new StreamReader(responseStream))
                {
                    message = sr.ReadToEnd();
                }
                throw new WebFaultException<string>(message, webResponse.StatusCode);
            }
            else
            {
                throw new WebFaultException<string>(webException.Message, webResponse.StatusCode);                            
            }
        }
    }
}