Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

.net

razor

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:

  • 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
like image 829
Ropstah Avatar asked Apr 27 '11 15:04

Ropstah


People also ask

What is the new @model directive in Razor View-engine?

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).

What are @razor directive attributes?

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 ).

What is a cshtml directive in Blazor?

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.

Can I generate framework-specific JavaScript (JS) components from Razor components?

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.


1 Answers

@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.

like image 200
Buildstarted Avatar answered Oct 08 '22 15:10

Buildstarted