WF4 Workflow Service Data Validation Design
In my previous previous post on the WCF Fault Spike and the other post on 4 Tenets of Service Oriented Data Validation I discussed some options and best practices for service data validation with WCF. In this post I want to consider how the same scenario applies to WF4.
Download Sample Code – WCF Service Fault and Validation Example
I created an application that ran three scenarios each resulting in an exception thrown from the service and a web UI page then shows what the sender can do based on the fault scenario. Here are my WF results for the same scenarios.
Scenario 1: WF Service throws an exception
Given
- A workflow service that throws an ArgumentOutOfRangeException
- There is no <serviceDebug> or <serviceDebug includeExceptionDetailInFaults="false" /> in web.config
When
- The service is invoked with a data that will cause the exception
Then
- The client proxy will catch a System.ServiceModel.FaultException
- The FaultCode name will be “InternalServiceFault”
- The Fault.Reason will be
"The server was unable to process the request due to an internal error. For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the <serviceDebug> configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the server trace logs."
Conclusions
No surprises here, probably anyone who has done Workflow Services for more than 5 minutes has run into this. For more information see the WCF documentation Sending and Receiving Faults. You might be tempted to just turn on IncludeExceptionDetailInFaults but don’t do it because it can lead to security vulnerabilities. Instead you need a better strategy for dealing with exceptions and that means you need to understand FaultException.
Scenario 2: WF Service throws a FaultException
As we saw in the previous example, WCF already throws a FaultException for you when it encounters an unhandled exception thrown from a Workflow Service. The problem is in this case that we want to let the caller know they sent an invalid argument even when they are not debugging.
Given
- A service that throws a FaultException
- A Code Activity that creates a version aware sender fault
public sealed class ThrowSenderFault : CodeActivity
{
public InArgument<string> Reason { get; set; }
protected override void Execute(CodeActivityContext context)
{
var faultReason = context.GetValue(this.Reason);
// Let the sender know it is their problem
// Workaround for creating a version aware FaultCode
var faultCode =
FaultCodeFactory.CreateVersionAwareSenderFaultCode(
ContractFaultCodes.InvalidArgument.ToString(), Service.Namespace);
throw new FaultException(faultReason, faultCode);
}
}
When
- The service is invoked with a data value that will cause an exception
Then
- The client proxy will catch a System.ServiceModel.FaultException
- The FaultCode.Name will be “Client” (for SOAP 1.1) or “Sender” (for SOAP 1.2)
- The Fault.Reason will be Argument Data is out of range value must be greater than zero
Conclusions
The main thing is that the client now gets a message saying that things didn’t work and its their fault. They can tell by looking at the FaultException.Code.IsSenderFault property. In my code you’ll notice a class I created called CreateVersionAwareSenderFaultCode to help deal with the differences between SOAP 1.1 and SOAP 1.2. You will find it in the sample.
Some interesting things I learned while testing this
- You should specify a Sender fault if you determine that there is a problem with the message and it should not be resubmitted
- BasicHttpBinding does SOAP 1.1 while the other bindings do SOAP 1.2 so if you are working with both you need to do the version aware sender fault code.
- FaultException.Action never shows up on the client side so don’t bother with it.
- FaultException.HelpLink and FaultException.Data (and other properties inherited from Exception) do not get serialized and will show up empty on the client side so you can’t use them for anything.
While this is better than throwing unhandled exceptions, there is an even better way and that is FaultContracts
Scenario 3: WCF Service with a FaultContract
Given
- A service operation with a correlated Send activity that Sends a FaultContract (this is how Workflow Services declare Fault Contracts)
When
- The service is invoked with a data value that will cause an exception
Then
- The client proxy will catch a System.ServiceModel.FaultException
- The FaultCode.Name will be “Client” (for SOAP 1.1) or “Sender” (for SOAP 1.2)
- The Fault.Reason will be Argument Data is out of range value must be greater than zero
- The FaultDetail will be the type TDetail which will be included in the generated in the service reference
Conclusions
This is the best choice. It allows you to pass all kinds of information to clients and it makes your error handling capability truly first class. In the sample code one thing I wanted to do was to use the FaultException.HelpLink property to pass a Url to a help page. Unfortunately I learned that none of System.Exception’s properties are propagated to the sender. No problem, I just added a HelpLink property to my ArgumentValidationFault type and used it instead in
FaultException.Details.HelpLink
Summary
You should use Fault Contracts with Workflow Services, but there are two issues to consider.
- How do you “declare” the FaultContract?
- Where do you throw the FaultException from?
How do you “declare” the FaultContract?
In Workflow Services, contracts are inferred (including fault contracts) to infer a Fault Contract
- Right click on the Receive activity that defines the operation you want to add the Fault Contract for and select Create SendReply to create an additional SendReply
- Paste the new SendReply activity somewhere in your workflow.
- Set the content of the SendReply to contain an expression of FaultException(Of TDetail)
- When the contract inference code looks at your workflow it will see the correlated SendReply and infer the FaultContract for the related Receive
Where do you throw the FaultException from?
If you want to do validation within your workflow you can use an “If” activity (for example) and if the condition is valid., send back the normal result and if the condition is invalid use the SendReply that sends a FaultException.
What if non-workflow code like a property setter or code activity throws a FaultException<TDetail>?
If you have declared a fault contract (as shown above) then the Fault Detail Type will be emitted into the WSDL so any code you like can throw the FaultException<TDetail>,
If you did not declare a fault contract then your code can throw a FaultException<TDetail> but the contract inference will not be aware of this and will not emit the fault contract. Senders will be able to catch FaultException but they will not have the fault detail type in their service reference and will not be able to catch FaultException<TDetail>
What if I throw a FaultException<TDetail> using a Throw activity?
Using the Throw activity does not change the situation at all. You still have to declare the fault contract even if you never invoke the SendReply in order to emit the fault detail type into the WSDL.
Happy Coding!
Ron
https://blogs.msdn.com/rjacobs
Twitter: @ronljacobs
Comments
Anonymous
January 19, 2011
Ron, I have watched this screencast several times and definitely have a use for validating my data. I see you using manual validation on properties... Is there anyway to validate data using the Entity Framework?Anonymous
January 19, 2011
You would have to be able to get EntityFramework to throw a FaultException<TDetail> when the validation fails. I don't think there is a way to do this. However, you could wrap the EntityFramework object inside of another type (similar to the way I did GetDataRequest) and then put the value inside of a Try/Catch block. Catch the internal exception from EF and throw a FaultException from there.Anonymous
January 19, 2011
The comment has been removedAnonymous
February 04, 2011
Ron, I have gotten this to work without a hitch with content, but I am uncertain how I could accomplish this with with parameters content? The reason is because I want to make my workflow service act just like a wcf service as you discussed here: blogs.msdn.com/.../making-a-workflowservice-work-like-a-wcf-service.aspx. Do you have any ideas. I also wanted to say thanks for the videos, they are a major help.Anonymous
February 04, 2011
If you send simple types as parameters then of course this approach won't work. Why not send the type with property setter as a parameter? Or do you have to implement an existing contract?