Action Filters in WCF Services - c#

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();
}
}

Related

Consuming a SOAP web service (Kronos SaasHR) and saving response into SQL server

I have been working on a project to connect to our time reporting vendor's SOAP service and get a report weekly (REST doesn't offer function call for getting the desired report). They have only given us the WSDL which has not been helpful: https://secure.entertimeonline.com/ta/padnos.wsdl and
https://secure.saashr.com/ta/PADNOS.soap
I have added the service reference to my solution and generated the proxy class, but each time I try to fetch the data, I get errors saying "Response is not well-formed XML." and "data at the root level is invalid. Line 1, position 1."
This is my first foray into consuming a web service and I cannot find any helpful material.
private void btnGo_Click(object sender, EventArgs e)
{
// service reference
// runReportByName
TSPHoursWorked.ServiceReference1.runReport_ByNameType rptName = new ServiceReference1.runReport_ByNameType();
rptName.version = 1;
rptName.reportCategory = "Calculated Time";
rptName.reportName = "Calculated Time By Entry";
rptName.reportSavedName = "DailyHoursWorked";
rptName.outputType = TSPHoursWorked.ServiceReference1.runReport_ByNameTypeOutputType.XML;
TSPHoursWorked.ServiceReference1.SaaSHRClient soap = new ServiceReference1.SaaSHRClient();
soap.ClientCredentials.UserName.UserName = "username";
soap.ClientCredentials.UserName.Password = "password";
var requestInterceptor = new InspectorBehavior();
soap.Endpoint.Behaviors.Add(requestInterceptor);
soap.runReport_ByName(rptName);
string requestXML = requestInterceptor.LastRequestXML;
outputText.Text = requestXML;
string responseXML = requestInterceptor.LastResponseXML;
outputText.Text += responseXML;
}
public class InspectorBehavior : IEndpointBehavior
{
public string LastRequestXML
{
get
{
return myMessageInspector.LastRequestXML;
}
}
public string LastResponseXML
{
get
{
return myMessageInspector.LastResponseXML;
}
}
private MyMessageInspector myMessageInspector = new MyMessageInspector();
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(myMessageInspector);
}
}
public class MyMessageInspector : IClientMessageInspector
{
public string LastRequestXML { get; private set; }
public string LastResponseXML { get; private set; }
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
LastResponseXML = reply.ToString();
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
LastRequestXML = request.ToString();
return request;
}
}
I would just like help simply connecting to their web service, and get the report and bulk insert into SQL server. Could anyone set me on the right path?
EDIT:
I've installed SoapUI and there are no URLs listed under the soap operation "Actions" column:
The problem was with our middle-man vendor to Kronos not having any technical knowledge. After a few weeks and finally getting in touch with Kronos, I was informed that:
a new user must be created without single sign-on
copy the groups/reports/etc of the main admin account to the new account
add highest security privilege to new account
Then you will be able to call the RESTful service. I hope this helps someone because our vendor wasted 2 weeks of our time

Implement simple logging in WCF

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());
}

Wcf sends xml content type instead of json,

have WCF server and WCF client. Below is the client code:
[GeneratedCode("System.ServiceModel", "3.0.0.0")]
[ServiceContract(ConfigurationName = "IMyService")]
public interface IMyService
{
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, UriTemplate = "DoSmth")]
[OperationContract(Action = "http://tempuri.org/IConfigService/SetSettings")]
OrigamiHttpResponse<List<ErrorRecords>> DoSmth(int param);
}
public class MyService: BaseClient<IMyService>
{
public ConfigServiceClient(AuthResult authObject) : base(authObject, null)
{
}
public OrigamiHttpResponse<List<ErrorRecords>> DoSmth(int param)
{
return Proxy.DoSmth(param);
}
}
public abstract class BaseClient<T> : BaseClient where T : class
{
protected T Proxy { get; private set; }
protected BaseClient(AuthResult authObject, IConfig config)
: base(authObject, config)
{
var factory = new ChannelFactory<T>("*");
if (factory.Endpoint == null || factory.Endpoint.Address == null)
throw new Exception("WCF Endpoint is not configured");
if (factory.Endpoint.Address.Uri.Scheme.ToLower() == "https")
HttpAccess.PrepareSslRequests();
if (_authObject != null)
{
var cb = new CookieBehavior(_authObject);
factory.Endpoint.Behaviors.Add(cb);
}
Proxy = factory.CreateChannel();
}
}
When I call method DoSmth() from console application, content type is json. But my architecture is that I am calling proxy method and then proxy server acts as client for wcf server and calls wcf method that is my DoSmth(). In this case content type is xml and i can't change it. May be the issue is in operation context, because it is one call from another. Could anyone help please?
This is caused by the fact that your WCF client (Proxy) is running in the operation context on the service method (which contains information about the incoming request), and that overrides the context which should be used by the outgoing request. To fix this you need to create a new operation context scope when doing the call, so that it will use the appropriate property from the WebInvoke/WebGet attributes:
public OrigamiHttpResponse<List<ErrorRecords>> DoSmth(int param)
{
using (new OperationContextScope((IContextChannel)Proxy))
{
return Proxy.DoSmth(param);
}
}

How to pass username and password to the SOAP header of a Web Service Call from C#?

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.

How to get the invoked operation name within a IClientMessageInspector?

I implemented an IClientMessageInspector to "intercept" outgoing web service call in my application. Is it possible to find out which operation is being called from inside the BeforeSendRequest and AfterReceiveReply?
There is a similar question here, How do i get the invoked operation name within a WCF Message Inspector, which is for the server side (the side receiving the request). I tried to do something similar, e.g.
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
var v = OperationContext.Current.OutgoingMessageProperties["HttpOperationName"];
return null;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
var v = OperationContext.Current.OutgoingMessageProperties["HttpOperationName"];
}
but during outgoing request it seems that OperationContext.Current is null, so I cannot use this. Any idea how to get it? Any idea how to do it cleanly (as opposed, to say, parse the SOAP xml)?
From the comments you asked how doing this could be done with IParameterInspector. The operation name is part of the Before/AfterCall methods.
Just to add to my comments on which inspector to use. From Carlos Figueira's blogs:
The message inspectors, described in the previous post of this series,
allows you complete control over the message going through the WCF
stack. They’re very powerful, but you have to know how to deal with
the Message object, which is not the most desirable way of
programming. If the service model in WCF hides all the messaging
framework by allowing us to define our services in terms of
strongly-typed operations (i.e., using nice primitive and user defined
types), there should be a way of intercepting requests / responses
after all the processing to extract those parameters from incoming
messages (or before they’re packaged in outgoing messages) is done.
The IParameterInspector is exactly that – before and after each call,
the inspector gets a chance to inspect the operation inputs, outputs
and return value, in the same types as defined by the operation
contract, no conversion needed (the only thing needed is a cast, since
the parameters are passed as objects).
This is a complete command line program that demonstrates:
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace WCFClientInspector
{
public class OperationLogger : IParameterInspector
{
public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
{
Console.WriteLine("Completed operation:" + operationName);
}
public object BeforeCall(string operationName, object[] inputs)
{
Console.WriteLine("Calling operation:" + operationName);
return null;
}
}
public class OperationLoggerEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
foreach (ClientOperation operation in clientRuntime.ClientOperations)
{
operation.ClientParameterInspectors.Add(new OperationLogger());
}
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
[ServiceContract]
public interface ISimple
{
[OperationContract]
void DoSomthing(string s);
}
public class SimpleService : ISimple
{
public void DoSomthing(string s)
{
Console.WriteLine("Called:" + s);
}
}
public static class AttributesAndContext
{
static void Main(string[] args)
{
ServiceHost simpleHost = new ServiceHost(typeof(SimpleService), new Uri("http://localhost/Simple"));
simpleHost.Open();
ChannelFactory<ISimple> factory = new ChannelFactory<ISimple>(simpleHost.Description.Endpoints[0]);
factory.Endpoint.EndpointBehaviors.Add(new OperationLoggerEndpointBehavior());
ISimple proxy = factory.CreateChannel();
proxy.DoSomthing("hi");
Console.WriteLine("Press ENTER to close the host.");
Console.ReadLine();
((ICommunicationObject)proxy).Shutdown();
simpleHost.Shutdown();
}
}
public static class Extensions
{
static public void Shutdown(this ICommunicationObject obj)
{
try
{
obj.Close();
}
catch (Exception ex)
{
Console.WriteLine("Shutdown exception: {0}", ex.Message);
obj.Abort();
}
}
}
}
It should give the output:
Calling operation:DoSomthing
Called:hi
Completed operation:DoSomthing
Press ENTER to close the host.
What about reply.Headers.Action and request.Headers.Action. Of course the rest is same tricky as in question linked. So the full code will be:
var action = reply.Headers.Action.Substring(reply.Headers.Action.LastIndexOf("/", StringComparison.OrdinalIgnoreCase) + 1);
or
var action = request.Headers.Action.Substring(request.Headers.Action.LastIndexOf("/", StringComparison.OrdinalIgnoreCase) + 1);

Categories