Error line numbers

Feb 13, 2011 at 9:35 PM

Is there any convenient way to get the script line number for a compilation error (as opposed to the error in the generated C# code)?

Coordinator
Feb 16, 2011 at 8:57 AM
Edited Feb 16, 2011 at 8:58 AM

It's not that easy to generate a line number from a compilation exception. The TemplateCompilationException is thrown after parsing, so at this stage it has no context from which to derive line and position, etc. You can get line and column numbers from a TemplateParsingException, but you have to enable strict mode to do so (parsing exceptions are disabled by default). To enable strict mode, create a new instance of TemplateService:

 

var service = TemplateServiceFactory.CreateTemplateService(Language.CSharp, true);
string template = "Hello @{Model";

string result = service.Parse(template, new { Name = "World" });

The markup in this example should cause a TemplateParsingException which has Line and Column properties detailing the location of the parsing exception.

Sep 12, 2011 at 5:28 PM

I had the same problem with needing the line number from the template and not the generated code. I investigated the problem and found a way to do it but it required changing the RazorEngine source.

The key was to rebase the line numbers before the TemplateCompilationException was thrown. So I ended up modifying the CompileType method in DirectCompilerServiceBase.cs to the following:

        public override Type CompileType(TypeContext context)
        {
            CompilerResults results = this.Compile(context);

            if (results.Errors != null && results.Errors.Count > 0)
            {
                var rebasedErrors = new CompilerErrorCollection();

                //get generated code
                var writer = new StringWriter();
                this.CodeDomProvider.GenerateCodeFromCompileUnit(this.GetCodeCompileUnit(context.ClassName, context.TemplateContent, context.Namespaces,
                                                                      context.TemplateType, context.ModelType), writer, new CodeGeneratorOptions());
                
                var generatedCode = writer.ToString().Split(new[] {Environment.NewLine}, StringSplitOptions.None);
                var templateCode = context.TemplateContent.Replace("\r\n", "\n").Split(new[] {"\n"}, StringSplitOptions.None);
                foreach (CompilerError error in results.Errors)
                {
                    var errorLine = generatedCode[error.Line - 1];
                    if (templateCode.Length <= 0) continue;
                    
                    for (var lineNumber = 0; lineNumber < templateCode.Count(); lineNumber++)
                    {
                        var templateCodeLine = templateCode[lineNumber];
                        var column = templateCodeLine.IndexOf(errorLine.Trim());
                        if (column <= 0) continue;
                        rebasedErrors.Add(new CompilerError(error.FileName, lineNumber + 1, column, error.ErrorNumber, error.ErrorText));
                        break;
                    }
                }
                throw new TemplateCompilationException(rebasedErrors);
            }

            return results.CompiledAssembly.GetType("CompiledRazorTemplates.Dynamic." + context.ClassName);
        }

The method works in the following way. IF there is an error, I generate the code again to get the generated source of the template. Then I iterate through the CompilerError collection and for each error, I find the corresponding error in the template code. Then I create a new CompilerError with the matching line and column number. Finally after each CompilerError has been rebased, I throw that collection of errors instead. 

This all seems to work for us, hopefully you'll find it as useful.

May 2, 2012 at 9:31 PM
Edited May 2, 2012 at 9:48 PM

Won't this work? Works in mine. I'm using .Net 4.0

try{
    ...
}
catch (TemplateCompilationException e)
{
    /*
     * Catch any template issues and spit out the file name and line numbers because this
     * doesn't happen by default.
     */
    StringBuilder errorList = new StringBuilder();
    errorList.AppendFormat("{0}", lastTemplateFile);
    errorList.AppendLine();
    errorList.AppendLine();
    errorList.AppendFormat("{0}", e.Message);
    errorList.AppendLine();
    errorList.AppendLine();
    errorList.AppendLine("Line numbers:");
    foreach (var error in e.Errors)
    {
        errorList.AppendFormat("\t({0}, {1})", error.Line, error.Column);
        errorList.AppendLine();
    }
    errorList.AppendLine();

    if (_showExceptions)
        Utils.WriteError(errorList.ToString());

    return -1;
}