Как написать службу c #, которую я также могу запускать как программу winforms?
-
05-07-2019 - |
Вопрос
У меня есть служба Windows, написанная на C #, которая действует как прокси для группы сетевых устройств к внутренней базе данных.Для тестирования, а также для добавления слоя моделирования для тестирования серверной части я хотел бы иметь графический интерфейс для оператора тестирования, чтобы иметь возможность запускать моделирование.Также для полосатой версии для отправки в качестве демо.Графический интерфейс и служба не обязательно должны запускаться одновременно.Каков наилучший способ добиться этой операции дуэли?
Редактировать:Вот мое решение, вычесывающее материал из этот вопрос , Работаю ли я как Сервис и Установите службу .NET Windows без InstallUtil.exe используя этот превосходный код Автор: Марк Гравелл
Он использует следующую строку, чтобы проверить, следует ли запускать графический интерфейс или запускаться как сервис.
if (arg_gui || Environment.UserInteractive || Debugger.IsAttached)
Вот код.
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;
}
}
}
Решение
По сути, у вас есть два варианта.Либо предоставьте API в службе, который затем вы можете вызвать из приложения пользовательского интерфейса, либо включите службу для запуска либо как приложение winforms, либо как сервис.
Первый вариант довольно прост - используйте remoting или WCF для предоставления доступа к API.
Второй вариант может быть достигнут путем перемещения "внутренностей" вашего приложения в отдельный класс, затем создайте оболочку service и оболочку win-forms, которые оба вызывают ваш класс "guts".
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);
}
}
Другие советы
Создайте новое приложение winforms, которое ссылается на сборку вашего сервиса.
Если вы используете приведенный ниже код:
[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
}
Тогда ваш EXE-файл будет запускаться либо как сервис (если он запущен SCM), либо как графический интерфейс пользователя (если он запущен любым другим процессом).
По сути, все, что я здесь сделал, используется Отражатель чтобы выяснить, из чего состоит мясо ServiceBase.Run
делает, и продублируйте его здесь (требуется отражение, потому что оно вызывает частные методы).Причина, по которой вы не звоните ServiceBase.Run
непосредственно заключается в том, что появляется окно сообщения, чтобы сообщить пользователь что служба не может быть запущена (если она не запущена SCM) и не возвращает ничего, что могло бы сообщить код что служба не может быть запущена.
Поскольку при этом используется отражение для вызова частных методов фреймворка, оно может некорректно функционировать в будущих версиях фреймворка. Будьте осторожны с кодом.
Существует также Огненный Демон.Это позволяет вам запускать любое приложение Windows как службу.
Видишь Работаю ли я как сервис для получения дополнительной полезной информации.
Самое важное, о чем идет речь, - это как надежно определить, работаем ли мы в интерактивном режиме или через сервис.
вы должны внедрить отдельный процесс, который может взаимодействовать с вашим сервисом.Хотя в XP и более ранних системах возможно иметь службу, отображающую пользовательский интерфейс, в Vista и более поздних версиях это больше невозможно.
Другая возможность заключается не в использовании сервиса, а в использовании приложения, которое находится на панели задач (например, Roxio Drag-to-Disc, и, скорее всего, там находится ваше антивирусное программное обеспечение), у которого есть значок внизу рядом с часами, который запускает меню при щелчке правой кнопкой мыши и пользовательский интерфейс при двойном щелчке.
Если ваш сервис модулирован должным образом, вы могли бы разместить сервис либо в исполняемом файле как сервис, либо в исполняемом файле с графическим интерфейсом для тестирования.Мы также используем этот метод с нашим сервисом, автономный исполняемый файл сервиса размещает сервис в продуктивной среде, но у нас также есть консольное приложение для размещения сервиса.
Разделите свой код на разные компоненты:один компонент для управления аспектами обслуживания и один для выполнения фактической бизнес-логики.Создавайте бизнес-логику из компонента service и взаимодействуйте с ней.Для тестирования (вашей бизнес-логики) вы можете создать WinForm или консольное приложение, которое использует компонент бизнес-логики без компонента service.А еще лучше, используйте фреймворк модульного тестирования для основной части вашего тестирования.Многие методы в сервисном компоненте, несомненно, также будут поддаваться модульному тестированию.
Если вы инкапсулируете свою бизнес-логику в классы сервисов, а затем используете шаблон factory для создания этих сервисов, вы можете использовать один и тот же набор сервисов для настольного приложения (desktop factory) и в качестве веб-сервисов (host в WCF).
Определение сервиса:
[ServiceContract]
public interface IYourBusinessService
{
[OperationContract]
void DoWork();
}
public class YourBusinessService : IYourBusinessService
{
public void DoWork()
{
//do some business logic here
}
}
Фабрика настольных WinForms для получения услуг at для ведения бизнеса:
public class ServiceFactory
{
public static IYourBusinessService GetService()
{
//you can set any addition info here
//like connection string for db, etc.
return new YourBusinessService();
}
}
Вы размещаете это либо с помощью класса WCF ServiceHost, либо в IIS.Оба позволяют вам указать, как создавать каждый экземпляр сервиса, чтобы вы могли выполнять инициализацию, например, строки подключения и т.д.
Вы можете создать службу для вызова другого исполняемого файла с аргументом командной строки, чтобы она запускалась без формы.Когда этот exe-файл вызывается без аргумента командной строки, он показывает форму и действует как обычно.