StructureMap 在 Web API 帮助页面 ModelDescriptionLink.cshtml 中导致 Stack Empty 异常

StructureMap causes Stack Empty exception in Web API Help Pages ModelDescriptionLink.cshtml

我有一个 Web API 项目,它使用 StructureMap 作为其 DI。一段时间以来它一直运行良好,但我在使用 Web API 帮助页面 (Microsoft.AspNet.WebApi.HelpPage) 时遇到一些问题,其中由于空堆栈而抛出 InvalidOperationExceptions。

我使用帮助页面创建了一个新的 Web API 项目,在我添加 StructureMap.WebApi2 包之前它工作正常,而前面提到的异常在此处抛出,在 ModelDescriptionLink.cshtml

else if (modelDescription is CollectionModelDescription)
{
    var collectionDescription = modelDescription as CollectionModelDescription;
    var elementDescription = collectionDescription.ElementDescription;
    @:Collection of @Html.DisplayFor(m => elementDescription.ModelType, "ModelDescriptionLink", new { modelDescription = elementDescription })
}
else
{
    @Html.DisplayFor(m => modelDescription)
}

当它试图显示 link 到他这个模型的资源描述时,它在 @:Collection of @Html.DisplayFor(m => elementDescription.ModelType, "ModelDescriptionLink", new { modelDescription = elementDescription }) 被抛出。

这是一个精简的路线,但仍然会导致异常:

[Route("Test")]
public IHttpActionResult Post([FromBody] IEnumerable<MySimpleModel> models)
{
    return null;
}

试图在 http://localhost:21966/Help/Api/POST-Test 访问此路由的文档导致异常:

我只能找到一个 example of someone having the same problem,他们的解决方案是从 StructureMap 切换到 Ninject 或通过空检查避免异常。

这是堆栈跟踪的顶部:

[InvalidOperationException: Stack empty.]
System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource) +52
System.Collections.Generic.Stack`1.Peek() +6693321
System.Web.WebPages.WebPageBase.get_Output() +51
System.Web.WebPages.WebPageBase.GetOutputWriter() +35
System.Web.WebPages.WebPageExecutingBase.BeginContext(String virtualPath, Int32 startPosition, Int32 length, Boolean isLiteral) +50
ASP._Page_Areas_HelpPage_Views_Help_DisplayTemplates_ModelDescriptionLink_cshtml.Execute() in c:...ModelDescriptionLink.cshtml:28
System.Web.WebPages.WebPageBase.ExecutePageHierarchy() +271
System.Web.Mvc.WebViewPage.ExecutePageHierarchy() +122
System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) +145
System.Web.Mvc.RazorView.RenderView(ViewContext viewContext, TextWriter writer, Object instance) +695
System.Web.Mvc.BuildManagerCompiledView.Render(ViewContext viewContext, TextWriter writer) +382
System.Web.Mvc.Html.ActionCacheViewItem.Execute(HtmlHelper html, ViewDataDictionary viewData) +278

通过在这个地方捕获异常,它稍后会在 HelpPageApiModel.cshtml 几乎相同的行上弹出:@Html.DisplayFor(m => m.ResourceDescription.ModelType, "ModelDescriptionLink", new { modelDescription = Model.ResourceDescription })。这是该堆栈跟踪的顶部:

[InvalidOperationException: Stack empty.]
System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource) +52
System.Collections.Generic.Stack`1.Pop() +6667365
System.Web.WebPages.WebPageBase.PopContext() +66
System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) +154

仍然不确定为什么 StructureMap.WebApi2 会出现这个问题,但我的解决方案基本上是在没有该库的情况下重写依赖项解析。 This blog post 对于不使用 IDependencyScope

实现它的方法非常有帮助

编辑:我被要求展示一个例子。这基本上是该博客上发布的解决方案,但我希望它能有所帮助。

为结构图添加自定义控制器激活器:

public class StructureMapWebApiControllerActivator : IHttpControllerActivator
{
    private readonly IContainer _container;

    public StructureMapWebApiControllerActivator(IContainer container)
    {
        _container = container;
    }

    public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
    {
        var nested = _container.GetNestedContainer();
        var instance = nested.GetInstance(controllerType) as IHttpController;
        request.RegisterForDispose(nested);
        return instance;
    }
}

这是我的容器初始化的样子:

public class IoC
{
    public static IContainer InitializeContainer()
    {
        IContainer container = new Container
        (
            c => c.Scan
            (
                scan =>
                {
                    scan.Assembly("assembly1");
                    scan.Assembly("assembly2");
                    scan.LookForRegistries();
                    scan.WithDefaultConventions();
                }
            )
        );
        return container;
    }
}

然后无论您在何处设置 HttpConfiguration(例如调用 MapHttpAttributeRoutes()),将控制器激活器替换为您的新结构图之一:

HttpConfig.MapHttpAttributeRoutes();
var container = IoC.InitializeContainer();
HttpConfig.Services.Replace(typeof(IHttpControllerActivator), new StructureMapWebApiControllerActivator(container));

在我的例子中,我使用的是 OWIN,所以我没有使用 GlobalConfiguration.Configuration HttpConfiguration,但想法是一样的。我相信通常有一个 WebApiConfig class 有一个通过 HttpConfiguration 的方法,所以你可以在那里进行替换。

再次编辑 HelpController.cs 的更改部分:

最初 StructureMap 试图使用第二个构造函数(其参数为 HttpConfiguration),导致它无法创建 HelpController 的实例。我通过将第二个构造函数标记为受保护来修复它。

我还在第一个构造函数的调用中将引用从 GlobalConfiguration 的 HttpConfiguration 更改为我自己的引用(从我的 Startup class),因为我使用的是 Owin。

public HelpController(): this(Startup.HttpConfig)
{
    logger.Trace("Help controller created");
}

protected HelpController(HttpConfiguration config)
{
    Configuration = config;
}