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

15

如果我在没有EditHistory成员的情况下呈现我的模板,它可以运行。然而,当我添加那个在我的应用程序中的额外成员时,我会得到一个异常无法加载文件或程序集“Models,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = null”或其某个依赖项。系统找不到指定的文件。 Models是包含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; }
}

我正在使用RazorDynamicObject将ContentModel包装起来,代码如下:

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


2
如果你将public EditHistory History {get;set;}替换为public IReadOnlyCollection<UserDetail> History {get;set;},或者用简单类型public IReadOnlyCollection<string> History {get;set;}进行相同的测试,会发生什么情况?只读集合有可能是问题吗?如果使用IListIEnumerable会怎样呢?结果相同吗? - Tommy
2
你有没有可能制作一个简单的VS项目来重现这个问题,并将其发布在某个地方(例如GitHub)? - C. Augusto Proiete
2
@CaioProiete - 好主意。我有一种感觉,不会有很多人熟悉这个特定的模块。我不熟悉,但是很想追踪到到底是什么导致了失败(嵌套复杂类型、只读集合等)。 - Tommy
大家好 - 给我一个小时左右,我会上传一个。 - Sam
1
一旦我恢复了包,就会出现这个错误“Type is not resolved for member 'RazorEngine.Templating.IsolatedRazorEngineService+DefaultConfigCreator,RazorEngine, Version=3.7.4.0," 你有什么想法吗? - Tommy
显示剩余3条评论
2个回答

5

首先,我无法使您的GitHub代码运行。以下内容基于我自己的重现代码。

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

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

这种方法在ASP.NET中无法工作,因为程序集(assemblies)位于bin子文件夹中。要解决这个问题,只需按照以下方式操作:

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

然而,ASP.NET默认会“影子复制程序集”。因此,只做这个更改可能会导致另一个异常:

ArgumentException:无法将对象类型转换为目标类型。

这是因为默认应用程序域中加载的程序集与沙盒中的程序集混淆了。默认应用程序域中的那些位于临时影子副本位置,而沙盒中的那些位于 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

我无法运行您的代码,因此根据您的代码创建了自己的代码:

  1. Create a new Console application (Visual Studio)

  2. In the package manager console, run: install-package razorengine

  3. Copy code from your repro:

  4. Mark models with [Serializable].

  5. Remove RazorDynamicObject

  6. To ensure that we really can render user details from the authors list, change the test template to:

    string template = "@Model.History.Authors[0].EmailAddress";
    
  7. Also, to make that template work, change Authors in EditHistory from IReadOnlyCollection<> to IReadOnlyList<>

我创建了一个GIST,其中包含生成的代码:
https://gist.github.com/mwikstrom/983c8f61eb10ff1e915a 这对我有用。它按照应该的方式打印出“hello@world.com”。
ASP.NET默认情况下会影子复制程序集,这会导致沙箱中出现额外的问题。
要在ASP.NET下使其工作,您需要进行以下更改:
  1. Disable ASP.NET shadow copying by adding the following under <system.web> in your Web.config file:

    <hostingEnvironment shadowCopyBinAssemblies="false"/>
    
  2. Append \bin to the sandbox's application base path. So in createRazorSandbox(...) do:

    adSetup.ApplicationBase = 
        AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "\\bin";
    
我已经测试过了,它可以正常运行。我的测试项目很简单:
  • 一个使用Visual Studio创建的空ASP.NET Web应用程序,其中包含install-package razorengine

  • 在Web.config中添加以下代码:<hostingEnvironment shadowCopyBinAssemblies="false"/>

  • 下面是Global.asax.cs的代码:

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


除了禁用影子复制之外,这里还有其他替代方案:

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


嗨,谢谢尝试。在发布之前,我已经尝试过这个问题了,现在我再次尝试使用我在GitHub上发布的示例代码(请参见楼主)。你能运行成功吗? - Sam
感谢您的帮助,但这在ASP.NET下不起作用。它需要在ASP.NET下运行。 - Sam
非常感谢您的帮助,我非常感激。您已经付出了很多努力,如果可以的话,我会给您投多个赞的。希望随着时间的推移,更多人会对此表示赞同。 - Sam
@Sam:不用谢。作为问题的提出者,您还可以选择一个答案作为“被采纳的答案”。这比投票更有说服力 :-)。请参见:http://stackoverflow.com/help/someone-answers - Mårten Wikström
我真是太傻了...我忘记了。我完成了悬赏任务,甚至没有想到将其标记为已回答...! - Sam
显示剩余2条评论

1

我大多数情况下不使用复杂类型,但通常的规则是只有原始数据类型可以被传输(这是我的个人规则,因为否则我经常会丢失值)。然而,在查看一些旧的源代码时,我注意到我确实使用了许多复杂类型,但我在控制器中填充了它们(例如在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; }
}

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