Implementing RequestWrapper for RESTful WCF service - wcf

I've written a simple object called RequestWrapper that contains single method of type:
TResult WrapRequest<TResult>(Func<TResult> action)
It wraps any action with try..catch, error handling, logging, database connection, transaction (commit & rollback), etc.
Currently I use it like this: (example, not production code)
return RequestWrapper.WrapRequest(() =>
{
Topic entity = GetRepository<Topic>().Find(uid);
if (entity == null)
throw new EntityNotFoundException("Topic not found.");
return new Topic
{
Name = entity.Name,
Posts = entity.Posts.Select(x => new Post
{
Body = x.Body,
}).ToList()
};
});
I simply wrap around every method of my RESTful web service (using WCF and WebHttpBinding).
My question is: How should I implement behavior that would do the wrapping for me automatically? Is it possible?

You can use a custom IOperationInvoker which wraps the original one does what you need. The code below shows a sample implementation of one, and you can find more information about invokers at http://blogs.msdn.com/b/carlosfigueira/archive/2011/05/17/wcf-extensibility-ioperationinvoker.aspx.
public class StackOverflow_10156890
{
[ServiceContract]
public interface ITest
{
[WebGet]
[WrappedOperationBehavior]
string Echo(string text);
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest)]
[WrappedOperationBehavior]
int Divide(int x, int y);
}
public class Service : ITest
{
public string Echo(string text)
{
return text;
}
public int Divide(int x, int y)
{
return x / y;
}
}
class RequestWrapperOperationInvoker : IOperationInvoker
{
IOperationInvoker originalInvoker;
public RequestWrapperOperationInvoker(IOperationInvoker originalInvoker)
{
this.originalInvoker = originalInvoker;
}
public object[] AllocateInputs()
{
return this.originalInvoker.AllocateInputs();
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
Console.WriteLine("Do initialization, etc. here");
object result = null;
try
{
result = this.originalInvoker.Invoke(instance, inputs, out outputs);
}
catch (Exception e)
{
Console.WriteLine("Log exception: {0}: {1}", e.GetType().FullName, e.Message);
result = null;
outputs = null;
}
finally
{
Console.WriteLine("Do finalization, etc. here");
}
return result;
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
throw new NotSupportedException("Only synchronous operations supported");
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
throw new NotSupportedException("Only synchronous operations supported");
}
public bool IsSynchronous
{
get { return true; }
}
}
class WrappedOperationBehaviorAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Invoker = new RequestWrapperOperationInvoker(dispatchOperation.Invoker);
}
public void Validate(OperationDescription operationDescription)
{
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
var endpoint = host.AddServiceEndpoint(typeof(ITest), new WebHttpBinding(), "");
endpoint.Behaviors.Add(new WebHttpBehavior());
host.Open();
Console.WriteLine("Host opened");
WebClient c = new WebClient();
Console.WriteLine(c.DownloadString(baseAddress + "/Echo?text=Hello%20world"));
c = new WebClient();
c.Headers[HttpRequestHeader.ContentType] = "application/json";
Console.WriteLine(c.UploadString(baseAddress + "/Divide", "{\"x\":12,\"y\":0}"));
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}

Related

WCF Client reading JSONP responses

I'm writing a simple client for a web service using WCF. Unfortunately, the web service only answers with JSONP messages, not plain JSON.
Is that possible to use built-in features from .NET 4.0 to do this or do I need to extend something else to strip function name, { and } from the answer I get from the server? I know how to read JSON responses, but not JSONP yet.
What you need is a custom message encoder. On the server side, it's the encoder which adds the padding (function call) to the response, so you need something similar on the client side to remove that padding before handling the message (likely delegating it to another encoder). The other thing you'll need to worry about at the encoder is that often the content-type used for JSONP (application/x-javascript) is not recognized as a JSON content-type (because it's not, it's a function call), so the encoder should also "translate" that content-type into one which is understood by the encoder to which the call is delegated.
The code below shows an example of such an encoder. The service has been modified to always wrap the result, as you mentioned your service does.
public class StackOverflow_11255528
{
[ServiceContract]
public interface ICalculator
{
[WebGet(ResponseFormat = WebMessageFormat.Json)]
int Add(int x, int y);
[WebGet(ResponseFormat = WebMessageFormat.Json)]
int Subtract(int x, int y);
}
[ServiceContract]
public class CalculatorService
{
[WebGet(ResponseFormat = WebMessageFormat.Json)]
public Stream Add(int x, int y)
{
return ReturnWrapped(x + y);
}
[WebGet(ResponseFormat = WebMessageFormat.Json)]
public Stream Subtract(int x, int y)
{
return ReturnWrapped(x - y);
}
private Stream ReturnWrapped(int result)
{
string callback = "Something";
string response = string.Format("{0}({1});", callback, result);
WebOperationContext.Current.OutgoingResponse.ContentType = "application/x-javascript";
return new MemoryStream(Encoding.UTF8.GetBytes(response));
}
}
public class JsonpAwareClientMessageEncodingBindingElement : MessageEncodingBindingElement
{
WebMessageEncodingBindingElement webEncoding;
public JsonpAwareClientMessageEncodingBindingElement()
{
this.webEncoding = new WebMessageEncodingBindingElement();
}
public override MessageEncoderFactory CreateMessageEncoderFactory()
{
return new JsonpAwareClientMessageEncoderFactory(this.webEncoding.CreateMessageEncoderFactory());
}
public override MessageVersion MessageVersion
{
get { return this.webEncoding.MessageVersion; }
set { this.webEncoding.MessageVersion = value; }
}
public override BindingElement Clone()
{
return new JsonpAwareClientMessageEncodingBindingElement();
}
public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
{
context.BindingParameters.Add(this);
return context.BuildInnerChannelFactory<TChannel>();
}
class JsonpAwareClientMessageEncoderFactory : MessageEncoderFactory
{
private MessageEncoderFactory factory;
public JsonpAwareClientMessageEncoderFactory(MessageEncoderFactory factory)
{
this.factory = factory;
}
public override MessageEncoder Encoder
{
get { return new JsonpAwareClientMessageEncoder(this.factory.Encoder); }
}
public override MessageVersion MessageVersion
{
get { return this.factory.MessageVersion; }
}
}
class JsonpAwareClientMessageEncoder : MessageEncoder
{
private MessageEncoder encoder;
public JsonpAwareClientMessageEncoder(MessageEncoder encoder)
{
this.encoder = encoder;
}
public override string ContentType
{
get { return this.encoder.ContentType; }
}
public override string MediaType
{
get { return this.encoder.MediaType; }
}
public override MessageVersion MessageVersion
{
get { return this.encoder.MessageVersion; }
}
public override bool IsContentTypeSupported(string contentType)
{
if (contentType == "application/x-javascript")
{
contentType = "application/json";
}
return this.encoder.IsContentTypeSupported(contentType);
}
public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
{
if (contentType == "application/x-javascript")
{
contentType = "application/json";
}
byte openParenthesis = (byte)'(';
byte closeParenthesis = (byte)')';
int startOfParenthesis = buffer.Offset;
int count = buffer.Count;
while (buffer.Array[startOfParenthesis] != openParenthesis)
{
startOfParenthesis++;
count--;
}
// Skipped 'Func', now skipping '('
startOfParenthesis++;
count--;
// Now need to trim the closing parenthesis and semicolon, if any
int endOfParenthesis = buffer.Offset + buffer.Count - 1;
while (buffer.Array[endOfParenthesis] != closeParenthesis)
{
endOfParenthesis--;
count--;
}
// Skipped back to ')', now remove it
endOfParenthesis--;
count--;
return this.encoder.ReadMessage(new ArraySegment<byte>(buffer.Array, startOfParenthesis, count), bufferManager, contentType);
}
public override Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType)
{
throw new NotSupportedException("Streamed mode not supported");
}
public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
{
return this.encoder.WriteMessage(message, maxMessageSize, bufferManager, messageOffset);
}
public override void WriteMessage(Message message, Stream stream)
{
throw new NotSupportedException("Streamed mode not supported");
}
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(CalculatorService), new Uri(baseAddress));
WebHttpBinding binding = new WebHttpBinding { CrossDomainScriptAccessEnabled = true };
host.AddServiceEndpoint(typeof(CalculatorService), binding, "").Behaviors.Add(new WebHttpBehavior());
host.Open();
Console.WriteLine("Host opened");
WebClient c = new WebClient();
Console.WriteLine(c.DownloadString(baseAddress + "/Add?x=5&y=8&callback=Func"));
CustomBinding clientBinding = new CustomBinding(
new JsonpAwareClientMessageEncodingBindingElement(),
new HttpTransportBindingElement { ManualAddressing = true });
ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>(clientBinding, new EndpointAddress(baseAddress));
factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
ICalculator proxy = factory.CreateChannel();
Console.WriteLine(proxy.Subtract(456, 432));
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
There is no direct way which I know but lets first try to understand difference between a JSON and JSONP
//JSON
{"prop":"val"}
//JSONP
func({"prop":"val"});
To get the JSON string you could simply strip every thing in between the "(" and ")" braces and then use different JSON libraries to convert it in to objects.
string jsonString = Regex.Match(jsonpString, #"\(([^)]*)\)").Groups[1].Value

WCF: MessageContract handle OnDeserialized Event

How can I handle an OnDeserialized event using MessageContract?
Need to do some data verification and transformation after receiving the message (but before executing methods).
For DataContract it was solved by declaration attribute.
But for MessageContract it doesn't work.
Is there any way to do this?
You are better of using WCF extension points instead of serialization. Specifically IOperationInvoker.
EDIT
Example:
Service and message definitions below. Note the new MyValidationBeforeInvokeBehavior attribute.
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
[OperationContract]
[MyValidationBeforeInvokeBehavior]
AddPatientRecordResponse AddPatientRecord(PatientRecord composite);
}
[MessageContract(IsWrapped = false, ProtectionLevel = ProtectionLevel.None)]
public class AddPatientRecordResponse
{
[MessageHeader(ProtectionLevel = ProtectionLevel.None)]
public Guid recordID;
[MessageHeader(ProtectionLevel = ProtectionLevel.None)]
public string patientName;
[MessageBodyMember(ProtectionLevel = ProtectionLevel.None)]
public string status;
}
[MessageContract(IsWrapped = false, ProtectionLevel = ProtectionLevel.None)]
public class PatientRecord
{
[MessageHeader(ProtectionLevel = ProtectionLevel.None)]
public Guid recordID;
[MessageHeader(ProtectionLevel = ProtectionLevel.None)]
public string patientName;
//[MessageHeader(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
[MessageHeader(ProtectionLevel = ProtectionLevel.None)]
public string SSN;
[MessageBodyMember(ProtectionLevel = ProtectionLevel.None)]
public string comments;
[MessageBodyMember(ProtectionLevel = ProtectionLevel.None)]
public string diagnosis;
[MessageBodyMember(ProtectionLevel = ProtectionLevel.None)]
public string medicalHistory;
}
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
public AddPatientRecordResponse AddPatientRecord(PatientRecord patient)
{
var response = new AddPatientRecordResponse
{
patientName = patient.patientName,
recordID = patient.recordID,
status = "Sucess"
};
return response;
}
}
Hook into wcf extensibility
public class MyValidationBeforeInvokeBehavior : Attribute, IOperationBehavior
{
public void Validate(OperationDescription operationDescription)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Invoker = new MyValidationBeforeInvoke(dispatchOperation.Invoker);
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
}
Custom operation invoker:
public class MyValidationBeforeInvoke : IOperationInvoker
{
private readonly IOperationInvoker _original;
public MyValidationBeforeInvoke(IOperationInvoker original)
{
_original = original;
}
public object[] AllocateInputs()
{
return _original.AllocateInputs();
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
var validator = new ValidatePatientRecord((PatientRecord) inputs[0]);
if (validator.IsValid())
{
var ret = _original.Invoke(instance, inputs, out outputs);
return ret;
}
else
{
outputs = new object[] {};
var patientRecord = (PatientRecord) inputs[0];
var returnMessage = new AddPatientRecordResponse
{
patientName = patientRecord.patientName,
recordID = patientRecord.recordID,
status = "Validation Failed"
};
return returnMessage;
}
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
return _original.InvokeBegin(instance, inputs, callback, state);
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
return _original.InvokeEnd(instance, out outputs, result);
}
public bool IsSynchronous
{
get { return _original.IsSynchronous; }
}
}
The essence is that we never invoke the service call because it will never get there due to the validation error. We can also return what happened to the client.
Validation class and client call (for completes):
public class ValidatePatientRecord
{
private readonly PatientRecord _patientRecord;
public ValidatePatientRecord(PatientRecord patientRecord)
{
_patientRecord = patientRecord;
}
public bool IsValid()
{
return _patientRecord.patientName != "Stack Overflow";
}
}
client:
class Program
{
static void Main(string[] args)
{
var patient = new PatientRecord { SSN = "123", recordID = Guid.NewGuid(), patientName = "Stack Overflow" };
var proxy = new ServiceReference1.Service1Client();
var result = proxy.AddPatientRecord(patient);
Console.WriteLine(result.status);
Console.ReadLine();
}
}

WCF Rest weird routing behavior

I have a resource available at
http://localhost/resource
I do not have any route like
http:/localhost/resource?name=john
but when I try to hit the above URI I get the result of http://localhost/resource. I was expecting a 404.
Any idea why its ignoring ?name=john and match the url ??
Query string parameters are optional and not "officially" part of the address - it goes from the scheme to the path (via host and port). There are many scenarios where you have one operation at address http://something.com/path, and in the operation code you look at the query string parameters to make some decision. For example, the code below looks for a "format" parameter in the query string which may or may not be passed, and all requests (with or without it) are correctly routed to the operation.
public class StackOverflow_10422764
{
[DataContract(Name = "Person", Namespace = "")]
public class Person
{
[DataMember]
public string Name { get; set; }
[DataMember]
public int Age { get; set; }
}
[ServiceContract]
public class Service
{
[WebGet(UriTemplate = "/NoQueryParams")]
public Person GetPerson()
{
string format = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["format"];
if (format == "xml")
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Xml;
}
else if (format == "json")
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Json;
}
return new Person { Name = "Scooby Doo", Age = 10 };
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));
host.Open();
Console.WriteLine("Host opened");
WebClient c = new WebClient();
Console.WriteLine(c.DownloadString(baseAddress + "/NoQueryParams"));
c = new WebClient();
Console.WriteLine(c.DownloadString(baseAddress + "/NoQueryParams?format=json"));
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
Update after comments:
If you want to force the request to contain exactly the parameters specified in the template, then you can use something like a message inspector to validate that.
public class StackOverflow_10422764
{
[DataContract(Name = "Person", Namespace = "")]
public class Person
{
[DataMember]
public string Name { get; set; }
[DataMember]
public int Age { get; set; }
}
[ServiceContract]
public class Service
{
[WebGet(UriTemplate = "/NoQueryParams")]
public Person GetPerson()
{
string format = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["format"];
if (format == "xml")
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Xml;
}
else if (format == "json")
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Json;
}
return new Person { Name = "Scooby Doo", Age = 10 };
}
[WebGet(UriTemplate = "/TwoQueryParam?x={x}&y={y}")]
public int Add(int x, int y)
{
return x + y;
}
}
public class MyForceQueryMatchBehavior : IEndpointBehavior, IDispatchMessageInspector
{
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
}
public void Validate(ServiceEndpoint endpoint)
{
}
#endregion
#region IDispatchMessageInspector Members
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
UriTemplateMatch uriTemplateMatch = (UriTemplateMatch)request.Properties["UriTemplateMatchResults"];
// TODO: may need to deal with the case of implicit UriTemplates, or if you want to allow for some query parameters to be ommitted
List<string> uriTemplateQueryVariables = uriTemplateMatch.Template.QueryValueVariableNames.Select(x => x.ToLowerInvariant()).ToList();
List<string> requestQueryVariables = uriTemplateMatch.QueryParameters.Keys.OfType<string>().Select(x => x.ToLowerInvariant()).ToList();
if (uriTemplateQueryVariables.Count != requestQueryVariables.Count || uriTemplateQueryVariables.Except(requestQueryVariables).Count() > 0)
{
throw new WebFaultException(HttpStatusCode.NotFound);
}
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
}
#endregion
}
static void SendRequest(string uri)
{
Console.WriteLine("Request to {0}", uri);
WebClient c = new WebClient();
try
{
Console.WriteLine(" {0}", c.DownloadString(uri));
}
catch (WebException e)
{
HttpWebResponse resp = e.Response as HttpWebResponse;
Console.WriteLine(" ERROR => {0}", resp.StatusCode);
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(Service), new WebHttpBinding(), "");
endpoint.Behaviors.Add(new WebHttpBehavior());
endpoint.Behaviors.Add(new MyForceQueryMatchBehavior());
host.Open();
Console.WriteLine("Host opened");
SendRequest(baseAddress + "/NoQueryParams");
SendRequest(baseAddress + "/NoQueryParams?format=json");
SendRequest(baseAddress + "/TwoQueryParam?x=7&y=9&z=other");
SendRequest(baseAddress + "/TwoQueryParam?x=7&y=9");
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}

Enabling SOAP Message Inspection for a Single Operation

How can I force an IMessageInspector to fire only when a specific operation is called, rather than firing whenever a call to the service is made? Currently, I am applying a custom IEndpointBehavior to the endpoint which calls this IMessageInspector, but this is not exactly what I would like to do...
The message inspectors are bound to the dispatch runtime object, which is a single one for each endpoint, not operation. If a parameter inspector works, then you can use it (it's bound to the operation), but if you need a message inspector, then it cannot be bound to a single operation. But you can, inside your inspector, check whether the operation is what you expect (based on the Action header, which is unique per operation), as shown in the code below.
public class StackOverflow_7502134
{
[ServiceContract]
public interface ITest
{
[OperationContract]
string Echo(string text);
[OperationContract]
int Add(int x, int y);
}
public class Service : ITest
{
public string Echo(string text)
{
return text;
}
public int Add(int x, int y)
{
return x + y;
}
}
public class MyInspector : IEndpointBehavior, IDispatchMessageInspector
{
string[] operationNames;
public MyInspector(params string[] operationNames)
{
this.operationNames = operationNames ?? new string[0];
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
}
public void Validate(ServiceEndpoint endpoint)
{
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
// by default, action == <serviceContractNamespace>/<serviceContractName>/<operationName>
string operationName = request.Headers.Action.Substring(request.Headers.Action.LastIndexOf('/') + 1);
if (this.operationNames.Contains(operationName))
{
Console.WriteLine("Inspecting request to operation {0}", operationName);
Console.WriteLine(request);
Console.WriteLine();
return operationName;
}
else
{
return null;
}
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
string operationName = correlationState as string;
if (operationName != null)
{
Console.WriteLine("Inspecting reply from operation {0}", operationName);
Console.WriteLine(reply);
Console.WriteLine();
}
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
MyInspector inspector = new MyInspector("Add"); // inspecting Add, not Echo
host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "").Behaviors.Add(inspector);
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new BasicHttpBinding(), new EndpointAddress(baseAddress));
ITest proxy = factory.CreateChannel();
Console.WriteLine("Calling Echo");
Console.WriteLine(proxy.Echo("Hello world"));
Console.WriteLine();
Console.WriteLine("Calling Add");
Console.WriteLine(proxy.Add(4, 5));
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}

How do I implement Orchestration on WCF using MessageInspector

I have a WCF Service, And I inspect that using MessageInspector ( inherit IDispatchMessageInspector)
I want to do some thing before running service and result of that ı want not run service.
I want to prevent client call but client dont receive exception.
Can you help me
This scenario looks like the post in the MSDN WCF forum entitled "IDispatchMessageInspector.AfterReceiveRequest - skip operation and manually generate custom response instead". If this is what you need (when you receive a message in the inspector, you decide that you want to skip the service operation, but return a message to the client and the client should not see an exception), then this answer should work for you as well. Notice that you'll need to create the response message in the same format as the client expects, otherwise you'll have an exception.
This code uses three of the (many) WCF extensibility points to achieve that scenario, a message inspector (as you've mentioned you're using), a message formatter and an operation invoker. I've blogged about them in an ongoing series about WCF extensibility at http://blogs.msdn.com/b/carlosfigueira/archive/2011/03/14/wcf-extensibility.aspx.
public class Post_55ef7692_25dc_4ece_9dde_9981c417c94a
{
[ServiceContract(Name = "ITest", Namespace = "http://tempuri.org/")]
public interface ITest
{
[OperationContract]
string Echo(string text);
}
public class Service : ITest
{
public string Echo(string text)
{
return text;
}
}
static Binding GetBinding()
{
BasicHttpBinding result = new BasicHttpBinding();
return result;
}
public class MyOperationBypasser : IEndpointBehavior, IOperationBehavior
{
internal const string SkipServerMessageProperty = "SkipServer";
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MyInspector(endpoint));
}
public void Validate(ServiceEndpoint endpoint)
{
}
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Formatter = new MyFormatter(dispatchOperation.Formatter);
dispatchOperation.Invoker = new MyInvoker(dispatchOperation.Invoker);
}
public void Validate(OperationDescription operationDescription)
{
}
class MyInspector : IDispatchMessageInspector
{
ServiceEndpoint endpoint;
public MyInspector(ServiceEndpoint endpoint)
{
this.endpoint = endpoint;
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
Message result = null;
HttpRequestMessageProperty reqProp = null;
if (request.Properties.ContainsKey(HttpRequestMessageProperty.Name))
{
reqProp = request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
}
if (reqProp != null)
{
string bypassServer = reqProp.Headers["X-BypassServer"];
if (!string.IsNullOrEmpty(bypassServer))
{
result = Message.CreateMessage(request.Version, this.FindReplyAction(request.Headers.Action), new OverrideBodyWriter(bypassServer));
}
}
return result;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
Message newResult = correlationState as Message;
if (newResult != null)
{
reply = newResult;
}
}
private string FindReplyAction(string requestAction)
{
foreach (var operation in this.endpoint.Contract.Operations)
{
if (operation.Messages[0].Action == requestAction)
{
return operation.Messages[1].Action;
}
}
return null;
}
class OverrideBodyWriter : BodyWriter
{
string bypassServerHeader;
public OverrideBodyWriter(string bypassServerHeader)
: base(true)
{
this.bypassServerHeader = bypassServerHeader;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement("EchoResponse", "http://tempuri.org/");
writer.WriteStartElement("EchoResult");
writer.WriteString(this.bypassServerHeader);
writer.WriteEndElement();
writer.WriteEndElement();
}
}
}
class MyFormatter : IDispatchMessageFormatter
{
IDispatchMessageFormatter originalFormatter;
public MyFormatter(IDispatchMessageFormatter originalFormatter)
{
this.originalFormatter = originalFormatter;
}
public void DeserializeRequest(Message message, object[] parameters)
{
if (message.Properties.ContainsKey(MyOperationBypasser.SkipServerMessageProperty))
{
Message returnMessage = message.Properties[MyOperationBypasser.SkipServerMessageProperty] as Message;
OperationContext.Current.IncomingMessageProperties.Add(MyOperationBypasser.SkipServerMessageProperty, returnMessage);
OperationContext.Current.OutgoingMessageProperties.Add(MyOperationBypasser.SkipServerMessageProperty, returnMessage);
}
else
{
this.originalFormatter.DeserializeRequest(message, parameters);
}
}
public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
if (OperationContext.Current.OutgoingMessageProperties.ContainsKey(MyOperationBypasser.SkipServerMessageProperty))
{
return null;
}
else
{
return this.originalFormatter.SerializeReply(messageVersion, parameters, result);
}
}
}
class MyInvoker : IOperationInvoker
{
IOperationInvoker originalInvoker;
public MyInvoker(IOperationInvoker originalInvoker)
{
if (!originalInvoker.IsSynchronous)
{
throw new NotSupportedException("This implementation only supports synchronous invokers");
}
this.originalInvoker = originalInvoker;
}
public object[] AllocateInputs()
{
return this.originalInvoker.AllocateInputs();
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
if (OperationContext.Current.IncomingMessageProperties.ContainsKey(MyOperationBypasser.SkipServerMessageProperty))
{
outputs = null;
return null; // message is stored in the context
}
else
{
return this.originalInvoker.Invoke(instance, inputs, out outputs);
}
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
throw new NotSupportedException();
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
throw new NotSupportedException();
}
public bool IsSynchronous
{
get { return true; }
}
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(ITest), GetBinding(), "");
endpoint.Behaviors.Add(new MyOperationBypasser());
foreach (var operation in endpoint.Contract.Operations)
{
operation.Behaviors.Add(new MyOperationBypasser());
}
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(GetBinding(), new EndpointAddress(baseAddress));
ITest proxy = factory.CreateChannel();
Console.WriteLine(proxy.Echo("Hello"));
Console.WriteLine("And now with the bypass header");
using (new OperationContextScope((IContextChannel)proxy))
{
HttpRequestMessageProperty httpRequestProp = new HttpRequestMessageProperty();
httpRequestProp.Headers.Add("X-BypassServer", "This message will not reach the service operation");
OperationContext.Current.OutgoingMessageProperties.Add(
HttpRequestMessageProperty.Name,
httpRequestProp);
Console.WriteLine(proxy.Echo("Hello"));
}
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}