コード例 #1
0
        public async Task Can_Load_Story_With_Resolved_Links()
        {
            var story = await _client.Story().WithSlug("/en/page-sections-buttons").ResolveLinks(ResolveLinksType.Url).Load <PageComponent>();

            var section = story !.Content !.Content !.First() as SectionComponent;
            var grid    = section !.Content !.First() as Grid1x1Component;
            var button  = grid !.Right !.First() as ButtonComponent;

            Assert.NotNull(button);
            Assert.NotNull(button?.Link?.Story);
            Assert.NotEqual(button?.Link?.CachedValue, button?.Link?.Story?.FullSlug);
            Assert.Equal(button?.Link?.Url, "/" + button?.Link?.Story?.FullSlug);
        }
コード例 #2
0
        public HeaderViewModel(StoryblokStoriesClient storiesClient, StoryblokStoryClient storyClient)
        {
            var pages = storiesClient.Stories()
                        .StartingWith("")
                        .ExcludingFields("body", "title", "description", "keywords")
                        .Having("menu_title", FilterOperation.NotIn, "")
                        .Load <PageComponent>().GetAwaiter().GetResult();

            var storiesForNavigation = new List <NavigationItem>();

            foreach (var minimalStory in pages)
            {
                if (!string.IsNullOrWhiteSpace(minimalStory.Content?.MenuTitle))
                {
                    var story = storyClient.Story().WithCulture(CultureInfo.CurrentUICulture).WithSlug(minimalStory.FullSlug).Load().GetAwaiter().GetResult();

                    storiesForNavigation.Add(new NavigationItem
                    {
                        FullSlug = story.FullSlug,
                        Title    = ((PageComponent)story.Content).MenuTitle,
                        Order    = ((PageComponent)story.Content).MenuOrder
                    });
                }
            }

            StoriesForNavigation = storiesForNavigation.OrderBy(x => x.Order);
        }
コード例 #3
0
        public async Task <HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
        {
            try
            {
                var story = await _storyblok.Story().WithSlug(_settings.SlugForHealthCheck).Load();

                if (story?.Content == null)
                {
                    throw new Exception("Story of story content is null.");
                }

                return(await Task.FromResult(HealthCheckResult.Healthy("Storyblok is healthy.")));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Storyblok health check failed.");
                return(await Task.FromResult(HealthCheckResult.Unhealthy("Storyblok is not healthy.")));
            }
        }
コード例 #4
0
        // ReSharper disable once UnusedMember.Global
        public async Task Invoke(HttpContext context, StoryblokStoryClient storyblokClient, IOptions <StoryblokOptions> options, ILogger <StoryblokMiddleware> logger)
        {
            var settings = options.Value;

            var slug = context.Request.Path.ToString();

            if (string.IsNullOrWhiteSpace(slug))
            {
                logger.LogTrace("Ignoring request, because no slug available.");
                await _next.Invoke(context);

                return;
            }

            if (!string.IsNullOrWhiteSpace(settings.SlugForClearingCache) && settings.SlugForClearingCache.Trim('/').Equals(slug.Trim('/'), StringComparison.InvariantCultureIgnoreCase))
            {
                storyblokClient.ClearCache();
                context.Response.StatusCode = (int)HttpStatusCode.OK;
                await context.Response.WriteAsync("Cache cleared.");

                return;
            }

            // only accept GET requests
            // please note that we check for the cache clearing slug above, because that's a POST
            if (context.Request.Method != HttpMethods.Get)
            {
                logger.LogTrace("Ignoring request, because it's not GET.");
                await _next.Invoke(context);

                return;
            }

            if (!string.IsNullOrWhiteSpace(settings.HandleRootWithSlug) && slug.Equals("/", StringComparison.InvariantCultureIgnoreCase))
            {
                logger.LogTrace($"Swapping slug from \"{slug}\" to \"{settings.HandleRootWithSlug}\", because it's the root URL.");
                slug = settings.HandleRootWithSlug;
            }
            else if (slug.Equals("/", StringComparison.InvariantCultureIgnoreCase))
            {
                // we are on the root path, and we shouldn't handle it - so we bail out
                logger.LogTrace("Ignoring request, because it's the root URL which is configured to be ignored.");
                await _next.Invoke(context);

                return;
            }

            slug = slug.Trim('/');

            var isInStoryblokEditor = context.Request.Query.IsInStoryblokEditor(settings);

            if (isInStoryblokEditor)
            {
                // make sure we can display inside of the Storyblok iframe
                context.Response.Headers.Add("Content-Security-Policy", "frame-ancestors 'self' app.storyblok.com");
            }

            if (settings.IgnoreSlugs.Any(x => slug.Equals(x.Trim('/'), StringComparison.InvariantCultureIgnoreCase)))
            {
                // don't handle this slug in the middleware, because exact match of URL
                logger.LogTrace($"Ignoring request \"{slug}\", because it's configured to be ignored (exact match).");
                await _next.Invoke(context);

                return;
            }

            if (settings.IgnoreSlugs.Any(x => x.EndsWith("*", StringComparison.InvariantCultureIgnoreCase) && slug.StartsWith(x.TrimEnd('*').Trim('/'), StringComparison.InvariantCultureIgnoreCase)))
            {
                // don't handle this slug in the middleware, because the configuration ends with a *, which means we compare via StartsWith
                logger.LogTrace($"Ignoring request \"{slug}\", because it's configured to be ignored (partial match).");
                await _next.Invoke(context);

                return;
            }

            StoryblokStory?story = null;

            // special handling of Storyblok preview URLs that contain the language, like ~/de/home vs. ~/home
            // if we have such a URL, we also change the current culture accordingly
            CultureInfo currentCulture = CultureInfo.CurrentUICulture;

            foreach (var supportedCulture in settings.SupportedCultures)
            {
                if (slug.StartsWith($"{supportedCulture}/", StringComparison.OrdinalIgnoreCase) || slug.Equals(supportedCulture, StringComparison.OrdinalIgnoreCase))
                {
                    var slugWithoutCulture = slug.Substring(supportedCulture.Length).Trim('/');

                    if (slugWithoutCulture.Equals("") && !string.IsNullOrWhiteSpace(settings.HandleRootWithSlug))
                    {
                        logger.LogTrace($"Swapping slug from \"{slug}\" to \"{settings.HandleRootWithSlug}\", because it's the root URL.");
                        slugWithoutCulture = settings.HandleRootWithSlug;
                    }

                    logger.LogTrace($"Trying to load story for slug \"{slugWithoutCulture}\" for culture {supportedCulture}.");
                    currentCulture = new CultureInfo(supportedCulture);
                    story          = await storyblokClient.Story().WithCulture(currentCulture).WithSlug(slugWithoutCulture).Load();

                    break;
                }
            }

            // we don't have the language in the URL, so we force the default language
            if (story == null)
            {
                currentCulture = new CultureInfo(settings.SupportedCultures.First());
                story          = await storyblokClient.Story().WithCulture(currentCulture).WithSlug(slug).Load();
            }

            // load the story with the current culture (usually set by request localization
            story ??= await storyblokClient.Story().WithCulture(CultureInfo.CurrentUICulture).WithSlug(slug).Load();

            // that's not a story, lets continue down the middleware chain
            if (story == null)
            {
                logger.LogTrace("Ignoring request, because no matching story found.");
                await _next.Invoke(context);

                return;
            }

            var componentName     = story.Content.Component;
            var componentMappings = StoryblokMappings.Mappings;

            if (!componentMappings.ContainsKey(componentName))
            {
                throw new Exception($"No component mapping found for a component '{componentName}'.");
            }

            var componentMapping = componentMappings[componentName];

            if (string.IsNullOrWhiteSpace(componentMapping.View))
            {
                logger.LogTrace($"Ignoring request, because no view specified on component of type \"{componentMapping.Type.FullName}\".");
                await _next.Invoke(context);

                return;
            }

            // we have a story, yay! Lets render it and stop with the middleware chain
            logger.LogTrace($"Rendering slug \"{slug}\" with view \"{componentMapping.View}\".");
            CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture = currentCulture; // set the thread culture to match the story

            // store the culture in the cookie, but only if there's not a cookie yet
            if (options.Value.ConfigureRequestLocalization &&
                !string.IsNullOrWhiteSpace(options.Value.CultureCookieName) &&
                !context.Request.Cookies.ContainsKey(options.Value.CultureCookieName))
            {
                context.Response.Cookies.Append(
                    options.Value.CultureCookieName,
                    CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(currentCulture)),
                    new CookieOptions
                {
                    MaxAge      = TimeSpan.FromDays(365),
                    IsEssential = true,
                    Secure      = false,
                    HttpOnly    = true
                });
            }

            var result = new ViewResult {
                ViewName = componentMapping.View
            };
            var modelMetadata   = new EmptyModelMetadataProvider();
            var modelDefinition = typeof(StoryblokStory <>).MakeGenericType(componentMapping.Type);
            var model           = Activator.CreateInstance(modelDefinition, story);

            result.ViewData = new ViewDataDictionary(modelMetadata, new ModelStateDictionary())
            {
                Model = model
            };
            await WriteResultAsync(context, result);
        }