AFAIK WCF has a very powerful configurable logging infrastructure, but in my case it's too complex. I want to implement something simple like access.log with pattern similar to this
%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"
Problem is that WCF is logging in XML in very complex format, and it's kinda verbose. Maybe there is some way to simplify this XML? It's ok that it's an XML instead of textfile, but it has multiple fields and data that takes space, makes log harder to read and so on.
The only way I found for now is implement my own IOperationInvoker for it, but maybe I can reuse builtin logging system? Please, advice.
I implemented it with custom behaviour. Here is implementation:
class LoggingBehaviour : 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 LoggingMessageInspector());
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
}
and custom logging inspector:
public class LoggingMessageInspector : IDispatchMessageInspector
{
private static readonly Logger CurrentClassLogger = LogManager.GetCurrentClassLogger();
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
return request.Headers.To;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
var requestUri = (Uri)correlationState;
var currentContext = WebOperationContext.Current;
if (currentContext == null)
{
CurrentClassLogger.Log(LogLevel.Error, "Cannot log reply to [{0}]: WebOperationContext is null", requestUri);
return;
}
try
{
var httpRequest = currentContext.IncomingRequest;
string host = httpRequest.Headers[HttpRequestHeader.Host];
string method = httpRequest.Method;
string userAgent = httpRequest.UserAgent;
var statusCode = currentContext.OutgoingResponse.StatusCode;
CurrentClassLogger.Log(LogLevel.Info, "[Host {0}] [{1} {2} {3} {4}] [{5}]", host, method, requestUri, (int) statusCode, statusCode, userAgent);
}
catch (Exception ex)
{
CurrentClassLogger.Error("Cannot log reply to [{0}] : {1}", requestUri, ex);
}
}
}
Then use it!
foreach (var endpoint in ServiceHost.Description.Endpoints)
{
endpoint.Behaviors.Add(new LoggingBehaviour());
}
Related
I have a XML response returned from a .NET Web Service. The API being used for the return response is a serialized response and it's stripping out the supplemental section of the XML since it's not part of the serialized class stack which I cannot change in the API. Is there a way get the raw XML response so I can get the supplemental:supplementalData section of my response message being returned or somehow just get the supplemental:supplementalData XML section it self?
Service API call is TestPort.TestPortType response = service.GetTestPortList() and the returned response doesn't contain the supplemental section since it's not part of the TestPort.TestResponse stack.
This is the Raw XML message returned via the Fiddle Analyzer:
<TestResponse xmlns="urn:oasis:names:tc:legalxml-filing:schema:xsd:TestResponse-4.0" xmlns:org="urn:org:ecf:extensions:Common" xmlns:j="http://niem.tech/niem/domains/jxdm/4.0" xmlns:s="http://niem.tech/niem/structures/2.0" xmlns:nc="http://niem.tech/niem/niem-core/2.0" xmlns:ecf="urn:oasis:names:tc:legalxml-filing:schema:xsd:CommonTypes-4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ecf:Error>
<ecf:ErrorCode>0</ecf:ErrorCode>
<ecf:ErrorText>No Error</ecf:ErrorText>
</ecf:Error>
<supplemental:supplementalData xmlns:supplemental="urn:oasis:names:tc:legalxml-filing:schema:xsd:supplementalData-4.0">
<nc:TestID>DATest159647</nc:TestID>
</supplemental:supplementalData>
</TestResponse>
This is the XML message returned from the API which is a serialized response, so since the supplemental:supplementalData section is not part of the serialized response it's ignored.
<TestResponse xmlns="urn:oasis:names:tc:legalxml-filing:schema:xsd:TestResponse-4.0" xmlns:org="urn:org:ecf:extensions:Common" xmlns:j="http://niem.tech/niem/domains/jxdm/4.0" xmlns:s="http://niem.tech/niem/structures/2.0" xmlns:nc="http://niem.tech/niem/niem-core/2.0" xmlns:ecf="urn:oasis:names:tc:legalxml-filing:schema:xsd:CommonTypes-4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ecf:Error>
<ecf:ErrorCode>0</ecf:ErrorCode>
<ecf:ErrorText>No Error</ecf:ErrorText>
</ecf:Error>
</TestResponse>
This is the XML section I'm trying to get access to from within my .Net hosting app after the response is returned, but it's being stripped out of the returned serialized response.
<supplemental:supplementalData xmlns:supplemental="urn:oasis:names:tc:legalxml-filing:schema:xsd:supplementalData-4.0">
<nc:TestID>DATest159647</nc:TestID>
</supplemental:supplementalData>
Using Xml Linq. I read a string from a file. You can use similar code to get the response string and create an xdocument.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
string xml = File.ReadAllText(FILENAME);
XDocument doc = XDocument.Parse(xml);
XElement supplementalData = doc.Descendants().Where(x => x.Name.LocalName == "supplementalData").FirstOrDefault();
XNamespace supplemental = supplementalData.GetNamespaceOfPrefix("supplemental");
XNamespace nc = supplementalData.GetNamespaceOfPrefix("nc");
string TestID = (string)supplementalData.Element(nc + "TestID");
}
}
}
If the server-side intercepts the SOAP message,it needs to implement the IDispatchMessageInspector interface.
public class ServerMessageLogger : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
Console.WriteLine(request);
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
Console.WriteLine(reply);
}
}
Request is the SOAP message received by the server-side from the client-side.
If the client-side intercepts the SOAP message,it needs to implement the IClientMessageInspector interface.
public class ClientMessageLogger : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
Console.WriteLine(reply);
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
Console.WriteLine(request);
return null;
}
}
We need to add the class that implements IDispatchMessageInspector or IClientMessageInspector to the behavior of the server-side or the client-side.
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, AllowMultiple = false)]
public class CustContractBehaviorAttribute : Attribute, IContractBehavior, IContractBehaviorAttribute,IOperationBehavior
{
public Type TargetContract => throw new NotImplementedException();
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
}
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
throw new NotImplementedException();
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new ClientMessageLogger());
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
throw new NotImplementedException();
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
dispatchRuntime.MessageInspectors.Add(new ServerMessageLogger());
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
return;
}
public void Validate(OperationDescription operationDescription)
{
throw new NotImplementedException();
}
}
Finally,we need to apply this behavior to the service.
[CustContractBehavior]
public interface IService1
{...
// Created obj for wcf service
ServiceSummary.ImageService.ManagerServiceClient obj1 = new ServiceSummary.ImageService.ManagerServiceClient();
// Forming a request body
var request = new ImageService.GetImageRequest
{
UserContextData = new ImageService.UserContextData
{
Country = Country.ToUpper(),
Region = Region.ToUpper()
},
};
// Invoking GetImageResponse and storing result in response variable
var response = obj1.GetImageResponse(request);
The response is returned of type class - how to get the response in XML format instead?
I am a little confused that why we need that primitive XML data. But we can completely get the source message, SOAP message envelops by using IClientMessageInspector.
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.dispatcher.iclientmessageinspector?redirectedfrom=MSDN&view=netframework-4.8
Here is an example, assumed that you call the service by using a client proxy.
public class ClientMessageLogger : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
Console.WriteLine(reply);
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
return null;
}
}
public class CustContractBehaviorAttribute : 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;
}
}
Then apply the contract behavior on the automatically generated service contract.
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="ServiceReference1.IService")]
[CustContractBehavior]
public interface IService {
Result.
Feel free to let me know if there is anything I can help with.
I want to write a C# code which calls a (remote) web service in another machine. For this I have to pass username and password in the SOAP header of the call.
I would like to know an example of code to make this in C#.
the produced XML should be like :
<env:Header>
<ns1:Security>
<ns1:UsernameToken>
<ns1:Username>XXXXXXXXXXXXXXXX</ns1:Username>
<ns1:Password>YYYYYYYYYYYYYYYY</ns1:Password>
</ns1:UsernameToken>
</ns1:Security>
</env:Header>
Thanks in advance
J.
Are many ways to do that. The CustomBinding is more flexible because it allow more controll, for that i propose you with that. Pasing header to endpoint is a simple way:
// binding
var binding = new CustomBinding();
binding.Elements.Clear();
binding.Elements.Add(new TextMessageEncodingBindingElement{MessageVersion = MessageVersion.Soap12});
binding.Elements.Add(new HttpTransportBindingElement{MaxReceivedMessageSize = 20000000,});
// endpoint
var endpoint = new EndpointAddress(new Uri(listeningUri), new MySecurityHeader())
var client = new Client(binding, endpoint);
client.SomeMethod();
where MySecurityHeader is an AddressHeader, for example:
public class MySecurityHeader : AddressHeader
{
public override string Name
{
get { return "Security"; }
}
public override string Namespace
{
get { return "<provide the appropiate namespace>"; }
}
protected override void OnWriteAddressHeaderContents(System.Xml.XmlDictionaryWriter writer)
{
// here you do what you want
writer.WriteRaw(String.Format(#"
<UsernameToken xmlns=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"">
<Username>user</Username>
<Password Type=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"">pass</Password>
</UsernameToken>").Trim());
}
}
this is an example using an IEndpointAdress
var customBinding = new CustomBinding();
customBinding.Elements.Add(new TextMessageEncodingBindingElement { MessageVersion = MessageVersion.Soap12, });
customBinding.Elements.Add(new HttpTransportBindingElement { MaxReceivedMessageSize = 20000000, });
var endpointAddres = new EndpointAddress(listeningUri);
var client = new Client(customBinding, endpointAddres);
// add my own IEndpointBehavior
client.ChannelFactory.Endpoint.Behaviors.Add(new CustomBehavior());
client.SomeMethod();
and this is the CustomBehavior definition
public class CustomBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
var inspector = new CustomMessageInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{}
public void Validate(ServiceEndpoint endpoint)
{}
}
public class CustomMessageInspector : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
request.Headers.Add(new MyMessageHeader());
return null;
}
}
public class MyMessageHeader : MessageHeader
{
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteRaw(String.Format(#"
<UsernameToken xmlns=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"">
<Username>user</Username>
<Password Type=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"">pass</Password>
</UsernameToken>").Trim());
}
public override string Name
{
get { return "MyHeaderName"; }
}
public override string Namespace
{
get { return "MyHeaderNamespace"; }
}
}
Note you have control before send the request and after receive your reply.
I hope this resolve your issue, if you have some problems with this yust ask me.
Is there something Like ActionFilterAttribute (From ASP.NET MVC) in WCF Services (Or anything like that). basically what I want to do is to log what is coming and goint to and from my services, and I don't want to write the logging code in every single ServiceContracts. yes the question is very general but you understand the idea what I want to do.
Yes there is it called as MessageInspectors/ParameterInspectors , here you can read about them
http://msdn.microsoft.com/en-us/library/aa717047%28v=vs.110%29.aspx
This is exactly what you are looking for , WCF custom behavior log logging
http://www.codeproject.com/Articles/243352/LoggingBehavior-How-to-Connect-Log-prints-with-the
Only confusing thing is you can have message inspector on WCF service and WCF proxy as well , in your case you need only for service side
I had to read a lot to find this out, I'm not an expert in WCF but given this information is a little scarce I'm sharing what have worked for me.
My Solution consists of using an OperationBehavior and a DispatcherMessageInspector
OperationBehavior
Allows you to change the binding information, validate de operation
description, and apply dispatcher behaviors.
DispatcherMessageInspector
Allows you to inspect and change the messages that are sent for your
service.
Dispatcher
Gets the messages from the communication channels and sends to the
right operation, and get the result back to the caller.
Service Operation
are your service methods
CODE SOLUTION
MESSAGE INSPECTOR
public class MyMessageInspector : IDispatchMessageInspector
{
List<string> targetOperations = new List<string>();
public MyMessageInspector(OperationDescription operation)
{
this.AddOperation(operation);
}
public void AddOperation(OperationDescription operation)
{
this.targetOperations.Add(operation.Messages[0].Action);
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
if (TargetOperationMatchesRequest(request))
{
request = ChangeMessage(request);
return true;
}else
{
return false;
}
}
public bool TargetOperationMatchesRequest(Message request)
{
string requestAction = request.Headers.To.AbsolutePath;
requestAction = requestAction.Substring(requestAction.LastIndexOf("/"));
string targetOperation = "";
foreach (string targetOperationPath in targetOperations)
{
targetOperation = targetOperationPath.Substring(targetOperationPath.LastIndexOf("/"));
if (targetOperation.Equals(requestAction))
{
return true;
}
}
return false;
}
public Message ChangeMessage(Message oldMessage)
{
Message newMessage = request.CreateBufferedCopy(Int32.MaxValue).CreateMessage();
//Change your message
return newMessage;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
}
}
OPERATION
public class MyOperation : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
MyMessageInspector inspector = dispatchOperation.Parent.MessageInspectors
.Where(x => x is MyMessageInspector)
.FirstOrDefault() as MyMessageInspector;
if (inspector != null)
{
inspector.AddOperation(operationDescription);
}
else
{
inspector = new MessageInspectors(operationDescription);
dispatchOperation.Parent.MessageInspectors.Add(inspector);
}
}
public void Validate(OperationDescription operationDescription)
{
}
}
CONTRACT
[ServiceContract]
public interface IService
{
[OperationContract]
[MyOperation]
OutputData MyMethod(InputData inputData);
}
SERVICE
public class Service : IService
{
[WebInvoke(Method = "POST", UriTemplate = "/json/MyMethod", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
public OutputData MyMethod(InputData inputData)
{
//Implementation
return new OutputData();
}
}
My wcf service is being hosted in iis7. Is there a way I could put break point at ApplyDispatchBehavior method and make sure that its being called at run time by hitting that breakpoint?? My overall problem is that method AfterReceiveRequest is not being hit in the CultureMessageInspector class. Details of my code have been posted under questions in the following links. Please help me.. thanks
wcf AfterReceiveRequest not getting called
passing culture value inside wcf service hosted by iis
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 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
}
Presuming that the PDB files are present in your bin directory you can simply attach a debugger to the IIS7 process and debug as normal.
Make sure that the web.config has debug="true" set.
See this answer for more details
Attach Debugger to IIS instance