You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Optimizely CMS 11升级至12后页面空白,未命中StartPage控制器

Optimizely CMS 11升级到12后访问显示空白页问题

我正在将站点从Optimizely CMS 11升级到12,访问时显示空白页且无任何报错信息。预期应跳转至Start Page(首页),但调试时断点从未命中StartPage Controller,请求流程在某处中断,直接返回空白页。

代码片段

Startup.cs

public class Startup
{
    private readonly IWebHostEnvironment _webHostingEnvironment;
    private readonly IConfiguration _configuration;

    public Startup(IWebHostEnvironment webHostingEnvironment, IConfiguration configuration)
    {
        _webHostingEnvironment = webHostingEnvironment;
        _configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        if (_webHostingEnvironment.IsDevelopment())
        {
            AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(_webHostingEnvironment.ContentRootPath, "App_Data"));

            services.Configure<SchedulerOptions>(options => options.Enabled = false);
        }

        services.AddApplicationInsightsTelemetry();

        services.AddHttpContextAccessor();
        services.AddMemoryCache();
        services.AddResponseCaching();

        services
            //.AddCmsAspNetIdentity<ApplicationUser>()
            .AddCms()
            .AddAlloy() //Service collections Extensions
            .AddFind()
            //.AddAdminUserRegistration()
            .AddEmbeddedLocalization<Startup>();

        services.AddCors(opt =>
        {
            opt.AddPolicy(name: "CorsPolicy", builder =>
            {
                builder.AllowAnyOrigin()
                    .AllowAnyHeader()
                    .AllowAnyMethod();
            });
        });
      services.AddContentDeliveryApi();
      services.AddAlloyHideTabs();

        // Required by Wangkanai.Detection to handle multiple devices
        services.AddDetection();

        services.AddSession(options =>
        {
            options.IdleTimeout = TimeSpan.FromSeconds(10);
            options.Cookie.HttpOnly = true;
            options.Cookie.IsEssential = true;
        });
        services.Configure<CookiePolicyOptions>(options =>
        {
            options.MinimumSameSitePolicy = SameSiteMode.Strict;
            options.Secure = CookieSecurePolicy.Always;
            options.HttpOnly = HttpOnlyPolicy.Always;
        });

        services.AddAuthentication(options =>
            {
                options.DefaultScheme = "okta.auth";
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
            .AddCookie("okta.auth", options =>
            {
                options.Events.OnSignedIn = async ctx =>
                {
                    if (ctx.Principal?.Identity is ClaimsIdentity claimsIdentity)
                    {
                        // Syncs user and roles so they are available to the CMS
                        var synchronizingUserService = ctx
                            .HttpContext
                            .RequestServices
                            .GetRequiredService<ISynchronizingUserService>();

                        await synchronizingUserService.SynchronizeAsync(claimsIdentity);
                    }
                };
            })
            .AddOpenIdConnect(options =>
            {
                options.SignInScheme = "okta.auth";
                options.SignOutScheme = "okta.auth";
                options.ClientId = _configuration["Okta:ClientId"];
                options.ClientSecret = _configuration["Okta:ClientSecret"];
                options.Authority = _configuration["Okta:Authority"];
                options.SignedOutCallbackPath = _configuration["Okta:PostLogoutRedirectUrl"];
                options.CallbackPath = "/signin-oidc";
                options.Scope.Add(_configuration["Okta:Scopes"]);
                options.ResponseType = "code";
                options.SaveTokens = true;

                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = false,
                    NameClaimType = "name",
                    RoleClaimType = "groups"
                };

                options.Events = new OpenIdConnectEvents
                {
                    OnAuthenticationFailed = async context =>
                    {
                        var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
                        var urlResolver = ServiceLocator.Current.GetInstance<UrlResolver>();

                        var startPage = contentLoader.Get<StartPage>(ContentReference.StartPage);

                        if (context.Request.Path.Value != null && !context.Request.Path.Value.ToLower().Contains("episerver"))
                        {
                            context.Response.Redirect($"{startPage.LoginPage.GetPublicUrl().TrimEnd('/')}?returnUrl={Uri.EscapeDataString(context.Request.GetEncodedUrl())}");
                            context.HandleResponse();
                        }

                        if (ContentReference.StartPage.ID != 0 && context.Exception.Message.Contains("User is not assigned to the client application"))
                        {
                            context.Response.Redirect($"{urlResolver.GetUrl(startPage.LoginPage)}?loginErrorMessage={Uri.EscapeDataString("User is not assigned to the client application. Make sure the user is assigned to the application or to a group assigned to the application in Okta.")}");
                            context.HandleResponse();
                        }
                        else
                        {
                            await context.Response.WriteAsync(context.Exception.Message);

                            context.HandleResponse();
                        }
                        await Task.CompletedTask;
                    },

                    OnRedirectToIdentityProvider = async context =>
                    {
                        if (context.Response.StatusCode == 401)
                        {
                            context.HandleResponse();
                        }

                        // If signing out, add the id_token_hint
                        if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
                        {
                            var idTokenHint = context.HttpContext.User.FindFirst(OpenIdConnectParameterNames.IdToken);

                            if (idTokenHint != null)
                            {
                                context.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                            }

                            context.HandleResponse();

                            await Task.CompletedTask;
                            return;
                        }

                        var contentLoader = context.HttpContext.RequestServices.GetRequiredService<IContentLoader>();

                        if (ContentReference.StartPage.ID != 0)
                        {
                            var startPage = contentLoader.Get<StartPage>(ContentReference.StartPage);
                            var loginPageUrl = startPage.LoginPage.GetPublicUrl();

                            if (!context.HttpContext.Request.Path.Value.ToLower().Contains("episerver") && startPage.LoginPage == null)
                            {
                                context.HttpContext.Response.StatusCode = 404;
                                context.HandleResponse();
                                return;
                            }

                            if (!context.HttpContext.Request.Path.Value.ToLower().Contains("episerver") && context.HttpContext.Request.Cookies["Epi-Auth-Flow"] == null)
                            {
                                context.HttpContext.Response.Redirect($"{loginPageUrl.TrimEnd('/')}?returnUrl={Uri.EscapeDataString(context.HttpContext.Request.GetEncodedUrl())}");
                                context.HandleResponse();
                                return;
                            }
                        }

                        HandleMultiSiteReturnUrl(context);

                        if (context.HttpContext.Response.StatusCode == 401 && context.HttpContext.User.Identity.IsAuthenticated)
                        {
                            context.HttpContext.Response.StatusCode = 403;
                            context.HandleResponse();
                            return;
                        }

                        if (context.HttpContext.Response.StatusCode == 401 && IsXhrRequest(context.HttpContext.Request))
                        {
                            context.HandleResponse();
                            return;
                        }

                        //context.HandleResponse();
                        await Task.CompletedTask;
                    },

                    OnAuthorizationCodeReceived = context =>
                    {
                        // Handle authorization code received
                        return Task.CompletedTask;
                    },

                    OnTokenValidated = (ctx) =>
                    {
                        var redirectUri = new Uri(ctx.Properties.RedirectUri, UriKind.RelativeOrAbsolute);
                        if (redirectUri.IsAbsoluteUri)
                        {
                            ctx.Properties.RedirectUri = redirectUri.PathAndQuery;
                        }

                        //// Sync user and the roles to EPiServer in the background
                        //var synchronizingUserService = ctx.HttpContext.RequestServices.GetRequiredService<ISynchronizingUserService>();
                       

                        return Task.CompletedTask;
                    }
                };
            });

        services.AddHttpContextAccessor();
        services.AddMemoryCache();

        services.AddTransient<IOktaServices>(serviceProvider => new OktaServices(serviceProvider.GetService<IConfiguration>()!));

        services.AddControllersWithViews();
        services.AddMvc(options =>
        {
            options.CacheProfiles.Add("PageControllerBaseOutputCacheProfile", new CacheProfile
            {
                Duration = 0,  // Cache duration in seconds. Set to 0 for no caching.
                Location = ResponseCacheLocation.None,
                NoStore = true
            });
        });
       
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        // Required by Wangkanai.Detection
        app.UseDetection();

        app.UseSession();

        app.UseStaticFiles();
        app.UseRouting();
        app.UseCors();

        app.UseAuthentication();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
         endpoints.MapControllers();
         endpoints.MapRazorPages();
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
            endpoints.MapControllers();
            endpoints.MapRazorPages();
            endpoints.MapContent();
        });

        // Map the "/util/logout.aspx" endpoint
        app.Map("/util/logout", map =>
        {
            map.Run(async context =>
            {
                // Sign out the user
                await context.SignOutAsync();

                if (ContentReference.StartPage.ID == 0)
                {
                    return;
                }

                var contentLoader = context.RequestServices.GetRequiredService<IContentLoader>();
                var urlResolver = context.RequestServices.GetRequiredService<UrlResolver>();

                var startPage = contentLoader.Get<StartPage>(ContentReference.StartPage);

                // Redirect to the start page
                context.Response.Redirect(
                    $"{context.Request.Scheme}://{context.Request.Host}{urlResolver.GetUrl(startPage)}");
            });
        });
    }

    public void ConfigureContainer(ServiceCollection context)
    {
        //Register custom implementations that should be used in favor of the default implementations
        context.AddTransient<IContentRenderer, ErrorHandlingContentRenderer>()
            .AddTransient<ContentAreaRenderer, AlloyContentAreaRenderer>()
            .AddTransient<IMessageSender, SmtpSender>();
    }

    private static bool IsXhrRequest(HttpRequest request)
    {
        const string xRequestedWith = "X-Requested-With";

        var query = request.Query;
        if (query.ContainsKey(xRequestedWith) && query[xRequestedWith] == "XMLHttpRequest")
        {
            return true;
        }

        var headers = request.Headers;
        return headers.ContainsKey(xRequestedWith) && headers[xRequestedWith] == "XMLHttpRequest";
    }

    private void HandleMultiSiteReturnUrl(RedirectContext context)
    {
     

        if (context.ProtocolMessage.RedirectUri == null)
        {
            var currentUrl = context.HttpContext.Request.GetEncodedUrl();
            context.ProtocolMessage.RedirectUri = currentUrl;
        }
    }
}

ServiceCollectionExtensions.cs

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddAlloy(this IServiceCollection services)
    {
        services.Configure<RazorViewEngineOptions>(options => options.ViewLocationExpanders.Add(new SiteViewEngineLocationExpander()));


        services.Configure<TinyMceConfiguration>(config =>
        {
            config.Default().AddSetting("extended_valid_elements",
                    "iframe[src|alt|title|width|height|align|name],picture,source[srcset|media],span")
                .AddPlugin(
                    "epi-link epi-image-editor epi-dnd-processor epi-personalized-content print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists textcolor " +
                    "wordcount imagetools contextmenu colorpicker textpattern help code")
                .Toolbar(
                    "bold italic strikethrough forecolor | epi-link image epi-image-editor epi-personalized-content | imagevault-insert-media imagevault-edit-media | bullist numlist | searchreplace fullscreen ",
                    "styleselect formatselect | alignleft aligncenter alignright alignjustify | removeformat | table toc | code"
                    , "")
                .Menubar("edit insert view format table tools help")
                .BodyClass("main-content-wrapper")
                .AddSetting("image_title", false)
                .AddSetting("image_dimensions", true);
        });

        services.AddTinyMceSpellChecker();

        services.Configure<DisplayOptions>(displayOption =>
        {
            displayOption.Add("full", "/displayoptions/full", Global.ContentAreaTags.FullWidth, string.Empty, "epi-icon__layout--full");
            displayOption.Add("wide", "/displayoptions/wide", Global.ContentAreaTags.TwoThirdsWidth, string.Empty, "epi-icon__layout--two-thirds");
            displayOption.Add("half", "/displayoptions/half", Global.ContentAreaTags.HalfWidth, string.Empty, "epi-icon__layout--half");
            displayOption.Add("narrow", "/displayoptions/narrow", Global.ContentAreaTags.OneThirdWidth, string.Empty, "epi-icon__layout--one-third");
        });


        services.Configure<MvcOptions>(options => options.Filters.Add<PageContextActionFilter>());

        services.AddDisplayResolutions();
        services.AddDetection();

        return services;
    }

    private static void AddDisplayResolutions(this IServiceCollection services)
    {
        services.AddSingleton<StandardResolution>();
        services.AddSingleton<IpadHorizontalResolution>();
        services.AddSingleton<IphoneVerticalResolution>();
        services.AddSingleton<AndroidVerticalResolution>();
    }
}

问题排查与解决方案

1. 认证中间件提前终止请求

OnRedirectToIdentityProvider事件开头的if (context.Response.StatusCode == 401)直接调用context.HandleResponse(),会终止后续请求处理流程,导致请求无法到达Optimizely内容路由。

  • 修复:注释或移除该段代码,确保未认证请求能正常触发OpenID Connect挑战流程:
// if (context.Response.StatusCode == 401)
// {
//     context.HandleResponse();
// }

2. 路由注册顺序错误

UseEndpoints中重复注册了MapControllers和MapRazorPages,且MapContent()(Optimizely内容路由)的位置靠后,导致MVC默认路由优先匹配,无法触发StartPage Controller。

  • 修复:调整路由注册顺序,优先注册Optimizely内容路由,移除重复注册:
app.UseEndpoints(endpoints =>
{
    endpoints.MapContent(); // 优先注册Optimizely内容路由
    endpoints.MapControllers();
    endpoints.MapRazorPages();
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

3. 首页与登录页配置异常

OnRedirectToIdentityProvider中直接访问ContentReference.StartPage和startPage.LoginPage,如果CMS中未正确设置首页或登录页,会导致空引用或无效重定向,最终返回空白页。

  • 修复:增加空值检查,处理配置异常场景:
if (ContentReference.StartPage.ID != 0)
{
    var startPage = contentLoader.Get<StartPage>(ContentReference.StartPage);
    if(startPage?.LoginPage != null)
    {
        var loginPageUrl = startPage.LoginPage.GetPublicUrl();
        // 原有重定向逻辑
    }
    else
    {
        context.HttpContext.Response.StatusCode = 404;
        await context.HttpContext.Response.WriteAsync("登录页未配置");
        context.HandleResponse();
        return;
    }
}

4. 开发环境错误页未生效

确保开发环境下启用详细错误页,方便排查隐藏异常:

  • 确认env.IsDevelopment()分支下的app.UseDeveloperExceptionPage()正常执行,可临时强制启用错误页测试:
// 临时添加,测试后移除
app.UseDeveloperExceptionPage();

内容的提问来源于stack exchange,提问作者surya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 13:32:00