studio - proyectos en c# modo consola ejemplos
¿Cómo escribir el servicio c#que también puedo ejecutar como un programa de winforms? (11)
Tengo un servicio de Windows escrito en C # que actúa como un proxy para un conjunto de dispositivos de red en la base de datos back-end. Para probar y también para agregar una capa de simulación para probar el back-end, me gustaría tener una GUI para que el operador de prueba pueda ejecutar la simulación. También para una versión rayada para enviar como una demostración. La GUI y el servicio no tienen que ejecutarse al mismo tiempo. ¿Cuál es la mejor manera de lograr esta operación de duelo?
Editar: Aquí está mi solución combinando cosas de esta pregunta , ¿Estoy funcionando como un servicio e instalo un servicio de Windows .NET sin InstallUtil.exe usando este excelente código de Marc Gravell
Utiliza la siguiente línea para probar si ejecutar la interfaz gráfica de usuario o ejecutar como servicio.
if (arg_gui || Environment.UserInteractive || Debugger.IsAttached)
Aquí está el código.
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;
}
}
}
Básicamente tienes dos opciones. Exhiba una API en el servicio que luego puede llamar desde la aplicación UI O habilite el servicio para que se ejecute como una aplicación winforms o un servicio.
La primera opción es bastante fácil: use la comunicación remota o WCF para exponer la API.
La segunda opción se puede lograr moviendo las "agallas" de su aplicación a una clase separada y luego crear un contenedor de servicios y un contenedor de win-forms que ambos llamen a su clase de "agallas".
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);
}
}
Cree una nueva aplicación de winforms que haga referencia al ensamblaje de su servicio.
Otra posibilidad es NO usar un servicio, sino usar una aplicación que reside en la barra de tareas (pensemos en Roxio Drag-to-Disc, y muy probablemente su software antivirus viva allí) que tiene un ícono desactivado por el reloj, que inicia un menú, cuando se hace clic con el botón derecho, y una IU cuando se hace doble clic.
Separe su código en diferentes componentes: un componente para administrar los aspectos del servicio y otro para realizar la lógica comercial real. Crea e interactúa con la lógica de negocios desde el componente de servicio. Para realizar pruebas (de su lógica comercial), puede crear una aplicación WinForm o de consola que use el componente de lógica de negocios sin el componente de servicio. Mejor aún, use un marco de prueba unitaria para la mayor parte de sus pruebas. Muchos de los métodos en el componente de servicio serán, sin duda, también comprobables por unidad.
Si encapsula su lógica comercial en clases de servicio y luego utiliza un patrón de fábrica para crear esos servicios, puede usar el mismo conjunto de servicios para una aplicación de escritorio (fábrica de escritorio) y como servicios web (host en WCF).
Definición del servicio:
[ServiceContract]
public interface IYourBusinessService
{
[OperationContract]
void DoWork();
}
public class YourBusinessService : IYourBusinessService
{
public void DoWork()
{
//do some business logic here
}
}
Fábrica para WinForms de escritorio para obtener servicios para hacer negocios:
public class ServiceFactory
{
public static IYourBusinessService GetService()
{
//you can set any addition info here
//like connection string for db, etc.
return new YourBusinessService();
}
}
Usted aloja esto ya sea con la clase WCF ServiceHost o en IIS. Ambos le permiten especificar cómo crear una instancia de cada instancia del servicio para que pueda realizar la inicialización, como cadenas de conexión, etc.
Si su servicio se modula correctamente, puede alojar el servicio en un ejecutable como servicio o con un archivo ejecutable con GUI para la prueba. También usamos este método con nuestro servicio, el ejecutable de servicio independiente aloja el servicio en un entorno productivo, pero también tenemos una aplicación de consola para alojar el servicio.
Si usas el siguiente código:
[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
}
Entonces su EXE se ejecutará como un servicio (si lo lanzó el SCM) o como una GUI (si se inició con cualquier otro proceso).
Esencialmente, todo lo que he hecho aquí se utiliza Reflector para descubrir qué es lo que hace el ServiceBase.Run
y duplicarlo aquí (se requiere reflexión porque llama a métodos privados). La razón para no llamar a ServiceBase.Run
directamente es que aparece un cuadro de mensaje para indicarle al usuario que el servicio no se puede iniciar (si no lo inició SCM) y no devuelve nada para indicarle al código que el servicio no puede ser empezado.
Debido a que esto utiliza la reflexión para llamar a métodos de marcos privados, es posible que no funcione correctamente en futuras revisiones del marco. Código de advertencia
También hay FireDaemon . Esto le permite ejecutar cualquier aplicación de Windows como un servicio.
debe implementar un proceso separado que pueda comunicarse con su servicio. Si bien es posible que XP y sistemas anteriores tengan un servicio que muestre una IU, eso ya no es posible en Vista y más adelante.
Puede crear el servicio para llamar a otro ejecutable con un argumento de línea de comando para que se ejecute sin el formulario. Cuando se llama a ese exe sin el argumento de la línea de comando, muestra la forma y actúa de forma normal.
Consulte ¿Estoy funcionando como servicio para obtener más información útil?
Lo más importante que se trata es cómo determinar de manera confiable si estamos funcionando de manera interactiva o a través de un servicio.