Moderne Revit Add-in-Entwicklung (Teil 2)

Moderne Revit Add-in-Entwicklung (Teil 2)

Im zweiten Teil diese dreiteiligen Posts möchte ich Ihnen zeigen, wie Sie Inversion of Control (IoC) in Ihrem Revit Add-in einsetzen können. Dabei ersetzen wir den .NET Build-in IoC-Container durch Autofac, den in der .NET-Welt wohl am häufigsten verwendeten IoC-Container.

Moderne Revit Add-in-Entwicklung (Teil 1)
Moderne Revit Add-in-Entwicklung (Teil 3)

IoC-Container

Inversion of Control oder kurz IoC ist ein Designprinzip, das die Umkehrung der Kontrolle empfiehlt. Anstatt dass eine Klasse ihre Abhängigkeiten selbst erstellt und verwaltet, wird diese Aufgabe an einen Container delegiert. Der Container erstellt und verwaltet Objekte und deren Lebenszyklus. Die Klasse selbst muss sich nicht mehr um die Erstellung von Abhängigkeiten kümmern. Tiefergehende Informationen hierzu finden Sie in den Artikeln Inversion Of Control sowie Inversion of Control Containers and the Dependency Injection pattern von Martin Fowler.

IoC-Container - wann lohnt sich das?

Die Verwendung eines IoC-Containers kann in verschiedenen Szenarien sinnvoll sein.

  • Skalierbarkeit und Wartbarkeit
    Wenn Ihre Anwendung wächst und komplexer wird, kann ein IoC-Container die Verwaltung von Abhängigkeiten erleichtern. Er ermöglicht eine bessere Skalierbarkeit und Wartbarkeit, da er die Erstellung und Auflösung von Abhängigkeiten automatisiert.

  • Testbarkeit
    IoC-Container erleichtern das Testen, insbesondere das Mocking von Abhängigkeiten. Sie können leichter Testfälle erstellen, indem Sie künstliche Implementierungen für bestimmte Abhängigkeiten bereitstellen.

  • Entkopplung
    Ein IoC-Container hilft dabei, die Abhängigkeiten zwischen Komponenten zu entkoppeln. Dadurch wird der Code flexibler und leichter austauschbar.

  • Modularität
    Wenn Sie Ihre Anwendung in Module unterteilen möchten, kann ein IoC-Container die Verwaltung der Abhängigkeiten zwischen diesen Modulen erleichtern.

  • Frameworks und Bibliotheken
    Viele Frameworks und Bibliotheken verwenden IoC-Container, um ihre eigenen Abhängigkeiten zu verwalten. Wenn Sie ein Framework verwenden, das einen IoC-Container unterstützt, ist es sinnvoll, diesen auch in Ihrer Anwendung zu verwenden.

Denken Sie jedoch daran, dass die Verwendung eines IoC-Containers auch Overhead mit sich bringen kann. In kleineren Anwendungen oder wenn die Abhängigkeiten einfach sind, kann es möglicherweise einfacher sein, sie manuell zu verwalten. Die Entscheidung hängt von den spezifischen Anforderungen Ihrer Anwendung ab.

Lifetime-Scopes

Wie oben bereits erwähnt, verwalten IoC-Container Objekte und deren Lebenszyklus. Objekte, welche nur temporär - z.B. beim Ausführen eines Kommandos - benötigt werden, sollten innerhalb eines Lifetime-Scopes erzeugt werden. Dieser Scope wird beim Start eines Kommandos erstellt und beim Beenden des Kommandos wieder geschlossen. Alle innerhalb des Lifetime-Scopes erzeugten Objekte werden dann automatisch beim Beenden des Kommandos (Schließen des Scopes) destruiert und der verwendete Speicher wird wieder freigegeben.

Wir dürfen an dieser Stelle jedoch nicht nur die Scopes auf Kommando-Ebene betrachten. Bereits beim Laden des Revit Add-ins ist es erforderlich, den IoC Container und somit den äußeren Lifetime-Scope zu erstellen. Wir können dies dazu benutzen, Objekte, wie z.B. Updater, bereits beim Start von Revit zu erzeugen. Werden Objekte als Singleton registriert, so kann auf diese kommandoübergreifend zugegriffen werden. Achten Sie in diesem Fall darauf, dass nur Objekte erzeugt werden, von denen Sie die selbe Instanz auch tatsächlich in verschiedenen Kommandos benötigen. Dies kann z.B. dann sinnvoll sein, wenn auf eine gemeinsam genutzte Ressource zugegriffen werden soll und das Laden dieser Ressource teuer (z.B. zeitaufwendig) ist.

Scotec.Revit

Möchten Sie IoC-Container in Ihrem Revit Add-in verwenden, ist dazu einiges an Vorarbeit erforderlich. Das Nuget-Package Scotec.Revit nimmt Ihnen diese Arbeit ab. Das Package enthält einfach zu verwendende Basisimplementierungen für Revit Apps, Kommandos, Updater und Fenster (WPF).

In den nachfolgenden Abschnitten zeige ich Ihnen, wie Sie Scotec.Revit - und somit IoC-Container" - in Ihrem Revit Add-in einsetzen können. Den vollständigen Quell-Code zu diesem Beitrag finden Sie auf GitHub im Projekt scotec-revit-tutorial.

Revit App erstellen

Erstellen Sie Ihre Revit-App mit IoC-Container einfach, indem Sie in Ihrem Projekt das Nuget-Paket Scotec.Revit referenzieren. Erstellen Sie eine neue App-Klasse und leiten Sie diese von RevitApp ab.

public class RevitTutorialApp : RevitApp
{
}

Überschreiben Sie die OnConfigure() Methode um Ihre Services am Service Locator zu registrieren.

protected override void OnConfigure(IHostBuilder builder)
{
    base.OnConfigure(builder);

    // Register services by using Autofac modules.
    builder.ConfigureContainer<ContainerBuilder>(containerBuilder => 
        containerBuilder.RegisterModule<RegistrationModule>());

    builder.ConfigureServices(services =>
    {
        // You can also register services here.
        // However, using autofac modules gives you more flexibility.
    });
}

In der OnStartup() Methode habe Sie die Möglichkeit Initialisierungs-Code auszuführen. Fügen Sie hier beispielsweise Tabs, Panels oder Buttons zur Ribbon-Bar hinzu.

protected override Result OnStartup()
{
    try
    {
             // var yourService = Services.GetService<YourService>();

            // Create tabs, panels ond buttons
            TabManager.CreateTab(Application, StringResources.Tab_Name);

            var panel = TabManager.GetPanel(Application, StringResources.Panel_Name, StringResources.Tab_Name);

            var button = (PushButton)panel.AddItem(CreateButtonData("Revit.Tutorial",
                StringResources.Command_Test_Text, StringResources.Command_Test_Description,
                typeof(TestCommand)));

            button.Enabled = true;
    }
    catch (Exception)
    {
        return Result.Failed;
    }

    return Result.Succeeded;
}

Nutzen Sie die OnShutdown() Methode um z.B. während des Starts angeforderte Ressourcen wieder freizugeben.

protected override Result OnShutdown()
{
    return Result.Succeeded;
}

Beim Laden des Add-ins wird die Revit App automatisch instanziiert. Dabei werden am IoC-Container folgende Objekte registriert:

  • Autodesk.Revit.UI.UIControlledApplication
    Repräsentiert das Autodesk Revit User-Interface

  • Autodesk.Revit.DB.AddInId
    ID des aktuellen Revit Add-ins

  • Autodesk.Revit.ApplicationServices.ControlledApplication
    Repräsentiert die Autodesk Revit Application

Auf diese Objekte können Sie aus allen Kommandos des Add-ins heraus zugreifen.

Die Revit App wird wie gewohnt mittels .addin Manifest registriert und beim Laden des Add-ins automatisch von Revit instanziiert.

<?xml version="1.0" encoding="utf-8"?>
<RevitAddIns>
	<AddIn Type="Application">
		<Name>Revit Tutorial</Name>
		<FullClassName>Revit.Tutorial.RevitTutorialApp</FullClassName>
		<Assembly>.\Revit.Tutorial\Revit.Tutorial.dll</Assembly>
		<AddInId>C58F0D46-D4E2-44FC-A0E1-48C5904AF877</AddInId>
		<VendorId>SCOTEC</VendorId>
		<VendorDescription>scotec Software Solutions AB</VendorDescription>
	</AddIn>
</RevitAddIns>

Kommandos

Auch Kommandos lassen sich auf einfache Weise erstellen. Leiten Sie dazau eine neue Klasse von RevitCommand ab und überschreiben Sie die OnExecute() Methode.

[Transaction(TransactionMode.Manual)]
public class TestCommand : RevitCommand
{
    public TestCommand()
    {
        NoTransaction = true;
    }

    protected override string CommandName => StringResources.Command_Test_Name;

    protected override Result OnExecute(ExternalCommandData commandData, IServiceProvider services)
    {
        ShowTestWindow(services);

        return Result.Succeeded;
    }

    private static void ShowTestWindow(IServiceProvider services)
    {
        var window = services.GetService<TestWindow>();

        window.ShowDialog();
    }
}

Zum Ausführen ruft Revit die Execute() Methode des Kommandos auf. In dieser Methode wird zunächst ein neuer Lifetime-Scope erzeugt. Damit wird sichergestellt, dass alle vom IoC-Container während der Ausführung des Kommandos erstellten Objekte nach Beendigung des Kommandos wieder freigegeben werden.

Während der Ausführung des Kommandos benötigt man in den meisten Fällen Zugriff auf das Dokument, die aktive View sowie die UIApplication. Alle drei Objekte werden daher am IoC-Container registriert und stehen während der gesamten Laufzeit des Kommandos zur Verfügung.

Im nächsten Schritt wird eine Transaktion gestartet und die überladene OnExecute() Methode der abgeleiteten Kommando-Klasse aufgerufen. In Abhängigkeit vom Rückgabewert dieser Methode wird dann die Transaktion entweder committed oder die Änderungen werden verworfen (rollback).

Transaktionen in Revit bieten die Möglichkeit, auftretende Fehler zu behandeln, bevor diese durch Revit bearbeitet werden. Dazu werden entsprechende Fehlerprozessoren an der aktuellen Transaktion registriert. So haben Sie die Möglichkeit, die Fehler innerhalb Ihrer Kommando-Klasse auszuwerten und falls notwendig, zu behandeln. Überschreiben Sie dazu einfach in der abgeleiteten Kommando-Klasse die Methoden OnPreprocessFailures() und OnProcessFailures().

In einigen Fällen möchten Sie vielleicht während der Ausführung des Kommandos keine Transaktion starten. Gründe hierfür sind beispielsweise, dass das Kommando das aktuelle Dokument nicht verändert oder Sie die Transaktion selbst verwalten möchten. Weisen Sie in diesem Fall dem Property NoTransaction den Wert true zu.

NoTransaction = true;

Hinweis: Ist kein Dokument geöffnet, während ein Kommando ausgeführt wird, wird unabhängig vom Status des NoTransaction Properties keine Transaktion gestartet.

Updater und Command Availability

Scotec.Revit enthält neben den Basisimplementierungen für die Apps und Kommandos ebenfalls Klassen, welche das IUpdater Interface sowie das IExternalCommandAvailability Interface implementieren. Verwenden Sie diese Klassen, wenn Sie Updater benötigen oder wenn Sie den Status der Buttons im Ribbon setzen möchten. Selbstverständlich steht Ihnen auch hier der IoC-Container zur Verfügung.

Autofac IoC-Container

.NET enthält einen eigenen IoC-Container, welcher bereits einen großen Teil der benötigten Funktionalität abdeckt. Scotec.Revit ersetzt diesen jedoch durch Autofac. Autofac bietet zahlreiche zusätzliche Features wie z.B. Named und Keyed Services, Delegate Factories oder Pooling. Scotec.Revit nutzt unter anderem die Möglichkeit, beim Erstellen eines neuen Lifetime-Scopes neue Services zu registrieren, welche dann beim Schließen des Scopes automatisch wieder entfernt werden. Die zusätzlich registrierten Services stehen somit nur innerhalb dieses Scopes zur Verfügung.

Benötigen Sie weitere Informationen hierzu?

Ich helfe Ihnen gerne weiter. Wenn Sie Fragen zu diesem Artikel oder zur Verwendung von Scotec.Revit haben, zögern Sie nicht, mich zu kontaktieren.

Sie benötigen Unterstützung bei der Entwicklung Ihres Revit Add-ins? Wir können die Details gerne in einem persönlichen Gespräch besprechen.



Moderne Revit Add-in-Entwicklung (Teil 1)
Moderne Revit Add-in-Entwicklung (Teil 3)

An error has occurred. This application may no longer respond until reloaded. An unhandled exception has occurred. See browser dev tools for details. Reload 🗙