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); }
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); }
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."))); } }
// 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); }