Подтвердить что ты не робот

Использование RazorEngine для одновременного анализа шаблонов Razor

Я использую библиотеку RazorEngine (http://razorengine.codeplex.com/) в веб-приложении MVC 3 для анализа строк (которые не являются представлениями) используя язык шаблонов Razor.

В целом, это работает отлично. Однако, когда несколько пользователей обращаются к коду, который одновременно анализирует шаблоны Razor, я иногда вижу ошибки, которые выглядят так, как они встречаются во внутреннем компиляторе Razor (см. Два из них ниже). У меня возникли проблемы с интерпретацией этих ошибок, но я предполагаю, что способ, которым я вызываю компилятор Razor, не безопасен concurrency.

Это известная проблема с компилятором Razor? Как обычные представления Razor (.cshtml) не попадают в эту проблему? Есть ли обходной путь для этого лучше, чем перенос всех моих приложений на Razor.Parse в мьютексе?

Мой код вызова выглядит следующим образом: простая обертка вокруг Razor.Parse:

    protected string ParseTemplate<T>(string templateString, T model)
    {
        //This binderAssembly line is required by NUnit to prevent template compilation errors
        var binderAssembly = typeof(Microsoft.CSharp.RuntimeBinder.Binder).Assembly;
        var result = Razor.Parse(templateString, model);
        return result;
    }

Ошибка:

System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: chunkLength
at System.Text.StringBuilder.ToString()
at System.Web.Razor.Generator.RazorCodeGenerator.BlockContext.MarkEndGeneratedCode()
at System.Web.Razor.Generator.RazorCodeGenerator.WriteBlock(BlockContext block)
at System.Web.Razor.Parser.ParserContext.FlushNextOutputSpan()
at System.Web.Razor.Parser.ParserContext.StartBlock(BlockType blockType, Boolean outputCurrentBufferAsTransition)
at System.Web.Razor.Parser.ParserBase.ParseComment()
at System.Web.Razor.Parser.ParserBase.TryParseComment(SpanFactory previousSpanFactory)
at System.Web.Razor.Parser.ParserBase.ParseBlockWithOtherParser(SpanFactory previousSpanFactory, Boolean collectTransitionToken)
at System.Web.Razor.Parser.HtmlMarkupParser.TryStartCodeParser(Boolean isSingleLineMarkup, Boolean documentLevel)
at System.Web.Razor.Parser.HtmlMarkupParser.ParseRootBlock(Tuple`2 nestingSequences, Boolean caseSensitive)
at System.Web.Razor.Parser.RazorParser.Parse(LookaheadTextReader input, ParserVisitor visitor)
at System.Web.Razor.RazorTemplateEngine.GenerateCodeCore(LookaheadTextReader input, String className, String rootNamespace, String sourceFileName, Nullable`1 cancelToken)
at System.Web.Razor.RazorTemplateEngine.GenerateCode(TextReader input, String className, String rootNamespace, String sourceFileName, Nullable`1 cancelToken)
at System.Web.Razor.RazorTemplateEngine.GenerateCode(TextReader input)
at RazorEngine.Compilation.CompilerServiceBase.GetCodeCompileUnit(String className, String template, ISet`1 namespaceImports, Type templateType, Type modelType)
at RazorEngine.Compilation.DirectCompilerServiceBase.Compile(TypeContext context)
at RazorEngine.Compilation.DirectCompilerServiceBase.CompileType(TypeContext context)
at RazorEngine.Templating.TemplateService.CreateTemplate(String template, Type modelType)
at RazorEngine.Templating.TemplateService.Parse[T](String template, T model, String name)
at RazorEngine.Razor.Parse[T](String template, T model, String name)

Ошибка два:

System.ObjectDisposedException: Cannot read from a closed TextReader.
at System.IO.StringReader.Read()
at System.Web.Razor.Text.BufferingTextReader.NextCharacter()
at System.Web.Razor.Text.BufferingTextReader.Read()
at System.Web.Razor.Parser.ParserContext.AcceptCurrent()
at System.Web.Razor.Parser.HtmlMarkupParser.ParseRootBlock(Tuple`2 nestingSequences, Boolean caseSensitive)
at System.Web.Razor.Parser.RazorParser.Parse(LookaheadTextReader input, ParserVisitor visitor)
at System.Web.Razor.RazorTemplateEngine.GenerateCodeCore(LookaheadTextReader input, String className, String rootNamespace, String sourceFileName, Nullable`1 cancelToken)
at System.Web.Razor.RazorTemplateEngine.GenerateCode(TextReader input, String className, String rootNamespace, String sourceFileName, Nullable`1 cancelToken)
at System.Web.Razor.RazorTemplateEngine.GenerateCode(TextReader input)
at RazorEngine.Compilation.CompilerServiceBase.GetCodeCompileUnit(String className, String template, ISet`1 namespaceImports, Type templateType, Type modelType)
at RazorEngine.Compilation.DirectCompilerServiceBase.Compile(TypeContext context)
at RazorEngine.Compilation.DirectCompilerServiceBase.CompileType(TypeContext context)
at RazorEngine.Templating.TemplateService.CreateTemplate(String template, Type modelType)
at RazorEngine.Templating.TemplateService.Parse[T](String template, T model, String name)
at RazorEngine.Razor.Parse[T](String template, T model, String name)
4b9b3361

Ответ 1

Update: Согласно сообщению на Github) теперь является потокобезопасным. Я не проверял достоверность его безопасности потоков, но предполагаю, что он был реализован должным образом. Пожалуйста, рассмотрите остальную часть этого ответа, полезную только для исторических целей.


Судя по коду, этот проект не выглядит удаленно потокобезопасным.

Razor.Parse

public static string Parse<T>(string template, T model, string name = null)
{
    return DefaultTemplateService.Parse<T>(template, model, name);
}

TemplateService.Parse

public string Parse<T>(string template, T model, string name = null)
{
    var instance = GetTemplate(template, typeof(T), name);
    ...
}

TemplateService.GetTemplate

internal ITemplate GetTemplate(string template, Type modelType, string name)
{
    if (!string.IsNullOrEmpty(name))
        if (templateCache.ContainsKey(name))
            return templateCache[name];

    var instance = CreateTemplate(template, modelType);

    if (!string.IsNullOrEmpty(name))
        if (!templateCache.ContainsKey(name))
            templateCache.Add(name, instance);

    return instance;
}

Итак, Razor.Parse является статическим методом. DefaultTemplateService является статическим свойством на Razor, а Parse и GetTemplate - это методы экземпляра, но эффективно вызывается статически из-за статического DefaultTemplateService. Это означает, что все потоки проходят один и тот же экземпляр и проходят через GetTemplate. Вы заметите, что GetTemplate изменяет состояние (templateCache) без каких-либо блокировок. Поэтому этот код не является потокобезопасным.