No matter how hard I try I cannot seem to be able to handle WCF faults in Silverlight.
In fact the error seems to never leave the server !
E.g. when I debug it, it stops on the line where I throw the FaultException saying it was not handled:
[SilverlightFaultBehavior]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class StoreService : IStoreContract
{
public System.Collections.Generic.List<string> GetStoreDesignNames()
{
try
{
StoreDataContext swdc = new StoreDataContext();
var query = from storeDesign in swdc.StoreDesignDBs select storeDesign.Name;
return query.ToList();
}
catch (System.Data.SqlClient.SqlException sqlExcept)
{
throw new FaultException<SqlFault>(new SqlFault() { Message = sqlExcept.Message });
}
}
}
The class that implements this method derives from a contract interface:
[ServiceContract(Namespace = "Store")]
public interface IStoreContract
{
/// <summary>
/// Obtain the list of store design names.
/// </summary>
[OperationContract,
FaultContract(typeof(SqlFault))]
List<String> GetStoreDesignNames();
}
And the SqlFault class is defined like this:
public class SqlFault
{
public String Message { get; set; }
}
On the client side I handle the error as follow:
// swc is the client
swc.GetStoreDesignNamesCompleted += new EventHandler<ServiceReference.GetStoreDesignNamesCompletedEventArgs>((obj, evt) =>
{
if (evt.Error == null)
{
// In case of success
MessageBox.Show(evt.Result.First());
}
else if (evt.Error is FaultException<ServiceReference.SqlFault>)
{
FaultException<ServiceReference.SqlFault> fault = evt.Error as FaultException<ServiceReference.SqlFault>;
Dispatcher.BeginInvoke(() =>
{
ErrorWindow ew = new ErrorWindow(fault.Detail.Message, "No details");
ew.Show();
});
}
});
swc.GetStoreDesignNamesAsync();
I have tried to put the [SilverlightFaultBehavior] attribute on the interface, to no avail. Even if I do without the interface I still have this error.
I have also tried to use a behavior extension in the web.config as described here but I get a warning saying the extension is not valid.
How does one go about properly handling WCF fault in Siverlight ?
Thanks in advance.
I haven't used WCF (been using WCF RIA Services) but I did come across this article a while ago.
Getting something better than “Server not found.” from WCF in Silverlight
After battling with this for hours I finally hacked something together that works.
This is really a horrible hack and I would have much preferred to use BehaviorExtension for this task. The trick is to set manually the HTTP status code in the body of the WCF method like so:
public System.Collections.Generic.List<string> GetStoreDesignNames()
{
try
{
StoreDataContext swdc = new StoreDataContext();
var query = from storeDesign in swdc.StoreDesignDBs select storeDesign.Name;
return query.ToList();
}
catch (System.Data.SqlClient.SqlException sqlExcept)
{
System.ServiceModel.Web.WebOperationContext ctx = System.ServiceModel.Web.WebOperationContext.Current;
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;
throw new FaultException<SqlFault>(new SqlFault() { Message = sqlExcept.Message });
}
}
The error message then correctly displays on the client side.
If anybody has a better solution than this I'd like to hear it.
Related
I'm trying to get the WCF service to run in InstanceContextMode.Single that way all requests can share the same state of the service. However, when I try to start the service with this behavior I can still see that the service's constructor gets called with each request. I couldn't figure out a quick way to update the ServiceBehaviorAttribute so that's why I'm replacing it (the default value for InstanceContextMode is not Single). Seems like there's one instance when we start it up and then another instance for all requests that come in later on. Any ideas what might be going wrong?
/// <summary>Constructor</summary>
CAutomation::CAutomation()
{
//TODO: pull from config
m_Host = gcnew ServiceHost(CAutomation::typeid, gcnew Uri("http://localhost:8001/GettingStarted"));
// add a service endpoint.
m_Host->AddServiceEndpoint(IAutomation::typeid, gcnew WSHttpBinding(), "Automation");
// add behavior
ServiceMetadataBehavior^ smb = gcnew ServiceMetadataBehavior();
smb->HttpGetEnabled = true;
m_Host->Description->Behaviors->Add(smb);
// enforce single instance behavior
m_Host->Description->Behaviors->RemoveAt(0);
ServiceBehaviorAttribute^ sba = gcnew ServiceBehaviorAttribute();
sba->InstanceContextMode = InstanceContextMode::Single;
m_Host->Description->Behaviors->Add(sba);
}
/// <summary>Starts the automation service.</summary>
void CAutomation::Start()
{
m_Host->Open();
}
Typically you set the ServiceBehaviorAttribute as a real attribute for the class that implements your service. I'm not C++/CLI expert, but I guess that since you're passing CAutomation::typeid to ServiceHost constructor, then CAutomation is your service class. Is that correct?
If so, then it should be enough to set ServiceBehaviorAttribute on the CAutomation class.
Igor Labutin pointed me in the right direction but the true issue here is that the creation of the service host object will create an instance of the class whose type is passed in to its constructor, at least when in [ServiceBehaviorAttribute(InstanceContextMode = InstanceContextMode::Single)]. Basically, the ServiceHost object should not have been the CAutomation class constructor. I moved that object outside of that constructor into another object which was responsible for when the service was supposed to start up and that corrected the issue. I'll paste a sample bit of code which helps to illustrate the better approach.
class Program
{
static void Main(string[] args)
{
Uri address = new Uri
("http://localhost:8080/QuickReturns/Exchange");
ServiceHost host = new ServiceHost(typeof(TradeService);
host.Open();
Console.WriteLine("Service started: Press Return to exit");
Console.ReadLine();
}
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,
ReturnUnknownExceptionsAsFaults=true)]
public class TradeService : ITradeService
{
private Hashtable tickers = new Hashtable();
public Quote GetQuote(string ticker)
{
lock (tickers)
{
Quote quote = tickers[ticker] as Quote;
if (quote == null)
{
// Quote doesn't exist
throw new Exception(
string.Format("No quotes found for ticker '{0}'",
ticker));
}
return quote;
}
}
public void PublishQuote(Quote quote)
{
lock (tickers)
{
Quote storedQuote = tickers[quote.Ticker] as Quote;
if (storedQuote == null)
{
tickers.Add(quote.Ticker, quote);
}
else
{
tickers[quote.Ticker] = quote;
}
}
}
}
I have these three packages loaded to my WCF service project:
EnterpriseLibrary.Common Version 6.0.1304.0
EnterpriseLibrary.ExceptionHandling 6.0.1304.0
EnterpriseLibrary.ExceptionHandling.WCF Version 6.0.1304.0
Here is the service interface and MyTestFault DataContract:
[ServiceContract]
public interface IRepairService
{
[OperationContract]
[FaultContract(typeof(MyTestFault))]
string SaveRepairCode(string failureCode, string description);
}
[DataContract]
public class MyTestFault
{
#region Member Fields
private string _message = "An unexpected error occured while executing the service method.";
#endregion
#region Properties
[DataMember]
public string Message
{
get { return _message; }
set { _message = value; }
}
#endregion
#region Constructor(s)
public MyTestFault() { }
#endregion
}
Here is the implementation of the service:
[ExceptionShielding("TestPolicy")]
public class RepairService : IRepairService
{
#region Private members
private WimDAL wimDAL;
ExceptionManager exManager;
#endregion
#region Constructor(s)
public RepairService()
{
wimDAL = new WimDAL();
var testPolicy = new List<ExceptionPolicyEntry>
{
{
new ExceptionPolicyEntry(
typeof(SqlException),
PostHandlingAction.ThrowNewException,
new IExceptionHandler[]
{
new FaultContractExceptionHandler(
typeof(MyTestFault),
"SqlException Occurred.",
new NameValueCollection(){ { "Message", "Message" }})
})
},
{
new ExceptionPolicyEntry(
typeof(Exception),
PostHandlingAction.ThrowNewException,
new IExceptionHandler[]
{
new FaultContractExceptionHandler(
typeof(MyTestFault),
"Exception Occurred.",
new NameValueCollection(){ { "Message", "Message" }})
})
}
};
var policies = new List<ExceptionPolicyDefinition>();
policies.Add(new ExceptionPolicyDefinition(
"TestPolicy", testPolicy));
exManager = new ExceptionManager(policies);
}
#endregion
/// <summary>
/// Insert a new fail code with description into RPCODE.
/// Duplicate primary key will throw SqlException that should be processed by EHAB
/// </summary>
public string SaveRepairCode(string failureCode, string description)
{
using (TransactionScope txScope = new TransactionScope())
{
WimSQLCommand sc = new WimSQLCommand() { StoredProcedure = "Repair.RPCODE_Insert" };
sc.Parameters.Add(new SqlParameter("#FailureCode", failureCode));
sc.Parameters.Add(new SqlParameter("#Desc", description));
exManager.Process(() => wimDAL.Execute_NonQueryNoReturn(sc), "TestPolicy");
txScope.Complete();
return "<Save_Repair_Code></Save_Repair_Code>";
}
}
}
Now, I have a TestClient console application that is part of the same project that has a reference to the project and a service reference. From there I call the SaveRepairCode() method and try to catch the specific fault exception like so:
ServiceReference1.RepairServiceClient r = new ServiceReference1.RepairServiceClient();
Console.WriteLine("Enter a new repair code:");
string repairCode = Console.ReadLine();
Console.WriteLine("Enter description:");
string description = Console.ReadLine();
try
{
r.SaveRepairCode(repairCode, description);
}
catch (FaultException<MyTestFault> ex)
{
//do something
throw;
}
catch (FaultException fex)
{
//do something
throw;
}
Finally, I run the console app and try to save a duplicate repair code. I know through debugging that this causes the SqlException to occur. After I step over the SqlException, I see the "FaultContractWrapperException was unhandled by user code" exception and it has the message I specified in the policy of "SqlException occurred.". When I step over that I get back to the client and see this error:
CommunicationException was unhandled
The server did not provide a meaningful reply; this might be caused by a contract mismatch, a premature session shutdown or an internal server error.
PS - this is Enterprise Library 6 with WCF and I made no manual changes to the web.config... and yes, includeExceptionDetailInFaults is set to false.
What am I missing here? Thanks in advance.
UPDATE
Looks like I was missing this one line of code after instantiating the new ExceptionManager.
ExceptionPolicy.SetExceptionManager(exManager);
Nice to see that this one line of code is NOT in the Enterprise Library 6 - April 2013.chm but it IS in the "Developer's Guide to Microsoft Enterprise Library-Preview.pdf" on page 90 of 269. After including that one line I get into the proper FaultException catch on the client.
With that being said, I still can't get other MyTestFault properties on the client. For example, if I add public string StoredProcedureName to MyTestFault and map it to the SqlException's "Procedure" property, I always see null on the client. The only change in the policy for this would be to add the mapping like so:
new NameValueCollection(){ { "Message", "{Message}" }, { "StoredProcedureName", "{Procedure}" } }
It turns out this line was the culprit.
exManager.Process(() => wimDAL.Execute_NonQueryNoReturn(sc), "TestPolicy");
Insead of using the ExceptionManager's Process method, just execute the command you expect a potential exception for like so.
wimDAL.Execute_NonQueryNoReturn(sc);
This does not follow what the "Developer's Guide to Microsoft Enterprise Library-Preview.pdf" says but I guess the documentation is still a work in progress. I hope this helps someone else.
I have a set of SOAP webservices that are wrapping exceptions using IErrorHandler, specifically:
public sealed class ErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
// don't wrap existing fault exceptions
if ((error is FaultException)) return;
// our basic service fault
var businessFault = new BusinessFault { FaultMessage = error.Message, FaultReference = "Internal" };
// Resource based faultReason
var faultReason = new FaultReason(Properties.Resources.BusinessFaultReason);
var faultcode = FaultCodeFactory.CreateVersionAwareSenderFaultCode(InternalFaultCodes.BusinessFailure.ToString(), Service.Namespace);
var faultException = new FaultException<BusinessFault>(
businessFault,
faultReason,
faultcode);
// Create message fault
var messageFault = faultException.CreateMessageFault();
// Create message using Message Factory method
fault = Message.CreateMessage(version, messageFault, faultException.Action);
}
}
I have now added extra endpoints for Json and Pox which work fine, unless an exception occurs. In the case of the Json endpoint the FaultException is returned as XML.
I am aware from other SO posts that in the case of REST I would be better throwing a WebHttpException:
throw new WebFaultException<BusinessFault>(detail, HttpStatusCode.BadRequest);
Or overriding the response message properties in ProvideFault, thus:
var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
var rmp = new HttpResponseMessageProperty
{
StatusCode = System.Net.HttpStatusCode.BadRequest,
StatusDescription = "See fault object for more information."
};
fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
However, MSDN has some interesting remarks about WebHttpException namely:
When using a WCF REST endpoint (WebHttpBinding and WebHttpBehavior or
WebScriptEnablingBehavior) the HTTP status code on the response is set
accordingly. However, WebFaultException can be used with non-REST
endpoints and behaves like a regular FaultException.
When using a WCF REST endpoint, the response format of the serialized
fault is determined in the same way as a non-fault response. For more
information about WCF REST formatting, see WCF REST Formatting.
It would suggest therefore that I need to convert my current ProvideFault method to provide a new WebHttpException (wrapping any existing Exceptions or FaultExceptions) and then SOAP would still work as well.
Would anyone like to take a stab at what that would look like (.Net4.0 btw)? I want one error handler to rule them all!
I was under the impression that using webHttpBinding was a way to get the "all-in-one" functionality of JSON/POX/SOAP as opposed to using separate bindings for each (i.e. wsHttpBinding, basicHttpBinding etc.). So wouldn't you be able to just throw the WebHttpException and then have that give you all the error details you needed regardless of the technology?
In a REST application I'm working on, I created a new class derived from WebFaultException<T> that attaches some additional data to caught service exceptions. Calling the CreatingMessageFault() method on the instance of the derived class let me return my selected exception data from the ProvideFault() method of the error handler as the SOAP fault, letting WCF determine the correct message format.
I am using webHttpBinding to bind all but some third-party services.
Edit: Added code example
public class ErrorHandler : IErrorHandler, IServiceBehavior
{
public virtual void ProvideFault( Exception error, MessageVersion version, ref Message fault )
{
// Include next level of detail in message, if any.
MyFaultException myFaultException =
((error is MyFaultException) &&
((MyFaultException)error).Detail != null)
? new MyFaultException(error.Message + " - " +
((MyFaultException)error).Detail.Message, error)
: new MyFaultException( error.Message, error );
MessageFault messageFault = myFaultException.CreateMessageFault();
fault = Message.CreateMessage( version, messageFault, myFaultException.Action );
}
}
and
/// <summary>
/// Class used to return exception data from my WCF services.
/// </summary>
/// <remarks>
/// This class is used by a web service to pass exception data back and a
/// data object to the client. This class inherits WebFaultException, which
/// is handled specially by the WCF WebServiceHost2 service class and
/// generates a WebException on the client.
/// </remarks>
public class MyFaultException : WebFaultException<BusinessFault>
{
public class MyFaultException : WebFaultException<BusinessFault>
{
public MyFaultException(string message)
: this(HttpStatusCode.BadRequest, message) { }
public MyFaultException(HttpStatusCode statusCode, string message)
: base(new BusinessFault(message), statusCode) { }
}
then in your service, you can throw the exception to pass fault data to your client:
try
{
// Successful operation proceeds normally.
}
catch (ApplicationException e)
{
// Failure generates MyFaultException.
throw new MyFaultException("Operation failed with " + e.Message);
}
I read that the best practice for using WCF proxy would be:
YourClientProxy clientProxy = new YourClientProxy();
try
{
.. use your service
clientProxy.Close();
}
catch(FaultException)
{
clientProxy.Abort();
}
catch(CommunicationException)
{
clientProxy.Abort();
}
catch (TimeoutException)
{
clientProxy.Abort();
}
My problem is, after I allocate my proxy, I assign event handlers to it and also initialize other method using the proxy:
public void InitProxy()
{
sdksvc = new SdkServiceClient();
sdksvc.InitClusteringObjectCompleted += new EventHandler<InitClusteringObjectCompletedEventArgs>(sdksvc_InitClusteringObjectCompleted);
sdksvc.InitClusteringObjectAsync(Utils.DSN, Utils.USER,Utils.PASSWORD);
sdksvc.DoClusteringCompleted += new EventHandler<DoClusteringCompletedEventArgs>(sdksvc_DoClusteringCompleted);
sdksvc.CreateTablesCompleted += new EventHandler<CreateTablesCompletedEventArgs>(sdksvc_CreateTablesCompleted);
}
I now need to call the InitProxy() method each Time I use the proxy if I want to use it as best practice suggests.
Any ideas on how to avoid this?
There are several options. One option is to write a helper class as follows:
public class SvcClient : IDisposable {
public SvcClient(ICommunicationObject service) {
if( service == null ) {
throw ArgumentNullException("service");
}
_service = service;
// Add your event handlers here, e.g. using your example:
sdksvc = new SdkServiceClient();
sdksvc.InitClusteringObjectCompleted += new EventHandler<InitClusteringObjectCompletedEventArgs>(sdksvc_InitClusteringObjectCompleted);
sdksvc.InitClusteringObjectAsync(Utils.DSN, Utils.USER,Utils.PASSWORD);
sdksvc.DoClusteringCompleted += new EventHandler<DoClusteringCompletedEventArgs>(sdksvc_DoClusteringCompleted);
sdksvc.CreateTablesCompleted += new EventHandler<CreateTablesCompletedEventArgs>(sdksvc_CreateTablesCompleted);
}
public void Dispose() {
try {
if( _service.State == CommunicationState.Faulted ) {
_service.Abort();
}
}
finally {
_service.Close();
}
}
private readonly ICommunicationObject _service;
}
To use this class write the following:
var clientProxy = new YourClientProxy();
using(new SvcClient(clientProxy)) {
// use clientProxy as usual. No need to call Abort() and/or Close() here.
}
When the constructor for SvcClient is called it then sets up the SdkServiceClient instance as desired. Furthermore the SvcClient class cleans up the service client proxy as well aborting and/or closing the connection as needed regardless of how the control flow leaves the using-block.
I don't see how the ClientProxy and the InitProxy() are linked but if they are linked this strong I'd move the initialization of the ClientProxy to the InitProxy (or make a method that initializes both) so you can control both their lifespans from there.
I am trying to catch a given FaultException on a WCF client. I basically need to extract a inner description from the fault class so that I can then package it in another exception for the upper layers to do whatever.
I've done this successfully a number of time, what makes it different this time is that fault is declared as an array, as you can see from the service reference attribute declared on top of the method that throws the exception:
[System.ServiceModel.FaultContractAttribute(typeof(FaultClass[]), Action = "http://whatever/", Name = "whateverBusinessFault")]
This is my code:
try
{
// call service here
}
catch (FaultException<FaultClass[]> ex)
{
if (ex.Detail != null && ex.Detail.Length > 0)
{
throw new CustomException(ex.Detail[0].description);
}
else
{
throw;
}
}
Problem is Detail (which is an array) is always coming back empty in the code even if I can see the data (description field etc.) in the SOAP response from WCF trace.
So the stuff I need is definitely coming back but for some reason either it doesn't get deserialized or I can't get to it from code.
Any help appreciated!
UPDATE:
Trying with #Darin suggestion but no luck, the string I am extracting from the XmlReader is "/r/n":
var sb = new StringBuilder();
using (XmlReader reader = fault.GetReaderAtDetailContents())
{
while (reader.Read())
sb.AppendLine(reader.ReadOuterXml());
}
var detail = sb.ToString();
Looks like the detail section is not coming up at all!
I found the solution on a UPS Forum :
https://developerkitcommunity.ups.com/index.php/Special:AWCforum/st/id371
"The problem was the visual studio didn't quite map out the ErrorDetail objects right. The ErrorDetail node is called "ErrorDetail", but the type generated for it is "ErrorDetailType." I edited the reference.cs class generated for each service I was using and added a TypeName:"
It is difficult to say where the problem is but I suspect the smoking gun is this axis web service not generating standard message. One way to workaround this would be to parse the XML yourself:
try
{
proxy.CallSomeMethod();
}
catch (FaultException ex)
{
var fault = ex.CreateMessageFault();
using (XmlReader reader = fault.GetReaderAtDetailContents())
{
// TODO: read the XML fault and extract the necessary information.
}
}
It took me ages to figure out how to get the full details message from a FaultException as a string. I eventually figured it out and wrote this extension method:
public static string GetDetail(this FaultException faultException)
{
if (faultException == null)
throw new ArgumentNullException(nameof(faultException));
MessageFault messageFault = faultException.CreateMessageFault();
if (messageFault.HasDetail) {
using (XmlDictionaryReader reader = messageFault.GetReaderAtDetailContents()) {
return reader.ReadContentAsString();
}
}
return null;
}
Originally I was using reader.Value but that only appeared to the return the first line of a multi-line details message. reader.ReadContentAsString() appears to get the whole thing, new lines included, which is what I wanted.
I came up with the simplest test case I could. I hope it will help you.
Server side:
[ServiceContract]
public interface IService1
{
[OperationContract]
[FaultContract(typeof(FaultClass[]))]
string Crash();
}
public class Service1 : IService1
{
public string Crash()
{
var exception = new FaultException<FaultClass[]>(new FaultClass[] { new FaultClass { Data = "TEST" } }, new FaultReason("Boom"));
throw exception;
}
}
[DataContract]
public class FaultClass
{
[DataMember]
public string Data { get; set; }
}
Client side:
try
{
using (var client = new Service1Client())
{
client.Crash();
}
}
catch(FaultException<FaultClass[]> e)
{
//Break here
}
I had a similar situation in trying to communicate data with faults (specifically a stack trace). See this question. I ended up solving it by creating my own serializable stack trace and including it in a derived FaultException class.