Error control/handler (capture) with Behavior in wcf. Business layer - c#

I have a project (backend) where it receives a call in the service layer, but it also makes calls to another API in the business layer.
I have added a behaviour to those calls so I do not have to make changes to several places in my code.
My Behaviour.config (the importatn is ExternalDependencyBehavior):
<?xml version="1.0" encoding="utf-8"?>
<behaviors>
<endpointBehaviors>
<!-- SPECIFIC BEHAVIOR EXTENSION FOR JSON URL DESERIALIZER-->
<behavior name="ServiceRESTBehavior">
<ConverterWebHttpBehaviourExtension />
</behavior>
<behavior name="ServiceSOAPBehavior" />
<behavior name="ExternalDependencyBehavior">
<ExternalClientBehavior/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="DefaultBehaviour">
<serviceMetadata httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
My code: (one method in business layer that call a other API external)
protected virtual Vendor.Sinacofi.SinacofiService.RespuestaSNPV1201 RequestToVendor(VendorWebService vendor, string clientFiscalId, BasicSearchCriterion criteria)
{
Vendor.Sinacofi.SinacofiService.RespuestaSNPV1201 result = new Vendor.Sinacofi.SinacofiService.RespuestaSNPV1201();
string errorLogProvider = null;
try
{
using (Vendor.Sinacofi.SinacofiService.SNPV1201SoapClient client = new Vendor.Sinacofi.SinacofiService.SNPV1201SoapClient())
{
/*Credentials*/
result = client.ConsultaRutificacion(
client.ClientCredentials.HttpDigest.ClientCredential.UserName,
client.ClientCredentials.HttpDigest.ClientCredential.Password,
clientFiscalId,
criteria.Value
);
}
return result;
}
catch (AxrException)
{
throw;
}
catch (FaultException fex)
{
this.logProvider.WriteInErrorMode(GetType().Name, MethodBase.GetCurrentMethod().Name, fex);
throw this.exceptionProvider.Create(ErrorCode.---------, ExceptionType.BUSINESS, fex);
}
catch (Exception ex)
{
this.logProvider.WriteInErrorMode(GetType().Name, MethodBase.GetCurrentMethod().Name, ex);
throw this.exceptionProvider.Create(ErrorCode.-------------, ExceptionType.BUSINESS, ex);
}
}
In summary:
When I arrive at the call:
"client.ConsultaRutification"
My behaviour is executed, I add the code (2 files):
using System;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace SIPE.Search.Helpers
{
/// <summary>
/// Implements methods that can be used to extend run-time behavior for an endpoint in either a client application.
/// </summary>
public class ExternalClientBehavior : BehaviorExtensionElement
{
protected override object CreateBehavior()
{
return new ExternalClientBehaviorClass();
}
public override Type BehaviorType
{
get
{
return typeof(ExternalClientBehaviorClass);
}
}
/// <summary>
/// JSON REST[GET] Converter Behavior
/// </summary>
private class ExternalClientBehaviorClass : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
ExternalClientMessageInspector clientInspector = new ExternalClientMessageInspector(endpoint);
clientRuntime.MessageInspectors.Add(clientInspector);
foreach (ClientOperation op in clientRuntime.Operations)
{
op.ParameterInspectors.Add(clientInspector);
}
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new ExternalClientMessageInspector(endpoint));
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
}
}
And file two:
namespace SIPE.Search.Helpers
{
/// <summary>
/// Intercepts send requests.
/// </summary>
public class ExternalClientMessageInspector : IClientMessageInspector, IParameterInspector, IErrorHandler
{
private ServiceEndpoint Endpoint { get; set; }
Dictionary<string, object> inputsParam;
public ExternalClientMessageInspector(ServiceEndpoint endpoint)
{
//empty
}
public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
{
((Dictionary<string, string>)(System.Web.HttpContext.Current.Items[operationName])).Add("OutputParam", JsonConvert.SerializeObject(returnValue));
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
//code not necessary
}
public object BeforeCall(string operationName, object[] inputs)
{
// code not neccesary
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
Message copy = buffer.CreateMessage(); // Create a copy to work with
request = buffer.CreateMessage(); // Restore the original message
return copy.GetReaderAtBodyContents().Name;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
// no implement
}
public bool HandleError(Exception error)
{
// no implement
return true;
}
public void NotifyError()
{
}
}
}
I need to be able to capture the errors, (for example 404 and others) of my calls within my behaviour.
I have tried to add several interfaces, but without success
The error is captured by the catch of the RequestToVendor method that is in the business layer.
But I need to be able to capture it in my behaviour.
thank you

Related

WCF Custom Headers/ Custom Behaviors class not available

I'm fairly new at WCF so i'm positive this is something i'm doing wrong. I was following this link Sample link
Basically i'm trying to implement 'IClientMessageInspector' so that every call to my WCF must contain custom attributes that I need for my WCF methods to perform the correct activities.
In my Console Consumer app I'm trying to add my custom endpoint behavior class. However when I go to add the class it's not available. Here is my consumer app you'll notice the line of code i'm having the issue with.
Program.cs
class Program
{
static void Main(string[] args)
{
using (Service1Client s1c = new Service1Client())
{
//cannot add 'CustomBehavior' this is my issue
s1c.ChannelFactory.Endpoint.EndpointBehaviors.Add(
}
}
}
My Service
Service1.svc.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
namespace WCFHeaderCalls
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
// NOTE: In order to launch WCF Test Client for testing this service, please select Service1.svc or Service1.svc.cs at the Solution Explorer and start debugging.
[CustomBehavior]
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;
}
}
}
IService1.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
namespace WCFHeaderCalls
{
// 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]
string GetData(int value);
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
// TODO: Add your service operations here
}
// Use a data contract as illustrated in the sample below to add composite types to service operations.
[DataContract]
public class CompositeType
{
bool boolValue = true;
string stringValue = "Hello ";
[DataMember]
public bool BoolValue
{
get { return boolValue; }
set { boolValue = value; }
}
[DataMember]
public string StringValue
{
get { return stringValue; }
set { stringValue = value; }
}
}
}
ContextClass.cs
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace WCFHeaderCalls
{
/// <summary>
/// This class will act as a custom context in the client side to hold the context information.
/// </summary>
public class ClientContext
{
public static string applicationKey;
public static string methodKey;
public static string accountName;
public static string accountPassword;
}
/// <summary>
/// This class will act as a custom context, an extension to the OperationContext.
/// This class holds all context information for our application.
/// </summary>
public class ServerContext : IExtension<OperationContext>
{
public string EmployeeID;
public string WindowsLogonID;
public string KerberosID;
public string SiteminderToken;
// Get the current one from the extensions that are added to OperationContext.
public static ServerContext Current
{
get
{
return OperationContext.Current.Extensions.Find<ServerContext>();
}
}
#region IExtension<OperationContext> Members
public void Attach(OperationContext owner)
{
}
public void Detach(OperationContext owner)
{
}
#endregion
}
}
MessageInspector.cs
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace WCFHeaderCalls
{
/// <summary>
/// This class is used to inspect the message and headers on the server side,
/// This class is also used to intercept the message on the
/// client side, before/after any request is made to the server.
/// </summary>
public class CustomMessageInspector : IClientMessageInspector, IDispatchMessageInspector
{
#region Message Inspector of the Service
/// <summary>
/// This method is called on the server when a request is received from the client.
/// </summary>
/// <param name="request"></param>
/// <param name="channel"></param>
/// <param name="instanceContext"></param>
/// <returns></returns>
public object AfterReceiveRequest(ref Message request,
IClientChannel channel, InstanceContext instanceContext)
{
// Create a copy of the original message so that we can mess with it.
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
Message messageCopy = buffer.CreateMessage();
// Read the custom context data from the headers
ServiceHeader customData = CustomHeader.ReadHeader(request);
// Add an extension to the current operation context so
// that our custom context can be easily accessed anywhere.
ServerContext customContext = new ServerContext();
if (customData != null)
{
customContext.KerberosID = customData.accountName;
customContext.SiteminderToken = customData.accountPassword;
}
OperationContext.Current.IncomingMessageProperties.Add(
"CurrentContext", customContext);
return null;
}
/// <summary>
/// This method is called after processing a method on the server side and just
/// before sending the response to the client.
/// </summary>
/// <param name="reply"></param>
/// <param name="correlationState"></param>
public void BeforeSendReply(ref Message reply, object correlationState)
{
// Do some cleanup
OperationContext.Current.Extensions.Remove(ServerContext.Current);
}
#endregion
#region Message Inspector of the Consumer
/// <summary>
/// This method will be called from the client side just before any method is called.
/// </summary>
/// <param name="request"></param>
/// <param name="channel"></param>
/// <returns></returns>
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
// Prepare the request message copy to be modified
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
ServiceHeader customData = new ServiceHeader();
customData.accountName = ClientContext.accountName;
customData.accountPassword = ClientContext.accountPassword;
CustomHeader header = new CustomHeader(customData);
// Add the custom header to the request.
request.Headers.Add(header);
return null;
}
/// <summary>
/// This method will be called after completion of a request to the server.
/// </summary>
/// <param name="reply"></param>
/// <param name="correlationState"></param>
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
#endregion
}
}
CustomHeader.cs
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace WCFHeaderCalls
{
[DataContract]
public class ServiceHeader
{
/// <summary>
/// this is the application-GUID specify this per app (OI,office,etc..)
/// </summary>
[DataMember]
public string applicationKey { get; set; }
/// <summary>
/// this is the method key you wish to implament
/// </summary>
[DataMember]
public string methodKey { get; set; }
/// <summary>
/// this is your account name
/// </summary>
[DataMember]
public string accountName { get; set; }
/// <summary>
/// this is your account password
/// </summary>
[DataMember]
public string accountPassword { get; set; }
}
public class CustomHeader : MessageHeader
{
private const string CUSTOM_HEADER_NAME = "HeaderName";
private const string CUSTOM_HEADER_NAMESPACE = "YourNameSpace";
private ServiceHeader _customData;
public ServiceHeader CustomData
{
get
{
return _customData;
}
}
public CustomHeader()
{
}
public CustomHeader(ServiceHeader customData)
{
_customData = customData;
}
public override string Name
{
get { return (CUSTOM_HEADER_NAME); }
}
public override string Namespace
{
get { return (CUSTOM_HEADER_NAMESPACE); }
}
protected override void OnWriteHeaderContents(
System.Xml.XmlDictionaryWriter writer, MessageVersion messageVersion)
{
XmlSerializer serializer = new XmlSerializer(typeof(ServiceHeader));
StringWriter textWriter = new StringWriter();
serializer.Serialize(textWriter, _customData);
textWriter.Close();
string text = textWriter.ToString();
writer.WriteElementString(CUSTOM_HEADER_NAME, "Key", text.Trim());
}
public static ServiceHeader ReadHeader(Message request)
{
Int32 headerPosition = request.Headers.FindHeader(CUSTOM_HEADER_NAME, CUSTOM_HEADER_NAMESPACE);
if (headerPosition == -1)
return null;
MessageHeaderInfo headerInfo = request.Headers[headerPosition];
XmlNode[] content = request.Headers.GetHeader<XmlNode[]>(headerPosition);
string text = content[0].InnerText;
XmlSerializer deserializer = new XmlSerializer(typeof(ServiceHeader));
TextReader textReader = new StringReader(text);
ServiceHeader customData = (ServiceHeader)deserializer.Deserialize(textReader);
textReader.Close();
return customData;
}
}
}
CustomBehavior.cs
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace WCFHeaderCalls
{
/// <summary>
/// This custom behavior class is used to add both client and server inspectors to
/// the corresponding WCF end points.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class CustomBehavior : Attribute, IServiceBehavior, IEndpointBehavior
{
#region IEndpointBehavior Members
void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
CustomMessageInspector inspector = new CustomMessageInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
ChannelDispatcher channelDispatcher = endpointDispatcher.ChannelDispatcher;
if (channelDispatcher != null)
{
foreach (EndpointDispatcher ed in channelDispatcher.Endpoints)
{
CustomMessageInspector inspector = new CustomMessageInspector();
ed.DispatchRuntime.MessageInspectors.Add(inspector);
}
}
}
void IEndpointBehavior.Validate(ServiceEndpoint endpoint) { }
#endregion
#region IServiceBehavior Members
void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription desc, ServiceHostBase host)
{
foreach (ChannelDispatcher cDispatcher in host.ChannelDispatchers)
foreach (EndpointDispatcher eDispatcher in cDispatcher.Endpoints)
eDispatcher.DispatchRuntime.MessageInspectors.Add(new CustomMessageInspector());
}
void IServiceBehavior.Validate(ServiceDescription desc, ServiceHostBase host) { }
#endregion
}
}
And finally here is my services web.config
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5"/>
</system.web>
<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>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<!--
To browse web app root directory during debugging, set the value below to true.
Set to false before deployment to avoid disclosing web app folder information.
-->
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>
The message behaviors won't be available via WSDL in generated proxy in your consumer app. So either you have to add the same code(classes) in client side for message inspector or create a shared project(class library) for contracts between service and client through which the behavior class will be available in both.

Client-side WCF ExceptionHandler

Is it possible to handle errors like TimeoutException, EndpointNotFoundException, OperationTimeoutException, etc on client side using BehaviorExtension?
I don't want to try-catch-log-restart-connection every time when i'm using WCF proxy.
This code does not working:
Behavyior:
public class EndpointBehavior:IEndpointBehavior
{
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)
{
var handler = new WcfErrorHandler();
clientRuntime.CallbackDispatchRuntime.ChannelDispatcher.ErrorHandlers.Add(handler);
}
}
Handler:
public class WcfErrorHandler:IErrorHandler
{
public bool HandleError(Exception error)
{
Log.Instance.Trace(#"WCF Service Error:", error);
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
var newEx = new FaultException(
string.Format(#"Exception caught at WcfErrorHandler{0} Method: {1}{2}Message:{3}",
Environment.NewLine, error.TargetSite.Name, Environment.NewLine, error.Message));
var msgFault = newEx.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, newEx.Action);
}
}
Extension for app.config
public class ExceptionLogBehaviorExtensionElement : BehaviorExtensionElement
{
public override Type BehaviorType
{
get
{
return typeof(EndpointBehavior);
}
}
protected override object CreateBehavior()
{
return new EndpointBehavior();
}
}
Your code is not working because IErrorHandler is a server-side-only feature.
In your code, it looks like you are adding the error handler to client-side requests, but actually that's not the case. That's because clientRuntime.CallbackDispatchRuntime is a server-like entity that lives in the client and is used to receive messages from the real server in duplex operations.
Regarding your main question about client-side exception handling, the best I can recommend is IClientMessageInspector. Here's some code to help you get started:
public sealed class LoggingEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new LoggingInspector());
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
}
public sealed class LoggingInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
Console.WriteLine("BeforeSendRequest");
return null;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
Console.WriteLine("AfterReceiveReply");
}
}
However, note that IClientMessageInspector has a drawback. Its AfterReceiveReply method is called on successful and faulty replies from the server, but is not called when there isn't any reply at all, e.g. on TimeoutException, EndpointNotFoundException, etc.

passing culture value inside wcf service hosted by iis

Here is my code..
private IHelloWorld ChannelFactoryWebService()
{
ServiceEndpoint tcpEndPoint = new ServiceEndpoint(
ContractDescription.GetContract(typeof(IHelloWorld)),
new NetTcpBinding(),
new EndpointAddress("net.tcp://myserver/CultureTest/Service.svc"));
ChannelFactory<IHelloWorld> factory = new ChannelFactory<IHelloWorld>(tcpEndPoint);
factory.Endpoint.Behaviors.Add(new CultureBehaviour());
return factory.CreateChannel();
}
[TestMethod] <-------------- FAILING TEST ----
public void ChangingCultureWASViaEndPointTest()
{
IHelloWorld client = ChannelFactoryWebService();
CultureInfo cultureInfo = new CultureInfo("ar-SA");
Thread.CurrentThread.CurrentCulture = cultureInfo;
client.Hello();
string culture = client.HelloCulture();
Assert.AreEqual("ar-SA", culture); <--- fails here.. I get en-US for culture
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, IncludeExceptionDetailInFaults = true)]
public class Server : IHelloWorld
{
#region IHelloWorld Members
public void Hello()
{
Console.WriteLine(string.Format("Hello world from the server in culture {0}", Thread.CurrentThread.CurrentCulture.Name));
}
public string HelloCulture()
{
string cultureName = Thread.CurrentThread.CurrentCulture.Name;
return cultureName;
}
#endregion
}
[ServiceContract]
public interface IHelloWorld
{
[OperationContract]
void Hello();
[OperationContract]
string HelloCulture();
}
public class CultureMessageInspector : IClientMessageInspector, IDispatchMessageInspector
{
private const string HeaderKey = "culture";
#region IDispatchMessageInspector Members
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
int headerIndex = request.Headers.FindHeader(HeaderKey, string.Empty);
if (headerIndex != -1)
{
Thread.CurrentThread.CurrentCulture = new CultureInfo(request.Headers.GetHeader<String>(headerIndex));
}
return null;
}
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
}
#endregion
#region IClientMessageInspector Members
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
request.Headers.Add(MessageHeader.CreateHeader(HeaderKey, string.Empty, Thread.CurrentThread.CurrentCulture.Name));
return null;
}
#endregion
}
public class CultureBehaviour : IEndpointBehavior
{
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
CultureMessageInspector inspector = new CultureMessageInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
CultureMessageInspector inspector = new CultureMessageInspector();
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
}
public void Validate(ServiceEndpoint endpoint)
{
}
#endregion
}
public class CultureBehaviorExtension : BehaviorExtensionElement
{
// BehaviorExtensionElement members
public override Type BehaviorType
{
get { return typeof(CultureBehaviour); }
}
protected override object CreateBehavior()
{
return new CultureBehaviour();
}
}
Web config for the hosting site for Service looks as follows:
<endpointBehaviors>
<behavior name="Default">
<CultureExtension/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above 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>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="CultureExtension" type="Extension.CultureBehaviorExtension, Extension, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
My question is, inside the following code when I go through the service hosted by website,
I am unable to get the culture value of ar-SA inside the Hello method of the service.
I tried my best to clarify the question here. Please let me know what else needs to be clarified.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, IncludeExceptionDetailInFaults = true)]
public class Server : IHelloWorld
{
#region IHelloWorld Members
public void Hello()
{
<--- problem here unable to get culture value of ar-SA here --->
Console.WriteLine(string.Format("Hello world from the server in culture {0}", Thread.CurrentThread.CurrentCulture.Name));
}
public string HelloCulture()
{
string cultureName = Thread.CurrentThread.CurrentCulture.Name;
Console.WriteLine("**************************hello**********************************");
Console.WriteLine(cultureName);
return cultureName;
}
#endregion
}
Its not really an answer why that particular code doesnt work, but why dont you just sent in the culture string and set it in your method on the WCF service?
Thats the way we usualy do it, or if its an authenticated user, just save it on the user so they can switch to any language regardless of computer config.

How do I restrict access to some methods in WCF?

I am a bit lost getting started with a simple WCF service. I have two methods and I want to expose one to the world and the second one I want to limit to certain users. Eventually I want to be able to use a client application to use the restricted method. So far I can access both methods anonymously:
C# Code
namespace serviceSpace
{
[ServiceContract]
interface ILocationService
{
[OperationContract]
string GetLocation(string id);
[OperationContract]
string GetHiddenLocation(string id);
}
[AspNetCompatibilityRequirements(
RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class LocationService : ILocationService
{
[WebGet(UriTemplate = "Location/{id}")]
public string GetLocation(string id)
{
return "O hai, I'm available to everyone.";
}
// only use this if authorized somehow
[WebGet(UriTemplate = "Location/hush/{id}")]
public string GetHiddenLocation(string id)
{
return "O hai, I can only be seen by certain users.";
}
}
}
Configuration
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="" helpEnabled="true"
automaticFormatSelectionEnabled="true"/>
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel>
</configuration>
How do I get started?
A lot of the answers I found were almost what I needed but not quite right. I wound up setting up ASP.net membership and implementing a custom attribute to pull an authorization header and process login as the request came in. All of the magic happens in BeforeCall and ParseAuthorizationHeader below:
public class UsernamePasswordAuthentication : Attribute, IOperationBehavior, IParameterInspector
{
public void ApplyDispatchBehavior(OperationDescription operationDescription,
DispatchOperation dispatchOperation)
{
dispatchOperation.ParameterInspectors.Add(this);
}
public void AfterCall(string operationName, object[] outputs,
object returnValue, object correlationState)
{
}
public object BeforeCall(string operationName, object[] inputs)
{
var usernamePasswordString = parseAuthorizationHeader(WebOperationContext.Current.IncomingRequest);
if (usernamePasswordString != null)
{
string[] usernamePasswordArray = usernamePasswordString.Split(new char[] { ':' });
string username = usernamePasswordArray[0];
string password = usernamePasswordArray[1];
if ((username != null) && (password != null) && (Membership.ValidateUser(username, password)))
{
Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(username), new string[0]);
return null;
}
}
// if we made it here the user is not authorized
WebOperationContext.Current.OutgoingResponse.StatusCode =
HttpStatusCode.Unauthorized;
throw new WebFaultException<string>("Unauthorized", HttpStatusCode.Unauthorized);
}
private string parseAuthorizationHeader(IncomingWebRequestContext request)
{
string rtnString = null;
string authHeader = request.Headers["Authorization"];
if (authHeader != null)
{
var authStr = authHeader.Trim();
if (authStr.IndexOf("Basic", 0) == 0)
{
string encodedCredentials = authStr.Substring(6);
byte[] decodedBytes = Convert.FromBase64String(encodedCredentials);
rtnString = new ASCIIEncoding().GetString(decodedBytes);
}
}
return rtnString;
}
public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void Validate(OperationDescription operationDescription)
{
}
}
From there I just need to add my new attribute to the service contract entries. Any request to that method will require a valid authorization header or a Not Authorized response will be sent back with doing any further processing.
[ServiceContract]
interface ILocationService
{
[OperationContract]
string GetLocation(string id);
[OperationContract]
[UsernamePasswordAuthentication] // this attribute will force authentication
string GetHiddenLocation(string id);
}
Use the following steps to restrict access to specific Windows users:
Open the Computer Management Windows applet.
Create a Windows group that contains the specific Windows users to which you wish to give access. For example, a group can be called “CalculatorClients”.
Configure your service to require ClientCredentialType = “Windows”. This will require clients to connect using Windows authentication.
Configure your service methods with the PrincipalPermission attribute to require connecting users be members of the CalculatorClients group.
// Only members of the CalculatorClients group can call this method.
[PrincipalPermission(SecurityAction.Demand, Role = "CalculatorClients")]
public double Add(double a, double b)
{
return a + b;
}

Handling 404 in WCF Rest Service

I have a wcf rest service on IIS 7.5. When someone visits a part of the endpoint that doesn't exist (i.e. http://localhost/rest.svc/DOESNOTEXIST vs http://localhost/EXISTS) they are presented with a Generic WCF gray and blue error page with status code 404. However, I would like to return something like the following:
<service-response>
<error>The url requested does not exist</error>
</service-response>
I tried configuring the custom errors in IIS, but they only work if requesting a page outside of the rest service (i.e. http://localhost/DOESNOTEXIST).
Does anyone know how to do this?
Edit
After the answer below I was able to figure out I needed to create a WebHttpExceptionBehaviorElement class that implements BehaviorExtensionElement.
public class WebHttpExceptionBehaviorElement : BehaviorExtensionElement
{
///
/// Get the type of behavior to attach to the endpoint
///
public override Type BehaviorType
{
get
{
return typeof(WebHttpExceptionBehavior);
}
}
///
/// Create the custom behavior
///
protected override object CreateBehavior()
{
return new WebHttpExceptionBehavior();
}
}
I was then able to reference it in my web.config file via:
<extensions>
<behaviorExtensions>
<add name="customError" type="Service.WebHttpExceptionBehaviorElement, Service"/>
</behaviorExtensions>
</extensions>
And then adding
<customError />
to my default endpoint behaviors.
Thanks,
Jeffrey Kevin Pry
First, create a custom behavior which subclasses WebHttpBehavior - here you will remove the default Unhandled Dispatch Operation handler, and attach your own:
public class WebHttpBehaviorEx : WebHttpBehavior
{
public override void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
base.ApplyDispatchBehavior(endpoint, endpointDispatcher);
endpointDispatcher.DispatchRuntime.Operations.Remove(endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation);
endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation = new DispatchOperation(endpointDispatcher.DispatchRuntime, "*", "*", "*");
endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation.DeserializeRequest = false;
endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation.SerializeReply = false;
endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation.Invoker = new UnknownOperationInvoker();
}
}
Then. make your unknown operation handler. This class will handle the unknown operation request and generate a "Message" which is the response. I've shown how to create a plain text message. Modifying it for your purposes should be fairly straight-forward:
internal class UnknownOperationInvoker : IOperationInvoker
{
public object[] AllocateInputs()
{
return new object[1];
}
private Message CreateTextMessage(string message)
{
Message result = Message.CreateMessage(MessageVersion.None, null, new HelpPageGenerator.TextBodyWriter(message));
result.Properties["WebBodyFormatMessageProperty"] = new WebBodyFormatMessageProperty(WebContentFormat.Raw);
WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
return result;
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
// Code HERE
StringBuilder builder = new System.Text.StringBuilder();
builder.Append("...");
Message result = CreateTextMessage(builder.ToString());
return result;
}
public System.IAsyncResult InvokeBegin(object instance, object[] inputs, System.AsyncCallback callback, object state)
{
throw new System.NotImplementedException();
}
public object InvokeEnd(object instance, out object[] outputs, System.IAsyncResult result)
{
throw new System.NotImplementedException();
}
public bool IsSynchronous
{
get { return true; }
}
}
At this point you have to associate the new behavior with your service.
There are several ways to do that, so just ask if you don't already know, and i'll happily elaborate further.
I had a similar problem, and the other answer did lead to my eventual success, it was not the clearest of answers. Below is the way I solved this issue.
My projects setup is a WCF service hosted as a svc hosted in IIS. I could not go with the configuration route for adding the behavior, because my assembly versions change every checkin due to continuous integration.
to overcome this obsticle, I created a custom ServiceHostFactory:
using System.ServiceModel;
using System.ServiceModel.Activation;
namespace your.namespace.here
{
public class CustomServiceHostFactory : WebServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
ServiceHost host = base.CreateServiceHost(serviceType, baseAddresses);
//note: these endpoints will not exist yet, if you are relying on the svc system to generate your endpoints for you
// calling host.AddDefaultEndpoints provides you the endpoints you need to add the behavior we need.
var endpoints = host.AddDefaultEndpoints();
foreach (var endpoint in endpoints)
{
endpoint.Behaviors.Add(new WcfUnkownUriBehavior());
}
return host;
}
}
}
As you can see above, we are adding a new behavior: WcfUnknownUriBehavior. This new custom behavior's soul duty is to replace the UnknownDispatcher. below is that implementation:
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.ServiceModel.Web;
namespace your.namespace.here
{
public class UnknownUriDispatcher : IOperationInvoker
{
public object[] AllocateInputs()
{
//no inputs are really going to come in,
//but we want to provide an array anyways
return new object[1];
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
var responeObject = new YourResponseObject()
{
Message = "Invalid Uri",
Code = "Error",
};
Message result = Message.CreateMessage(MessageVersion.None, null, responeObject);
WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
outputs = new object[1]{responeObject};
return result;
}
public System.IAsyncResult InvokeBegin(object instance, object[] inputs, System.AsyncCallback callback, object state)
{
throw new System.NotImplementedException();
}
public object InvokeEnd(object instance, out object[] outputs, System.IAsyncResult result)
{
throw new System.NotImplementedException();
}
public bool IsSynchronous
{
get { return true; }
}
}
}
Once you have these objects specified, you can now use the new factory within your svc's "markup":
<%# ServiceHost Language="C#" Debug="true" Service="your.service.namespace.here" CodeBehind="myservice.svc.cs"
Factory="your.namespace.here.CustomServiceHostFactory" %>
And that should be it. as long as your object "YourResponseObject" can be serialized, it's serialized representation will be sent back to the client.

Categories

Resources