SSL Passthrough with WCF or TransportWithMessageCredential over plain HTTP - c#

I was actually following this article
https://social.msdn.microsoft.com/Forums/vstudio/en-US/87a254c8-e9d1-4d4c-8f62-54eae497423f/how-to-ssl-passthrough-from-bigip?forum=wcf
I have complete step 1 and 2 but after implement custom binding that was discuss in step 3 I got following error
The Scheme cannot be computed for this binding because this CustomBinding lacks a TransportBindingElement. Every binding must have at least one binding element that derives from TransportBindingElement.
My Code for Custom Binding is as follows:
public class MyCustomBinding : Binding
{
private HttpTransportBindingElement transport;
private BinaryMessageEncodingBindingElement encoding;
public MyCustomBinding()
: base()
{
this.InitializeValue();
}
public override BindingElementCollection CreateBindingElements()
{
BindingElementCollection elements = new BindingElementCollection();
elements.Add(this.encoding);
elements.Add(this.transport);
return elements;
}
public override string Scheme
{
get { return this.transport.Scheme; }
}
private void InitializeValue()
{
this.transport = new HttpTransportBindingElement();
this.encoding = new BinaryMessageEncodingBindingElement();
}
}
public class MyCustomBindingCollectionElement : BindingCollectionElement
{
// type of custom binding class
public override Type BindingType
{
get { return typeof(MyCustomBinding); }
}
// override ConfiguredBindings
public override ReadOnlyCollection<IBindingConfigurationElement> ConfiguredBindings
{
get
{
return new ReadOnlyCollection<IBindingConfigurationElement>(
new List<IBindingConfigurationElement>());
}
}
// return Binding class object
protected override Binding GetDefault()
{
return new MyCustomBinding();
}
public override bool ContainsKey(string name) {
return true;
}
protected override bool TryAdd(string name, Binding binding, Configuration config)
{
return true;
}
}
and my web.Config is as follows:
<bindings>
<customBinding>
<binding name="MyCustomBinding">
<binaryMessageEncoding maxReadPoolSize="64" maxWritePoolSize="16"
maxSessionSize="2048">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
</binaryMessageEncoding>
<textMessageEncoding
messageVersion="Soap11WSAddressingAugust2004"/>
<httpsTransport manualAddressing="false" maxBufferPoolSize="524288"
maxReceivedMessageSize="65536" allowCookies="false" authenticationScheme="Anonymous"
bypassProxyOnLocal="false" decompressionEnabled="true" hostNameComparisonMode="StrongWildcard"
keepAliveEnabled="true" maxBufferSize="65536" proxyAuthenticationScheme="Anonymous"
realm="" transferMode="Buffered" unsafeConnectionNtlmAuthentication="false" useDefaultWebProxy="true"/>
</binding>
</customBinding>
</bindings>
<services>
<service name="ADHA.ADHAServiceApi" >
<endpoint address="" binding="customBinding" bindingConfiguration="MyCustomBinding" contract="ADHA.IADHAService">
</endpoint>
<endpoint address="mex" binding="customBinding" contract="IMetadataExchange" />
</service>
</services>
<extensions>
<bindingExtensions>
<!--<add name="ProxyElement" type="ADHA.Model.HttpTransportBindingElementProxy, ADHA"/>-->
<add name="MyCustomBinding" type="ADHA.Model.MyCustomBindingCollectionElement,ADHA,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bindingExtensions>
</extensions>

Related

Is it possible to use both a certificate and UserName credential type with a service? C#

I am working on a project that makes requests to an API (which I think is WCF). The API requires an X509 certificate as a credential to be able to make requests. After referencing the service for the client and trying to make some requests, I was told that my requests were also missing a UserName header credential in order to authenticate with the API. Is it possible to use both types of credentials in a request and if so would anyone know how to set it up? Sorry if this post is poorly made, this is my first post on this website.
Edit: The username header that I need is at the SOAP message level. A header in the actual SOAP xml request.
This is my program code (with personal info taken out):
using System;
using System.Security.Cryptography.X509Certificates;
using EFM_User_Service_Test.EfmUserService;
namespace EFM_User_Service_Test
{
class Program
{
static void Main(string[] args)
{
X509Certificate2 cert = new X509Certificate2("path to pfx file", "password for file");
EfmUserServiceClient client = new EfmUserServiceClient();
client.ClientCredentials.ClientCertificate.Certificate = cert;
client.ClientCredentials.UserName.UserName = "test#email.com";
client.ClientCredentials.UserName.Password = "test password";
client.Open();
AuthenticateRequestType req = new AuthenticateRequestType();
req.Email = "test#email.com";
req.Password = "test password";
AuthenticateResponseType response = client.AuthenticateUser(req);
string user_id = "";
if (response.Error != null && response.Error.ErrorCode != "0")
{
Console.WriteLine(response.Error.ErrorCode);
Console.WriteLine(response.Error.ErrorText);
} else
{
Console.WriteLine("{0} {1} is now signed in",response.FirstName,response.LastName);
Console.WriteLine("authenticated user id: "+response.UserID);
user_id = response.UserID;
}
GetUserRequestType user_req = new GetUserRequestType();
user_req.UserID = user_id;
var user_response = client.GetUser(user_req);
if (user_response.Error != null)
{
Console.WriteLine("Error Code: "+user_response.Error.ErrorCode);
Console.WriteLine("Error Text: "+user_response.Error.ErrorText);
} else
{
Console.WriteLine(user_response.User.Email);
Console.WriteLine(user_response.User.FirstName);
Console.WriteLine(user_response.User.LastName);
Console.WriteLine(user_response.User.Email);
}
client.Close();
}
}
}
and this is my App.config file for the program which seemed to be automatically generated when I added the service reference
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IEfmUserService" messageEncoding="Mtom">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="Certificate" />
</security>
</binding>
<binding name="BasicHttpBinding_IEfmUserService1" messageEncoding="Mtom">
<security mode="Transport" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="in place of actual address"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IEfmUserService"
contract="EfmUserService.IEfmUserService" name="BasicHttpBinding_IEfmUserService" />
</client>
</system.serviceModel>
</configuration>
In time I solved this by adding a ServiceBehavior to my app.config file. Note that in my case it's a WPF application.
<configuration>
<system.serviceModel>
<extensions>
<behaviorExtensions>
<!-- Add this -->
<add name="basicAuthenticationBehavior" type="ProjectNamespace.Behaviors.BasicAuthenticationBehaviorExtensionElement, ProjectAssemblyName, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<!-- Add this -->
<endpointBehaviors>
<behavior name="basicAuthenticationEndpointBehavior">
<basicAuthenticationBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<!-- After adding your service reference, your bindings appear here -->
<binding name="DMSinterfaceSoap">
<security mode="Transport" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<!-- After adding your service reference, your endpoints appear here -->
<endpoint address="https://soa.example.com/dmsinterface/DMS_service.asmx" binding="basicHttpBinding" behaviorConfiguration="basicAuthenticationEndpointBehavior" ... />
</client>
</system.serviceModel>
<configuration>
The BasicAuthenticationBehaviorExtensionElement looks like this
// Add System.Configuration reference to your ExecutingAssembly
internal class BasicAuthenticationBehaviorExtensionElement : BehaviorExtensionElement
{
/// <summary>Set this property if you want to send specific headers along with each request</summary>
public static TCredentials Credentials { internal get; set; }
public override Type BehaviorType
{
get { return typeof(BasicAuthenticationEndpointBehavior); }
}
protected override object CreateBehavior()
{
return new BasicAuthenticationEndpointBehavior();
}
}
class BasicAuthenticationEndpointBehavior : IEndpointBehavior
{
public BasicAuthenticationEndpointBehavior()
{
}
public void Validate(ServiceEndpoint endpoint) { }
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(new BasicAuthenticationClientMessageInspector());
}
}
class BasicAuthenticationClientMessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
if (BasicAuthenticationBehaviorExtensionElement.Credentials != null)
{
SetRequestHeader(ref request, "USERID", BasicAuthenticationBehaviorExtensionElement.Credentials.Credential.UserName);
SetRequestHeader(ref request, "PSWD", BasicAuthenticationBehaviorExtensionElement.Credentials.Credential.Password);
}
return null;
}
private void SetRequestHeader(ref Message request, string key, string value)
{
object httpRequestMessageObject;
if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
{
var httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
if (httpRequestMessage != null)
{
httpRequestMessage.Headers[key] = (value ?? string.Empty);
}
else
{
httpRequestMessage = new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(key, (value ?? string.Empty));
request.Properties[HttpRequestMessageProperty.Name] = httpRequestMessage;
}
}
else
{
var httpRequestMessage = new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(key, (value ?? string.Empty));
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
}
}
public void AfterReceiveReply(ref Message reply, object correlationState) { }
}
This adds the authentication header to each request, based on the object stored in static variable.
Then you can send the soap call with the authentication certificate:
#region Get Certificate
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
var foundCerts = store.Certificates.Find(X509FindType.FindBySerialNumber, credential.CertificateSerial, false);
if (foundCerts.Count == 0)
throw new Exceptions.NoValidCertificateException();
#endregion
#region Setup client
// Check the App.config for available endpoint configuration names
var client = new MySoapService.DMSinterfaceSoapClient("DMSinterfaceSoap.0");
if (client.Endpoint.Binding is System.ServiceModel.BasicHttpBinding basicBinding)
{
// DMSinterfaceSoap and DMSinterfaceSoap.0 are BasicHttpBindings
// The ClientCredentialType is by default set to None. You should be able to combine HttpClientCredentialTypes but sadly that's not possible.
basicBinding.Security.Transport.ClientCredentialType = System.ServiceModel.HttpClientCredentialType.Certificate;
}
else if (client.Endpoint.Binding is System.ServiceModel.Channels.CustomBinding customBinding)
{
// DMSinterfaceSoap12 and DMSinterfaceSoap12.0 are CustomBindings
// Whatever additional configuration needs to be performed
}
#endregion
// Set the USERID and PSWD header
BasicAuthenticationBehaviorExtensionElement.Credentials.Credential = credential;
// Send certificate along with request
client.ClientCredentials.ClientCertificate.Certificate = foundCerts[0];
var error = client.GetResult(req, out res);
// Clear the USERID and PSWD header
BasicAuthenticationBehaviorExtensionElement.Credentials.Credential = null;

Leading whitespaces disappear with WCF MTOM

According to this post...
Space disappears on transferring via wcf (xml)
... there is a configrmed bug, when sending a string with leading spaces (e.g. foo using an MTOM binding in WCF. The leading spaces will not be preserved.
I tried to fix the bug using the given suggestion but had no luck yet. Here is my code:
The binding that we use:
<binding name="foo_wsHTTPbinding_MitSSL_MTOM" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647" sendTimeout="00:02:00" receiveTimeout="00:01:00" openTimeout="00:01:00" closeTimeout="00:01:00" messageEncoding="Mtom">
<readerQuotas maxArrayLength="2147483647" maxStringContentLength="2147483647" />
<security mode="Transport">
<transport clientCredentialType="Windows" proxyCredentialType="Windows" />
<message clientCredentialType="Windows" />
</security>
</binding>
The code that we added:
Inside the config:
<extensions>
<behaviorExtensions>
<add name="preserveLeadingSpacesBehavior" type="Core.ServiceBase.PreserveLeadingSpacesBehaviorExtensionElement, Core.ServiceBase"/>
</behaviorExtensions>
</extensions>
<behaviors>
<serviceBehaviors>[...]</serviceBehaviors>
<endpointBehaviors>
[...]
<behavior>
<preserveLeadingSpacesBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
PreserveLeadingSpacesBehaviorExtensionElement.cs:
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace Core.ServiceBase
{
public class PreserveLeadingSpacesBehaviorExtensionElement : BehaviorExtensionElement
{
protected override object CreateBehavior()
{
return new PreserveLeadingSpacesBehavior();
}
public override Type BehaviorType
{
get { return typeof(PreserveLeadingSpacesBehavior); }
}
}
public class PreserveLeadingSpacesBehavior : IEndpointBehavior
{
public void Validate(ServiceEndpoint endpoint)
{
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new PreserveLeadingSpacesInspector());
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
}
public class PreserveLeadingSpacesInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
request = request.CreateBufferedCopy(int.MaxValue).CreateMessage();
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
}
}
}
According to the blog post here, this should resolve the problem.
But it is not working so far. When setting a breakpoint, it gets hit in AfterReceiveRequest and I see in the message, that the spaces are still there, but in my service method, it disappeared.
Heres my test method on the server site:
public string Foo(string bar)
{
return bar;
}
Do you have any idea, why it could still not work?

WCF protobuf endpoint 400 bad request

I have WCF service with DataContract json serialization. I would like to add service endpoints to consume Protobuf data messages.
I tried to use nugget package ProtoBuf.Services.WCF.
Added endpoint via web.config configuration. However, every request on protobuf endpoint with address "proto" returns 400 Bad request. Web.config sample is written bellow. Endpoint with default address "" works properly.
Get method:
HTTP 200 OK http://localhost:65460/BeaconService.svc/GetData
HTTP 400 BAD REQUEST: http://localhost:65460/BeaconService.svc/proto/GetData
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding transferMode="Streamed">
<security mode="None" />
</binding>
</webHttpBinding>
<basicHttpBinding>
<binding messageEncoding="Mtom">
<security mode="None" />
</binding>
</basicHttpBinding>
</bindings>
<extensions>
<behaviorExtensions>
<add name="protobuf" type="ProtoBuf.ServiceModel.ProtoBehaviorExtension, protobuf-net" />
</behaviorExtensions>
</extensions>
<services>
<service behaviorConfiguration="DefaultServiceBehavior" name="Services.BeaconService">
<endpoint address="" behaviorConfiguration="httpBehavior" binding="webHttpBinding" contract="Services.IBeaconService" />
<endpoint address="proto" behaviorConfiguration="protoBehavior" binding="basicHttpBinding" contract="Services.IBeaconService" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="protoBehavior">
<protobuf />
</behavior>
<behavior name="httpBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</system.serviceModel>
Please, which part of configuration is defective. Eventualy, what is proper way to call Get method on "proto" WCF endpoint to avoid HTTP 400 Bad request message?
Unfortunately, I have failed to implement ProtoBuf.Services.WCF and decided to use another approach. In general, WCF by default uses DataContractSerializer.
After reading this article, I realized It is possible to replace this serializer with another one, f.e. protobuf serializer in this library. So I created behavior extension, which replaces DataContractSerializer with my custom ProtobufSerializer. In configuration added another endpoint, which has set behavior extension to use my custom ProtobufSerializer.
WebHttpBehavior:
public class ProtobufBehavior : WebHttpBehavior
{
protected override IDispatchMessageFormatter GetRequestDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
{
return new ProtobufDispatchFormatter(operationDescription);
}
protected override IDispatchMessageFormatter GetReplyDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
{
return new ProtobufDispatchFormatter(operationDescription);
}
}
Dispatch formatter:
namespace Services.Extension.ProtobufSerializationExtension
{
public class ProtobufDispatchFormatter : IDispatchMessageFormatter
{
OperationDescription operation;
bool isVoidInput;
bool isVoidOutput;
public ProtobufDispatchFormatter(OperationDescription operation)
{
this.operation = operation;
this.isVoidInput = operation.Messages[0].Body.Parts.Count == 0;
this.isVoidOutput = operation.Messages.Count == 1 || operation.Messages[1].Body.ReturnValue.Type == typeof(void);
}
public void DeserializeRequest(Message message, object[] parameters)
{
if (!message.IsEmpty)
{
XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
bodyReader.ReadStartElement("Binary");
byte[] rawBody = bodyReader.ReadContentAsBase64();
MemoryStream ms = new MemoryStream(rawBody);
using (StreamReader sr = new StreamReader(ms))
for (int i = 0; i < parameters.Length; i++)
parameters[i] = Serializer.Deserialize(operation.Messages[i].Body.Parts[i].Type, sr.BaseStream);
}
}
public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
byte[] body;
using (MemoryStream ms = new MemoryStream())
using (StreamWriter sw = new StreamWriter(ms))
{
Serializer.Serialize(sw.BaseStream, result);
sw.Flush();
body = ms.ToArray();
}
Message replyMessage = Message.CreateMessage(messageVersion, operation.Messages[1].Action, new RawBodyWriter(body));
replyMessage.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));
return replyMessage;
}
class RawBodyWriter : BodyWriter
{
internal static readonly byte[] EmptyByteArray = new byte[0];
byte[] content;
public RawBodyWriter(byte[] content)
: base(true)
{
this.content = content;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement("Binary");
writer.WriteBase64(content, 0, content.Length);
writer.WriteEndElement();
}
}
}
}
Extension element:
namespace Services.Extension.ProtobufSerializationExtension
{
public class ProtobufSerializationServiceElement : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(ProtobufBehavior); }
}
protected override object CreateBehavior()
{
return new ProtobufBehavior();
}
}
}
Web config:
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding transferMode="Streamed">
<security mode="None" />
</binding>
</webHttpBinding>
</bindings>
<extensions>
<behaviorExtensions>
<add name="protobufExtension" type="Services.Extension.ProtobufSerializationExtension.ProtobufSerializationServiceElement, Services" />
</behaviorExtensions>
</extensions>
<services>
<service behaviorConfiguration="DefaultServiceBehavior" name="Services.BeaconService">
<endpoint address="" behaviorConfiguration="httpBehavior" binding="webHttpBinding" contract="Services.IBeaconService" />
<endpoint address="proto" behaviorConfiguration="protoBehavior" binding="webHttpBinding" contract="Services.IBeaconService" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="protoBehavior">
<webHttp/>
<protobufExtension/>
</behavior>
<behavior name="httpBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</system.serviceModel>
Services.Extension.ProtobufSerializationExtension is name of my custom namespace inside application structure. Hope this helps someone.

WCF Callback not calling back

I have read many articles here on SO, and on MSDN and Code Project. And still the answer eludes me.
I have created a WCF service that has a callback contract, and a Windows Forms app (test harness) that implements the callback interface.
Here are the interfaces:
[ServiceContract]
public interface ICacheService
{
[OperationContract]
bool AddToCache(string type, string key, string source, object item, int duration);
[OperationContract]
bool Remove(string key);
}
[ServiceContract(SessionMode=SessionMode.Required, CallbackContract=typeof(ISearchCallback))]
public interface ICacheSearch
{
[OperationContract(IsOneWay=true)]
void SearchCache(string source);
}
public interface ISearchCallback
{
[OperationContract(IsOneWay=true)]
void OnCallback(object data);
}
My service (left out the Add and Remove to conserve space - they work as expected):
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode=ConcurrencyMode.Multiple)]
public class CachingService : ICacheService, ICacheSearch, IDisposable
{
private List<ICache> _cacheList = new List<ICache>();
private ISearchCallback _callback = null;
public void SearchCache(string source)
{
try
{
_callback = OperationContext.Current.GetCallbackChannel<ISearchCallback>();
List<object> items = new List<object>();
foreach (ICache cache in this._cacheList)
{
foreach (CacheItem item in cache.GetCacheItemsBySource(source))
{
items.Add(item.Value);
}
}
ReadOnlyCollection<object> outList = new ReadOnlyCollection<object>(items);
_callback.OnCallback(outList);
}
catch { }
}
}
My service is hosted in a Windows Service. Here is the system.serviceModel section from app.config:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding
name="WSHttpBinding_ICacheService"
closeTimeout="00:01:00"
openTimeout="00:01:00"
receiveTimeout="00:01:00"
sendTimeout="00:01:00"
maxBufferPoolSize="2147483647"
maxReceivedMessageSize="2147483647">
<readerQuotas maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxDepth="2147483647" maxNameTableCharCount="2147483647" maxStringContentLength="2147483647"/>
</binding>
</wsHttpBinding>
<wsDualHttpBinding>
<binding
name="WSDualHttpBinding_ICacheSearch"
closeTimeout="00:01:00"
openTimeout="00:01:00"
receiveTimeout="00:01:00"
sendTimeout="00:01:00"
maxBufferPoolSize="2147483647"
maxReceivedMessageSize="2147483647"
clientBaseAddress="http://localhost:5057/wsDualHttpBinding"
useDefaultWebProxy="true"
transactionFlow="false">
<readerQuotas maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxDepth="2147483647" maxNameTableCharCount="2147483647" maxStringContentLength="2147483647"/>
</binding>
</wsDualHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="CacheService.CachingService">
<host>
<baseAddresses>
<add baseAddress="http://localhost:5055/CachingService/"/>
</baseAddresses>
</host>
<endpoint address="Search" binding="wsDualHttpBinding" contract="CacheService.ICacheSearch" />
<endpoint address="" binding="wsHttpBinding" contract="CacheService.ICacheService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<client>
<endpoint address="http://localhost:5055/CachingService/Search" binding="wsDualHttpBinding"
bindingConfiguration="WSDualHttpBinding_ICacheSearch" contract="CacheService.ICacheSearch"
name="WSDualHttpBinding_ICacheSearch" />
<endpoint address="http://localhost:5055/CachingService" binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_ICacheService" contract="CacheService.ICacheService"
name="WSHttpBinding_ICacheService" />
</client>
</system.serviceModel>
Finally, I created a simple Windows Forms app to test. It is declared as:
[CallbackBehavior(UseSynchronizationContext = false, ConcurrencyMode = ConcurrencyMode.Single)]
public partial class Form1 : Form, cacheService.ICacheSearchCallback
{
InstanceContext context = null;
cacheService.CacheServiceClient _service = null;
cacheService.CacheSearchClient _proxy = null;
private void Form1_Load(object sender, EventArgs e)
{
_syncContext = System.Threading.SynchronizationContext.Current;
_service = new cacheService.CacheServiceClient();
this.dataGridView1.AutoGenerateColumns = true;
context = new InstanceContext(this);
_proxy = new cacheService.CacheSearchClient(context);
RefreshList();
}
private void RefreshList()
{
_proxy.SearchCache("mySource");
}
public void OnCallback(object data)
{
this._data = data;
this.dataGridView1.DataSource = this._data;
}
The call to the service (_proxy.SearchCache("mySource")) successfully executes the method on the service and service calls _callback.OnCallback(outList). But the callback method implemented in my test app is never called and so the data are never conveyed to the test app.
Again, I have looked at every recent WCF article on SO and many other blogs and code sites. I feel like I need a another pair of eyes to point out the obvious so I can get back to work.
Many thanks,
Robert

WCF service will not return 500 Internal Server Error. Instead, only 400 Bad Request

I've created a simple RESTful WCF file streaming service. When an error occurs, I would like a 500 Interal Server Error response code to be generated. Instead, only 400 Bad Requests are generated.
When the request is valid, I get the proper response (200 OK), but even if I throw an Exception I get a 400.
IFileService:
[ServiceContract]
public interface IFileService
{
[OperationContract]
[WebInvoke(Method = "GET",
BodyStyle = WebMessageBodyStyle.Bare,
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "/DownloadConfig")]
Stream Download();
}
FileService:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class GCConfigFileService : IGCConfigFileService
{
public Stream Download()
{
throw new Exception();
}
}
Web.Config
<location path="FileService.svc">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
<system.serviceModel>
<client />
<behaviors>
<serviceBehaviors>
<behavior name="FileServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="web">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
<services>
<service name="FileService"
behaviorConfiguration="FileServiceBehavior">
<endpoint address=""
binding="webHttpBinding"
bindingConfiguration="FileServiceBinding"
behaviorConfiguration="web"
contract="IFileService"></endpoint>
</service>
</services>
<bindings>
<webHttpBinding>
<binding
name="FileServiceBinding"
maxBufferSize="2147483647"
maxReceivedMessageSize="2147483647"
transferMode="Streamed"
openTimeout="04:01:00"
receiveTimeout="04:10:00"
sendTimeout="04:01:00">
<readerQuotas maxDepth="2147483647"
maxStringContentLength="2147483647"
maxArrayLength="2147483647"
maxBytesPerRead="2147483647"
maxNameTableCharCount="2147483647" />
</binding>
</webHttpBinding>
</bindings>
SIMPLE:
Try out throw new WebFaultException(HttpStatusCode.InternalServerError);
To specify an error details:
throw new WebFaultException<string>("Custom Error Message!", HttpStatusCode.InternalServerError);
ADVANCED:
If you want better exception handling with defining HTTP status for each exception you'll need to create a custom ErrorHandler class for example:
class HttpErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return false;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (fault != null)
{
HttpResponseMessageProperty properties = new HttpResponseMessageProperty();
properties.StatusCode = HttpStatusCode.InternalServerError;
fault.Properties.Add(HttpResponseMessageProperty.Name, properties);
}
}
}
Then you need to create a service behaviour to attach to your service:
class ErrorBehaviorAttribute : Attribute, IServiceBehavior
{
Type errorHandlerType;
public ErrorBehaviorAttribute(Type errorHandlerType)
{
this.errorHandlerType = errorHandlerType;
}
public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
{
}
public void AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
IErrorHandler errorHandler;
errorHandler = (IErrorHandler)Activator.CreateInstance(errorHandlerType);
foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
channelDispatcher.ErrorHandlers.Add(errorHandler);
}
}
}
Attaching to behaviour:
[ServiceContract]
public interface IService
{
[OperationContract(Action = "*", ReplyAction = "*")]
Message Action(Message m);
}
[ErrorBehavior(typeof(HttpErrorHandler))]
public class Service : IService
{
public Message Action(Message m)
{
throw new FaultException("!");
}
}

Categories

Resources