Handling Failures

Handling Failures

Normally posted failures are processed by Revit's standard failure resolution UI at the end of a transaction (specifically when Transaction.Commit() or Transaction.Rollback() are invoked). The user is presented information and options to deal with the failures.

If an operation (or set of operations) on the document requires some special treatment from a Revit add-in for certain errors, failure handling can be customized to carry out this resolution. Custom failure handling can be supplied:

Finally, the API offers the ability to completely replace the standard failure processing user interface using the interface IFailuresProcessor. Although the first two methods for handling failures should be sufficient in most cases, this last option can be used in special cases, such as to provide a better failure processing UI or when an application is used as a front-end on top of Revit.

Overview of Failure Processing

It is important to remember there are many things happening between the call to Transaction.Commit() and the actual processing of failures. Auto-join, overlap checks, group checks and workset editability checks are just to name a few. These checks and changes may make some failures disappear or, more likely, can post new failures. Therefore, conclusions cannot be drawn about the state of failures to be processed when Transaction.Commit() is called. To process failures correctly, it is necessary to hook up to the actual failures processing mechanism.

When failures processing begins, all changes to a document that are supposed to be made in the transaction are made, and all failures are posted. Therefore, no uncontrolled changes to a document are allowed during failures processing. There is a limited ability to resolve failures via the restricted interface provided by FailuresAccessor. If this has happened, all end of transaction checks and failures processing have to be repeated. So there may be a few failure resolution cycles at the end of one transaction.

Each cycle of failures processing includes 3 steps:

  1. Preprocessing of failures (FailuresPreprocessor)
  2. Broadcasting of failures processing event (FailuresProcessing event)
  3. Final processing (FailuresProcessor)

Each of these 3 steps can control what happens next by returning different FailureProcessingResults. The options are:

  • Continue - has no impact on execution flow. If FailuresProcessor returns "Continue" with unresolved failures, Revit will instead act as if "ProceedWithRollBack" was returned.
  • ProceedWithCommit - interrupts failures processing and immediately triggers another loop of end-of-transaction checks followed by another failures processing. Should be returned after an attempt to resolve failures. Can easily lead to an infinite loop if returned without any successful failure resolution. Cannot be returned if transaction is already being rolled back and will be treated as "ProceedWithRollBack" in this case.
  • ProceedWithRollback - continues execution of failure processing, but forces transaction to be rolled back, even if it was originally requested to commit. If before ProceedWithRollBack is returned FailureHandlingOptions are set to clear errors after rollback, no further error processing will take place, all failures will be deleted and transaction is rolled back silently. Otherwise default failure processing will continue, failures may be delivered to the user, but transaction is guaranteed to be rolled back.
  • WaitForUserInput - Can be returned only by FailuresProcessor if it is waiting for an external event (typically user input) to complete failures processing.

Depending on the severity of failures posted in the transaction and whether the transaction is being committed or rolled back, each of these 3 steps may have certain options to resolve errors. All information about failures posted in a document, information about ability to perform certain operations to resolve failures and API to perform such operations are provided via the FailuresAccessor class. The Document can be used to obtain additional information, but it cannot be changed other than via FailuresAccessor.

FailuresAccessor

A FailuresAccessor object is passed to each of failure processing steps as an argument and is the only available interface to fetch information about failures in a document. While reading from a document during failure processing is allowed, the only way to modify a document during failure resolution is via methods provided by this class. After returning from failure processing, the instance of the class is deactivated and cannot be used any longer.

Information Available from FailuresAccessor

The FailuresAccessor object offers some generic information such as:

  • Document for which failures are being processed or preprocessed
  • Highest severity of failures posted in the document
  • Transaction name and failure handling options for transaction being finished
  • Whether transaction was requested to be committed or rolled back.

The FailuresAccessor object also offers information about specific failures via the GetFailuresMessages() method.

Options to resolve failures

The FailuresAccessor object provides a few ways to resolve failures:

  • Failure messages with a severity of Warning can be deleted with the DeleteWarning() or DeleteAllWarnings() methods.
  • ResolveFailure() or ResolveFailures() methods can be used to resolve one or more failures using the last failure resolution type set for each failure.
  • DeleteElements() can resolve failures by deleting elements related to the failure.
  • Or delete all failure messages and replace them with one "generic" failure using the ReplaceFailures() method.

IFailuresPreprocessor

The IFailuresPreprocessor interface can be used to provide custom failure handling for a specific transaction only. IFailuresPreprocessor is an interface that may be used to perform a preprocessing step to either filter out anticipated transaction failures or to post new failures. Failures can be "filtered out" by:

  • silently removing warnings that are known to be posted for the transaction and are deemed as irrelevant for the user in the context of a particular transaction
  • silently resolving certain failures that are known to be posted for the transaction and that should always be resolved in a context of a given transaction
  • silently aborting the transaction in cases where "imperfect" transactions should not be committed or aborting the transaction is preferable over user interaction for a given workflow.

The IFailuresPreprocessor interface gets control first during the failure resolution process. It is nearly equivalent to checking and resolving failures before finishing a transaction, except that IFailuresPreprocessor gets control at the right time, after all failures guaranteed to be posted and/or after all irrelevant ones are deleted.

There may be only one IFailuresPreprocessor per transaction and there is no default failure preprocessor. If one is not attached to the transaction (via the failure handling options), this first step of failure resolution is simply omitted.

Code Region 26-3: Handling failures from IFailuresPreprocessor

public class SwallowTransactionWarning : IExternalCommand
{
        public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
                Autodesk.Revit.ApplicationServices.Application app =
                        commandData.Application.Application;
                Document doc = commandData.Application.ActiveUIDocument.Document;
                UIDocument uidoc = commandData.Application.ActiveUIDocument;
                
                FilteredElementCollector collector = new FilteredElementCollector(doc);
                ICollection<Element> elementCollection =
                        collector.OfClass(typeof(Level)).ToElements();
                Level level = elementCollection.Cast<Level>().ElementAt<Level>(0);
 
 
                Transaction t = new Transaction(doc);
                t.Start("room");
                FailureHandlingOptions failOpt = t.GetFailureHandlingOptions();
                failOpt.SetFailuresPreprocessor(new RoomWarningSwallower());
                t.SetFailureHandlingOptions(failOpt);
                
                doc.Create.NewRoom(level, new UV(0, 0));
                t.Commit();
                
                return Autodesk.Revit.UI.Result.Succeeded;
        }
}
public class RoomWarningSwallower : IFailuresPreprocessor
{
        public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor)
        {
                IList<FailureMessageAccessor> failList = new List<FailureMessageAccessor>();
                // Inside event handler, get all warnings
                failList = failuresAccessor.GetFailureMessages();        
                foreach (FailureMessageAccessor failure in failList)
                { 
                        // check FailureDefinitionIds against ones that you want to dismiss, FailureDefinitionId failID = failure.GetFailureDefinitionId();
                        // prevent Revit from showing Unenclosed room warnings
                        if (failID == BuiltInFailures.RoomFailures.RoomNotEnclosed)
                        {
                                failuresAccessor.DeleteWarning(failure);
                        }
                }
        
                return FailureProcessingResult.Continue;
        }
}

FailuresProcessing Event

The FailuresProcessing event is most suitable for applications that want to provide custom failure handling without a user interface, either for the entire session or for many unrelated transactions. Some use cases for handling failures via this event are:

  • automatic removal of certain warnings and/or automatic resolving of certain errors based on office standards (or other criteria)
  • custom logging of failures

The FailuresProcessing event is raised after IFailuresPreprocessor (if any) has finished. It can have any number of handlers, and all of them will be invoked. Since event handlers have no way to return a value, the SetProcessingResult() on the event argument should be used to communicate status. Only Continue, ProceedWithRollback or ProceedWithCommit can be set.

The following example shows an event handler for the FailuresProcessing event.

Code Region 26-4: Handling the FailuresProcessing Event

private void CheckWarnings(object sender, FailuresProcessingEventArgs e)
{
        FailuresAccessor fa = e.GetFailuresAccessor();
        IList<FailureMessageAccessor> failList = new List<FailureMessageAccessor>();
        failList = fa.GetFailureMessages(); // Inside event handler, get all warnings
        foreach (FailureMessageAccessor failure in failList)
        { 
        
                // check FailureDefinitionIds against ones that you want to dismiss, FailureDefinitionId failID = failure.GetFailureDefinitionId();
                // prevent Revit from showing Unenclosed room warnings
                if (failID == BuiltInFailures.RoomFailures.RoomNotEnclosed)
                {
                        fa.DeleteWarning(failure);
                }
        }
}

FailuresProcessor

The IFailuresProcessor interface gets control last, after the FailuresProcessing event is processed. There is only one active IFailuresProcessor in a Revit session. To register a failures processor, derive a class from IFailuresProcessor and register it using the Application.RegisterFailuresProcessor() method. If there is previously registered failures processor, it is discarded. If a Revit add-in opts to register a failures processor for Revit that processor will become the default error handler for all Revit errors for the session and the standard Revit error dialog will not appear. If no failures processors are set, there is a default one in the Revit UI that invokes all regular Revit error dialogs. FailuresProcessor should only be overridden to replace the existing Revit failure UI with a custom failure resolution handler, which can be interactive or have no user interface.

If the RegisterFailuresProcessor() method is passed NULL, any transaction that has any failures is silently aborted (unless failures are resolved by first two steps of failures processing).

The IFailuresProcessor.ProcessFailures() method is allowed to return WaitForUserInput, which leaves the transaction pending. It is expected that in this case, FailuresProcessor leaves some UI on the screen that will eventually commit or rollback a pending transaction - otherwise the pending state will last indefinitely, essentially freezing the document.

The following example of implementing the IFailuresProcessor checks for a failure, deletes the failing elements and sets an appropriate message for the user.

Code Region 26-5: IFailuresProcessor

[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Automatic)]   public class MyFailuresUI : IExternalApplication
{
        static AddInId m_appId = new AddInId(new Guid("9F179363-B349-4541-823F-A2DDB2B86AF3"));
        public Autodesk.Revit.UI.Result OnStartup(UIControlledApplication uiControlledApplication)
        {
                IFailuresProcessor myFailUI = new FailUI();
                Autodesk.Revit.ApplicationServices.Application.RegisterFailuresProcessor(myFailUI);
                return Result.Succeeded;
        }
        
        public Autodesk.Revit.UI.Result OnShutdown(UIControlledApplication application)
        {
                return Result.Succeeded;
        }
        
        public class FailUI : IFailuresProcessor
        {
                public void Dismiss(Document document)
                {
                        // This method is being called in case of exception or document destruction to 
                        // dismiss any possible pending failure UI that may have left on the screen
                }
        
                public FailureProcessingResult ProcessFailures(FailuresAccessor failuresAccessor)
                {
                        IList<FailureResolutionType> resolutionTypeList =
                                new List<FailureResolutionType>(); 
                        IList<FailureMessageAccessor> failList = new List<FailureMessageAccessor>();
                        // Inside event handler, get all warnings
                        failList = failuresAccessor.GetFailureMessages(); 
                        string errorString = "";
                        bool hasFailures = false;
                        foreach (FailureMessageAccessor failure in failList)
                        {
                        
                                // check how many resolutions types were attempted to try to prevent
                                // entering infinite loop
                                resolutionTypeList = 
                                        failuresAccessor.GetAttemptedResolutionTypes(failure);
                                if (resolutionTypeList.Count >= 3)
                                {
                                        TaskDialog.Show("Error", "Cannot resolve failures - transaction will be rolled back.");
                                        return FailureProcessingResult.ProceedWithRollBack;
                                }
 
                                errorString += "IDs ";
                                foreach (ElementId id in failure.GetFailingElementIds())
                                {
                                        errorString += id + ", ";
                                        hasFailures = true;
                                }
                                errorString += "\nWill be deleted because: " + failure.GetDescriptionText() + "\n";
                                failuresAccessor.DeleteElements(
                                                        failure.GetFailingElementIds() as IList<ElementId>);
                        }
                        if (hasFailures)
                        {
                                TaskDialog.Show("Error", errorString);
                                return FailureProcessingResult.ProceedWithCommit;
                        }
                        
                        return FailureProcessingResult.Continue;
                }
        }
}