I have a service that implement the Async pattern:
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginLoadDocument(Byte[] value, AsyncCallback callback, object state);
Boolean EndLoadDocument(IAsyncResult asyncResult);
The "BeginLoadDocument" run a private method "CallBack" in the service side using a ThreadPool:
public IAsyncResult BeginLoadDocument(string id, AsyncCallback callback, object state)
{
PendingAsyncResult<string> asyncResult =
new PendingAsyncResult<string>(id, callback, state);
ThreadPool.QueueUserWorkItem(new WaitCallback(Callback), asyncResult);
return asyncResult;
}
the Callback method load the document and set the result for the "EndLoadDocument".
So far so good, but how I can handle the exceptions?
If I throw an excetion in the server side, I get a FaultedException'1 wasn't handled.
I did try to use the attribute [FaultContract(typeof(InforError))] where "InfoError" is my custum DataMember, but it does not work.
I am building the proxy using the svcutil /a http:....
You can catch an exception client-side as follows:
try {
MyClient.MyCall();
}
catch (FaultException<IOException> exc) {
// Log and handle exception
}
Where the real exception thrown was, in this example, an IOException.
You'll also need a FaultContract, as you indicated you are, on the Service Interface, as such:
[ServiceContract]
public interface IMyService {
[OperationContract]
[FaultContract(typeof(IOException))]
void MyCall();
}
**** EDIT ****
I'm a little fuzzy on something you wrote:
[FaultContract(typeof(InforError))] where "InfoError" is my custum DataMember
What do you mean by 'DataMember'? What's the definition for InfoError?
The [FaultContract] should be defined on the service interface method... in your post you sound like you're trying to add it to the client side; this is not correct. If I modify your example code, it would look like:
[ServiceContract]
public interface IMyService {
[OperationContract(AsyncPattern = true)]
[FaultContract(typeof(InfoErrorException))]
IAsyncResult BeginLoadDocument(Byte[] value, AsyncCallback callback, object state);
string EndLoadDocument(IAsyncResult asyncResult);
If your service interface is decorated as such, the client should be able to receive FaultExceptions when you call EndLoadDocument (provided the exception that was thrown was an InfoErrorException exception).
On the server side, you have to trap exceptions, then wrap them in a FaultException, as such:
catch (IOException exp) {
InfoErrorException myException = new InfoErrorException();
myException.Reason = "I failed: " + exp.Message;
throw new FaultException<InfoErrorException>(myException);
}
I believe (but would have to double-check) that you can also catch a FaultException on the client side without specifying the type... similar to catching the generic System.Exception.
Your try...catch for the FaultException should be in your callback, around the statement to call EndLoadDocument().
Looks like you are using Silverlight.
Problem is that WCF service returns HTTP Status different to 200, so browser do not provide additional data about response to Silverlight Runtime.
The solution is to use custom ErrorHandler to provide necessary HTTP Code:
/// <summary>Sets the HTTP code to 200 for faults.</summary>
public class HttpStatusCode200ErrorHandler : IErrorHandler
{
public Type ServiceType { get; set; }
public HttpStatusCode200ErrorHandler(Type serviceType)
{
ServiceType = serviceType;
}
public bool HandleError(Exception error)
{
return false;
}
public virtual void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
fault.Properties[HttpResponseMessageProperty.Name] =
new HttpResponseMessageProperty { StatusCode = System.Net.HttpStatusCode.OK };
}
}
You can attach it to your service using following ServiceBehavior attribute:
/// <summary>Applies HttpStatusCode200ErrorHandler.</summary>
[AttributeUsage(AttributeTargets.Class)]
public class HttpStatusCode200BehaviorAttribute : Attribute, IServiceBehavior
{
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
dispatcher.ErrorHandlers.Add(new HttpStatusCode200ErrorHandler(serviceDescription.ServiceType));
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
For more details look at Understanding WCF Faults in Silverlight.
Related
I'm well aware of how to use FaultException with details. I know I can declare details contract, then I need to decorate the method which is expected to throw this kind of exceptions with [FaultContract(type(DetailsContractType))] and then I do throw FaultException in that method. All that is understood and worked. What I need is to be able to throw FaultException from all methods of all contracts in my WCF host. Adding [FaultContract(type(DetailsContractType))] to each method of each operation contract seems to much to me. Is there another way to allow this kind of exceptions without decorating methods with that attribute? If I just remove that attribute everything stops working and the exception becomes just FaultException on the client side. I was thinking about DataContractResolver but it looks like it is not involved in DetailsContractType resolution. Any ideas, hints, solutions?
Using IErrorHandler does not relieve you from decorating contract operations with the FaultContractAttribute what I'm trying to avoid. It is even stated in the example you referred, there is a comment there
// This behavior requires that the contract have a SOAP fault with a detail type of
GreetingFault.
and
throw new InvalidOperationException(String.Format(
"EnforceGreetingFaultBehavior requires a "
+ "FaultContractAttribute(typeof(GreetingFault)) in each operation contract. "
+ "The \"{0}\" operation contains no FaultContractAttribute.",
opDesc.Name)
);
You can implement the IErrorHandler interface to uniformly handle errors in WCF,here is a Demo:
[ServiceContract]
public interface IDemo {
[OperationContract]
void DeleteData(int dataId);
}
class DemoService : IDemo
{
public void DeleteData(int dataId)
{
if (dataId<0) {
throw new ArgumentException("error");
}
}
}
The above code is the interface and implementation class of WCF service.
class MyCustErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
FaultException faultException = new FaultException(error.Message);
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version,messageFault,"my-test-error");
}
}
The above code is the implementation class of the IErrorHandler interface.
class MyEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
return;
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
MyCustErrorHandler handler = new MyCustErrorHandler();
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(handler);
}
public void Validate(ServiceEndpoint endpoint)
{
return;
}
}
We add a custom error handling class by extending the Behavior method of the endpoint.
ServiceEndpoint ep = selfHost.AddServiceEndpoint(typeof(IDemo), new BasicHttpBinding(), "CalculatorService");
MyEndpointBehavior myEndpointBehavior = new MyEndpointBehavior();
ep.EndpointBehaviors.Add(myEndpointBehavior);
The client executes the following code will print "error" in the console:
try {
demoClient.DeleteData(-3);
}
catch (FaultException fault) {
string err = fault.Reason.GetMatchingTranslation().Text;
Console.WriteLine(err);
}
For more information about IErrorhandler,Please refer to the following link:
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.dispatcher.ierrorhandler?view=netframework-4.8
UPDATE
If you don’t want to use IErrorhandler, you can also use FaultReason:
public string SayHello(string name) {
if (name.Length<2) {
FaultReasonText faultReasonText = new FaultReasonText("name length cannot be less than 2");
FaultReason reason = new FaultReason(faultReasonText);
throw new FaultException(reason);
}
return "hello";
}
The client needs to catch exceptions when calling:
try {
string res = channnel.Sayhello("B");
}
catch (FaultException fex) {
if (fex.Reason != null) {
FaultReason reason = fex.Reason;
//Get error information
FaultReasonText txt = reason.GetMatchingTranslation();
Console.WriteLine(txt.Text);
}
}
Many posts here on SO asks for help in solving a 400 http bad request. However, I couldn't find a post that explains how to get the underlying cause of the exception.
Assume that both supplier and client running .NET, and also assume that we're on debug time:
If I'm the web service supplier, how can I make sure the client will recieve the reason for the bad request?
(I tried decorating the web service class with the attribute [ServiceBehavior(IncludeExceptionDetailInFaults = true)], but it seems to have no effect).
If I'm the web service client, how can I see what's the reason for the bad request?
You can try including the exception in the fault in the rest service
catch (Exception ex)
{
Log.Fatal("Could not complete process", ex);
throw new WebFaultException<string>(
string.Format(ex.Message),
HttpStatusCode.BadRequest);
}
The above code will handle both scenarios, assuming you have a logger enabled, you can just get the log files on the server, the client would receive the exception message
I guess you could just send the client a friendly message so that they don't anything about your specific implementation, or just create a class that you can call
WebFaultException< T >
with.
EDIT 2
If you are getting an issue before you can get the request, you can try the following
Add a FaultHandler to your Service
[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class GenericErrorHandlingBehavior : Attribute, IErrorHandler, IServiceBehavior
{
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
//throw new NotImplementedException();
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
//throw new NotImplementedException();
// Adds a CoreServiceBehavior to each ChannelDispatcher
foreach (var channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
var channelDispatcher = channelDispatcherBase as ChannelDispatcher;
channelDispatcher.ErrorHandlers.Add(new GenericErrorHandlingBehavior());
}
}
public bool HandleError(Exception error)
{
//throw new NotImplementedException();
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error is FaultException)
return;//nothing to do if the error is a fault
//the type cannot be determined[ default to this value]
var exception = new FaultException<GenericServiceFault>(
new GenericServiceFault(), new FaultReason(error.Message));
MessageFault messageFault = exception.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, exception.Action);
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
//throw new NotImplementedException();
}
}
The GenericServiceFault class is just a simple DataContract class,
You can then Log the error in your ProvideFaultMethod, or The HandleError method and provide a meaningful error to the user
You can use the GenericErrorHandlingBehavior on your service implementation as an attribute, i.e [GenericErrorHandlingBehavior]
For a service, there is a group of faults which can be thrown by all operations, so in order to centralize that, I have made a behavior, FaultAdderBehavior, which adds fault contracts to all operations on a service. It seems to work fine as the contracts are added to the WSDL and the client can catch the fault with a line like:
...
catch(FaultException<MyFault> e){ ... }
...
I have also made an IErrorHandler which converts non-fault exceptions to a certain kind of fault. See below.
The problem is, that the fault which is constructed in the error handler cannot be caught on the client. That is, it cannot be caught as a generic FaultException<MyFault>, but only as FaultException.
If I explicitly adds a FaultContract(typeof(MyFault)) to the operation, the client can suddenly catch the generic fault exception just fine.
So that could indicate that something is wrong with my FaultAdderBehavior after all. Or is there something wrong with my error handler?
I have noticed, that the fault.Action given as argument to CreateMessage() is null. This raises my concern.
Below is an example illustrating the problem. It is the method ShouldThrowFault() that causes headaches, while ThrowsDirectly() works exactly as desired.
To summarize, my question is: why can't the client catch the generic FaultException<MyFault> when it comes from the error handler?
[ServiceContract]
public interface IUncatchableFaultService
{
[OperationContract]
// [FaultContract(typeof(MyFault))]
void ShouldThrowFault(string arg1);
[OperationContract]
void ThrowsDirectly();
}
[FaultAdderBehavior(typeof(MyFault), typeof(MyFault2))]
[MyErrorHandlerBehavior]
internal class UncatchableFaultService : IUncatchableFaultService
{
public void ShouldThrowFault(string arg1)
{
throw new Exception();
}
public void ThrowsDirectly()
{
throw new FaultException<MyFault>(new MyFault());
}
}
[DataContract]
public class MyFault
{
}
[DataContract]
public class MyFault2
{
}
public class MyErrorHandlerBehaviorAttribute : Attribute, IServiceBehavior
{
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase dispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = dispatcherBase as ChannelDispatcher;
if (channelDispatcher == null) continue;
channelDispatcher.ErrorHandlers.Add(new MyErrorHandler());
}
}
private class MyErrorHandler : IErrorHandler
{
public void ProvideFault(Exception error, MessageVersion version, ref Message message)
{
if (error is FaultException) return;
var fault = new FaultException<MyFault>(new MyFault(), "I am a fault.");
MessageFault messageFault = fault.CreateMessageFault();
message = Message.CreateMessage(version, messageFault, fault.Action);
}
public bool HandleError(Exception error)
{
return false;
}
}
}
public class FaultAdderBehaviorAttribute : Attribute, IContractBehavior
{
private Type[] faults;
public FaultAdderBehaviorAttribute(params Type[] faults)
{
this.faults = faults;
}
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
foreach (OperationDescription op in contractDescription.Operations)
foreach (Type fault in this.faults)
op.Faults.Add(this.ExposeFault(fault));
}
private FaultDescription ExposeFault(Type fault)
{
string action = fault.Name;
DescriptionAttribute attr = (DescriptionAttribute)Attribute.GetCustomAttribute(fault, typeof(DescriptionAttribute));
if (attr != null) action = attr.Description;
FaultDescription description = new FaultDescription(action);
description.DetailType = fault;
description.Name = fault.Name;
return description;
}
}
There is a problem with FaultAdderBehaviorAttribute and you are right to be worried that fault.Action is null.
For FaultException functionality to work properly you must have non-null actions for each fault.
When you declare the FaultContract on the operation itself you are implicitly using WCF's ability to automatically generate the action string (further details). However, when you use your FaultAdderBehaviorAttribute, any default actions for declared operations have already been generated, and you are failing to provide a valid action.
I try to implement an Asynchronous Service Operation (http://msdn.microsoft.com/en-us/library/ms731177.aspx) with masstransit request/response.
[ServiceContract]
public interface IService1
{
[OperationContractAttribute(AsyncPattern = true)]
IAsyncResult BeginMyOperation(string data, AsyncCallback callback, object asyncState);
string EndMyOperation(IAsyncResult result);
}
public class Service1 : IService1
{
private string _answer;
public IAsyncResult BeginMyOperation(string data, AsyncCallback callback, object asyncState)
{
return Bus.Instance.BeginPublishRequest(
new MyRequestMessage { Data = data }, callback, asyncState, cfg =>
{
cfg.Handle<MyResponseMessage>(c => _answer = c.Answer);
cfg.SetTimeout(5.Seconds());
});
}
public string EndMyOperation(IAsyncResult result)
{
Bus.Instance.EndPublishRequest<MyResponseMessage>(result);
return _answer;
}
}
But EndPublishRequest throws an Exception "The argument is not an IRequest". Am I doing something wrong?
Yeah, the exception message needs to be a bit better, if you look at the signature though:
public static bool EndPublishRequest<TRequest>(this IServiceBus bus, IAsyncResult asyncResult)
it becomes apparent that the request message type is the required type arg here.
after add my formatter to operation behavior :
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
ServerMessageFormatter Formatter = new ServerMessageFormatter();
dispatchOperation.Formatter = Formatter;
}
In Formatter I have empty Deserialize method cause I want to use default behavior
public void DeserializeRequest(System.ServiceModel.Channels.Message message, object[] parameters)
{}
but In Serialize
public System.ServiceModel.Channels.Message SerializeReply(System.ServiceModel.Channels.MessageVersion messageVersion, object[] parameters, object result)
{
//some code
}
Problem is that after enable this class, parameters in service method always was show as null, but in IDispatchMessageInspector class I see that parameters is send properly. I don't know why it's happening, I only add this message formatter code , it is possible that empty class for Deserialize causes this ?
When we are implementing IDispatchMessageFormatter interface usually we are not thinking that method DeserializeRequest is something important as it is not returning any data. This is misleading as method needs to do something.
The easiest way to make parameters correctly passed is to use base DispatchMessageFormatter. Add it to the constructor.
public class ResponseJsonFormatter : IDispatchMessageFormatter
{
IDispatchMessageFormatter basicDispatchMessageFormatter;
OperationDescription Operation;
public ResponseJsonFormatter(OperationDescription operation, IDispatchMessageFormatter inner)
{
this.Operation = operation;
this.basicDispatchMessageFormatter = inner;
}
public void DeserializeRequest(Message message, object[] parameters)
{
basicDispatchMessageFormatter.DeserializeRequest(message, parameters);
}
public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
string json=Newtonsoft.Json.JsonConvert.SerializeObject(result);
byte[] bytes = Encoding.UTF8.GetBytes(json);
Message replyMessage = Message.CreateMessage(messageVersion, Operation.Messages[1].Action, new RawDataWriter(bytes));
replyMessage.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));
return replyMessage;
}
}
And initiate it in the behavior:
public class ClientJsonDateFormatterBehavior : IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
// throw new NotImplementedException();
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Formatter = new ResponseJsonFormatter(operationDescription, dispatchOperation.Formatter);
}
public void Validate(OperationDescription operationDescription)
{
// throw new NotImplementedException();
}
}
You can check the working example here in the github branch DateTimeFormatterWithParams
There is no default behavior if you do not provide your own logic on DeserializeRequest. You need to either reference the existing formatter and delegate manually in your ServerMessageFormater or provide your own logic.