Posting Failures

Posting Failures

To use the failure posting mechanism to report problems, the following steps are required:

  1. New failures not already defined in Revit must be defined and registered in the FailureDefinitionRegistry during the OnStartup() call of the ExternalApplication.
  2. Find the failure definition id, either from the BuiltInFailures classes or from the pre-registered custom failures using the class related to FailureDefinition.
  3. Post the failure to the document that has a problem using the classes related to FailureMessage to set options and details related to the failure.

Defining and registering a failure

Each possible failure in Revit must be defined and registered during Revit application startup by creating a FailureDefinition object that contains some persistent information about the failure such as identity, severity, basic description, types of resolution and default resolution.

The following example creates two new failures, a warning and an error, that can be used for walls that are too tall. In this example, they are used in conjunction with an Updater that will do the failure posting (in a subsequent code sample in this chapter). The FailureDefinitionIds are saved in the Updater class since they will be required when posting failures. The sections following explain the FailureDefinition.CreateFailureDefinition() method parameters in more detail.

Code Region 26-1: Defining and registering a failure

WallWarnUpdater wallUpdater = new WallWarnUpdater();
UpdaterRegistry.RegisterUpdater(wallUpdater);
ElementClassFilter filter = new ElementClassFilter(typeof(Wall));
UpdaterRegistry.AddTrigger(wallUpdater.GetUpdaterId(), filter, Element.GetChangeTypeGeometry());

// define a new failure id for a warning about walls
FailureDefinitionId warnId = new FailureDefinitionId(new Guid("FB4F5AF3-42BB-4371-B559-FB1648D5B4D1"));
 
// register the new warning using FailureDefinition
FailureDefinition failDef = FailureDefinition.CreateFailureDefinition(warnId, FailureSeverity.Warning, "Wall is too big (>100'). Performance problems may result.");

FailureDefinitionId failId = new FailureDefinitionId(new Guid("691E5825-93DC-4f5c-9290-8072A4B631BC"));
 
FailureDefinition failDefError = FailureDefinition.CreateFailureDefinition(failId, FailureSeverity.Error, "Wall is WAY too big (>200'). Performance problems may result.");
// save ids for later reference
wallUpdater.WarnId = warnId;
wallUpdater.FailureId = failId;

FailureDefinitionId

A unique FailureDefinitionId must be used as a key to register the FailureDefinition. Each unique FailureDefinitionId should be created using a GUID generation tool. Later, the FailureDefinitionId can be used to look up a FailureDefinition in FailureDefinitionRegistry, and to create and post FailureMessages.

Severity

When registering a new failure, a severity is specified, along with the FailureDefinitionId and a text description of the failure that can be displayed to the user. The severity determines what actions are allowed in a document and whether the transaction can be committed at all. The severity options are:

  • Warning - Failure that can be ignored by end-user. Failures of this severity do not prevent transactions from being committed. This severity should be used when Revit needs to communicate a problem to the user, but the problem does not prevent the user from continuing to work on the document
  • Error - Failure that cannot be ignored. If FailureMeassage of this severity is posted, the current transaction cannot be committed unless the failure is resolved via an appropriate FailureResolution. This severity should be used when work on the document cannot be continued unless the problem is resolved. If the failure has no predefined resolutions available or these resolutions fail to resolve the problem, the transaction must be aborted in order to continue working with the document. It is strongly encouraged to have at least one resolution in each failure of this severity.
  • DocumentCorruption - Failure that forces the Transaction to be rolled back as soon as possible due to known corruption to a document. When failure of this severity is posted, reading of information from a document is not allowed. The current transaction must be rolled back first in order to work with the document. This severity is used only if there is known data corruption in the document. This type of failure should generally be avoided unless there is no way to prevent corruption or to recover from it locally.

A fourth severity, None, cannot be specified when defining a new FailureDefinition.

Failure Resolutions

When a failure can be resolved, all possible resolutions should be predefined in the FailureDefinition class. This informs Revit what failure resolutions can possibly be used with a given failure. The FailureDefinition contains a full list of resolution types applicable to the failure, including a user-visible caption of the resolution.

The number of resolutions is not limited, however as of the 2011 Revit API, the only exposed failure resolution is DeleteElements. When more than one resolution is specified, unless explicitly changed using the SetDefaultResolutionType() method, the first resolution added becomes the default resolution. The default resolution is used by the Revit failure processing mechanism to resolve failures automatically when applicable. The Revit UI only uses the default resolution, but Revit add-ins, via the Revit API, can use any applicable resolution, and can provide an alternative UI for doing that (as described in the Handling Failures section later in this chapter).

In the case of a failure with a severity of DocumentCorruption, by the time failure resolution could occur, the transaction is already aborted, so there is nothing to resolve. Therefore, FailureResolutions should not be added to API-defined Failures of severity DocumentCorruption.

Posting a failure

The Document.PostFailure() method is used to notify the document of a problem. Failures will be validated and possibly resolved at the end of the transaction. Warnings posted via this method will not be stored in the document after they are resolved. Failure posting is used to address a state of the document which may change before the end of the transaction or when it makes sense to defer resolution until the end of the transaction. Not all failures encountered by an external command should post a failure. If the failure is unrelated to the document, a task dialog should be used. For example, if the Revit UI is in an invalid state to perform the external command.

To post a failure, create a new FailureMessage using the FailureDefinitionId from when the custom failure was defined, or use a BuiltInFailure provided by the Revit API. Set any additional information in the FailureMessage object, such as failing elements, and then call Document.PostFailure() passing in the new FailureMessage. Note that the document must be modifiable in order to post a failure.

A unique FailureMessageKey returned by PostFailure() can be stored for the lifetime of transaction and used to remove a failure message if it is no longer relevant. If the same FailureMessage is posted two or more times, the same FailureMessageKey is returned. If a posted failure has a severity of DocumentCorruption, an invalid FailureMessageKey is returned. This is because a DocumentCorruption failure cannot be unposted.

The following example shows an IUpdate class (referenced in the "Defining and registering a failure" code region above) that posts a new failure based on information received in the Execute() method.

Code Region 26-2: Posting a failure

public class WallWarnUpdater : IUpdater
{
        static AddInId m_appId;
        UpdaterId m_updaterId;
        FailureDefinitionId m_failureId = null;
        FailureDefinitionId m_warnId = null;
        
        // constructor takes the AddInId for the add-in associated with this updater
        public WallWarnUpdater(AddInId id)
        {
                m_appId = id;
                m_updaterId = new UpdaterId(m_appId, new Guid("69797663-7BCB-44f9-B756-E4189FE0DED8"));
        }
        
        public void Execute(UpdaterData data)
        {
                Document doc = data.GetDocument();
                Autodesk.Revit.ApplicationServices.Application app = doc.Application;
                foreach (ElementId id in data.GetModifiedElementIds())
                {
                        Wall wall = doc.GetElement(id) as Wall;
                        Autodesk.Revit.DB.Parameter p = wall.get_Parameter("Unconnected Height");
                        if (p != null)
                        {
                                if (p.AsDouble() > 200)
                                {
                                        FailureMessage failMessage = new FailureMessage(FailureId);
                                        failMessage.SetFailingElement(id);
                                        doc.PostFailure(failMessage);
                                }
                                else if (p.AsDouble() > 100)
                                {
                                        FailureMessage failMessage = new FailureMessage(WarnId);
                                        failMessage.SetFailingElement(id);
                                        doc.PostFailure(failMessage);
                                }
                        }
                }
        }

        public FailureDefinitionId FailureId
        {
                get { return m_failureId; }
                set { m_failureId = value; }
        }
        
        public FailureDefinitionId WarnId
        {
                get { return m_warnId; }
                set { m_warnId = value; }
        }
        
        public string GetAdditionalInformation() 
        { 
                return "Give warning and error if wall is too tall"; 
    }
        
        public ChangePriority GetChangePriority() 
        { 
                return ChangePriority.FloorsRoofsStructuralWalls; 
        }
        
        public UpdaterId GetUpdaterId() 
        { 
                return m_updaterId; 
        }
        
        public string GetUpdaterName() 
        { 
                return "Wall Height Check"; 
        }
}

Removal of posted failures

Because there may be multiple changes to a document and multiple regenerations in the same transaction, it is possible that some failures are no longer relevant and they may need to be removed to prevent "false alarms". Specific messages can be un-posted by calling the Document.UnpostFailure() method and passing in the FailureMessageKey obtained when PostFailure() was called. UnpostFailure() will throw an exception if the severity of the failure is DocumentCorruption.

It is also possible to automatically remove all posted failures when a transaction is about to be rolled back (so that the user is not bothered to hit Cancel) by using the Transaction.SetFailureHandlingOptions() method.