WCF REST service url routing based on query parameters - c#

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;
}

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);
}
}
}
}

Related

Unable to define user/password for WCF service

I have a WCF web service working with basic authentification.
I want to define a user/password for this service. So I wrote my web.config to user basic authentification :
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="UsernameWithTransportCredentialOnly">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Basic"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceWithMetaData">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceAuthorization serviceAuthorizationManagerType="InterfaceWS.CredentialsChecker,App_Code.CredentialsChecker"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="ServiceWithMetaData" name="InterfaceWS.MyService" >
<endpoint
address="https://localhost:44336/MyService.svc"
binding="basicHttpBinding"
bindingConfiguration="UsernameWithTransportCredentialOnly"
name="BasicEndpoint"
contract="InterfaceWS.IErpService">
</endpoint>
</service>
</services>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="false" />
</system.serviceModel>
I created a class inherited from ServiceAuthorizationManager :
namespace InterfaceWS
{
public class CredentialsChecker : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
//Extract the Authorization header, and parse out the credentials converting the Base64 string:
var authHeader = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];
if ((authHeader != null) && (authHeader != string.Empty))
{
var svcCredentials = System.Text.ASCIIEncoding.ASCII
.GetString(Convert.FromBase64String(authHeader.Substring(6)))
.Split(':');
var user = new
{
Name = svcCredentials[0],
Password = svcCredentials[1]
};
if ((user.Name == "testuser" && user.Password == "testpassword"))
{
//User is authrized and originating call will proceed
return true;
}
else
{
//not authorized
return false;
}
}
else
{
//No authorization header was provided, so challenge the client to provide before proceeding:
WebOperationContext.Current.OutgoingResponse.Headers.Add("WWW-Authenticate: Basic realm=\"MyWCFService\"");
//Throw an exception with the associated HTTP status code equivalent to HTTP status 401
throw new WebFaultException(HttpStatusCode.Unauthorized);
}
}
public override bool CheckAccess(OperationContext operationContext)
{
return false;
}
}
}
But the CheckAccessCore is never reached and i'm unable to connect to my Service. What did I do wrong ?
serviceAuthorization is about granting access to particular resources based on user credentials. You want authentication.
You can use serviceCredentials/userNameAuthentication tags in the configuration.
Example of configuration is given here: https://learn.microsoft.com/en-us/dotnet/framework/wcf/samples/membership-and-role-provider

C# WCF "System.Net.WebException: The remote server returned an error: (400) Bad Request."

I am getting "System.Net.WebException: The remote server returned an error: (400) Bad Request" error when trying to access my WCF methods. I think it might be on the client side. When I use the Promo Standards tool to validate it, it works fine http://services.promostandards.org/webserviceValidator/home
Below is information from WCF and client.
WCF Config file
<system.serviceModel>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true">
</serviceHostingEnvironment>
<bindings>
<basicHttpBinding>
<binding name="basicHttpBindingConfig" messageEncoding="Text" textEncoding="utf-8">
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"/>
</security>
</binding>
<binding name="basicHttpBindingConfig2" messageEncoding="Text" textEncoding="utf-8">
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"/>
</security>
</binding>
<binding name="basicHttpBindingConfig3" messageEncoding="Text" textEncoding="utf-8">
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"/>
</security>
</binding>
<binding name="basicHttpBindingConfig4" messageEncoding="Text" textEncoding="utf-8">
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<!-- Note: the service name must match the configuration name for the service implementation. -->
<service name="Proactive_WebAPI.Services.Product" behaviorConfiguration="ProductDataServiceBehavior">
<!-- Add the following endpoint. -->
<!-- Note: your service must have an http base address to add this endpoint. -->
<endpoint
address="http://api.proactiveclothing.com/services/Product.svc"
binding="basicHttpBinding"
bindingConfiguration="basicHttpBindingConfig"
contract="Proactive.Product.ProductDataService"
listenUri="/"/>
</service>
<service name="Proactive_WebAPI.Services.Media" behaviorConfiguration="MediaContentServiceBehavior">
<!-- Add the following endpoint. -->
<!-- Note: your service must have an http base address to add this endpoint. -->
<endpoint
address="http://api.proactiveclothing.com/services/Media.svc"
binding="basicHttpBinding"
bindingConfiguration="basicHttpBindingConfig2"
contract="Proactive.Media.MediaContentService"
listenUri="/"/>
</service>
<service name="Proactive_WebAPI.Services.PricingAndConfiguration" behaviorConfiguration="PricingAndConfigurationBehavior">
<!-- Add the following endpoint. -->
<!-- Note: your service must have an http base address to add this endpoint. -->
<endpoint
address="http://api.proactiveclothing.com/services/PricingAndConfiguration.svc"
binding="basicHttpBinding"
bindingConfiguration="basicHttpBindingConfig2"
contract="Proactive.Pricing.PricingAndConfigurationService"
listenUri="/"/>
</service>
<service name="Proactive_WebAPI.Services.Inventory" behaviorConfiguration="InventoryServiceBehavior">
<!-- Add the following endpoint. -->
<!-- Note: your service must have an http base address to add this endpoint. -->
<endpoint
address="http://api.proactiveclothing.com/services/Inventory.svc"
binding="basicHttpBinding"
bindingConfiguration="basicHttpBindingConfig4"
contract="Proactive.Inventory.InventoryService"
listenUri="/"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ProductDataServiceBehavior" >
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true" externalMetadataLocation="http://api.proactiveclothing.com/wsdl/ProductDataService.wsdl"/>
<!-- 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="true"/>
</behavior>
<behavior name="MediaContentServiceBehavior" >
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true" externalMetadataLocation="http://api.proactiveclothing.com/wsdl/MediaContentService.wsdl"/>
<!-- 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="true"/>
</behavior>
<behavior name="PricingAndConfigurationBehavior" >
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true" externalMetadataLocation="http://api.proactiveclothing.com/wsdl/PricingAndConfiguration.wsdl"/>
<!-- 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="true"/>
</behavior>
<behavior name="InventoryServiceBehavior" >
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true" externalMetadataLocation="http://api.proactiveclothing.com/wsdl/InventoryService.wsdl"/>
<!-- 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="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Client Code
private string Connect_WebRequest()
{
ASCIIEncoding encoding = new ASCIIEncoding();
string SampleXml = "<GetProductSellableRequest xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://www.promostandards.org/WSDL/ProductDataService/1.0.0/\">";
SampleXml += "<wsVersion xmlns=\"http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/\">Token1</wsVersion>";
SampleXml += "<id xmlns=\"http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/\">Token1</id>";
SampleXml += "<password xmlns=\"http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/\">xxxx</password>";
SampleXml += "<productId xmlns=\"http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/\">Token1</productId>";
SampleXml += "<partId xmlns=\"http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/\">Token1</partId>";
SampleXml += "<isSellable xmlns=\"http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/\">true</isSellable>";
SampleXml += "</GetProductSellableRequest>";
string postData = SampleXml.ToString();
byte[] data = encoding.GetBytes(postData);
string url = "http://api.proactiveclothing.com/services/Product.svc/getProductSellable";
string strResult = string.Empty;
// declare httpwebrequet wrt url defined above
HttpWebRequest webrequest = (HttpWebRequest)WebRequest.Create(url);
// set method as post
webrequest.Method = "POST";
// set content type
webrequest.ContentType = "text/xml";
// set content length
webrequest.ContentLength = data.Length;
// get stream data out of webrequest object
Stream newStream = webrequest.GetRequestStream();
newStream.Write(data, 0, data.Length);
newStream.Close();
//Gets the response
WebResponse response = webrequest.GetResponse();
//Writes the Response
Stream responseStream = response.GetResponseStream();
StreamReader sr = new StreamReader(responseStream);
string s = sr.ReadToEnd();
return s;
}
Two things you are missing. Wrap your xml in a soap Envelope, and add a SOAPAction header for the endpoint you are calling. In this case "getProductSellable":
class Program
{
static void Main(string[] args)
{
ASCIIEncoding encoding = new ASCIIEncoding();
string SampleXml = #"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Body xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<GetProductSellableRequest xmlns=""http://www.promostandards.org/WSDL/ProductDataService/1.0.0/"">
<wsVersion xmlns=""http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/"">1.0.0</wsVersion>
<id xmlns=""http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/"">Token1</id>
<password xmlns=""http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/"">Token1</password>
<productId xmlns=""http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/"">Token1</productId>
<partId xmlns=""http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/"">token1</partId>
<isSellable xmlns=""http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/"">true</isSellable>
</GetProductSellableRequest>
</s:Body>
</s:Envelope>";
try
{
byte[] data = encoding.GetBytes(SampleXml);
string url = "http://api.proactiveclothing.com/services/Product.svc";
HttpWebRequest webrequest = (HttpWebRequest)WebRequest.Create(url);
webrequest.Method = "POST";
webrequest.Headers.Add("SOAPAction", "getProductSellable");
webrequest.ContentType = "text/xml";
webrequest.ContentLength = data.Length;
using (Stream newStream = webrequest.GetRequestStream())
{
newStream.Write(data, 0, data.Length);
using (WebResponse response = webrequest.GetResponse())
using (Stream responseStream = response.GetResponseStream())
{
StreamReader sr = new StreamReader(responseStream);
string s = sr.ReadToEnd();
Console.WriteLine(s);
}
}
}
catch (WebException webex)
{
Console.WriteLine(webex.Message);
}
Console.ReadKey();
}
}
For your PHP clients you could do something like this, which makes use of the WSDL located here: http://api.proactiveclothing.com/wsdl/ProductDataService.wsdl
(though, they should know how to do this if you provide them the wsdl)
Untested
<?php
 
$arr = array(
    'GetProductSellableRequest' =>
        array(
            'wsVersion' => ????,
            'id' => ?????,
            'password' => ?????,
'productId' => ????,
'partId' => ????,
'isSellable' => ????       
);
 
    try {
        $sc = new SoapClient("ProductDataService.wsdl", array(
            'location' => 'http://api.proactiveclothing.com/services/Product.svc')
         );
        $resp = $sc-> Submit($arr);
   
        echo(var_dump($resp));
    }
    catch (SoapFault $fault) {
        echo(var_dump($fault));
    }
?>

Why WCF nettcpBinding is slow on local machine

I am trying to check the WCF latency on my local machine. And it takes 2-4 millisecond/request (which seems to slow). I am not sending or receiving any complex object (so no serialization/deserialization involved).
Here is the service part:
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
public CompositeType GetDataUsingDataContract(CompositeType composite)
{
if (composite == null)
{
throw new ArgumentNullException("composite");
}
if (composite.BoolValue)
{
composite.StringValue += "Suffix";
}
return composite;
}
}
Here is the server's config.
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<!-- 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"/>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https"/>
</protocolMapping>
<bindings>
<netTcpBinding>
<binding name="defaultNetTcpBinding" closeTimeout="00:10:00" sendTimeout="00:10:00" receiveTimeout="00:10:00" openTimeout="00:10:00" maxReceivedMessageSize="2147483647" maxBufferPoolSize="2147483647" maxBufferSize="2147483647">
<readerQuotas maxArrayLength="2147483647" maxStringContentLength="2147483647" />
<security mode="None"></security>
</binding>
</netTcpBinding>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
<services>
<service name="WcfTestApplication.Service1">
<endpoint address="net.tcp://localhost/WcfTestApplication/Service1.svc" contract="WcfTestApplication.IService1" binding="netTcpBinding" />
<endpoint address="mex" contract="IMetadataExchange" binding="mexTcpBinding"/>
</service>
</services>
And here is the client code
NetTcpBinding binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.None;
EndpointAddress address = new EndpointAddress("net.tcp://localhost/WcfTestApplication/Service1.svc");
ChannelFactory<IService1> channelFactory = new ChannelFactory<IService1>(binding, address);
IService1 _clientProxy = channelFactory.CreateChannel();
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1000; i++)
{
IService1 _clientProxy1 = channelFactory.CreateChannel();
var data = _clientProxy1.GetData(4);
((IClientChannel)_clientProxy1).Close();
}
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds.ToString());
Console.ReadLine();
Updated:
Strange enough, there is no difference when I change binding to Named pipe. It still takes 2-4 millisecond/request.

How to use HTTPS with WCF SessionMode.Required - simplest possible example

UPDATE (8/7/2014) - The solution to this problem was that I needed to add a class that derived from "UserNamePasswordValidator" and register it in Web.Config.
I have created a simple test WCF service and test console client application (see below for code). I am using .NET 4.5.1. I have already searched for duplicates on StackOverflow (found similar posts here and here) - however I feel that the referenced posts are potentially outdated, and also feel that my post is more limited in scope.
OK now for the example:
The solution currently uses sessions (in ITestService.cs):
[ServiceContract(SessionMode = SessionMode.Required)]
... and uses wsHttpBinding (see below app.config and web.config).
When I deploy this to a server, I am successfully able to access it via a web browser using HTTPS like this: https://myserver.com/test/testservice.svc
However, when I change the endpoint in the client app.config from:
http://localhost:20616/TestService.svc/TestService.svc
to:
https://myserver.com/test/testservice.svc
and run the console application again, I receive the error: "The provided URI scheme 'https' is invalid; expected 'http'. Parameter name: via"
My question is, what is the minimum changes I need to make for this to work, without changing SessionMode.Required?
Here is the client console application code. Please be sure to change the App.Config value for "mycomputer\Matt" to the correct value for your machine.
Program.cs
using System;
namespace TestClient
{
class Program
{
static void Main(string[] args)
{
Console.Clear();
Console.WriteLine("Attempting to log in...");
try
{
TestServiceReference.TestServiceClient client = new TestServiceReference.TestServiceClient();
bool loginSuccess = client.LogIn("admin", "password");
if (loginSuccess)
{
Console.WriteLine("Successfully logged in.");
string secretMessage = client.GetSecretData();
Console.WriteLine("Retrieved secret message: " + secretMessage);
}
else
{
Console.WriteLine("Log in failed!");
}
}
catch (Exception exc)
{
Console.WriteLine("Exception occurred: " + exc.Message);
}
Console.WriteLine("Press ENTER to quit.");
Console.ReadLine();
}
}
}
App.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/>
</startup>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_ITestService"/>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="https://myserver.com/test/testservice.svc" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ITestService" contract="TestServiceReference.ITestService" name="WSHttpBinding_ITestService">
<identity>
<userPrincipalName value="mycomputer\Matt"/>
</identity>
</endpoint>
<!--<endpoint address="http://localhost:20616/TestService.svc/TestService.svc" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ITestService" contract="TestServiceReference.ITestService" name="WSHttpBinding_ITestService">
<identity>
<userPrincipalName value="mycomputer\Matt"/>
</identity>
</endpoint>-->
</client>
</system.serviceModel>
</configuration>
WCF Service code.
ITestService.cs:
using System.ServiceModel;
namespace WcfSessionsOverHttpsTest
{
[ServiceContract(SessionMode = SessionMode.Required)]
public interface ITestService
{
[OperationContract(IsInitiating = true)]
bool LogIn(string username, string password);
[OperationContract(IsInitiating = false, IsTerminating = true)]
bool LogOut();
[OperationContract(IsInitiating = false)]
string GetSecretData();
}
}
TestService.svc:
namespace WcfSessionsOverHttpsTest
{
public class TestService : ITestService
{
public bool IsAuthenticated { get; set; }
bool ITestService.LogIn(string username, string password)
{
if (username == "admin" && password == "password")
{
IsAuthenticated = true;
return true;
}
else
{
IsAuthenticated = false;
return false;
}
}
bool ITestService.LogOut()
{
IsAuthenticated = false;
return true;
}
string ITestService.GetSecretData()
{
if (!IsAuthenticated)
{
throw new System.Security.Authentication.AuthenticationException("User has not logged in.");
}
else
{
string secretMessage = "The Red Sox are going to win the World Series in 2016";
return secretMessage;
}
}
}
}
Web.config:
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/>
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.5.1"/>
<httpRuntime targetFramework="4.5"/>
</system.web>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="wsHttpEndpointBinding" closeTimeout="00:10:00" openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00" maxReceivedMessageSize="2147483647">
<readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="WcfSessionsOverHttpsTest.TestService">
<endpoint address="/TestService.svc" binding="wsHttpBinding" bindingConfiguration="wsHttpEndpointBinding" contract="WcfSessionsOverHttpsTest.ITestService"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="wsHttpBinding" scheme="http"/>
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>
Thanks in advance for any help!
Matt
The solution to this problem was that I needed to add a class that derived from "UserNamePasswordValidator" and register it in Web.Config.
public class CustomUserNameValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
return;
}
}
Web.config:
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpsGetEnabled="true" />
<!-- 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="true" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MyProgram.CustomUserNameValidator,MyProgram" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>

Call WCF via another WCF call

I have a situation where I would like to make a WCF call as another call is coming in.
Site1 request--> Site2
Site2 request --> Site3
Site2 <-- Site3 response
Site1 <-- Site2 response
The problem I am having is that when Site2 tries to send a message to Site3 while Site1 is sending to Site2; Site2 says it cannot find Site3.
The actual error message is:
Could not find endpoint element with name 'Endpoint_IEchoService' and contract
'FakeCompany.API.Services.Contract.IEchoService' in the ServiceModel client
configuration section. This might be because no configuration file was found
for your application, or because no endpoint element matching this name could be
found in the client element.
Each site is the same configuration and code base. The client, proxy and server are all in the same project. The apps are clones calling each other. It is one website with multiple address bindings. Other regular calls between the sites work fine until I try a call within a call.
As you can probably guess from the contact name, not much in the complex way is happening in my echo service. Single echo calls between the sites work. My problem is when i make a cascade call on the service side to another site.
I am wondering if this is not allowed or if a configuration setting change is required.
Some code and config.
Endpoint addresses are changed at runtime.
If you see something "funky", it is because the client, proxy and service inherit from generic base classes.
//-- ServiceModel Client
<endpoint address="http://FakeCompany.unittest/Services/EchoService.svc"
binding="basicHttpBinding" bindingConfiguration="SecureBinding"
contract="FakeCompany.API.Services.Contract.IEchoService" name="Endpoint_IEchoService">
<identity>
<servicePrincipalName value="host/mikev-ws" />
</identity>
</endpoint>
//-- Bindings
<bindings>
<basicHttpBinding>
<binding name="SecureBinding"
maxReceivedMessageSize="10000000"
sendTimeout="00:05:00">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Basic" />
</security>
</binding>
</basicHttpBinding>
</bindings>
//-- Behaviours
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceTypeBehaviors">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
//-- Services
<service name="FakeCompany.API.Services.Service.EchoService" behaviorConfiguration="MyServiceTypeBehaviors">
<endpoint address="" binding="basicHttpBinding" contract="FakeCompany.API.Services.Contract.IEchoService" bindingConfiguration="SecureBinding" />
</service>
//-- TEST
[Test]
public void CascadeMessage()
{
//-- TEST: That a wcf call can occur within another wcf call.
//-- ARRANGE
DTO_Echo_Cascade_Request request = new DTO_Echo_Cascade_Request(unit1, unit2);
request.NextCall = string.Format("{0};{1};{2};", unit3, unit4, unit5);
//-- ACT
var response = EchoAgent.Cascade(request);
//-- ASSERT
Assert.AreEqual("TBA", response.Response);
}
//-- AGENT
internal static DTO_Echo_Response Cascade(DTO_Echo_Cascade_Request request)
{
DTO_Echo_Response response;
using (EchoServiceClient serviceClient = new EchoServiceClient(request))
{
response = serviceClient.Cascade(request);
}
return response;
}
//-- CLIENT
public DTO_Echo_Response Cascade(DTO_Echo_Cascade_Request request)
{
return Process(() => Proxy.Cascade(request));
}
CONTRACT, DTO, PROXY are omitted.
//-- SERVICE
public DTO_Echo_Response Cascade(DTO_Echo_Cascade_Request request)
{
DTO_Echo_Response response = new DTO_Echo_Response();
response.Response += string.Format("Hello from {0};", request.TargetAddress);
if (request.NextCall.NotNullOrEmpty())
{
var split = request.NextCall.Split(new [] {';'}, StringSplitOptions.RemoveEmptyEntries);
if (split.GetUpperBound(0) > 0)
{
DTO_Echo_Cascade_Request nextRequest = new DTO_Echo_Cascade_Request(request.TargetAddress, split[0]);
for (int i = 1; i < split.GetUpperBound(0); i++)
{
nextRequest.NextCall += split[i] + ";";
}
response.Response += EchoAgent.Cascade(nextRequest).Response;
}
}
return response;
}
The exception occurs on the following line
response.Response += EchoAgent.Cascade(nextRequest).Response;

Categories

Resources