孤立的 RazorEngine 无法将模型传递给不同的 AppDomain

Isolated RazorEngine failing to pass model to different AppDomain

当我在没有 EditHistory 成员的情况下呈现我的模板时,这有效。但是,当我在我的应用程序中添加该附加成员时,出现异常 Could not load file or assembly 'Models, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified. 模型是包含 ContentModel、EditHistory 和 UserDetail 的项目。

public class ContentModel
{
    public string Html { get; set; }
    public string Title { get; set; }
    public EditHistory History { get; set; }
}

public class EditHistory
{
    public IReadOnlyCollection<UserDetail> Authors { get; set; }
}

public class UserDetail
{
    public string Name { get; set; }
    public string EmailAddress { get; set; }
}

我将 ContentModel 包装在 RazorDynamicObject 中: Razor.Run("default.cshtml", typeof(ContentModel), RazorDynamicObject.Create(cm));

如上所述,它在 EditHistory 不存在的情况下工作,但在存在时失败。

沙盒是根据 https://antaris.github.io/RazorEngine/Isolation.html

中的做法逐字设置的

如何让它与复杂的自定义类型一起工作?

运行 ASP.NET.

编辑 我已经创建了我面临的问题的最小复制。它位于 https://github.com/knightmeister/RazorEngineIssue。如果包恢复失败,手动 install-package razorengine.

首先;我一直无法获得您的 GitHub-代码 运行ning。以下是我自己复现的代码

我认为您遇到了无法加载文件或程序集异常,因为当您设置沙箱 AppDomain 时,您正在设置:

adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;

这在 ASP.NET 中不起作用,因为程序集位于 bin 子文件夹中。要解决此问题,只需改为执行此操作:

adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase 
                          + "\bin";

但是,ASP.NET 默认为 shadow copy assemblies。因此,仅进行此更改可能会导致另一个异常:

ArgumentException: Object type cannot be converted to target type.

那是因为在默认应用程序域和沙箱中加载的程序集之间存在混淆。默认应用程序域中的那些位于临时卷影副本位置,沙箱中的那些位于 Web 应用程序根目录的 bin 文件夹中。

解决此问题的最简单方法是通过在 Web.config 中的 <system.web> 下添加以下行来禁用卷影复制:

<hostingEnvironment shadowCopyBinAssemblies="false"/>

此外;我认为跳过使用 RazorDynamicObject 并用 [Serializable] 标记你的模型会更好也更容易。事实上,我从来没有 RazorDynamicObject 正常工作。

这个答案的其余部分总结了我为得出这个结论所做的工作


我认为这是由于 RazorEngine 中的错误或限制所致。 (我不太确定了,很可能是卷影复制和RazorDynamicObject不能一起工作)

我花了几个小时试图弄清楚如何让它工作,但我总是以从 RazorEngine 抛出安全异常而告终。

但是,有一个可能的解决方法:抛弃 RazorDynamicObject 并将您的模型 类 标记为可序列化。

[Serializable]
public class ContentModel
{
    public string Html { get; set; }
    public string Title { get; set; }
    public EditHistory History { get; set; }
}

[Serializable]
public class EditHistory
{
    public IReadOnlyCollection<UserDetail> Authors { get; set; }
}

[Serializable]
public class UserDetail
{
    public string Name { get; set; }
    public string EmailAddress { get; set; }
}

然后做:

Razor.Run("default.cshtml", typeof(ContentModel), cm); // no RazorDynamicObject

我无法得到你的重现代码 运行ning,所以我根据你的代码创建了我自己的重现代码:

  1. 创建新的控制台应用程序(Visual Studio)

  2. 在包管理器控制台中,运行:install-package razorengine

  3. 从您的复制中复制代码:

  4. [Serializable]标记模型。

  5. 删除RazorDynamicObject

  6. 为了确保我们真的可以从作者列表中呈现用户详细信息,请将测试模板更改为:

    string template = "@Model.History.Authors[0].EmailAddress";
    
  7. 此外,要使该模板起作用,请将 EditHistory 中的 AuthorsIReadOnlyCollection<> 更改为 IReadOnlyList<>

我用结果代码创建了一个 GIST:
https://gist.github.com/mwikstrom/983c8f61eb10ff1e915a

这对我有用。它会按原样打印 hello@world.com


ASP.NET 默认情况下将对程序集进行影子复制,这会导致沙盒出现其他问题。

要在 ASP.NET 下运行,您必须进行以下更改:

  1. 通过在 Web.config 文件的 <system.web> 下添加以下内容来禁用 ASP.NET 卷影复制:

    <hostingEnvironment shadowCopyBinAssemblies="false"/>
    
  2. \bin 附加到沙箱的应用程序基本路径。所以在 createRazorSandbox(...) 中做:

    adSetup.ApplicationBase = 
        AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "\bin";
    

我已经测试过了,效果很好。我的测试项目很简单:

  • 一个空的 ASP.NET Web 应用程序(使用 Visual Studio 创建),具有 install-package razorengine

  • <hostingEnvironment shadowCopyBinAssemblies="false"/> 在 Web.config.

  • 以下Global.asax.cs:

https://gist.github.com/mwikstrom/ea2b90fd0d306ba3498c


这里列出了其他替代方法(除了禁用卷影复制):

https://github.com/Antaris/RazorEngine/issues/224

我大多不使用复杂类型,但一般规则通常是只有原始数据类型可以正常传输(我自己的规则,因为否则我经常会丢失值)。然而,在查看一些旧源代码时,我注意到我确实使用了许多复杂类型,但我将它们填充到控制器中(例如,在 Public ActionResult Index() 中)。经过一些阅读后,我认为如果你使用类似于此的东西可能会起作用(未经测试,MSDN source, 2nd source):

[MetadataType(typeof(EditHistory))]
public partial class ContentModel
{
   public string Html { get; set; }
   public string Title { get; set; }
   public EditHistory History { get; set; }
}

[MetadataType(typeof(UserDetail))]
public partial class EditHistory
{
   public IReadOnlyCollection<UserDetail> Authors { get; set; }
}

public class UserDetail
{
   public string Name { get; set; }
   public string EmailAddress { get; set; }
}