Domanda

Ho un servizio Windows scritto in C # che funge da proxy per un gruppo di dispositivi di rete nel database back-end. Per i test e anche per aggiungere un livello di simulazione per testare il back-end, vorrei avere una GUI per consentire all'operatore di test di eseguire la simulazione. Anche per una versione ridotta a strisce da inviare come demo. La GUI e il servizio non devono essere eseguiti contemporaneamente. Qual è il modo migliore per realizzare questa operazione di duello?

Modifica: Ecco la mia soluzione che combina cose da questa domanda , Sono in esecuzione come servizio e Installa un servizio Windows .NET senza InstallUtil.exe utilizzando questo eccellente codice di Marc Gravell

Utilizza la seguente riga per verificare se eseguire la GUI o eseguire come servizio.

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

Ecco il codice.


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;
      }
   }

}
È stato utile?

Soluzione

Fondamentalmente hai due scelte. Esporre un'API sul servizio che è quindi possibile chiamare dall'app UI O abilitare l'esecuzione del servizio come app winforms o come servizio.

La prima opzione è piuttosto semplice: utilizzare il telecomando o WCF per esporre l'API.

La seconda opzione può essere ottenuta spostando " budella " della tua app in una classe separata, quindi crea un wrapper di servizio e un wrapper win-form che chiamino entrambi nel tuo "quotazione". 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);
    }
}

Altri suggerimenti

Crea una nuova app winforms i riferimenti all'assembly del tuo servizio.

Se si utilizza il codice seguente:

[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
}

Quindi il tuo EXE verrà eseguito come servizio (se avviato da SCM) o come GUI (se avviato da qualsiasi altro processo).

In sostanza, tutto ciò che ho fatto qui è usato Reflector per capire che cosa la carne di ServiceBase.Run lo fa e duplicalo qui (è necessaria la riflessione, perché chiama metodi privati). Il motivo per non chiamare direttamente ServiceBase.Run è che si apre una finestra di messaggio per dire all'utente che il servizio non può essere avviato (se non avviato da SCM) e non restituisce nulla per indicare al codice che il servizio non può essere avviato.

Poiché utilizza la riflessione per chiamare metodi del framework privato, potrebbe non funzionare correttamente nelle future revisioni del framework. Caveat codor.

C'è anche FireDaemon . Ciò consente di eseguire qualsiasi applicazione Windows come servizio.

Vedi Sono in esecuzione come servizio per ulteriori utili informazioni.

La cosa più importante trattata è come determinare in modo affidabile se stiamo funzionando in modo interattivo o tramite un servizio.

devi implementare un processo separato in grado di comunicare con il tuo servizio. Sebbene sia possibile su XP e sistemi precedenti avere un servizio che mostra un'interfaccia utente, ciò non è più possibile su Vista e versioni successive.

Un'altra possibilità è quella di NON utilizzare un servizio, ma di utilizzare un'applicazione che risiede nella barra delle applicazioni (pensa a Roxio Drag-to-Disc, e molto probabilmente il tuo software antivirus risiede laggiù) che ha un'icona giù l'orologio, che avvia un menu, quando viene fatto clic con il pulsante destro del mouse, e un'interfaccia utente quando si fa doppio clic.

Se il servizio è modulato correttamente, è possibile ospitare il servizio in un eseguibile come servizio o con un eseguibile con interfaccia grafica per il test. Usiamo questo metodo anche con il nostro servizio, l'eseguibile di servizio autonomo ospita il servizio in un ambiente produttivo, ma abbiamo anche un'app console per l'hosting del servizio.

Separare il codice in diversi componenti: un componente per gestire gli aspetti del servizio e uno per eseguire la logica aziendale effettiva. Crea e interagisci con la logica aziendale dal componente del servizio. Per i test (della propria logica aziendale) è possibile creare un'applicazione WinForm o console che utilizza il componente della logica aziendale senza il componente del servizio. Meglio ancora, utilizzare un framework di test unitari per gran parte dei test. Molti dei metodi nel componente di servizio saranno indubbiamente testabili anche in unità.

Se si incapsula la propria logica aziendale nelle classi di servizio e quindi si utilizza un modello di fabbrica per creare tali servizi, è possibile utilizzare lo stesso set di servizi per un'applicazione desktop (desktop factory) e come servizi Web (host in WCF).

Definizione del servizio:

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

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

}

Factory per WinForms desktop per accedere ai servizi per fare affari:

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

Lo ospiti con la classe ServiceHost WCF o in IIS. Entrambi consentono di specificare come creare un'istanza di ogni istanza del servizio in modo da poter eseguire l'inizializzazione come stringhe di connessione, ecc.

È possibile creare il servizio per chiamare un altro eseguibile con un argomento della riga di comando in modo che venga eseguito senza il modulo. Quando tale exe viene chiamato senza l'argomento della riga di comando, mostra il modulo e agisce normalmente.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top