Call WCF via another WCF call - c#

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;

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

WCF REST service url routing based on query parameters

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

Simple REST WCF Service Example Code - Endpoint not found

I was trying an example partially from Chapter 5 - RESTful .Net, but couldn't make it work for some reason (receiving 404-Not found).
using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Web;
[ServiceContract]
public class RestService
{
[OperationContract]
[WebGet(UriTemplate = "Hosting")]
public void Hosting()
{
Console.WriteLine("RestService::Hosting()");
WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;
}
static void Main(string[] args)
{
var host = new ServiceHost(typeof(RestService));
var endpoint = host.AddServiceEndpoint(typeof(RestService), new WebHttpBinding(), "http://localhost:8080/Hosting");
endpoint.Behaviors.Add(new WebHttpBehavior());
host.Open();
Console.ReadKey();
}
}
It works (returns status-code OK) if I use WebServiceHost as follows
static void Main(string[] args)
{
var host = new WebServiceHost(typeof(RestService), new Uri("http://localhost:8080"));
host.Open();
Console.ReadKey();
}
So the question is how to make it work with ServiceHost (without any configuration file etc. if possible) ?
WebServiceHost creates an EndPoint for you, nothing wrong if you continue using it. Refer this link for more details...
But you can also add below configuration to your service configuration to use ServiceHost, I have given an example, you can change it to reflect your service classes.
<system.serviceModel>
<services>
<service name="YourService.DateTimeService" behaviorConfiguration="customBehavior">
<endpoint address="Basic" binding="basicHttpBinding" contract="DifferentBindings.IDateTime">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="Web" binding="webHttpBinding" contract="DifferentBindings.IDateTime" behaviorConfiguration="webHttpBehavior">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8731/DifferentBindings/DateTimeService/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information,
set the value below to false before deployment -->
<serviceMetadata httpGetEnabled="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>
<endpointBehaviors>
<behavior name="webHttpBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
So I was curious how complex this will be with WCF. Tried it out. It's streight forward.
I used this tutorial to create a simple service that has a doWork method that expects a string and returns a greeting.
Greeting:
[DataContract]
public class Greeting
{
[DataMember]
public string Str { get; set; }
}
Svc Contract:
[OperationContract]
[WebInvoke(Method = "ResponseFormat = WebMessageFormat.BodyStyle = WebMessageBodyStyle.Bare, UriTemplate = "sayHello/{name}/")]
Greeting DoWork(string name);
Svc Impl:
public class GreetingService : IGreetingService
{
public Greeting DoWork(string name)
{
return new Greeting {Str = string.Format("Hello {0}", name)};
}
}
Then you can first test it by:
Right click on the svc file in visual studio > view in browser
add 'sayHello/test' to the url
see the greeting in the browser
A consumer for this can be implemented in AngularJS using either the $http oder the $resource service
<!DOCTYPE html>
<html ng-app="restTest">
<head>
<title></title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular-resource.min.js"></script>
<script language="javascript">
// set up the app
var app = angular.module('restTest', ['ngResource']);
// create a controller
app.controller('RestTestCtrl', function ($scope, $http, $resource) {
// initial greeting
$scope.greeting = "Not greeted yet";
// say hello, consuming the svc using $http
$scope.sayHelloHttp = function () {
var url = "http://localhost:7507/GreetingService.svc/sayHello/" + $scope.inName + "/";
$http.get(url).
success(function(data) {
$scope.greeting = data.Str;
});
}
// say hello, consuming the svc using $resource
$scope.sayHelloRest = function () {
var GreetingSvc = $resource("http://localhost:7507/GreetingService.svc/sayHello/:name");
GreetingSvc.get({ name: $scope.inName }, function(data) {
$scope.greeting = data.Str;
});
}
});
</script>
</head>
<body ng-controller="RestTestCtrl">
<!-- bind the value of this input to the scope -->
<input type="text" ng-model="inName"/>
<button ng-click="sayHelloHttp()">$http</button>
<button ng-click="sayHelloRest()">$resource</button>
<!-- bind the greeting property -->
<div>{{greeting}}</div>
</body>
</html>
I guess everything above can be expressed more advance but it should give you a basic and working example to get started.

silverlight is calling WCF service but not passing parameters

I have a WCF service in a project with the following setup
//interface
namespace AttendanceSystem
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together.
[ServiceContract]
public interface IService1
{
[OperationContract]
void DoWork(string s);
}
}
// and the following implementation
namespace AttendanceSystem
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
public class Service1 : IService1
{
public void DoWork(string s)
{
StreamWriter file = new StreamWriter(#"C:\users\waqasjafri\desktop\test.txt");
if (s == null)
{
file.WriteLine("value is null");
}
else
{
file.WriteLine("value is not null");
}
file.Close();
}
}
}
Here is the web.config file in the website application part of my solution that pertains to the wcf service
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
and the ServiceReference.clientconfig file in the silverlight project
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IService1" maxBufferSize="2147483647"
maxReceivedMessageSize="2147483647">
<security mode="None" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:48886/Service1.svc" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IService1" contract="ServiceReference1.IService1"
name="BasicHttpBinding_IService1" />
</client>
</system.serviceModel>
//here is the calling code ... it is in an event that gets triggered on a button click
ServiceReference1.Service1Client obj = new ServiceReference1.Service1Client();
obj.DoWorkAsync("Test");
The parameter is always being passed as null. what is going wrong? I can't figure it out since I'm new to integrating WCF/silverlight/Asp.net.
Update your service reference again. Try like this, In silverlight your method should have a completed event,
ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();
client.DoWorkCompleted += (a, ae) =>
{
};
client.DoWorkAsync("Test");
You might be exceeding the maxStringContentLength which is 8192 by default. See here:
http://msdn.microsoft.com/en-us/library/ms731361(v=vs.110).aspx
http://msdn.microsoft.com/en-us/library/ms731325(v=vs.110).aspx
You may want to consider using binary message encoding as well.

c# WCF HTTP rest client with WebHttpBinding/Behavior for POSTing RAW data in request body

I have a HTTP REST service endpoint I want to connect to send an XML stream in RAW format. Therefore I created a client side service contract "IHttpXmlClient" with a respective ClientBase inherited implementation.
using System.ServiceModel.Web;
using System.ServiceModel;
using System.IO;
namespace MyNamespace
{
[ServiceContract]
public interface IHttpXmlClient
{
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, Method = "POST")]
void Send(Stream data);
}
public class HttpXmlClient : ClientBase<IHttpXmlClient>, IHttpXmlClient
{
public void Send(Stream data)
{
Channel.Send(data);
}
}
}
As configuration I defined the following in my App.config:
<system.serviceModel>
<client>
<endpoint address="http://thehttpxmlserver.com:8000/receiver?client=itsme"
binding="webHttpBinding"
bindingConfiguration="webHttp_HttpXmlClient"
contract="MyNamespace.IHttpXmlClient"
behaviorConfiguration="HttpXmlClientEndpointBehavior">
</endpoint>
</client>
<bindings>
<webHttpBinding>
<binding name="webHttp_HttpXmlClient">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Basic" />
</security>
</binding>
</webHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="HttpXmlClientEndpointBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
The implementation to connect the HTTP REST service endpoint and send the respective XML data looks like this:
var myXml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><root>Hello World</root>";
using (var data = new MemoryStream(Encoding.UTF8.GetBytes(myXml)))
{
var client = new HttpXmlClient();
client.ClientCredentials.UserName.UserName = "myuser";
client.ClientCredentials.UserName.Password = "mypassword";
client.Open();
client.Send(data);
}
Everything is just working perfectly except the "content-type" HTTP header contains the wrong information. I used fiddler to trace the raw outgoing information from my machine. Here goes the result:
POST http://thehttpxmlserver.com:8000/receiver?client=itsme HTTP/1.1
Content-Type: application/octet-stream
Authorization: Basic bXl1c2VyOm15cGFzc3dvcmQ=
Host: thehttpxmlserver.com:8000
Content-Length: 65
Expect: 100-continue
<?xml version="1.0" encoding="utf-8"?><root>Hello World</root>
What I want is to set the "content-type" header to the value "text/xml;charset=UTF-8". Is that possible somehow? Of course I want to keep my above implementation and I definitely don't want to implement a manual WebRequest, where I loose the easy configuration in my application's config file!
Thank you very much in advance!
I found the solution by adding the following code to my ClientBase inherited class:
public class HttpXmlClient : ClientBase<IHttpXmlClient>, IHttpXmlClient
{
public void Send(Stream data)
{
using (new OperationContextScope((IContextChannel) Channel))
{
var request = WebOperationContext.Current.OutgoingRequest;
request.ContentType = "text/xml;charset=UTF-8";
Channel.Send(data);
}
}
}

Categories

Resources