【问题标题】:Load dll and host it as windows service加载 dll 并将其作为 Windows 服务托管
【发布时间】:2013-04-28 15:35:41
【问题描述】:

我有 3 个 Restful 服务,我想将它们作为 Windows 服务托管。我创建了一个安装程序,它将同时托管所有三种服务。我想让我的安装程序有点可配置。我想向同一个安装程序添加新服务,而不需要它来编辑代码。

这是我当前的安装程序代码。在这里,我引用 dll 并托管它们。这只是任何 Windows 宿主项目的普通代码。

App.config 文件

 <services>
  <service name="Service1">
    <endpoint binding="webHttpBinding" contract="IService1" ehaviorConfiguration="REST"/>
  </service>
  <service name="Service2">
    <endpoint binding="webHttpBinding" contract="IService2" behaviorConfiguration="REST"/>
  </service>
  <service name="Service3">
    <endpoint binding="webHttpBinding" contract="IService3" behaviorConfiguration="REST"/>
  </service>
</services>
<behaviors>
  <serviceBehaviors>
    <behavior>
      <serviceMetadata httpGetEnabled="True"/>
      <serviceDebug includeExceptionDetailInFaults="true"/>
    </behavior>
  </serviceBehaviors>

  <endpointBehaviors>
    <behavior name="REST">
      <webHttp automaticFormatSelectionEnabled="true" defaultOutgoingResponseFormat="Json" helpEnabled="true"/>
    </behavior>
  </endpointBehaviors>
</behaviors>

安装程序中的代码。

public partial class Service : ServiceBase
{
    public ServiceHost Service1Host = null;
    public ServiceHost Service2Host = null;
    public ServiceHost Service3Host = null;

    public Service()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {


        Uri Service1_baseAddress = new Uri("http://localhost:9999/Service1");
        Uri Service2_baseAddress = new Uri("http://localhost:9999/Service2");
        Uri Service3_baseAddress = new Uri("http://localhost:9999/Service3");

        if (Service1Host != null)
        {
            Service1Host.Close();
        }
        Service1Host = new ServiceHost(typeof(Service1), Service1_baseAddress);                        
        Service1Host.Open();

        if (Service2Host != null)
        {
            Service2Host.Close();
        }
        Service2Host = new ServiceHost(typeof(Service2), Service2_baseAddress); 
        Service2Host.Open();

        if (Service3Host != null)
        {
            Service3Host.Close();
        }
        Service3Host = new ServiceHost(typeof(Service3), Service3_baseAddress);
        Service3Host.Open();
    }

    protected override void OnStop()
    {
        if (Service1Host != null)
        {
            Service1Host.Close();
            Service1Host= null;
        }
        if (Service2Host != null)
        {
            Service2Host.Close();
            Service2Host = null;
        }
        if (Service3Host != null)
        {
            Service3Host.Close();
            Service3Host = null;
        }
    }
}

我尝试过的就在这里。我从 app.config 中删除了服务端点配置,并在代码中完成了它。我将所有 dll 放在一个文件夹中,并在代码中加载这些 dll。为了获得服务和接口,我在 app.config 中添加参数,如下所示,这将提供服务名称和接口名称以从加载的程序集中检索。一切正常。但是我有一个小问题,我已经在下面解释了。

新建 App.config 文件

<appSettings>
<add key="Service1.dll" value="Service1"/>
<add key="IService1.dll" value="IService1"/>
<add key="Service2.dll" value="Service2"/>
<add key="IService2.dll" value="IService2"/>
<add key="Service3.dll" value="Service3"/>
<add key="IService3.dll" value="IService3"/>
</appSettings>

新的 Windows 主机代码

public partial class Service : ServiceBase
{
    public ServiceHost ServiceHost = null;

    public WinService()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
    string[] files = Directory.GetFiles(@"C:\Users\Desktop\WindowsHost\dlls", "*.dll");
        for (int i = 0; i < files.Length; i++)
            files[i] = Path.GetFileName(files[i]);

        foreach (var dllName in files)
        {
            string filePath = @"C:\Users\Desktop\WindowsHost\dlls\" + dllName;
            Assembly assembly = Assembly.LoadFrom(filePath);

            string serviceName = ConfigurationManager.AppSettings[dllName];
            string interfaceName = ConfigurationManager.AppSettings["I" + dllName];

            Type serviceToHost = assembly.GetType(serviceName);
            var instance = Activator.CreateInstance(serviceToHost);

            Type contract = service.GetInterface(interfaceName, true);

            string address = dllName.Remove(dllName.LastIndexOf("."));

            Uri baseAddress = new Uri("http://localhost:9999/" + address);

            if (ServiceHost != null)
            {
                ServiceHost.Close();
            }
            ServiceHost = new ServiceHost(instance, baseAddress);
            ServiceEndpoint sEP = ServiceHost.AddServiceEndpoint(contract, new WebHttpBinding(), "");
            WebHttpBehavior webHttpBeh = sEP.Behaviors.Find<WebHttpBehavior>();

            if (webHttpBeh != null)
            {
                webHttpBeh.AutomaticFormatSelectionEnabled = true;
                webHttpBeh.DefaultOutgoingResponseFormat = WebMessageFormat.Json;
                webHttpBeh.HelpEnabled = true;
            }
            else
            {
                WebHttpBehavior newWebHttpBeh = new WebHttpBehavior();
                newWebHttpBeh.AutomaticFormatSelectionEnabled = true;
                newWebHttpBeh.DefaultOutgoingResponseFormat = WebMessageFormat.Json;
                newWebHttpBeh.HelpEnabled = true;
                sEP.Behaviors.Add(newWebHttpBeh);
            }

            ServiceHost.Open();
        }
    }

    protected override void OnStop()
    {
        if (ServiceHost != null)
        {
            ServiceHost.Close();
            ServiceHost = null;
        }
    }
}

我在这里做的是创建加载的 dll 的实例并将其作为 Windows 服务托管。

ServiceHost = new ServiceHost(instance, baseAddress);

如果您的服务行为将 InstanceContextMode 设置为单一,这将正常工作。否则它会给出错误。

错误: “服务无法启动。System.InvalidOperationException:为了使用接受服务实例的 ServiceHost 构造函数之一,必须将服务的 InstanceContextMode 设置为 InstanceContextMode.Single。这可以通过 ServiceBehaviorAttribute 进行配置。否则,请考虑使用带有 Type 参数的 ServiceHost 构造函数。”

我试着像这样修改代码

ServiceHost = new ServiceHost(typeof(serviceToHost), baseAddress);

但它不起作用。请帮忙。有没有其他方法可以实现这一点。

谢谢

【问题讨论】:

  • 这是一种有趣的方法,但为什么不简单地为每个 Web 服务做一个 Windows 服务呢?如果您的一项服务发生故障并且未捕获和处理异常怎么办 - 您可能会关闭 Windows 服务和所有托管的 Web 服务。
  • 谢谢蒂姆。我理解风险。但这是要求。这段代码工作正常。唯一的问题是我没有将所有服务的 InstanceContextMode 设置为 single。
  • 如果Type serviceToHost = assembly.GetType(serviceName) 为您提供实现服务的类(即实现定义合同的接口的类),我会尝试ServiceHost = new ServiceHost(serviceToHost, baseAddress); - 您已经有了类型,无需调用 typeof再次,因为构造函数需要一个 Type 对象。
  • 蒂姆,你就是那个男人。它工作正常。不过,我不得不对代码进行一些更改。必须创建一个 ServiceHost 数组。并使用它来托管所有服务。让我们看看这段代码能持续多久。 :) 再次感谢
  • 我很高兴你得到它的工作 - 你可以考虑发布你的最终解决方案作为一个答案(你可以在一段时间后自动接受它),以供可能处于相同情况的未来开发人员使用。此外,由于您有一个单点故障(一切都在一个 Windows 服务中),我建议您考虑在您的服务中实现 IErrorHandler - 这样您就可以捕获任何原本无法处理的错误并降低风险把一切都搞砸了。

标签: c# asp.net wcf rest .net-4.0


【解决方案1】:

这是完整的工作代码。我已经根据需要添加了端点和服务行为。我猜不是每个人都需要相同的配置。我取 dll 名称并将其附加到基地址的末尾。因此,它根据 dll 名称为每个服务创建新地址。 app.config 中定义的参数应与 dll 的名称完全匹配。例如我使用如下三个 dll,那么我的 app.config 将如下面的代码所示。

  1. Service1.dll
  2. Service2.dll
  3. Service3.dll

App.config

<appSettings>
 <add key="baseAddress" value="http://localhost:9999/"/>
 <add key="Service1.dll" value="namespace.Service1"/>
 <add key="Service1.dll" value="namespace.IService1"/>
 <add key="Service2.dll" value="namespace.Service2"/>
 <add key="Service2.dll" value="namespace.IService2"/>
 <add key="Service3.dll" value="namespace.Service3"/>
 <add key="Service3.dll" value="namespace.IService3"/>
</appSettings>

Windows 安装程序代码

public ServiceHost[] serviceHost = null;

    public MyService()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {            
        try
        {
            //Get path for the executing assemblly
            string exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            //Path to the dlls to be hosted
            string filePath = exePath + "\\DLLsToHost\\";
            //Retrieve only dll files from the folder
            string[] files = Directory.GetFiles(@filePath, "*.dll");

            //get the dll file names
            for (int i = 0; i < files.Length; i++)
                files[i] = Path.GetFileName(files[i]);

            //create an array of ServiceHost type
            serviceHost = new ServiceHost[files.Length];
            //get the base address for the services from config file
            string address = ConfigurationManager.AppSettings["baseAddress"];

            int j = 0;

            foreach (var dllName in files)
            {
                string dllPath = filePath + dllName;
                //Load the dll
                Assembly assembly = Assembly.LoadFrom(@dllPath);
                //Get the class name implementing the service
                string serviceName = ConfigurationManager.AppSettings[dllName];
                //get the interface name implemented by the class
                string interfaceName = ConfigurationManager.AppSettings["I" + dllName];

                if (serviceName == null || interfaceName == null)
                {
                    //Log the error
                }
                else
                {
                    //Get the class implementing the service
                    Type service = assembly.GetType(serviceName);

                    if (service != null)
                    {
                        //Get the interface implemented by the class
                        Type contract = service.GetInterface(interfaceName, true);

                        if (contract != null)
                        {
                            //Create a base address for the service
                            Uri baseAddress = new Uri(address + dllName.Remove(dllName.LastIndexOf(".")));

                            if (serviceHost[j] != null)
                            {
                                serviceHost[j].Close();
                            }

                            serviceHost[j] = new CustomServiceHost(service, baseAddress);
                            //add the service endpoint and contract
                            ServiceEndpoint sEP = serviceHost[j].AddServiceEndpoint(contract, new WebHttpBinding(), "");
                            WebHttpBehavior webHttpBeh = sEP.Behaviors.Find<WebHttpBehavior>();

                            //Set the service and endpoint behaviours
                            if (webHttpBeh != null)
                            {
                                webHttpBeh.AutomaticFormatSelectionEnabled = true;
                                webHttpBeh.DefaultOutgoingResponseFormat = WebMessageFormat.Json;
                                webHttpBeh.HelpEnabled = true;
                                sEP.Behaviors.Add(new BehaviorAttribute());     //Add CORS support
                            }
                            else
                            {
                                WebHttpBehavior newWebHttpBeh = new WebHttpBehavior();
                                newWebHttpBeh.AutomaticFormatSelectionEnabled = true;
                                newWebHttpBeh.DefaultOutgoingResponseFormat = WebMessageFormat.Json;
                                newWebHttpBeh.HelpEnabled = true;
                                sEP.Behaviors.Add(newWebHttpBeh);
                                sEP.Behaviors.Add(new BehaviorAttribute());     //Add CORS support
                            }

                            serviceHost[j].Open();
                        }
                        else
                        {
                            //Log the error
                        }
                    }
                    else
                    {
                        //Log the error
                    }
                }
                j++;
            }
        }
        catch(Exception ex)
        {
            //Throw the exception OR Log it
        }
    }

    protected override void OnStop()
    {
        try
        {
            for (int k = 0; k <= serviceHost.Length - 1; k++)
            {
                if (serviceHost[k] != null)
                {
                    serviceHost[k].Close();
                    serviceHost[k] = null;
                }
            }
        }
        catch (Exception ex)
        {
            //Log
        }
    }
}

欢迎任何更新代码的建议。谢谢。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-25
    相关资源
    最近更新 更多