0

My MvvmCross based desktop application (Windows/WPF + MacOS/Cocoa) creates some temporary files during its normal operation. The files are created within the async command and deleted before the command ends.

However, because the command is async, the user can close the application before the command finishes by simply clicking the red cross in the main window. In this case the command will not complete its work and the temporary files will not be cleaned.

The application consists of a single View (derived from MvxWpfView) and a corresponding ViewModel. The TipCalc sample (https://github.com/MvvmCross/MvvmCross-Samples/tree/master/TipCalc) can be used as a starting point.

I need to somehow handle the closing of the application and wait for all async operations to finish.

What I have tried so far (class names given as in the TipCalc sample mentioned above):

  • implement IDisposable and IAsyncDisposable for view model TipViewModel and view TipView. Dispose & DisposeAsync just not called at all.
  • override ViewDestroy in the TipViewModel view model - it is also not called.
  • subscribe to the TipView.Unload event inside the TipView itself - event handler not called.
  • checked for relevant methods to overload or implement into the MvxWpfSetup and IMvxAppStart - nothing relevant found.
  • tried to handle Window.Close event on the Window level then transfer the call into the View (MvxWpfView) or ViewModel, but could not find the normal way to do everything synchronously or with correct waiting of the async operations.

So, how do I handle the app close and clean up my temporary files?

Serg
  • 3,454
  • 2
  • 13
  • 17

3 Answers3

1

One sure way is to open the file with FileOptions.DeleteOnClose, the file will always get deleted even if the program ends.

convertedFileStream = new StreamReader(new FileStream(ConvertedFilepath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.DeleteOnClose));
Ceres
  • 3,524
  • 3
  • 18
  • 25
  • Good way! Unfortunately, some of the files are created by the external process (via the `Process.Start`), so such an approach can't be used for them. I need the the way to execute some logic to locate these files and delete them. – Serg Jun 28 '23 at 19:29
0

You can start your cleanup from the Application.Exit event handler.

You have basically two options to track or identify temporary files of your application:

  • Create all temporary files in a specific subfolder. This subfoldeer should be in the user's Windows temp folder to allow him to cleanup manually in case you missed something.
  • Create a global recent file list e.g. in your App class to store all temporary file paths

Then in the event handler either delete the temporary subfolder or delete each individual file by enumerating the global recent files list.

For external processes you don't have control where they create their temporary files. Those processes are responsible to cleanup their artifacts like you are responsible for your application artifacts.

For simplicity the following example uses static properties to make the temporary folder globally available.
In advanced scenarios for example when you are writing unit tests and use dependency injection, you should move the code that manages the temporary directory (create and delete) to a dedicated e.g. TempFolderManager class that you inject as shared instance into the application where needed.

public partial class App : Application
{
  public string? ApplicationTempDirectoryFullName => this.ApplicationTempDirectory?.FullName;

  // Make the actual DirectoryInfo private to protect against random manipulation
  private DirectoryInfo ApplicationTempDirectory { get; set; }
  private const string TempFolderNamePrefix = "MyApplicationTemp";

  public App()
  {
    this.Exit += App_Exit; 

    // Create the temp folder 
    // and expose it as static property to allow your application to reference it
    // for example to read or create temporary files.
    this.ApplicationTempDirectory = Directory.CreateTempSubdirectory(App.TempFolderNamePrefix);
  }

  void App_Exit(object sender, ExitEventArgs e)
  {
    // Delete the temporary folder and all sub-items (recursively)
    this.ApplicationTempDirectory.Delete(true);
  }
}

Next, to create a temporary file in the application's special folder simply reference the folder path exposed by the App class:

App application = (App)Application.Current;
string tempFolderPath = application.ApplicationTempDirectoryFullName;
string fileName = "temporaryFile.tmp";
string fullFileName = Path.Combine(tempFolderPath, fileName);
await using FileStream temporaryFile = File.Create(fullFileName);
  • Thank you for your answer! Yes, it is a working solution, but it is almost entirely platform specific. I still wonder if there is a platform-agnostic solution exists, that somehow fits into the MvvmCross cross-platform paradigm. – Serg Jun 29 '23 at 15:00
  • It's not platform specific. Only the entry point is specific. As suggested in my answer you should move the related code to manage the temporary resources to a dedicated class. Then you would only have to subscribe to the application's exit event (which is the only platform specific part) and call e.g. `TempFileManager.ClearAll()`. –  Jun 29 '23 at 15:15
0

Finally figured it out. The situation was reduced to the known MvvmCross problem https://github.com/MvvmCross/MvvmCross/issues/3481#issuecomment-1614698577 (which is only partially fixed).

Resume:

  • I have only one window in my applicatoin. So, when I close this window, the WPF hanles this as "the last window is closing". In this case WPF does not fire Unloaded event for the Window and all its internals (https://github.com/dotnet/wpf/issues/1442#issuecomment-518472218)
  • MvvmCross uses exactly the Unloaded events to manage the lifetime of the views. So, for the views that belong to such a last window will not receive any of the end-of-life notifications.
  • The fix which is already implemented in MvvmCross only handles the case when you use window presentation. It does not handle the content presentation placed in the main window.

The fix: I used platform-specific solution from Awaiting Asynchronous function inside FormClosing Event to handle the last window close and to await async operations in my view model. The drawback of this solution is that I use some knowledge about the internal implementation of the default MvvmCross WPF presenter to get the reference to the VM from the Window_closing handler, which is obviously fragile and is not a best practice at all.

Serg
  • 3,454
  • 2
  • 13
  • 17