Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ContentCulturePicker still not working with liquid templates (since v1.8.0) #16670

Open
jagbarcelo opened this issue Sep 5, 2024 · 7 comments
Labels
Milestone

Comments

@jagbarcelo
Copy link
Contributor

Describe the bug

Since the release 1.8.0, OC is not able to render ContentCulturePicker using liquid templates (a RuntimeBinderException is thrown)

Orchard Core version

We try to update our installation every new version is released. Now we have tried with v1.8.4 but the issue still persists. We are forced to stay with v1.7.2 (which is the last one that works fine with ContentCulturePicker and our liquid templates).

To Reproduce

  • We have enabled Localization with two languages: English & Spanish.
  • We have a custom theme with "Layout.liquid" and "ContentCulturePickerContainer.liquid" (among others)
    image
    image
  • The site simply fails for every page. The homepage for instance:
    image

Expected behavior

A dropdown with the available languages should be rendered (along with the rest of the page)
image

Additional info

We've had this issue for quite a long time and because of that we've been monitoring the some of the project related issues, such as:

We have tested and tweaked both our "ContentCulturePickerContainer.liquid" and "Layout.liquid" with every comment and suggestion from those threads, with no luck. None of them work: either no localization menu appears or an exception is thrown.

The best thing we have been able to get has been by getting rid of the "ContentCulturePickerContainer.liquid" completely and embed the following code within the "Layout.liquid" itself:

[...]
<body id="page-top" dir="{{ Culture.Dir }}">
    <div class="culture-wrapper">
        <div class="culture-picker">
            <ul>
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" href="#" id="oc-culture-picker" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{ Culture.Name }}</a>
                    <div class="dropdown-menu" aria-labelledby="oc-culture-picker">
                    {% for aCulture in Site.Properties["LocalizationSettings"].SupportedCultures %}
                        {% if aCulture != Culture.Name %}
                        <a class="dropdown-item" href="{{ aCulture | switch_culture_url }}">{{ aCulture }}</a>
                        {% endif %}
                      {% endfor %}
                    </div>
                </li>
            </ul>
        </div>
    </div>
[...]

With this tweak, we can render a simple language menu, as this:
image
This is not as good as the original because the object Culture only seems to have a Name property. Any attempt to use DisplayName, EnglishName, NativeName,etc, they all yield an empty string.

Logs and screenshots

Full "Raw exception details" are as follow:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot perform runtime binding on a null reference
   at CallSite.Target(Closure, CallSite, Object)
   at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
   at AspNetCoreGeneratedDocument.Views_ContentCulturePicker.ExecuteAsync()
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper.RenderPartialCoreAsync(String partialViewName, Object model, ViewDataDictionary viewData, TextWriter writer)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper.PartialAsync(String partialViewName, Object model, ViewDataDictionary viewData)
   at OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay.<ProcessAsync>g__Awaited|11_0(Task`1 task)
   at OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay.ExecuteAsync(DisplayContext context)
   at OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay.ExecuteAsync(DisplayContext context)
   at OrchardCore.DisplayManagement.Liquid.Tags.ShapeTag.WriteToAsync(List`1 argumentsList, TextWriter writer, TextEncoder encoder, TemplateContext context)
   at Fluid.Parser.FluidTemplate.Awaited(ValueTask`1 task, TextWriter writer, TextEncoder encoder, TemplateContext context, IReadOnlyList`1 statements, Int32 startIndex)
   at OrchardCore.DisplayManagement.Liquid.LiquidViewTemplate.RenderAsync(RazorPage`1 page)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper.RenderPartialCoreAsync(String partialViewName, Object model, ViewDataDictionary viewData, TextWriter writer)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper.PartialAsync(String partialViewName, Object model, ViewDataDictionary viewData)
   at OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay.<ProcessAsync>g__Awaited|11_0(Task`1 task)
   at OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay.ExecuteAsync(DisplayContext context)
   at OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay.ExecuteAsync(DisplayContext context)
   at OrchardCore.DisplayManagement.Theming.ThemeLayout.ExecuteAsync()
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderLayoutAsync(ViewContext context, ViewBufferTextWriter bodyWriter)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync(ActionContext context, ViewResult result)
   at Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync(ActionContext context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultFilters>g__Awaited|28_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at OrchardCore.Seo.Services.RobotsMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at OrchardCore.Apis.GraphQL.GraphQLMiddleware.Invoke(HttpContext context, IAuthorizationService authorizationService, IAuthenticationService authenticationService, ISchemaFactory schemaService, IDocumentWriter documentWriter)
   at SixLabors.ImageSharp.Web.Middleware.ImageSharpMiddleware.Invoke(HttpContext httpContext, Boolean retry)
   at OrchardCore.Liquid.ScriptsMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
   at OrchardCore.Diagnostics.DiagnosticsStartupFilter.<>c__DisplayClass3_0.<<Configure>b__1>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
   at OrchardCore.ContentPreview.PreviewStartupFilter.<>c.<<Configure>b__0_1>d.MoveNext()
--- End of stack trace from previous location ---
   at OrchardCore.Modules.ModularTenantRouterMiddleware.Invoke(HttpContext httpContext)
   at OrchardCore.Modules.ModularTenantContainerMiddleware.<>c__DisplayClass4_0.<<Invoke>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at OrchardCore.Environment.Shell.Scope.ShellScope.UsingAsync(Func`2 execute, Boolean activateShell)
   at OrchardCore.Environment.Shell.Scope.ShellScope.UsingAsync(Func`2 execute, Boolean activateShell)
   at OrchardCore.Environment.Shell.Scope.ShellScope.UsingAsync(Func`2 execute, Boolean activateShell)
   at OrchardCore.Environment.Shell.Scope.ShellScope.UsingAsync(Func`2 execute, Boolean activateShell)
   at OrchardCore.Environment.Shell.Scope.ShellScope.UsingAsync(Func`2 execute, Boolean activateShell)
   at OrchardCore.Modules.ModularTenantContainerMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
@sebastienros sebastienros added this to the 2.x milestone Sep 5, 2024
Copy link
Contributor

github-actions bot commented Sep 5, 2024

We triaged this issue and set the milestone according to the priority we think is appropriate (see the docs on how we triage and prioritize issues).

This indicates when the core team may start working on it. However, if you'd like to contribute, we'd warmly welcome you to do that anytime. See our guide on contributions here.

@sebastienros
Copy link
Member

This PR is the one adding supported_cultures and CultureInfo registration such that Name, DisplayName and other properties can be used. This was done for 2.0 however.

A simple mitigation would be to just add

services.Configure<TemplateOptions>(o =>
{
    o.MemberAccessStrategy.Register<CultureInfo>();
})

in one of your Startup classes.

In parallel we need to see why you are getting this exception when re-defining the shape template in liquid. To my knowledge this has been a recommended way of customization this shape so this should work. Maybe this works in 2.0 (if you can try the main branch).

@jagbarcelo
Copy link
Contributor Author

Regarding your suggested mitigation, I've modified the Startup class for the Theme (in v1.8.4) and added:

        public override void ConfigureServices(IServiceCollection services)
        {
            services.Configure<TemplateOptions>(o =>
            {
                o.MemberAccessStrategy.Register<CultureInfo>();
            });
        }

However, this seems to have no effect in the Layout.liquid file for the Theme and {{ Culture.DisplayName }}, {{ Culture.EnglishName }} or {{ Culture.NativeName }} return an empty string in all cases. Only {{ Culture.Name }} returns either en-GB or es-ES.

@sebastienros
Copy link
Member

That could only possible if something else is add Name support for CultureInfo. Can you try to add specifically these other properties in the registration? with this syntax:

options.MemberAccessStrategy.Register<CultureInfo>("Name", "EnglishName", "NativeName");

@jagbarcelo
Copy link
Contributor Author

No luck with that syntax either. Same results: Empty strings. Only {{ Culture.Name }} returns either en-GB or es-ES.

However, using our tweak described above (embedding the content language picker within the "Layout.liquid" itself) and using translations, we've managed to mimick the original behaviour. So, we are now using:

[...]
<body id="page-top" dir="{{ Culture.Dir }}">
    <div class="culture-wrapper">
        <div class="culture-picker">
            <ul>
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" href="#" id="oc-culture-picker" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{ Culture.Name | t }}</a>
                    <div class="dropdown-menu" aria-labelledby="oc-culture-picker">
                    {% for aCulture in Site.Properties["LocalizationSettings"].SupportedCultures %}
                        {% if aCulture != Culture.Name %}
                        <a class="dropdown-item" href="{{ aCulture | switch_culture_url }}">{{ aCulture | t }}</a>
                        {% endif %}
                      {% endfor %}
                    </div>
                </li>
            </ul>
        </div>
    </div>
[...]

We had to edit the files en-GB.po and es-ES.po to manually set translations for the strings en-GB or es-ES. It is quite a cumbersome fix, but does the job for the moment: we will be able to upgrade to v1.8.4 keeping the original look and feel of the site.

We can live with it for the moment, hoping the issue will be fixed in v2.x. Can you confirm this? And also... is there any expected/approximate date for that? Thanks.

@hishamco
Copy link
Member

@MikeAlhayek could you please look into this while you are the last one working in the ContentCulturePicker

@gaojianzhuang
Copy link

gaojianzhuang commented Oct 11, 2024

This is my solution, it works well for me with the orchardCore 2.0.0. I have added it in the Layout.liquid template file.

<div class="culture-picker dropdown">
    <button class="btn btn-success btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">{{Culture.Name}}</button>
    <ul class="dropdown-menu dropdown-menu-end shadow">
        {% for culture in cultures %}
            {% if culture.Name != Culture.Name  %}
            <li><a class="dropdown-item" href="{{ culture.Name | switch_culture_url }}">{{ culture.Name }}</a></li>
            {% else %}
            <li><a class="dropdown-item active" href="{{ culture.Name | switch_culture_url }}">{{ culture.Name }}</a></li>
            {% endif %}
        {% endfor %}
    </ul>
</div>

image

Hope it can help you. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants