Razor in 'custom environment' doesn't accept @model directive
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);
精彩评论