6 votes

Winforms : Comment enregistrer les formulaires avec le conteneur IoC

Contexte

Je suis en train de construire une application winforms où j'utilise un conteneur IoC (SimpleInjector) pour enregistrer mes types. Dans mon application, la majorité des écrans (c'est-à-dire les formulaires) n'auront qu'une seule instance à un moment donné.

Problème

Pour les formulaires qui ne nécessitent qu'une seule instance à un moment donné, je peux les enregistrer en tant que singletons :

container.Register<IHomeView, HomeView>(Lifestyle.Singleton);

Cela me permet d'utiliser le conteneur pour garder la trace de tous les formulaires. Dans ce cas, cependant, lorsqu'un formulaire est fermé, il est éliminé (les formulaires implémentent IDisposable). Si l'application tente de rouvrir ce formulaire à l'aide du conteneur, l'instance du formulaire dans le conteneur est éliminée et une exception est levée.

Pregunta

Quelle est la bonne façon de traiter cette question ? Je vois actuellement deux solutions :

  1. Pour chaque formulaire, remplacez la fermeture du formulaire pour masquer le formulaire, plutôt que de le fermer réellement. Je n'aime pas vraiment cette idée. J'ai l'impression que je préférerais fermer le formulaire à chaque fois et commencer avec un nouveau formulaire.
  2. Enregistrez le formulaire avec un style de vie transitoire plutôt que comme un singleton. Dans ce cas, le conteneur agit plutôt comme une usine. Je rencontre deux problèmes : a) je perds la possibilité de suivre les formulaires à travers le conteneur, et, b) le conteneur lève une exception pendant la vérification disant que les types jetables ne devraient pas être enregistrés comme transitoires (ce que je ne comprends pas pourquoi). Ces problèmes s'appliquent également aux formulaires pour lesquels j'aurai besoin de plusieurs instances à la fois.

Je peux contourner le problème b) en supprimant l'avertissement de diagnostic pendant la vérification.

registration = container.GetRegistration(typeof(ILoginView)).Registration;
registration.SuppressDiagnosticWarning(DiagnosticType.DisposableTransientComponent, "Winforms registration supression.");

Quelle est l'approche correcte à adopter ici ? Est-ce que je rate quelque chose ?

19voto

Ric .Net Points 4668

Idéalement, vous voudriez enregistrer vos formulaires en tant que Singleton . Cependant, d'après mon expérience, cela donne lieu à des erreurs difficiles à déboguer, en particulier lorsque vous utilisez une fonction BindingSource pour lier vos données à quoi que ce soit.

Un deuxième problème lié à l'utilisation de Singleton car le style de vie est que si votre application utilise des fenêtres sans modèle, cette fenêtre lancera un message d'erreur. ObjectDisposedException lorsqu'il est ouvert une seconde fois, car le cadre d'application Windows Forms se débarrasse du formulaire à la première fermeture, alors que Simple Injector devrait s'en charger. Donc Simple Injector créera une - et exactement une - instance, s'il est enregistré comme Singleton. Si quelqu'un d'autre (par exemple votre application, le framework Windows Forms) dispose de l'objet, il ne sera pas recréé.

La solution la plus simple, qui est également facile à comprendre, consiste à enregistrer vos formulaires en tant que Transient . Et oui, vous devez supprimer les avertissements de diagnostic. La raison de cet avertissement de diagnostic selon le documentation :

Un composant qui implémente IDisposable aurait généralement besoin d'un nettoyage déterministe, mais Simple Injector ne suit pas et ne dispose pas implicitement des composants enregistrés avec le style de vie transitoire.

Simple Injector est incapable de se débarrasser d'un composant transitoire car il ne peut pas déterminer quand l'objet doit être éliminé. Cela signifie toutefois que les formulaires qui sont ouverts de manière modale avec un appel à '.ShowDialog()' ne seront jamais éliminés ! Et comme une application Windows Forms fonctionne généralement pendant une longue période, voire une semaine ou un mois, il en résultera éventuellement un Win32Exception avec un message : "Error Creating Window Handle". Ce qui signifie essentiellement que vous avez épuisé toutes les ressources de l'ordinateur.

L'élimination des formulaires est donc importante. Et bien que Simple Injector soit capable de faire ce travail si vous utilisiez un fichier Portée Avec Windows Forms, ce n'est pas si facile à mettre en œuvre. Vous devez donc vous charger vous-même de disposer des formulaires fermés qui ont été affichés à l'aide de l'option ShowDialog() .

En fonction de votre cas d'utilisation spécifique, il existe plusieurs façons de mettre en œuvre un système de gestion de l'information. FormOpener o NavigationService . Une façon de le faire :

public interface IFormOpener
{
    void ShowModelessForm<TForm>() where TForm : Form;
    DialogResult ShowModalForm<TForm>() where TForm : Form;
}

public class FormOpener : IFormOpener
{
    private readonly Container container;
    private readonly Dictionary<Type, Form> openedForms;

    public FormOpener(Container container)
    {
        this.container = container;
        this.openedForms = new Dictionary<Type, Form>();
    }

    public void ShowModelessForm<TForm>() where TForm : Form
    {
        Form form;
        if (this.openedForms.ContainsKey(typeof(TForm)))
        {
            // a form can be held open in the background, somewhat like 
            // singleton behavior, and reopened/reshown this way
            // when a form is 'closed' using form.Hide()   
            form = this.openedForms[typeof(TForm)];
        }
        else
        {
            form = this.GetForm<TForm>();
            this.openedForms.Add(form.GetType(), form);
            // the form will be closed and disposed when form.Closed is called
            // Remove it from the cached instances so it can be recreated
            form.Closed += (s, e) => this.openedForms.Remove(form.GetType());
        }

        form.Show();
    }

    public DialogResult ShowModalForm<TForm>() where TForm : Form
    {
        using (var form = this.GetForm<TForm>())
        {
            return form.ShowDialog();
        }
    }

    private Form GetForm<TForm>() where TForm : Form
    {
        return this.container.GetInstance<TForm>();
    }
}

Cette classe doit être enregistrée comme Singleton :

container.RegisterSingleton<IFormOpener, FormOpener>();

Et peut être utilisé en injectant ce service dans par exemple votre formulaire Root de l'application :

public partial class RootForm : Form
{
    private readonly IFormOpener formOpener;

    public RootForm(IFormOpener formOpener)
    {
        this.formOpener = formOpener;
        this.InitializeComponent();
    }

    private void ShowCustomers_Click(object sender, EventArgs e)
    {
        this.formOpener.ShowModelessForm<AllCustomersForm>();
    }

    private void EditCustomer_Click(object sender, EventArgs e)
    {
        var result = this.formOpener.ShowModalForm<EditCustomerForm>();
        // do something with result
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X