自动启动/预热功能在IIS 7.5 / WCF服务中无法工作

16
为了测试从头开始的IIS/WCF实现时的很多问题,我按照这里很好的演示步骤构建了HelloWorld服务和客户端。我添加了net.tcp的端点,并且在其自己的名为HW的ApplicationPool下,服务已经成功地端到端地工作在 IIS 7.5(Windows 7上)。
我正在尝试让宣布的AutoStart和Preload(或“预热缓存”)功能正常工作。我非常仔细地遵循了这里这里所介绍的指示(相似但最好有第二种意见)。这意味着我:
1)设置应用程序池的 startMode...
<applicationPools> 
     <!-- ... -->
     <add name="HW" managedRuntimeVersion="v4.0" startMode="AlwaysRunning" /> 
</applicationPools>

2) ...启用 serviceAutoStart 并将指针设置为我的 serviceAutoStartProvider

<site name="HW" id="2">
    <application path="/" applicationPool="HW" serviceAutoStartEnabled="true" serviceAutoStartProvider="PreWarmMyCache" />
    <!-- ... -->
</site>

3)...并命名该提供程序,并在其下方完整列出类的GetType().AssemblyQualifiedName

<serviceAutoStartProviders> 
    <add name="PreWarmMyCache" type="MyWCFServices.Preloader, HelloWorldServer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> 
</serviceAutoStartProviders>

using System;

namespace MyWCFServices
{
    public class Preloader : System.Web.Hosting.IProcessHostPreloadClient
    {
        public void Preload(string[] parameters)
        {
            System.IO.StreamWriter sw = new System.IO.StreamWriter(@"C:\temp\PreloadTest.txt");
            sw.WriteLine("Preload executed {0:G}", DateTime.Now);
            sw.Close();
        }
    }
}

遗憾的是,所有这些手动配置以及一对调用都没有起到作用。任务管理器中没有w3wp.exe进程在运行(尽管我启动HelloWorldClient时可以看到它),没有文本文件,最重要的是没有满意的结果。

关于这个功能,无论是在SO还是更广泛的网络上都缺乏讨论,这里提出的少数几个类似的问题都没有得到关注,这引起了一两个警报。也许这并不是必需的,但有没有专家曾经走过这条路愿意发表意见呢?(如果您能建议一个好的托管位置,我很乐意提供整个解决方案。)


编辑:我尝试在Preload方法中将路径重置为相对的App_Data文件夹(另一个SO答案建议这样做),但没有影响。此外,我了解到w3wp.exe进程会在简单地浏览本地主机时启动。该进程消耗令人印象深刻的17MB内存来提供其单个微小的OperationContract,而价格却提供了零的预加载价值。17MB的ColdDeadCache。


事件日志中有没有任何线索?任何抛出的异常都应该在那里显示出来。 - Addys
不,没有任何问题。如果(如所述)服务正常工作,我不确定为什么您会期望出现异常。 - downwitch
你可以检查一下以下几点:
  • 你的站点ID是否为2?
  • 你的站点和应用程序池的名称是否相同?
  • 你是否指定了比示例中更多的属性,只指定示例中的属性会有所不同吗?
- Shiraz Bhaiji
4个回答

4
这是解决您问题的稍微不同的方法:
  1. 使用Windows Server AppFabric进行服务自启动
  2. 使用WCF基础结构来执行自定义启动代码
关于第一点:只要您没有使用MVC的ServiceRoute来注册服务,那么Appfabric AutoStart功能应该可以直接使用。必须在Web.config的serviceActivations部分或使用物理*.svc文件中指定服务。
关于第二点:为了将自定义启动代码注入到WCF管道中,您可以使用这样的属性:
using System;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace WCF.Extensions
{
    /// <summary>
    /// Allows to specify a static activation method to be called one the ServiceHost for this service has been opened.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
    public class ServiceActivatorAttribute : Attribute, IServiceBehavior
    {
        /// <summary>
        /// Initializes a new instance of the ServiceActivatorAttribute class.
        /// </summary>
        public ServiceActivatorAttribute(Type activatorType, string methodToCall)
        {
            if (activatorType == null) throw new ArgumentNullException("activatorType");
            if (String.IsNullOrEmpty(methodToCall)) throw new ArgumentNullException("methodToCall");

            ActivatorType = activatorType;
            MethodToCall = methodToCall;
        }

        /// <summary>
        /// The class containing the activation method.
        /// </summary>
        public Type ActivatorType { get; private set; }

        /// <summary>
        /// The name of the activation method. Must be 'public static void' and with no parameters.
        /// </summary>
        public string MethodToCall { get; private set; }


        private System.Reflection.MethodInfo activationMethod;

        #region IServiceBehavior
        void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            serviceHostBase.Opened += (sender, e) =>
                {
                    this.activationMethod.Invoke(null, null);
                };
        }

        void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            // Validation: can get method
            var method = ActivatorType.GetMethod(name: MethodToCall,
                             bindingAttr: System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public,
                             callConvention: System.Reflection.CallingConventions.Standard,
                             types: Type.EmptyTypes,
                             binder: null,
                             modifiers: null);
            if (method == null)
                throw new ServiceActivationException("The specified activation method does not exist or does not have a valid signature (must be public static).");

            this.activationMethod = method;
        }
        #endregion
    }
}

...可以像这样使用:

public static class ServiceActivation
{
    public static void OnServiceActivated()
    {
        // Your startup code here
    }
}

[ServiceActivator(typeof(ServiceActivation), "OnServiceActivated")]
public class YourService : IYourServiceContract
{

}

这正是我们长期以来在许多服务中使用的方法。使用 WCF 的 ServiceBehavior 来自定义启动代码(而不是依赖于 IIS 基础结构)的额外好处在于它可以在任何托管环境中工作(包括自托管),并且更容易测试。


很有趣,我们已经朝着AppFabric的方向前进了,所以这是一个不错的衔接。喜欢你的代码,即使没有经过测试,也能看出它想要去哪里。赏金归你,但如果我在评论中有更多问题,你必须跟上 ;) - downwitch
@ServiceGuy,我只是想澄清一下,你的两个观点是相互依存的(即使用AppFabric 在WCF基础结构中添加钩子)。换句话说,第二点并不能解决AutoStart IIS托管上下文中的问题。这正确吗? - pamphlet
@小册子:是的,1和2都是必要的,(1)实际上自动启动您的服务,(2)执行自定义的OnStart逻辑。 - mthierba

3

我知道这听起来很荒谬,但我遇到了同样的问题(在更改配置后w3wp.exe没有自动启动),原因是我在编辑applicationHost.config文件时没有以管理员模式运行文本编辑器。这是我的愚蠢错误。

为自己辩护,我使用的是Notepad ++,当它告诉我保存时实际上并没有保存。


2
也许你正在使用64位系统?已知存在一个“特性”,即保存会被重定向到32位文件夹 :-) https://dev59.com/8W025IYBdhLWcg3w96wW#17595896 - Konstantin
1
天啊,我刚刚因为同样的问题浪费了半天时间。给你点赞! :-) - Craig Vermeer
1
@Konstantin……实际上那应该是正确的答案。这太糟糕了。感谢你的提示,救了我的一天。 - Sleeper Smith
这帮助我解决了问题。谢谢Chris。 - PaRsH

1
也许你使用的是64位系统?在Windows中有一个已知的"特性", 保存会被重定向到32位文件夹,因此不会检测到任何更改。
(我已将我的评论转换为回答,因为回答可能更容易找到)

1

我也做了同样的事情。它有效...

在预加载方法中,我复制了一些代码,这些代码来自一个很好的白皮书,可以在这里找到!

预加载方法看起来像...

 public void Preload(string[] parameters) 
 {     
        bool isServceActivated = false; 
        int attempts = 0; 
        while (!isServceActivated && (attempts <10)) 
        {
            Thread.Sleep(1 * 1000);
            try
            {
                string virtualPath = "/Test1/Service1.svc";
                ServiceHostingEnvironment.EnsureServiceAvailable(virtualPath);
                isServceActivated = true;
            }
            catch (Exception exception)
            {
                attempts++;
                //continue on these exceptions, otherwise fail fast 
                if (exception is EndpointNotFoundException ||
                    exception is ServiceActivationException || 
                    exception is ArgumentException) 
                {
                    //log 
                }
                else
                {
                    throw;
                }
            }
        }
   }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接