开发者

Razor in 'custom environment' doesn't accept @model directive

开发者 https://www.devze.com 2023-03-01 05:45 出处:网络
I\'m trying to parse and compile Razor templates in a sandboxed environment a.k.a. a custom host based on this information (architecture see below).

I'm trying to parse and compile Razor templates in a sandboxed environment a.k.a. a custom host based on this information (architecture see below).

I'm having troubles getting intellisense to work, so i specified a BuildProvider as stated here and followed the 'workaround' provided in the answer to that question.

On @model MyAssembly.MyModel intellisense gives the following error:

Could not load file or assembly 'System.Web.WebPages.Razor' or one of it's dependencies.

(the assembly is referenced and copied local, as well as all other related Razor assemblies)

When parsing and compiling a template however the following error is thrown:

Line: 33 Col: 7 Error: T开发者_如何学Che name 'model' does not exist in the current context

Any leads/suggestions?

p.s. If i remove the @model directive the template parses and compiles fine

Architecture:

  • Webapplication: references Class library and provides the .cshtml template files using a model from the 3d party class library.
  • Class library: contains RazorHost and BaseTemplate and references 3d party library to add model to .cshtml files provided by webapplication.
  • 3d Party Class: provides model for webapplication


@model is something very specific to MVC's implementation of Razor. As such, out of the box, it doesn't work. I've uploaded a patch to the RazorEngine on codeplex that adds @model support to it's engine and it would be pretty easy to implement it outside of that specific version. http://razorengine.codeplex.com/SourceControl/list/patches

It basically involves overriding the CodeGenerator that razor uses to generate it's class files and override TryVisitSpecialSpan

protected override bool TryVisitSpecialSpan(Span span) {
    return TryVisit<ModelSpan>(span, VisitModelSpan); 
      //This is where you would add more special span tests 
      //|| TryVisit<SomeOtherSpan>(span, Method);
}

void VisitModelSpan(ModelSpan span) {
    string modelName = span.ModelTypeName;

    if (DesignTimeMode) {
        WriteHelperVariable(span.Content, "__modelHelper");
    }
}

Then you also have to create your own CSharpCodeParser

    public class CSharpRazorCodeParser : CSharpCodeParser {
        public string TypeName { get; set; }

        public CSharpRazorCodeParser() {
            RazorKeywords.Add("model", WrapSimpleBlockParser(System.Web.Razor.Parser.SyntaxTree.BlockType.Directive, ParseModelStatement));
        }

        bool ParseModelStatement(CodeBlockInfo block) {
            End(MetaCodeSpan.Create);

            SourceLocation endModelLocation = CurrentLocation;

            Context.AcceptWhiteSpace(includeNewLines: false);

            if (ParserHelpers.IsIdentifierStart(CurrentCharacter)) {
                using (Context.StartTemporaryBuffer()) {
                    AcceptTypeName();
                    Context.AcceptTemporaryBuffer();
                }
            } else {
                OnError(endModelLocation, "Model Keyword Must Be Followed By Type Name");
            }

            End(ModelSpan.Create(Context, TypeName));

            return false;
        }
    }

And even after that you have to override the Host to use your new classes

public class RazorEngineHost : System.Web.Razor.RazorEngineHost {

    public RazorEngineHost(RazorCodeLanguage codeLanguage, Func<MarkupParser> markupParserFactory)
        : base(codeLanguage, markupParserFactory) { }

    public override System.Web.Razor.Generator.RazorCodeGenerator DecorateCodeGenerator(System.Web.Razor.Generator.RazorCodeGenerator generator) {
        if (generator is CSharpRazorCodeGenerator) {
            return new CSharpRazorCodeGenerator(generator.ClassName,
                                                   generator.RootNamespaceName,
                                                   generator.SourceFileName,
                                                   generator.Host, false);
        }

        return base.DecorateCodeGenerator(generator);
    }

    public override ParserBase DecorateCodeParser(ParserBase incomingCodeParser) {
        if (incomingCodeParser is CSharpCodeParser) {
            return new CSharpRazorCodeParser();
        } else {
            return base.DecorateCodeParser(incomingCodeParser);
        }
    }
}

You also have to create your own custom CodeSpan

public class ModelSpan : CodeSpan {
    public ModelSpan(SourceLocation start, string content, string modelTypeName) : base(start, content) {
        this.ModelTypeName = modelTypeName;
    }

    public string ModelTypeName { get; private set; }

    public override int GetHashCode() {
        return base.GetHashCode() ^ (ModelTypeName ?? String.Empty).GetHashCode();
    }

    public override bool Equals(object obj) {
        ModelSpan span = obj as ModelSpan;
        return span != null && Equals(span);
    }

    private bool Equals(ModelSpan span) {
        return base.Equals(span) && string.Equals(ModelTypeName, span.ModelTypeName, StringComparison.Ordinal);
    }

    public new static ModelSpan Create(ParserContext context, string modelTypeName) {
        return new ModelSpan(context.CurrentSpanStart, context.ContentBuffer.ToString(), modelTypeName);
    }
}

This implementation doesn't do anything other than tell the designer what model to use. It shouldn't affect compilation at all but allow the compiler to ignore this particular command.


There is a simple solution for IntelliSense to work with 'custom Razor environment' and Resharper (e.g. to use RazorEngine for reporting). To Enable IntelliSense create the following class in your project:

public class EnableIntelliSenseFor<T> 
{
  public readonly T Model;
}

At the top of your .cshtml file add this line:

@inherits EnableIntelliSenseFor<YourModelType>

Then when parsing the template simply remove the top line as suggested by ThiagoPXP with this:

template = RemoveInheritsDirective(template);            
var html = Razor.Parse(template, model);     

private static string RemoveInheritsDirective(string template)
{
   return template.StartsWith("@inherits") 
      ? template.Substring(template.IndexOf('\n') + 1) 
      : template;
}

Access your model with @Model and IntelliSense should work as expected.


I had the same issue and my solution was quite simple.

having the @model at the beginning of the file is great cause it gives us some intellisense. However, it breaks the razor engine so my solution was to remove the model declaration on runtime before call the parser.

string template = File.ReadAllText(@"C\myRazorView.cshtml");

var model = new MyViewModel { Name = "Foo", Surname = "Bar" };

//remove model declaration from the view file
template = template.Replace("@model MyViewModel", "");
string result = Razor.Parse(template, model);

In my case all views are using the same model, therefore using String.Replace() in the first line did the trick for me.

You could enhance it removing the first line in another way like regex or something else.


For me this worked (using RazorEngine 3.3 on NET4.0):

1.this line to top of cshtml

@inherits RazorEngine.Templating.TemplateBase<MyModel>

2.this in Page_Load

var templateName = System.IO.Path.ChangeExtension( Request.PhysicalPath, "cshtml");
var template = System.IO.File.ReadAllText(templateName);
var r = Razor.Parse<MyModel>(template, new MyModel {
                FileName = "Example.pdf",
                MessageId = Guid.NewGuid()
            }, "MyPage");
Response.Write(r);
0

精彩评论

暂无评论...
验证码 换一张
取 消