Изолированный RazorEngine, неспособный передать модель другим 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.

Edit Я создал минимальное воспроизведение проблемы, с которой я столкнулся. Он находится в https://github.com/knightmeister/RazorEngineIssue. Если восстановление пакета завершается неудачно, вручную install-package razorengine.

Ответы

Ответ 1

Прежде всего; Я никогда не мог запустить ваш GitHub-код. Следующее основано на моем собственном коде воспроизведения.

Я думаю, что вы получаете Невозможно загрузить файлы или сборку-исключения, потому что когда вы настраиваете десктопную среду AppDomain, которую вы устанавливаете:

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

Это не будет работать в ASP.NET, потому что сборки находятся в подпапке bin. Чтобы исправить это, просто сделайте это вместо:

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

Однако ASP.NET будет по умолчанию сборками теневых копий. Поэтому просто выполнение этого изменения может вызвать другое исключение:

ArgumentException: тип объекта не может быть преобразован в тип цели.

Это потому, что происходит смешение между сборками, загруженными в домене приложения по умолчанию и в песочнице. Те, которые находятся в домене приложения по умолчанию, расположены во временном месте теневой копии, а те, которые находятся в песочнице, находятся в папке bin вашего корня веб-приложения.

Самый простой способ исправить это - отключить теневое копирование, добавив следующую строку под <system.web> в ваш Web.config:

<hostingEnvironment shadowCopyBinAssemblies="false"/>

Кроме того; Я думаю, что лучше и проще пропустить с помощью RazorDynamicObject и вместо этого пометить ваши модели с помощью [Serializable]. На самом деле я никогда не получал RazorDynamicObject работоспособно.

Остальная часть этого ответа суммирует то, что я сделал, чтобы прийти к такому выводу


Я думаю, что это связано с ошибкой или ограничением в RazorEngine. (я не уверен в этом больше, вполне возможно, что теневое копирование и RazorDynamicObject не могут работать вместе)

Я потратил пару часов, пытаясь понять, как это сделать, но я всегда получал исключение безопасности из RazorEngine.

Существует, однако, возможное обходное решение: Ditch 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

Я не смог запустить ваш репрограммный код, поэтому я создал свой собственный на основе вашего кода:

  • Создайте новое консольное приложение (Visual Studio)

  • В консоли диспетчера пакетов запустите: install-package razorengine

  • Скопируйте код из вашего репродукции:

  • Отметьте модели с помощью [Serializable].

  • Удалить RazorDynamicObject

  • Чтобы убедиться, что мы действительно можем отображать данные пользователя из списка авторов, измените тестовый шаблон на:

    string template = "@Model.History.Authors[0].EmailAddress";
    
  • Кроме того, чтобы заставить этот шаблон работать, измените Authors в EditHistory с IReadOnlyCollection<> на IReadOnlyList<>

Я создал GIST с результирующим кодом:
https://gist.github.com/mwikstrom/983c8f61eb10ff1e915a

Это работает для меня. Он печатает [email protected] так, как должен.


ASP.NET будет создавать теневые копии сборок по умолчанию, и это вызовет дополнительные проблемы с песочницей.

Чтобы получить эту работу в ASP.NET, вам нужно будет сделать следующие изменения:

  • Отключите теневое копирование ASP.NET, добавив следующее в <system.web> в файл Web.config:

    <hostingEnvironment shadowCopyBinAssemblies="false"/>
    
  • Добавить \bin в базовый путь приложения для песочницы. Итак, в createRazorSandbox(...) do:

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

Я тестировал это, и он работает нормально. Мой тестовый проект просто:

  • Пустое веб-приложение ASP.NET(созданное с помощью 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

Ответ 2

В основном я не использую сложные типы, но обычно правило состоит в том, что только примитивные типы данных передаются нормально (мое собственное правило, поскольку значения часто теряются для меня в противном случае). Однако, глядя на какой-то старый исходный код, я заметил, что использовал множество сложных типов, но я заполнил их в контроллере (например, в Public ActionResult Index()). После некоторого чтения я думаю, что это может сработать, если вы используете что-то похожее на это (untested, источник MSDN, 2-й источник):

[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; }
}