Is there anyway to know the current exception when a workflow is faulting without declaring an explicit variable on the catch activity?

Yanıt Is there anyway to know the current exception when a workflow is faulting without declaring an explicit variable on the catch activity?

  • 07 Ekim 2009 Çarşamba 14:14
     
     
    In .Net 3.0, we made something like the "WorkflowElement" class. In this one, we could access the CurrentException. We were looking for the first parent FaultHandlerActivity and return his Fault property. Since 4.0 beta 1, it seem we can, using the TryCatch activity, have a variable to know the exception we caught. But is there any way to be able to access the exception without explicitly doing declaring it in the workflow? We would like to be able to use our old "WorkflowElement" without doing blitz in our API.

Tüm Yanıtlar

  • 26 Ekim 2009 Pazartesi 22:51
     
      Kod İçerir
    I'm not sure if this is exactly what you are asking, but you can do the following:

    public class ExceptionScope : NativeActivity
    {
         public Activity Body { get; set; }
    
         protected override void Execute(NativeActivityContext context)
         {
              if (this.Body != null)
              {
                   context.ScheduleActivity(this.Body, null, OnFault);
              }
         }
    
         void OnFault(NativeActivityFaultContext context, Exception exception, ActivityInstance propagatedFrom)
         {
              // You can check out exception.  If you call context.HandleFault then the fault will not propagate past this activity.
         }
    }
    In general, the stricter data model of WF 4 does not allow arbitrary walking of the activity tree to get values.
    Nate
  • 27 Ekim 2009 Salı 13:02
     
     

    Thanks for taking time to answer, but it's not really what I was looking for.

    In fact, I though of findin the parent Catch<T> and check the value of the exception it has declared, but without having to go in the designer of every custom activities and set the argument of the exception to the variable name in the Catch<T>.

    In 3.5, we were offring this functinnality "free" to our developer, so they didn't have to bother with this. So we are just trying to keep the functionnalities we had (to know the current exception). The workflows are made in our systems, but the functionnalities are offred by our library, so we try to be the less breaking change as we can.

    If there isn't any possibilities, we won't have choice to be breaking changes...

  • 27 Ekim 2009 Salı 20:50
    Moderatör
     
     
    I still don't quite understand the goal.  Are you trying to make the exception "ambient" to some portion of the workflow?  For example, if my workflow is defined as:

    new TryCatch
    {
        Try = new Throw<Exception>(),
        Catches =
        {
            new Catch<Exception>
            {
                Action = new ActivityAction<Exception>
                {
                    Argument = exception,
                    Handler = new SomeActivity
                    {
                    }
                }
            }
        }
    }

    Are you saying that you want SomeActivity to have access to "exception" without having to bind to "exception"?  I'm trying to understand the scenario so that I know which piece of functionality to point you to.

    For example, if the user is going to supply you with some activity that you are going to place into a pre-existing tree of workflow, then I would point you to look at the ActivityAction/Func stuff.  TryCatch uses this pattern and the ActivityAction<Exception> provides an exception to the user of the TryCatch.

    If, for some reason, you need the user to be able to pull the exception out of thin air anywhere within the subtree of the catch, then I would point you towards ExecutionProperties.  You can use a NativeActivity to insert a new ExecutionProperty (basically a named object) and that ExecutionProperty can be accessed by name by any NativeActivity under that scope.
  • 28 Ekim 2009 Çarşamba 13:55
     
      Kod İçerir

    Sorry for the confusion, the english isn't my primary language, and sometimes I have some difficulties to express myself clearly.

    Yes what I want is to have an ambiant exception, so I don't have to bind the SomeActivity to the exception because it just don't have any InArgument<Exception>. The ExecutionProperties seem to be what I need. I have been able to make something with the hint :

          public static void Test6 ()
          {
             string workflowXml = @"<TryCatch xmlns=""http://schemas.microsoft.com/netfx/2009/xaml/activities"" xmlns:local=""clr-namespace:ConsoleApplication1;assembly=ConsoleApplication1"" xmlns:s1=""clr-namespace:System;assembly=mscorlib"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
    		<TryCatch.Try>
    			<Throw Exception=""[New System.InvalidOperationException()]"" />
    		</TryCatch.Try>
    		<TryCatch.Catches>
    			<Catch x:TypeArguments=""s1:Exception"">
    				<ActivityAction x:TypeArguments=""s1:Exception"">
    					<ActivityAction.Argument>
    						<DelegateInArgument x:TypeArguments=""s1:Exception"" Name=""exception"" />
    					</ActivityAction.Argument>
    					<local:EnableAmbiantException CurrentException=""[exception]"" >
    						<local:EnableAmbiantException.Body>
    							<local:SampleActivity />
    						</local:EnableAmbiantException.Body>
    					</local:EnableAmbiantException>
    				</ActivityAction>
    			</Catch>
    		</TryCatch.Catches>
    	</TryCatch>";
    
             Activity workflow;
             using (StringReader reader = new StringReader (workflowXml))
             {
                workflow = ActivityXamlServices.Load (reader);
             }
    
             bool testOk = false;
             try
             {
                WorkflowInvoker.Invoke (workflow);
             }
             catch (InvalidOperationException exc)
             {
                testOk = exc.Message == "Ambiant exception found";
             }
          }
       }
    
       // EnableAmbiantException : Activity to use the current exception as Ambiant
    public class EnableAmbiantException : NativeActivity { public Collection<Activity> Body { get; set; } public InArgument<Exception> CurrentException { get; set; } public EnableAmbiantException () { Body = new Collection<Activity> (); } protected override void Execute (NativeActivityContext context) { if (Body != null) { context.Properties.Add ("CurrentException", CurrentException.Get (context)); foreach (var activity in Body) { context.ScheduleActivity (activity); } } } }
    // SampleActivity : activity that doesn't have any InArgument<Exception> that need to access the current exception
    public class SampleActivity : NativeActivity { protected override void Execute (NativeActivityContext context) { TestService service = new TestService (context); if (service.CurrentException != null) { throw new InvalidOperationException ("Ambiant exception found"); } else { throw new InvalidOperationException ("Ambiant exception non working"); } } } // Utility to access the current exception
    public class TestService { NativeActivityContext _context; public TestService (NativeActivityContext context) { _context = context; } public Exception CurrentException { get { return (Exception) _context.Properties.Find ("CurrentException"); } } }

    I need to use the activity "EnableAmbiantException" to activate the ambiant exception. So my fear is it's easy to forget, especially with nested TryCatch (if we forget in the EnableAmbiantException in a nested TryCatch, we will get the wrong exception). Unit tests will help us, but can we still do better? Also, it only work if the SampleActivity (the one requiring the ambiant exception) is a NativeActivity...

  • 28 Ekim 2009 Çarşamba 15:42
    Moderatör
     
     Yanıt Kod İçerir

    Let's address the NativeActivity only concern first.  There is a class Handle in System.Activities that you can subclass to create a "modeled" property.  Handles have the following properties:

    * To expose a handle to the runtime your activity must declare a Variable<> of your handle type.  For example, Variable<MyHandle>.  This variable can be a public variable (metadata.AddVariable) or an implementation variable (metadata.AddImplementationVariable).
    * Handle lifetimes are managed by the runtime.  A handle must have a default constructor.  The runtime will create the handle and call Initialize as soon as the variable comes into scope.  The runtime will call Uninitialize when the variable goes out of scope.
    * Handles can be added to the execution properties collection using the HandleScope<T> activity.  The name of the Handle will be the type name.  Alternately, you can explicitly add the handle to the execution properties (like you are doing in the code above) using the Handle.ExecutionPropertyName as the property name.
    * CodeActivity and AsyncCodeActivity can access handle properties using CodeActivityContext.GetProperty<THandle>().

    So, to apply this to your example above, you could do the following:

    public class EnableAmbientException : NativeActivity
    {
        private Variable<ExceptionHandle> exceptionHandle;
    
        public EnableAmbientException()
        {
            this.exceptionHandle = new Variable<ExceptionHandle>();
        }
        
        public InArgument<Exception> Exception { get; set; }
        public Activity Body { get; set; }
        
        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            // Call this to use reflection to pick up the public
            // metadata such as the InArgument<Exception> and the
            // child Body.
            base.CacheMetadata(metadata);
            
            metadata.AddImplementationVariable(this.exceptionHandle);
        }
        
        protected override void Execute(NativeActivityContext context)
        {
            // The runtime created an instance of this object and stored
            // it in the variable on our behalf.
            ExceptionHandle handle = this.exceptionHandle.Get(context);
            handle.Exception = this.Exception.Get(context);
            context.ExecutionProperties.Add(handle.ExecutionPropertyName, handle);
            
            if (this.Body != null)
            {
                context.ScheduleActivity(this.Body);
            }
        }
    }
    
    public class ExceptionHandle : Handle
    {
        public Exception Exception { get; set; }
    }
    
    public class AmbientExceptionNativeActivity : NativeActivity
    {
        protected override void Execute(NativeActivityContext context)
        {
            ExceptionHandle handle = context.ExecutionProperties.Find(typeof(ExceptionHandle).FullName) as ExceptionHandle;
            
            if (handle != null)
            {
                // Do something with handle.Exception
            }
        }
    }
    
    // You could also imagine providing a helper activity.
    // This also serves as an example of how to use CodeActivity
    // to work with handles.
    public class GetAmbientException : CodeActivity<Exception>
    {
        protected override Exception Execute(CodeActivityContext context)
        {
            ExceptionHandle handle = context.GetProperty<ExceptionHandle>();
            
            if (handle != null)
            {
                return handle.Exception;
            }
            else
            {
                return null;
            }
        }
    }
    As for how to help yourself to do the right thing, you could always create your own OM on top of the TryCatch OM.  For example:

    public class AmbientTryCatch : Activity
    {
        public AmbientTryCatch()
        {
            this.Catches = new Collection<AmbientCatches>();
            this.Implementation = () =>
            {
                TryCatch result = new TryCatch
                {
                    Try = this.Try
                };
                
                foreach (AmbientCatch ambientCatch in this.Catches)
                {
                    result.Catches.Add(ambientCatch.CreateCatch());
                }
                
                return result;
            };
        }
        
        public Activity Try { get; set; }
        public Collection<AmbientCatch> Catches { get; private set; }    
    }
    
    // This base class allows us to have a collection of catches
    // even though the public OM is based on a generic type.
    public abstract class AmbientCatch
    {
        internal abstract Catch CreateCatch();
    }
    
    public class AmbientCatch<TException> : AmbientCatch
    {
        // It sounds like you might want to change the signature of this
        // action which is fine because you own the OM.
        public ActivityAction<TException> Action { get; set; }
        
        internal override Catch CreateCatch()
        {
            Catch<TException> catch = new Catch<TException>();
            DelegateInArgument<TException> exceptionArgument = new DelegateInArgument<TException>();
            catch.Action = new ActivityAction<TException>
            {
                Argument = exceptionArgument,
                Handler = new EnableAmbientException
                {
                    // We have an implicit conversion between DelegateInArgument<T> and InArgument<T>
                    Exception = exceptionArgument,
                    Body = new InvokeAction<TException>
                    {
                        Argument = exceptionArgument,
                        Action = this.Action
                    }
                }
            };
            
            return catch;
        }
    }
    • Yanıt Olarak İşaretleyen Instriker 09 Kasım 2009 Pazartesi 13:14
    •  
  • 09 Kasım 2009 Pazartesi 13:14
     
     
    The Handle will do well in my situation. I will be able to implement the ambiant exception to my existings activities with no restriction and without the need to bind each activity in the workflow designer.

    Thanks.