I'm using asp.net core on windows and have a file with classes generated by the dotnet-svcutil. I'm using nlog for the logging purpose. Is there a way I can log all the raw requests and responses to and from the external service?
Already tried logman https://github.com/dotnet/wcf/blob/master/Documentation/HowToUseETW.md, but first - it doesn't show the raw soap, only events, and second - I need logs to be logged by the configured nlog.
Behaviour:
public class LoggingEndpointBehaviour : IEndpointBehavior
{
public LoggingMessageInspector MessageInspector { get; }
public LoggingEndpointBehaviour(LoggingMessageInspector messageInspector)
{
MessageInspector = messageInspector ?? throw new ArgumentNullException(nameof(messageInspector));
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(MessageInspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
Inspector:
public class LoggingMessageInspector : IClientMessageInspector
{
public LoggingMessageInspector(ILogger<LoggingMessageInspector> logger)
{
Logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
}
public ILogger<LoggingMessageInspector> Logger { get; }
public void AfterReceiveReply(ref Message reply, object correlationState)
{
using (var buffer = reply.CreateBufferedCopy(int.MaxValue))
{
var document = GetDocument(buffer.CreateMessage());
Logger.LogTrace(document.OuterXml);
reply = buffer.CreateMessage();
}
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
using (var buffer = request.CreateBufferedCopy(int.MaxValue))
{
var document = GetDocument(buffer.CreateMessage());
Logger.LogTrace(document.OuterXml);
request = buffer.CreateMessage();
return null;
}
}
private XmlDocument GetDocument(Message request)
{
XmlDocument document = new XmlDocument();
using (MemoryStream memoryStream = new MemoryStream())
{
// write request to memory stream
XmlWriter writer = XmlWriter.Create(memoryStream);
request.WriteMessage(writer);
writer.Flush();
memoryStream.Position = 0;
// load memory stream into a document
document.Load(memoryStream);
}
return document;
}
}
Usage:
if (configuration.GetValue<bool>("Logging:MessageContent"))
client.Endpoint.EndpointBehaviors.Add(serviceProvider.GetRequiredService<LoggingEndpointBehaviour>());
Found the answer here:
https://learn.microsoft.com/en-us/dotnet/framework/wcf/extending/how-to-inspect-or-modify-messages-on-the-client
The order of actions:
Implement the System.ServiceModel.Dispatcher.IClientMessageInspector interface. Here you can inspect/modify/log messages
Implement a System.ServiceModel.Description.IEndpointBehavior or System.ServiceModel.Description.IContractBehavior depending upon the scope at which you want to insert the client message inspector.
System.ServiceModel.Description.IEndpointBehavior allows you to change behavior at the endpoint level.
System.ServiceModel.Description.IContractBehavior allows you to change behavior at the contract level.
Insert the behavior prior to calling the ClientBase.Open or the ICommunicationObject.Open method on the System.ServiceModel.ChannelFactory.
Related
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();
}
}
}
I would like to an HTTP header to my WCF SOAP Service. My end goal is to send API keys through this HTTP header.
Below is my code:
[ServiceBehavior(Namespace = "http://****.com/**/1.1")]
public class MyWcfSvc : IMyVerify
{
const int MaxResponseSize = 0xffff; // 64K max size - Normally it will be MUCH smaller than this
private static readonly NLogLogger Logger;
static MyWcfSvc()
{
Logger = new NLogLogger();
// Add an HTTP Header to an outgoing request
HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();
requestMessage.Headers["User-Auth"] = "MyHttpHeaderValue";
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;
}
}
I do not see User-Auth header under HTTP request headers.
I also tried with another way.
public AnalyzeResponse Analyze(AnalyzeRequest analyzeRequest)
{
HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();
requestMessage.Headers["User-Auth"] = "MyHttpHeaderValue";
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;
.
.
. Rest of the service implementation
.
.
.
}
But, still, I don't see any HTTP header information with the request message. I am using SOAP UI to send the requests and to view the responses.
How should I go about this? Am I suppose to make changes to the Service related to class? Or I need to make some changes to web.config file?
SOAP Header
To add a SOAP header, use the following code client-side:
using (OperationContextScope scope = new OperationContextScope((IContextChannel)channel))
{
MessageHeader<string> header = new MessageHeader<string>("MyHttpHeaderValue");
var untyped = header.GetUntypedHeader("User-Auth", ns);
OperationContext.Current.OutgoingMessageHeaders.Add(untyped);
// now make the WCF call within this using block
}
And then, server-side, grab it using:
MessageHeaders headers = OperationContext.Current.IncomingMessageHeaders;
string identity = headers.GetHeader<string>("User-Auth", ns);
NB. ns is The namespace URI of the header XML element.
HTTP Header
To add an Http header:
// Add a HTTP Header to an outgoing request
HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();
requestMessage.Headers["MyHttpHeader"] = "MyHttpHeaderValue";
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;
And to grab it server-side
IncomingWebRequestContext request = WebOperationContext.Current.IncomingRequest;
WebHeaderCollection headers = request.Headers;
Console.WriteLine(request.Method + " " + request.UriTemplateMatch.RequestUri.AbsolutePath);
foreach (string headerName in headers.AllKeys)
{
Console.WriteLine(headerName + ": " + headers[headerName]);
}
If you are trying to add an HTTP request header to the client request, you can follow the procedure below.
Create a client message inspector. For example:
public class CustomInspector : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
HttpRequestMessageProperty reqProps = request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
if(reqProps == null)
{
reqProps = new HttpRequestMessageProperty();
}
reqProps.Headers.Add("Custom-Header", "abcd");
request.Properties[HttpRequestMessageProperty.Name] = reqProps;
return null;
}
}
Create an endpoint behavior to load this inspector:
public class CustomBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new CustomInspector());
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
Finally add this behavior to the endpoint.
class Program
{
static void Main(string[] args)
{
ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>("BasicHttpsBinding_ICalculator");
factory.Endpoint.EndpointBehaviors.Add(new CustomBehavior());
var client = factory.CreateChannel();
var number = client.Add(1, 2);
Console.WriteLine(number.ToString());
}
}
The above example works on my side. I could see the request header with Fiddler.
There is better solution on client-side than Leonardo's. His solution requires to manually modify each request. Here is solution with ClientMessageInspector, which automatically adds header to each outgoing request.
1: Define MessageInspector with overrides:
Bellow is the only one override method, the rest is empty.
public class ClientMessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
HttpRequestMessageProperty property = new HttpRequestMessageProperty();
property.Headers["User-Agent"] = "value";
request.Properties.Add(HttpRequestMessageProperty.Name, property);
return null;
}
...
}
Bind this Message inspector to EndPointBehavior.
Bellow is the only one override method, the rest is empty.
public class CustomEndpointBehavior : IEndpointBehavior
{
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new ClientMessageInspector());
}
...
}
The last step is to add the new behavior to the WCF endpoint:
Endpoint.EndpointBehaviors.Add(new CustomEndpointBehavior());
We are working on WCF Routing Service which redirects different soap actions to different endpoints.
We want to this service rewrite query string included in router url: #router url#?param=param to endpoint: #endpoint url#?param=param.
Our webservices accepts query strings when call directly, this strings are visible in router (context) but on the end this strings are removed from url.
Do you know how add this strings to the end of endpoint url in every request?
We solved the problem.
You must create new binding:
public class QueryHttpBinding : BasicHttpBinding
{
public override BindingElementCollection CreateBindingElements()
{
var result = base.CreateBindingElements();
var http = result.Find<HttpTransportBindingElement>();
if (http != null)
{
http.ManualAddressing = true;
}
var https = result.Find<HttpsTransportBindingElement>();
if (https != null)
{
https.ManualAddressing = true;
}
return result;
}
}
And Client message inspector:
public class CustomInspectorBehavior : IClientMessageInspector
{
object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
{
UriBuilder builder = new UriBuilder(channel.RemoteAddress.ToString());
builder.Path += "?" + ((HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name]).QueryString;
request.Headers.To = builder.Uri;
return null;
}
void IClientMessageInspector.AfterReceiveReply(ref Message reply, object correlationState)
{
}
}
Next you must create new endpoint Behavior:
public class Behavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
var inspector = new CustomInspectorBehavior();
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
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
}
}