statiqdev / Statiq.Framework

A flexible and extensible static content generation framework for .NET.
https://statiq.dev/framework
MIT License
426 stars 74 forks source link

Error when pipeline in empty namespace #191

Closed pascalberger closed 3 years ago

pascalberger commented 3 years ago

I get an exception in Content/PostProcess pipeline:

[DBUG] Content/PostProcess » RenderContentPostProcessTemplates » ExecuteIf » ExecuteIf » ExecuteIf » ExecuteConfig » RenderRazor » Creating new RazorCompiler for null base page type
[DBUG] Content/PostProcess » RenderContentPostProcessTemplates » ExecuteIf » ExecuteIf » ExecuteIf » ExecuteConfig » RenderRazor » Compiling /index.md
[ERRO] Content/PostProcess » RenderContentPostProcessTemplates » ExecuteIf » ExecuteIf » ExecuteIf » ExecuteConfig » RenderRazor » [C:/git/swissgrc/doc.main/docs/input/index.md => index.html] Value cannot be null. (Parameter 'value')
[DBUG] Exception while executing pipeline Content/PostProcess: System.ArgumentNullException: Value cannot be null. (Parameter 'value')
   at Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeWriter.Write(String value)
   at Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeWriterExtensions.WriteUsing(CodeWriter writer, String name, Boolean endLine)
   at Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeWriterExtensions.WriteUsing(CodeWriter writer, String name)
   at Microsoft.AspNetCore.Razor.Language.CodeGeneration.RuntimeNodeWriter.WriteUsingDirective(CodeRenderingContext context, UsingDirectiveIntermediateNode node)
   at Microsoft.AspNetCore.Razor.Language.CodeGeneration.DefaultDocumentWriter.Visitor.VisitUsingDirective(UsingDirectiveIntermediateNode node)
   at Microsoft.AspNetCore.Razor.Language.Intermediate.UsingDirectiveIntermediateNode.Accept(IntermediateNodeVisitor visitor)
   at Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeVisitor.Visit(IntermediateNode node)
   at Microsoft.AspNetCore.Razor.Language.CodeGeneration.DefaultCodeRenderingContext.RenderChildren(IntermediateNode node)
   at Microsoft.AspNetCore.Razor.Language.CodeGeneration.DefaultDocumentWriter.Visitor.VisitDefault(IntermediateNode node)
   at Microsoft.AspNetCore.Razor.Language.CodeGeneration.DefaultDocumentWriter.Visitor.VisitNamespaceDeclaration(NamespaceDeclarationIntermediateNode node)
   at Microsoft.AspNetCore.Razor.Language.Intermediate.NamespaceDeclarationIntermediateNode.Accept(IntermediateNodeVisitor visitor)
   at Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeVisitor.Visit(IntermediateNode node)
   at Microsoft.AspNetCore.Razor.Language.CodeGeneration.DefaultCodeRenderingContext.RenderChildren(IntermediateNode node)
   at Microsoft.AspNetCore.Razor.Language.CodeGeneration.DefaultDocumentWriter.Visitor.VisitDefault(IntermediateNode node)
   at Microsoft.AspNetCore.Razor.Language.CodeGeneration.DefaultDocumentWriter.Visitor.VisitDocument(DocumentIntermediateNode node)
   at Microsoft.AspNetCore.Razor.Language.CodeGeneration.DefaultDocumentWriter.WriteDocument(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
   at Microsoft.AspNetCore.Razor.Language.DefaultRazorCSharpLoweringPhase.ExecuteCore(RazorCodeDocument codeDocument)
   at Microsoft.AspNetCore.Razor.Language.RazorEnginePhaseBase.Execute(RazorCodeDocument codeDocument)
   at Microsoft.AspNetCore.Razor.Language.DefaultRazorEngine.Process(RazorCodeDocument document)
   at Microsoft.AspNetCore.Razor.Language.DefaultRazorProjectEngine.ProcessCore(RazorCodeDocument codeDocument)
   at Microsoft.AspNetCore.Razor.Language.RazorProjectEngine.Process(RazorProjectItem projectItem)
   at Statiq.Razor.RazorCompiler.GetCompilation(RazorProjectItem projectItem)
   at Statiq.Razor.RazorCompiler.<>c__DisplayClass11_0.<CompilePage>b__0(CompilerCacheKey _)
   at Statiq.Common.ConcurrentCache`2.<>c__DisplayClass2_1.<GetOrAdd>b__1()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
   at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
   at System.Lazy`1.CreateValue()
   at System.Lazy`1.get_Value()
   at Statiq.Common.ConcurrentCache`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Statiq.Razor.CachingCompiler.GetOrAddCachedCompilation(CompilerCacheKey cacheKey, Func`2 valueFactory)
   at Statiq.Razor.RazorCompiler.CompilePage(RenderRequest request, Int32 contentCacheCode, RazorProjectItem projectItem)
   at Statiq.Razor.RazorCompiler.GetPageFromStreamAsync(IServiceProvider serviceProvider, RenderRequest request)
   at Statiq.Razor.RazorCompiler.RenderPageAsync(RenderRequest request)
   at Statiq.Razor.RazorService.RenderAsync(RenderRequest request)
   at Statiq.Razor.RenderRazor.<>c__DisplayClass16_0.<<ExecuteContextAsync>g__RenderDocumentAsync|1>d.MoveNext()

Repro steps

  1. Create this csproj:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net5.0</TargetFramework>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Statiq.Web" Version="1.0.0-beta.31" />
      </ItemGroup>
    
    </Project>
  2. Create this program.cs

    using System.Threading.Tasks;
    using Statiq.App;
    using Statiq.Core;
    using Statiq.Web;
    
    namespace Docs
    {
        public class Program
        {
            public static async Task<int> Main(string[] args) =>
              await Bootstrapper
                .Factory
                .CreateWeb(args)
                .RunAsync();
        }
    }
    
    public class SearchIndex : Pipeline
    {
    }
  3. Add an empty input\index.md

Workaround

Move pipeline into namespace:

    using System.Threading.Tasks;
    using Statiq.App;
    using Statiq.Core;
    using Statiq.Web;

    namespace Docs
    {
        public class Program
        {
            public static async Task<int> Main(string[] args) =>
              await Bootstrapper
                .Factory
                .CreateWeb(args)
                .RunAsync();
        }

        public class SearchIndex : Pipeline
        {
        }
    }
daveaglick commented 3 years ago

Finally getting a chance to look into this bug - some observations:

daveaglick commented 3 years ago

Actually, the Markdown thing makes sense because the exception is happening inside the Razor engine and only Markdown, HTML, and Razor files are processed by Razor (as defined in the Statiq Web pipelines).

Another data point is that the bug is happening for any class defined in the global namespace, not just pipelines. In the call stack I can see it's being triggered during namespace writing in the Razor engine when generating code.

So...pretty sure the issue is that the IExecutionContext.Namespaces collection is getting a null value for the global namespace. That collection is automatically populated based on reflecting every object in every reference at startup. I suspect that when a reflected object doesn't have a namespace (I.e. the global namespace), we just add the result of the namespace reflection (which is null) to the collection.

daveaglick commented 3 years ago

Fixed, will go out with the next release.