e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
424 lines
15 KiB
C#
424 lines
15 KiB
C#
//-----------------------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace System.Activities.Statements
|
|
{
|
|
using System;
|
|
using System.Activities;
|
|
using System.Activities.DynamicUpdate;
|
|
using System.Activities.Runtime;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Runtime.Collections;
|
|
using System.Runtime.Serialization;
|
|
using System.Windows.Markup;
|
|
|
|
public sealed class TryCatch : NativeActivity
|
|
{
|
|
CatchList catches;
|
|
Collection<Variable> variables;
|
|
|
|
Variable<TryCatchState> state;
|
|
|
|
FaultCallback exceptionFromCatchOrFinallyHandler;
|
|
|
|
internal const string FaultContextId = "{35ABC8C3-9AF1-4426-8293-A6DDBB6ED91D}";
|
|
|
|
public TryCatch()
|
|
: base()
|
|
{
|
|
this.state = new Variable<TryCatchState>();
|
|
}
|
|
|
|
public Collection<Variable> Variables
|
|
{
|
|
get
|
|
{
|
|
if (this.variables == null)
|
|
{
|
|
this.variables = new ValidatingCollection<Variable>
|
|
{
|
|
// disallow null values
|
|
OnAddValidationCallback = item =>
|
|
{
|
|
if (item == null)
|
|
{
|
|
throw FxTrace.Exception.ArgumentNull("item");
|
|
}
|
|
}
|
|
};
|
|
}
|
|
return this.variables;
|
|
}
|
|
}
|
|
|
|
[DefaultValue(null)]
|
|
[DependsOn("Variables")]
|
|
public Activity Try
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[DependsOn("Try")]
|
|
public Collection<Catch> Catches
|
|
{
|
|
get
|
|
{
|
|
if (this.catches == null)
|
|
{
|
|
this.catches = new CatchList();
|
|
}
|
|
return this.catches;
|
|
}
|
|
}
|
|
|
|
[DefaultValue(null)]
|
|
[DependsOn("Catches")]
|
|
public Activity Finally
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
FaultCallback ExceptionFromCatchOrFinallyHandler
|
|
{
|
|
get
|
|
{
|
|
if (this.exceptionFromCatchOrFinallyHandler == null)
|
|
{
|
|
this.exceptionFromCatchOrFinallyHandler = new FaultCallback(OnExceptionFromCatchOrFinally);
|
|
}
|
|
|
|
return this.exceptionFromCatchOrFinallyHandler;
|
|
}
|
|
}
|
|
|
|
protected override void OnCreateDynamicUpdateMap(NativeActivityUpdateMapMetadata metadata, Activity originalActivity)
|
|
{
|
|
metadata.AllowUpdateInsideThisActivity();
|
|
}
|
|
|
|
protected override void UpdateInstance(NativeActivityUpdateContext updateContext)
|
|
{
|
|
TryCatchState state = updateContext.GetValue(this.state);
|
|
if (state != null && !state.SuppressCancel && state.CaughtException != null && this.FindCatch(state.CaughtException.Exception) == null)
|
|
{
|
|
// This is a very small window of time in which we want to block update inside TryCatch.
|
|
// This is in between OnExceptionFromTry faultHandler and OnTryComplete completionHandler.
|
|
// A Catch handler could be found at OnExceptionFromTry before update, yet that appropriate Catch handler could have been removed during update and not be found at OnTryComplete.
|
|
// In such case, the exception can be unintentionally ----ed without ever propagating it upward.
|
|
// Such TryCatch state is detected by inspecting the TryCatchState private variable for SuppressCancel == false && CaughtException != Null && this.FindCatch(state.CaughtException.Exception) == null.
|
|
updateContext.DisallowUpdate(SR.TryCatchInvalidStateForUpdate(state.CaughtException.Exception));
|
|
}
|
|
}
|
|
|
|
protected override void CacheMetadata(NativeActivityMetadata metadata)
|
|
{
|
|
if (Try != null)
|
|
{
|
|
metadata.AddChild(this.Try);
|
|
}
|
|
|
|
if (this.Finally != null)
|
|
{
|
|
metadata.AddChild(this.Finally);
|
|
}
|
|
|
|
Collection<ActivityDelegate> delegates = new Collection<ActivityDelegate>();
|
|
|
|
if (this.catches != null)
|
|
{
|
|
foreach (Catch item in this.catches)
|
|
{
|
|
ActivityDelegate catchDelegate = item.GetAction();
|
|
if (catchDelegate != null)
|
|
{
|
|
delegates.Add(catchDelegate);
|
|
}
|
|
}
|
|
}
|
|
|
|
metadata.AddImplementationVariable(this.state);
|
|
|
|
metadata.SetDelegatesCollection(delegates);
|
|
|
|
metadata.SetVariablesCollection(this.Variables);
|
|
|
|
if (this.Finally == null && this.Catches.Count == 0)
|
|
{
|
|
metadata.AddValidationError(SR.CatchOrFinallyExpected(this.DisplayName));
|
|
}
|
|
}
|
|
|
|
internal static Catch FindCatchActivity(Type typeToMatch, IList<Catch> catches)
|
|
{
|
|
foreach (Catch item in catches)
|
|
{
|
|
if (item.ExceptionType == typeToMatch)
|
|
{
|
|
return item;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected override void Execute(NativeActivityContext context)
|
|
{
|
|
ExceptionPersistenceExtension extension = context.GetExtension<ExceptionPersistenceExtension>();
|
|
if ((extension != null) && !extension.PersistExceptions)
|
|
{
|
|
// We will need a NoPersistProperty if we catch an exception.
|
|
NoPersistProperty noPersistProperty = context.Properties.FindAtCurrentScope(NoPersistProperty.Name) as NoPersistProperty;
|
|
if (noPersistProperty == null)
|
|
{
|
|
noPersistProperty = new NoPersistProperty(context.CurrentExecutor);
|
|
context.Properties.Add(NoPersistProperty.Name, noPersistProperty);
|
|
}
|
|
}
|
|
|
|
this.state.Set(context, new TryCatchState());
|
|
if (this.Try != null)
|
|
{
|
|
context.ScheduleActivity(this.Try, new CompletionCallback(OnTryComplete), new FaultCallback(OnExceptionFromTry));
|
|
}
|
|
else
|
|
{
|
|
OnTryComplete(context, null);
|
|
}
|
|
}
|
|
|
|
protected override void Cancel(NativeActivityContext context)
|
|
{
|
|
TryCatchState state = this.state.Get(context);
|
|
if (!state.SuppressCancel)
|
|
{
|
|
context.CancelChildren();
|
|
}
|
|
}
|
|
|
|
void OnTryComplete(NativeActivityContext context, ActivityInstance completedInstance)
|
|
{
|
|
TryCatchState state = this.state.Get(context);
|
|
|
|
// We only allow the Try to be canceled.
|
|
state.SuppressCancel = true;
|
|
|
|
if (state.CaughtException != null)
|
|
{
|
|
Catch toSchedule = FindCatch(state.CaughtException.Exception);
|
|
|
|
if (toSchedule != null)
|
|
{
|
|
state.ExceptionHandled = true;
|
|
if (toSchedule.GetAction() != null)
|
|
{
|
|
context.Properties.Add(FaultContextId, state.CaughtException, true);
|
|
toSchedule.ScheduleAction(context, state.CaughtException.Exception, new CompletionCallback(OnCatchComplete), this.ExceptionFromCatchOrFinallyHandler);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
OnCatchComplete(context, null);
|
|
}
|
|
|
|
void OnExceptionFromTry(NativeActivityFaultContext context, Exception propagatedException, ActivityInstance propagatedFrom)
|
|
{
|
|
if (propagatedFrom.IsCancellationRequested)
|
|
{
|
|
if (TD.TryCatchExceptionDuringCancelationIsEnabled())
|
|
{
|
|
TD.TryCatchExceptionDuringCancelation(this.DisplayName);
|
|
}
|
|
|
|
// The Try activity threw an exception during Cancel; abort the workflow
|
|
context.Abort(propagatedException);
|
|
context.HandleFault();
|
|
}
|
|
else
|
|
{
|
|
Catch catchHandler = FindCatch(propagatedException);
|
|
if (catchHandler != null)
|
|
{
|
|
if (TD.TryCatchExceptionFromTryIsEnabled())
|
|
{
|
|
TD.TryCatchExceptionFromTry(this.DisplayName, propagatedException.GetType().ToString());
|
|
}
|
|
|
|
context.CancelChild(propagatedFrom);
|
|
TryCatchState state = this.state.Get(context);
|
|
|
|
// If we are not supposed to persist exceptions, enter our noPersistScope
|
|
ExceptionPersistenceExtension extension = context.GetExtension<ExceptionPersistenceExtension>();
|
|
if ((extension != null) && !extension.PersistExceptions)
|
|
{
|
|
NoPersistProperty noPersistProperty = (NoPersistProperty)context.Properties.FindAtCurrentScope(NoPersistProperty.Name);
|
|
if (noPersistProperty != null)
|
|
{
|
|
// The property will be exited when the activity completes or aborts.
|
|
noPersistProperty.Enter();
|
|
}
|
|
}
|
|
|
|
state.CaughtException = context.CreateFaultContext();
|
|
context.HandleFault();
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnCatchComplete(NativeActivityContext context, ActivityInstance completedInstance)
|
|
{
|
|
// Start suppressing cancel for the finally activity
|
|
TryCatchState state = this.state.Get(context);
|
|
state.SuppressCancel = true;
|
|
|
|
if (completedInstance != null && completedInstance.State != ActivityInstanceState.Closed)
|
|
{
|
|
state.ExceptionHandled = false;
|
|
}
|
|
|
|
context.Properties.Remove(FaultContextId);
|
|
|
|
if (this.Finally != null)
|
|
{
|
|
context.ScheduleActivity(this.Finally, new CompletionCallback(OnFinallyComplete), this.ExceptionFromCatchOrFinallyHandler);
|
|
}
|
|
else
|
|
{
|
|
OnFinallyComplete(context, null);
|
|
}
|
|
}
|
|
|
|
void OnFinallyComplete(NativeActivityContext context, ActivityInstance completedInstance)
|
|
{
|
|
TryCatchState state = this.state.Get(context);
|
|
if (context.IsCancellationRequested && !state.ExceptionHandled)
|
|
{
|
|
context.MarkCanceled();
|
|
}
|
|
}
|
|
|
|
void OnExceptionFromCatchOrFinally(NativeActivityFaultContext context, Exception propagatedException, ActivityInstance propagatedFrom)
|
|
{
|
|
if (TD.TryCatchExceptionFromCatchOrFinallyIsEnabled())
|
|
{
|
|
TD.TryCatchExceptionFromCatchOrFinally(this.DisplayName);
|
|
}
|
|
|
|
// We allow cancel through if there is an exception from the catch or finally
|
|
TryCatchState state = this.state.Get(context);
|
|
state.SuppressCancel = false;
|
|
}
|
|
|
|
Catch FindCatch(Exception exception)
|
|
{
|
|
Type exceptionType = exception.GetType();
|
|
Catch potentialCatch = null;
|
|
|
|
foreach (Catch catchHandler in this.Catches)
|
|
{
|
|
if (catchHandler.ExceptionType == exceptionType)
|
|
{
|
|
// An exact match
|
|
return catchHandler;
|
|
}
|
|
else if (catchHandler.ExceptionType.IsAssignableFrom(exceptionType))
|
|
{
|
|
if (potentialCatch != null)
|
|
{
|
|
if (catchHandler.ExceptionType.IsSubclassOf(potentialCatch.ExceptionType))
|
|
{
|
|
// The new handler is more specific
|
|
potentialCatch = catchHandler;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
potentialCatch = catchHandler;
|
|
}
|
|
}
|
|
}
|
|
|
|
return potentialCatch;
|
|
}
|
|
|
|
[DataContract]
|
|
internal class TryCatchState
|
|
{
|
|
[DataMember(EmitDefaultValue = false)]
|
|
public bool SuppressCancel
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[DataMember(EmitDefaultValue = false)]
|
|
public FaultContext CaughtException
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[DataMember(EmitDefaultValue = false)]
|
|
public bool ExceptionHandled
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
}
|
|
|
|
class CatchList : ValidatingCollection<Catch>
|
|
{
|
|
public CatchList()
|
|
: base()
|
|
{
|
|
this.OnAddValidationCallback = item =>
|
|
{
|
|
if (item == null)
|
|
{
|
|
throw FxTrace.Exception.ArgumentNull("item");
|
|
}
|
|
};
|
|
}
|
|
|
|
protected override void InsertItem(int index, Catch item)
|
|
{
|
|
if (item == null)
|
|
{
|
|
throw FxTrace.Exception.ArgumentNull("item");
|
|
}
|
|
|
|
Catch existingCatch = TryCatch.FindCatchActivity(item.ExceptionType, this.Items);
|
|
|
|
if (existingCatch != null)
|
|
{
|
|
throw FxTrace.Exception.Argument("item", SR.DuplicateCatchClause(item.ExceptionType.FullName));
|
|
}
|
|
|
|
base.InsertItem(index, item);
|
|
}
|
|
|
|
protected override void SetItem(int index, Catch item)
|
|
{
|
|
if (item == null)
|
|
{
|
|
throw FxTrace.Exception.ArgumentNull("item");
|
|
}
|
|
|
|
Catch existingCatch = TryCatch.FindCatchActivity(item.ExceptionType, this.Items);
|
|
|
|
if (existingCatch != null && !object.ReferenceEquals(this[index], existingCatch))
|
|
{
|
|
throw FxTrace.Exception.Argument("item", SR.DuplicateCatchClause(item.ExceptionType.FullName));
|
|
}
|
|
|
|
base.SetItem(index, item);
|
|
}
|
|
}
|
|
}
|
|
}
|