I have a wcf service where my interface looks something like this:
[ServiceContract]
public interface IMyService
{
[OperationContract]
[AllowedFileExtension]
void SaveFile(string fileName);
}
My goal is to inspect the incoming message to verify the fileName. So my AllowedFileExtensionAttribute class looks like this:
public class AllowedFileExtensionsAttribute : Attribute, IOperationBehavior
{
private readonly string _callingMethodName;
private readonly string[] _allowedFileExtension;
public AllowedFileExtensionsAttribute([CallerMemberName]string callingMethodName = null)
{
_callingMethodName = callingMethodName;
}
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
}
public void Validate(OperationDescription operationDescription)
{
}
}
Invoking this from for instance WCF Test Client or a simple console application, my Attribute class is not invoked, it goes directly to the implmentation. What am I doing wrong here?
You can use WCF MessageInspector to intercept the request and do whatever you wish to do.
A message inspector is an extensibility object that can be used in the service model's client runtime and dispatch runtime programmatically or through configuration and that can inspect and alter messages after they are received or before they are sent.
You can implement both IDispatchMessageInspector and IClientMessageInspector interfaces. Read the incoming data in the AfterReceiveRequest, store it in a threadstatic variable, and if required use it in BeforeSendRequest.
AfterReceiveRequest is invoked by the dispatcher when a message has been received in pipeline.You can manipulate this request which has been passed as reference parameter.
See the msdn doc.
public class SimpleEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(
new SimpleMessageInspector()
);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
public class SimpleMessageInspector : IClientMessageInspector, IDispatchMessageInspector
{
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
//modify the request send from client(only customize message body)
request = TransformMessage2(request);
//you can modify the entire message via following function
//request = TransformMessage(request);
return null;
}
}
Check this post for details.
Related
I have a standalone C# WCF service running as a Windows service. I have the requirement to add custom headers like X-Frame-Options to all responses. I have tried to add an instance of the following class to ServiceEndpoint.Behaviors
internal class ServerInterceptor : IDispatchMessageInspector, IEndpointBehavior
{
object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
return null;
}
void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)
{
reply.Properties.Add("X-Frame-Options", "deny");
}
void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
}
void IEndpointBehavior.Validate(ServiceEndpoint endpoint) { }
void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
}
This doesn't add any HTTP header to the response although the class gets called as the debugger can step into the BeforeSendReply function. Furthermore if I replace reply.Properties with reply.Headers then the header is added, but not to the HTTP headers but to the SOAP headers.
How can I add a HTTP header like X-Frame-Options to the response?
I made an example, which is used to add extra CORS HTTP header, wish it is instrumental for you.
Message Inspector.
public class CustomHeaderMessageInspector : IDispatchMessageInspector
{
Dictionary<string, string> requiredHeaders;
public CustomHeaderMessageInspector(Dictionary<string, string> headers)
{
requiredHeaders = headers ?? new Dictionary<string, string>();
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
string displayText = $"Server has received the following message:\n{request}\n";
Console.WriteLine(displayText);
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
if (!reply.Properties.ContainsKey("httpResponse"))
reply.Properties.Add("httpResponse", new HttpResponseMessageProperty());
var httpHeader = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
foreach (var item in requiredHeaders)
{
httpHeader.Headers.Add(item.Key, item.Value);
}
string displayText = $"Server has replied the following message:\n{reply}\n";
Console.WriteLine(displayText);
}
}
Custom Contract Attribute.
public class MyBehaviorAttribute : Attribute, IContractBehavior, IContractBehaviorAttribute
{
public Type TargetContract => typeof(MyBehaviorAttribute);
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
var requiredHeaders = new Dictionary<string, string>();
requiredHeaders.Add("Access-Control-Allow-Origin", "*");
requiredHeaders.Add("Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS");
requiredHeaders.Add("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");
dispatchRuntime.MessageInspectors.Add(new CustomHeaderMessageInspector(requiredHeaders));
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
}
}
Apply the contract behavior.
[ServiceContract(Namespace = "mydomain")]
[MyBehavior]
public interface IService
{
[OperationContract]
[WebGet]
string SayHello();
}
Result.
Feel free to let me know if there is anything I can help with.
What I'm trying to achieve is passing credentials/token to WCF services in every requests. BTW, this credential IS NOT windows credentials, they are fetched from custom db, and the authentication logic is quite simple, tenantId+username+password.
I'm currently using message inspector to insert these kind of information in the headers and fetch them from server-side inspector(using OperationContext).
But in order to stay thread-safe,I have to wrap the requests in every winform request like this:
using (new OperationContextScope((WcfService as ServiceClient).InnerChannel))
{
MessageHeader hdXXId = MessageHeader.CreateHeader("XXId", "CustomHeader", WinformSomeVariable.XXId);
OperationContext.Current.OutgoingMessageHeaders.Add(hdXXId);
_objXX = WcfService.GetXXById(id);
}
Like showed above, this is quite heavy and obviously not a smart way to handle this situation. So is there any way to hold these kind of information safely and can as well fetch them in the WCF Inspectors?
Many thanks!
PS. Thanks to #Abraham Qian, I was being silly the whole time. Just put the client inspector within the same winform project will solve this issue.
Just ignore the question of how to refactor your authentication for a moment.
As for how to use the IClientMessageInspector interface to create a persistent message header, the following code snippet might be useful (Assume that invocation by using Channel Factory)
class Program
{
static void Main(string[] args)
{
Uri uri = new Uri("http://localhost:1300");
IService service = ChannelFactory<IService>.CreateChannel(new BasicHttpBinding(), new EndpointAddress(uri));
try
{
Console.WriteLine(service.SayHello());
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
[ServiceContract(Namespace = "mydomain")]
[CustomContractBehavior]
public interface IService
{
[OperationContract]
string SayHello();
}
public class ClientMessageLogger : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
string displayText = $"the client has received the reply:\n{reply}\n";
Console.Write(displayText);
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
//Add custom message header
request.Headers.Add(MessageHeader.CreateHeader("myheader","mynamespace",2000));
string displayText = $"the client send request message:\n{request}\n";
Console.WriteLine(displayText);
return null;
}
}
public class CustomContractBehaviorAttribute : Attribute, IContractBehavior, IContractBehaviorAttribute
{
public Type TargetContract => typeof(IService);
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new ClientMessageLogger());
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
return;
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
return;
}
}
We used to use WCF over ASP.NET and recently switched to WCF over ASP.NET Core. This was quite hard to implement because ASP.Net Core doesn't support WCF out of the box. For one thing, the whole web.config XML configuration model has been dumped in ASP.NET Core so we cannot configure WCF tracing there.
I.e. this document is useless:
https://learn.microsoft.com/en-us/dotnet/framework/wcf/diagnostics/tracing/configuring-tracing
We had to hack ASP.NET Core by putting a http proxy between WCF and port 80. WCF is actually running on another port.
The question is, how do we enable WCF tracing if ASP.NET Core doesn't pay attention to the web.config?
In case of client side tracing I used custom endpoint behaviour (IEndpointBehavior) with custom message logging inspector (IClientMessageInspector) to get input and output messages.
Client initialization:
_serviceClient = new MyCustomServiceClient();
_serviceClient.Endpoint.Address = new System.ServiceModel.EndpointAddress(_configParams.ServiceUri);
_serviceClient.Endpoint.EndpointBehaviors.Add(new EndpointLoggingBehavior("MyCustomService"));
Implementation of EndpointLoggingBehavior:
public class EndpointLoggingBehavior : IEndpointBehavior
{
public EndpointLoggingBehavior(string serviceName)
{
_serviceName = serviceName;
}
private readonly string _serviceName;
public void AddBindingParameters(ServiceEndpoint endpoint,
System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new MessageLoggingInspector(_serviceName));
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
Implementation of MessageLoggingInspector:
public class MessageLoggingInspector : IClientMessageInspector
{
private readonly string _serviceName;
public MessageLoggingInspector(string serviceName)
{
_serviceName = serviceName;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
// copying message to buffer to avoid accidental corruption
var buffer = reply.CreateBufferedCopy(int.MaxValue);
reply = buffer.CreateMessage();
// creating copy
var copy = buffer.CreateMessage();
//getting full input message
var fullInputMessage = copy.ToString();
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
// copying message to buffer to avoid accidental corruption
var buffer = request.CreateBufferedCopy(int.MaxValue);
request = buffer.CreateMessage();
// creating copy
var copy = buffer.CreateMessage();
//getting full output message
var fullOutputMessage = copy.ToString();
return null;
}
}
Then, of course, you will need to write these messages to any storage.
You'll use ETW Tracing for WCF on .NET Core
https://github.com/dotnet/wcf/blob/master/Documentation/HowToUseETW.md
In my experience, you have some limitations
Tracing is on for All WCF Apps, rather than configuring for a single app through config file
You cannot output Messages with ETW tracing
SvcTraceViewer.exe doesn't work well for trace review, you'll need to move to PerfView.exe which may present a learning curve
Benefits of ETW
You avoid the performance hit from classic forms of tracing
No more config change to start/stop tracing
Building on the excellent answer by Petr Pokrovskiy, this is how you can redirect the trace to the standard .NET Core log:
Client initialization:
ILogger<MyCustomService> = ...; // use dependency injection to get instance
_serviceClient = new MyCustomServiceClient();
_serviceClient.Endpoint.Address = new System.ServiceModel.EndpointAddress(_configParams.ServiceUri);
_serviceClient.Endpoint.SetTraceLogging(logger);
Implementation of SetTraceLogging:
using Microsoft.Extensions.Logging;
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace Common.ServiceModel.Logging
{
public static class ServiceEndpointExtensions
{
public static void SetTraceLogging(this ServiceEndpoint serviceEndpoint, ILogger logger)
{
if (logger == null)
throw new ArgumentNullException(nameof(logger));
if (logger.IsEnabled(LogLevel.Trace))
serviceEndpoint.EndpointBehaviors.Add(new ClientMessageLoggingBehavior(logger));
}
}
internal sealed class ClientMessageLoggingBehavior :
IEndpointBehavior
{
private readonly ILogger _logger;
public ClientMessageLoggingBehavior(ILogger logger)
{
_logger = logger;
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new ClientMessageLogger(_logger));
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
internal sealed class ClientMessageLogger :
IClientMessageInspector
{
private readonly ILogger _logger;
public ClientMessageLogger(ILogger logger)
{
this._logger = logger;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
// copying message to buffer to avoid accidental corruption
reply = Clone(reply);
this._logger.LogTrace("Received SOAP reply:\r\n{0}", reply.ToString());
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
// copying message to buffer to avoid accidental corruption
request = Clone(request);
this._logger.LogTrace("Sending SOAP request:\r\n{0}", request.ToString());
return null;
}
private static Message Clone(Message message)
{
return message.CreateBufferedCopy(int.MaxValue).CreateMessage();
}
}
}
we are working with UPS shipment API and there are certain issues we are facing. After contacting UPS technical support, they have asked to provide them with the SOAP envelope (Request/Respose) in xml format.
Kindly assist that how can that be acquired from the code . below is the service call to UPS API.
ShipmentResponse shipmentReponse =
shipService.ProcessShipment(shipmentRequest);
any help appreciated.
If you want to do this from the program itself, you can add an endpoint behaviour, assuming you're using WCF, to save the soap request and response.
Usage would be something like this,
using (ChannelFactory<IService> scf = new ChannelFactory<IService>(new BasicHttpBinding(), "http://localhost:8000/Soap"))
{
scf.Endpoint.EndpointBehaviors.Add(new SimpleEndpointBehavior()); // key bit
IService channel = scf.CreateChannel();
string s;
s = channel.EchoWithGet("Hello, world");
Console.WriteLine(" Output: {0}", s);
}
It is described here in detail here: http://msdn.microsoft.com/en-us/library/ms733786%28v=vs.110%29.aspx, the key methods for you are AfterReceiveReply and BeforeSendRequest, where you can store or save the SOAP xml as necessary.
// Client message inspector
public class SimpleMessageInspector : IClientMessageInspector
{
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
// log response xml
File.WriteAllText(#"c:\temp\responseXml.xml", reply.ToString());
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)
{
// log request xml
File.WriteAllText(#"c:\temp\requestXml.xml", request.ToString());
return null;
}
}
public class SimpleEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
// No implementation necessary
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(new SimpleMessageInspector());
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
// No implementation necessary
}
public void Validate(ServiceEndpoint endpoint)
{
// No implementation necessary
}
}
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.