Question

J'ai un service Windows écrit en C # qui agit en tant que proxy pour un ensemble de périphériques réseau de la base de données principale. Pour tester et également ajouter une couche de simulation pour tester le back-end, j'aimerais disposer d'une interface graphique pour que l'opérateur de test puisse exécuter la simulation. Aussi pour une version rayée à envoyer comme une démo. L'interface graphique et le service ne doivent pas nécessairement être exécutés en même temps. Quel est le meilleur moyen de réaliser cette opération duel?

Modifier: Voici ma solution en combinant des éléments de cette question , Est-ce que je cours en tant que service et Installez un service Windows .NET sans InstallUtil.exe avec cet excellent code par Marc Gravell

Il utilise la ligne suivante pour déterminer si l’exécution de l’interface graphique ou du service est exécutée.

 if (arg_gui || Environment.UserInteractive || Debugger.IsAttached)

Voici le code.


using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.ComponentModel;
using System.ServiceProcess;
using System.Configuration.Install;
using System.Diagnostics;

namespace Form_Service
{
   static class Program
   {
      /// 
      /// The main entry point for the application.
      /// 
      [STAThread]
      static int Main(string[] args)
      {
         bool arg_install =  false;
         bool arg_uninstall = false;
         bool arg_gui = false;
         bool rethrow = false;
         try
         {
            foreach (string arg in args)
            {
               switch (arg)
               {
                  case "-i":
                  case "-install":
                     arg_install = true; break;
                  case "-u":
                  case "-uninstall":
                     arg_uninstall = true; break;
                  case "-g":
                  case "-gui":
                     arg_gui = true; break;
                  default:
                     Console.Error.WriteLine("Argument not expected: " + arg);
                     break;
               }
            }
            if (arg_uninstall)
            {
               Install(true, args);
            }
            if (arg_install)
            {
               Install(false, args);
            }
            if (!(arg_install || arg_uninstall))
            {
               if (arg_gui || Environment.UserInteractive || Debugger.IsAttached)
               {
                  Application.EnableVisualStyles();
                  Application.SetCompatibleTextRenderingDefault(false);
                  Application.Run(new Form1());
               }
               else
               {
                  rethrow = true; // so that windows sees error... 
                  ServiceBase[] services = { new Service1() };
                  ServiceBase.Run(services);
                  rethrow = false;
               }
            }
            return 0;
         }
         catch (Exception ex)
         {
            if (rethrow) throw;
            Console.Error.WriteLine(ex.Message);
            return -1;
         }
      }

      static void Install(bool undo, string[] args)
      {
         try
         {
            Console.WriteLine(undo ? "uninstalling" : "installing");
            using (AssemblyInstaller inst = new AssemblyInstaller(typeof(Program).Assembly, args))
            {
               IDictionary state = new Hashtable();
               inst.UseNewContext = true;
               try
               {
                  if (undo)
                  {
                     inst.Uninstall(state);
                  }
                  else
                  {
                     inst.Install(state);
                     inst.Commit(state);
                  }
               }
               catch
               {
                  try
                  {
                     inst.Rollback(state);
                  }
                  catch { }
                  throw;
               }
            }
         }
         catch (Exception ex)
         {
            Console.Error.WriteLine(ex.Message);
         }
      }
   }

   [RunInstaller(true)]
   public sealed class MyServiceInstallerProcess : ServiceProcessInstaller
   {
      public MyServiceInstallerProcess()
      {
         this.Account = ServiceAccount.NetworkService;
      }
   }

   [RunInstaller(true)]
   public sealed class MyServiceInstaller : ServiceInstaller
   {
      public MyServiceInstaller()
      {
         this.Description = "My Service";
         this.DisplayName = "My Service";
         this.ServiceName = "My Service";
         this.StartType = System.ServiceProcess.ServiceStartMode.Manual;
      }
   }

}
Était-ce utile?

La solution

Vous avez essentiellement deux choix. Exposez une API sur le service que vous pouvez ensuite appeler à partir de l'application d'interface utilisateur OU activez le service en tant qu'application Winforms ou en tant que service.

La première option est assez simple: utilisez la communication à distance ou WCF pour exposer l'API.

La deuxième option peut être obtenue en déplaçant le curseur "tripes". de votre application dans une classe distincte, puis créez un wrapper de service et un wrapper Win-Form qui appellent tous les deux votre " guts " classe.

static void Main(string[] args)
{
    Guts guts = new Guts();

    if (runWinForms)
    {
        System.Windows.Forms.Application.EnableVisualStyles();
        System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);

        FormWrapper fw = new FormWrapper(guts);

        System.Windows.Forms.Application.Run(fw);
    }
    else
    {
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[] { new ServiceWrapper(guts) };
        ServiceBase.Run(ServicesToRun);
    }
}

Autres conseils

Créez une nouvelle application winforms qui référence l'assemblage de votre service.

Si vous utilisez le code ci-dessous:

[DllImport("advapi32.dll", CharSet=CharSet.Unicode)]
static extern bool StartServiceCtrlDispatcher(IntPtr services);
[DllImport("ntdll.dll", EntryPoint="RtlZeroMemory")]
static extern void ZeroMemory(IntPtr destination, int length);

static bool StartService(){
    MySvc svc = new MySvc(); // replace "MySvc" with your service name, of course
    typeof(ServiceBase).InvokeMember("Initialize", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
        null, svc, new object[]{false});
    object entry = typeof(ServiceBase).InvokeMember("GetEntry",
        BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, svc, null);
    int len = Marshal.SizeOf(entry) * 2;
    IntPtr memory = Marshal.AllocHGlobal(len);
    ZeroMemory(memory, len);
    Marshal.StructureToPtr(entry, memory, false);
    return StartServiceCtrlDispatcher(memory);
}

[STAThread]
static void Main(){
    if(StartService())
        return;

    Application.Run(new MainWnd()); // replace "MainWnd" with whatever your main window is called
}

Ensuite, votre fichier EXE s'exécutera en tant que service (s'il est lancé par le SCM) ou en tant qu'interface graphique (s'il est lancé par un autre processus).

En gros, tout ce que j'ai fait ici est utilisé par réflecteur . Qu'est-ce que la viande de ServiceBase.Run fait, et le dupliquer ici (une réflexion est nécessaire, car cela appelle des méthodes privées). Si vous n’appelez pas ServiceBase.Run directement, c’est qu’une boîte de message apparaît pour indiquer à l’utilisateur que le service ne peut pas être démarré (s’il n’a pas été lancé par le GDS) et ne renvoie rien pour indiquer au code que le service ne peut pas être démarré.

Comme cette méthode utilise la réflexion pour appeler des méthodes de cadre privées, il est possible que cela ne fonctionne pas correctement lors de futures révisions du cadre. Codeur de mise en garde.

Il existe également un FireDaemon . Cela vous permet d'exécuter n'importe quelle application Windows en tant que service.

Voir Puis-je utiliser un service pour plus d'informations? informations.

La chose la plus importante abordée est de savoir comment déterminer de manière fiable si nous fonctionnons de manière interactive ou via un service.

vous devez mettre en œuvre un processus distinct pouvant communiquer avec votre service. Bien qu’il soit possible sur un système XP ou antérieur d’avoir un service affichant une interface utilisateur, ce n’est plus possible sous Vista et les versions ultérieures.

Une autre possibilité consiste à NE PAS utiliser un service, mais à utiliser une application située dans la barre des tâches (pensez à la fonction Drag-to-Disc de Roxio, et très probablement votre logiciel antivirus y habite), qui possède une icône en bas de l'horloge, qui lance un menu, lorsque vous cliquez dessus avec le bouton droit, et une interface utilisateur lorsque vous double-cliquez dessus.

Si votre service est correctement modulé, vous pouvez l'héberger soit dans un exécutable en tant que service, soit avec un exécutable avec interface graphique pour le test. Nous utilisons également cette méthode avec notre service. L'exécutable autonome du service héberge le service dans un environnement productif, mais nous disposons également d'une application console pour héberger le service.

Séparez votre code en différents composants: un composant pour gérer les aspects de service et un pour exécuter la logique métier réelle. Créez et interagissez avec la logique applicative à partir du composant de service. Pour tester (de votre logique métier), vous pouvez créer une application WinForm ou console qui utilise le composant de logique applicative sans le composant de service. Mieux encore, utilisez un cadre de tests unitaires pour l'essentiel de vos tests. De nombreuses méthodes de la composante service seront sans aucun doute testables à l’unité.

Si vous encapsulez votre logique métier dans des classes de service, puis utilisez un modèle d'usine pour créer ces services, vous pouvez utiliser le même ensemble de services pour une application de bureau (usine de postes de travail) et en tant que services Web (hôte dans WCF).

Définition du service:

[ServiceContract]
public interface IYourBusinessService
{
    [OperationContract]
    void DoWork();
}

public class YourBusinessService : IYourBusinessService
{
    public void DoWork()
    {
        //do some business logic here
    }

}

Usine pour WinForm de bureau offrant des services pour faire des affaires:

public class ServiceFactory
{
    public static IYourBusinessService GetService()
    {
        //you can set any addition info here
        //like connection string for db, etc.
        return new YourBusinessService();
    }
}

Vous hébergez cela avec la classe WCF ServiceHost ou dans IIS. Les deux vous permettent de spécifier comment instancier chaque instance du service afin que vous puissiez effectuer une initialisation telle que des chaînes de connexion, etc.

Vous pouvez créer le service pour appeler un autre exécutable avec un argument de ligne de commande afin de l'exécuter sans le formulaire. Lorsque cet exécutable est appelé sans l'argument de ligne de commande, il affiche le formulaire et agit normalement.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top