umbraco / Umbraco-CMS

Umbraco is a free and open source .NET content management system helping you deliver delightful digital experiences.
https://umbraco.com
MIT License
4.42k stars 2.67k forks source link

Razor Runtime View Compilation does not work in Razor Class Libraries #12292

Closed xdjoshuaaz closed 1 year ago

xdjoshuaaz commented 2 years ago

Which exact Umbraco version are you using? For example: 9.0.1 - don't just write v9

9.4.3

Bug summary

If Razor Runtime View Compilation is configured for Razor Class Libraries (RCL) (e.g., in the RCL's Composer) that is consumed by an Umbraco 9 project, runtime view compilation doesn't work.

Specifics

Seems to be related to the logic in the AddInMemoryModelsRazorEngine method, where it takes a copy of the container's service registrations, but composers are registered by default after the copy has been made so any new FileProviders registered in MvcRazorRuntimeCompilationOptions in composers aren't known to the RefreshingRazorViewEngine's view engine factory function.

Replacing the view engine with the default one using the following code works around the issue. Call the following code after the UmbracoBuilder calls. However, this will break re-compilation of views when in-memory models are re-generated.

    public static IServiceCollection FixRuntimeViewCompilationForRazorClassLibrariesInUmbraco(this IServiceCollection services)
    {
        if (services.FirstOrDefault(x => x is
            {
                ServiceType: { } serviceType,
                ImplementationFactory.Method.Name: { } factoryName, // requires C#10 langversion
            } && serviceType == typeof(IRazorViewEngine) && factoryName.Contains("AddInMemoryModelsRazorEngine")
        ) is { } faultyViewEngineRegistration)
        {
            services.Remove(faultyViewEngineRegistration);
        }

        return services;
    }

Another work around would be to configure runtime view compilation options before the IUmbracoBuilder.AddWebsite call where ModelsBuilder is registered. This can be done manually, or by moving AddComposers above AddWebsite (which changes DI registration order)

I think we should only enable the RefreshingRazorViewEngine if in-memory models are being used, so that RCLs work for source code models projects, and perhaps document this gotcha in the Models Builder docs. The model mode check would need to be done using IConfiguration as the IOptions<ModelsBuilderSettings> won't be available at the time where the check is needed.

I'm happy to look into implementing the above suggestion once triaged and if it's up for grabs.

Steps to reproduce

I've created a repo @ https://github.com/xdjoshuaaz/UmbracoRCLRuntimeViewCompilationBug

1) Create an Umbraco 9 solution 2) Create a Razor Class Library project in the solution 3) Reference the RCL project from the Umbraco project 4) Create a Composer in the RCL with runtime view compilation configured as documented in these Microsoft docs

Expected result / actual result

Expected: Dummy text should have changed (i.e. the Razor view is re-compiled) Actual: Dummy text does not change (i.e. the Razor view did not re-compile)

nikolajlauridsen commented 1 year ago

Hey @xdjoshuaaz, and sorry for the late reply.

However, taking a look at your issue this should be fixed for V11 by #13107, where we no longer take a copy of the containers service registration, instead using a modified runtime view compiler for our InMemory needs. and by #13201 where we only register this custom implementation if InMemory is actually used, so I'll go ahead and close this one for now since it's not really a candidate for fixin' in 10 😄 But thank you so much for your detailed description.