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: The 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:
In today’s post I’m going to discuss the new @model directive that is now supported with the new Razor view-engine, and which helps make view files more concise and cleaner. ASP.NET MVC 3 ships with a new view-engine option called “Razor” (in addition to continuing to support/enhance the existing .aspx view engine).
Razor directive attributes are represented by implicit expressions with reserved keywords following the @ symbol. A directive attribute typically changes the way an element is parsed or enables different functionality. This scenario only applies to Razor components ( .razor ).
The directive: In a .cshtml file indicates that the file is a Razor Page. For more information, see Custom routes and Introduction to Razor Pages in ASP.NET Core. Specifies that a Razor component should handle requests directly. For more information, see ASP.NET Core Blazor routing.
Generate framework-specific JavaScript (JS) components from Razor components for web frameworks, such as Angular or React. This capability isn't included with .NET 6, but is enabled by the new support for rendering Razor components from JS.
@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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With