Perform Add-in Save Operation During App Closing Based on User Response

Perform Add-in Save Operation During App Closing Based on User Response

sivakameswaran_a
Observer Observer
200 Views
2 Replies
Message 1 of 3

Perform Add-in Save Operation During App Closing Based on User Response

sivakameswaran_a
Observer
Observer

Hi all,
I’m trying to handle the App_Closing event in my application and trigger a custom add-in save operation (which performs a database save) based on the user’s response from a “Save changes?” prompt dialog.
Specifically, I want to:

Display a save prompt when the application is closing
Capture the user’s response (Yes / No / Cancel)
If the user selects Yes, execute the add-in save logic (which internally performs a DB save) during the closing process

However, I’m unsure about the best way to safely trigger this save operation within the closing event without causing issues (e.g., blocking the shutdown).
Has anyone implemented a similar pattern or can suggest the correct approach?
Thanks in advance!

0 Likes
201 Views
2 Replies
Replies (2)
Message 2 of 3

naveen.kumar.t
Autodesk Support
Autodesk Support

Hi @sivakameswaran_a ,

I think handling the add-in save operation directly inside the ApplicationClosing event may not be the right approach. At that stage Revit is already in the shutdown process, and the ApplicationClosing event is also non-cancellable. So doing heavy operations like DB save, document updates, or showing dialogs may lead to stability issues or even delay the shutdown process.

A better approach would be to handle this in the DocumentClosing event itself. There we can check if the add-in has any unsaved changes and show a “Save changes?” prompt to the user.


Based on the user selection:

  • If user clicks Yes, execute the add-in save logic and perform the DB save
  • If user clicks No, continue the closing process without saving

The safest approach is to use DocumentClosing event.  ApplicationClosing should ideally be used only for lightweight cleanup activities like releasing resources.


Naveen Kumar T
Developer Technical Services
Autodesk Developer Network

0 Likes
Message 3 of 3

jose_Miquelao
Explorer
Explorer

The Core Pattern

The key insight is that App_Closing (via Window.Closing or Application.SessionEnding) gives you a CancelEventArgs, so you can cancel the close, let the async save finish, then close programmatically.


Implementation

csharp
Copiar

 

// MainWindow.xaml.cs

public partial class MainWindow : Window
{
    private bool _isSaveHandled = false;

    protected override void OnClosing(CancelEventArgs e)
    {
        // If the save was already handled (second pass), let it close
        if (_isSaveHandled)
        {
            base.OnClosing(e);
            return;
        }

        // Cancel the close immediately so we control the flow
        e.Cancel = true;

        // Handle the save prompt asynchronously
        _ = HandleClosingAsync();
    }

    private async Task HandleClosingAsync()
    {
        var result = ShowSavePrompt();

        switch (result)
        {
            case MessageBoxResult.Yes:
                try
                {
                    await ExecuteSaveAsync();
                    _isSaveHandled = true;
                    this.Close(); // Re-trigger closing, this time _isSaveHandled = true
                }
                catch (Exception ex)
                {
                    MessageBox.Show(
                        $"Save failed: {ex.Message}\nClose anyway?",
                        "Save Error",
                        MessageBoxButton.YesNo,
                        MessageBoxImage.Error                    ) == MessageBoxResult.Yes                        ? ForceClose()
                        : /* stay open */ _ = Task.CompletedTask;
                }
                break;

            case MessageBoxResult.No:
                // Discard changes and close
                _isSaveHandled = true;
                this.Close();
                break;

            case MessageBoxResult.Cancel:
                // Do nothing — close was already cancelled
                break;
        }
    }

    private MessageBoxResult ShowSavePrompt()
    {
        return MessageBox.Show(
            "You have unsaved changes. Do you want to save before closing?",
            "Save Changes?",
            MessageBoxButton.YesNoCancel,
            MessageBoxImage.Warning        );
    }

    private async Task ExecuteSaveAsync()
    {
        // Your add-in DB save logic here
        await YourAddIn.SaveToDatabaseAsync();
    }

    private void ForceClose()
    {
        _isSaveHandled = true;
        this.Close();
    }
}

Why e.Cancel = true First?

If you await anything inside OnClosing synchronously, the event returns before the await completes and the window closes anyway. By cancelling immediately and re-triggering Close() after the save, you get full control over the lifecycle.


Handling the HasUnsavedChanges Guard

You probably don't want the prompt to appear if nothing changed:

csharp
Copiar

 

protected override void OnClosing(CancelEventArgs e)
{
    if (_isSaveHandled || !YourAddIn.HasUnsavedChanges)
    {
        base.OnClosing(e);
        return;
    }

    e.Cancel = true;
    _ = HandleClosingAsync();
}

If You're Using Application.SessionEnding (System Shutdown)

This event has very limited time — avoid async DB saves here. Instead, do a synchronous, fast save or save to a local recovery file:

csharp
Copiar

 

private void App_SessionEnding(object sender, SessionEndingCancelEventArgs e)
{
    // Keep it fast and synchronous
    YourAddIn.SaveRecoverySnapshot();
}

Summary of the Pattern

User Action Behavior
YesCancel close → await save → re-close
NoCancel close → skip save → re-close
CancelClose stays cancelled, app stays open
System shutdownFast sync save only

 

Exportar
 

 

Copiar

The _isSaveHandled flag acting as a re-entry guard is the cleanest way to avoid infinite loops when you call this.Close() from within the closing handler.

0 Likes