{"id":23084732,"url":"https://github.com/juryrigcoder/storybloksharp","last_synced_at":"2026-02-25T09:41:03.259Z","repository":{"id":268402806,"uuid":"904230145","full_name":"juryrigcoder/StoryblokSharp","owner":"juryrigcoder","description":"Dotnet implementation of Storyblok client","archived":false,"fork":false,"pushed_at":"2025-01-18T07:11:46.000Z","size":120,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-30T15:27:28.315Z","etag":null,"topics":["csharp","dotnet","storyblok"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/juryrigcoder.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"contributing.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-16T13:47:14.000Z","updated_at":"2025-01-19T09:33:06.000Z","dependencies_parsed_at":null,"dependency_job_id":"a15340f6-fbbb-4a34-923e-c326ae729dc7","html_url":"https://github.com/juryrigcoder/StoryblokSharp","commit_stats":{"total_commits":5,"total_committers":1,"mean_commits":5.0,"dds":0.0,"last_synced_commit":"08b0072fb5865324cff12120a0c7b004775863d7"},"previous_names":["juryrigcoder/storybloksharp"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juryrigcoder%2FStoryblokSharp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juryrigcoder%2FStoryblokSharp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juryrigcoder%2FStoryblokSharp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juryrigcoder%2FStoryblokSharp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/juryrigcoder","download_url":"https://codeload.github.com/juryrigcoder/StoryblokSharp/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251731110,"owners_count":21634506,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","storyblok"],"created_at":"2024-12-16T16:43:11.520Z","updated_at":"2026-02-25T09:41:03.230Z","avatar_url":"https://github.com/juryrigcoder.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# StoryblokSharp\n\nA modern .NET client for the Storyblok Headless CMS API. This project is a C# port of the official [storyblok-js-client](https://github.com/storyblok/storyblok-js-client), maintaining feature parity while providing a strongly-typed interface for .NET applications. It supports both Storyblok's Content Delivery API (v2) and Management API (v1).\n\n## Rationale\n\nAs a personal project be kind! This is shared as is and is not for production.\n\n## Acknowledgements\n\nThis project is a port of the [storyblok-js-client](https://github.com/storyblok/storyblok-js-client) JavaScript library, created and maintained by the Storyblok team ([@storyblok](https://github.com/storyblok)). We are grateful for their excellent work which serves as the foundation for this .NET implementation.\n\nAdditionally, we've included the richtext implementation of [richtext](https://github.com/storyblok/richtext) Javascript library, used to interact with the richtext components.\n\nLicense: This project, like the original storyblok-js-client, is licensed under MIT.\n\nOriginal Client Repository: https://github.com/storyblok/storyblok-js-client\nOriginal Richtext Repository: https://github.com/storyblok/richtext\n\n## Heritage\n\nThis library is based on the official [storyblok-js-client](https://github.com/storyblok/storyblok-js-client) JavaScript library and follows the same core principles and features. We've tried to carefully translated the JavaScript implementation to idiomatic C#, taking advantage of .NET's type system and modern features while maintaining compatibility with Storyblok's APIs. Key aspects we've preserved include:\n\n- Same configuration options and initialization patterns\n- Compatible caching mechanisms\n- Equivalent rate limiting and retry logic\n- Parallel rich text rendering capabilities\n- Similar helper utilities and extension methods\n\n## Features\n\n- 🚀 Built for modern .NET (targets .NET 9.0)\n- 💪 Strongly typed story content\n- 🔄 Automatic rate limiting and retry handling\n- 💾 Configurable caching\n- 🔍 Rich text rendering\n- 🔒 Thread-safe operations\n- 📦 Available as a NuGet package\n- 🔗 Dependency injection friendly\n- 🏎️ Async/await support\n\n## Also:\n\n- 💡Support for both published and draft content\n- 💡Comprehensive story querying and filtering\n- 💡Rich text rendering with customizable schema\n- 💡Built-in caching with memory and custom providers\n- 💡Rate limiting and retry handling\n- 💡Blazor component integration\n- 💡Image optimization\n- 💡HTML sanitization\n\n## Installation\n\nInstall via Github packages - NuGet:\n\n```bash\ndotnet add package StoryblokSharp\n```\n\nThe NuGet package is hosted in this GitHub repository's package registry.\n\n\n## Story Retrieval\n\n### Single Story\n\nRetrieve a single story by its slug:\n\n```csharp\n// Get a published story\nvar story = await _client.GetStoryAsync\u003cHomeContent\u003e(\"home\", new StoryQueryParameters \n{\n    Version = \"published\"\n});\n\n// Get a draft story\nvar draftStory = await _client.GetStoryAsync\u003cHomeContent\u003e(\"home\", new StoryQueryParameters \n{\n    Version = \"draft\"\n});\n\n// Get a story in a specific language\nvar germanStory = await _client.GetStoryAsync\u003cHomeContent\u003e(\"home\", new StoryQueryParameters \n{\n    Version = \"published\",\n    Language = \"de\"\n});\n```\n\n### Multiple Stories\n\nRetrieve multiple stories with filtering and pagination:\n\n```csharp\n// Get all published stories\nvar stories = await _client.GetStoriesAsync\u003cBlogPost\u003e(new StoryQueryParameters \n{\n    Version = \"published\",\n    PerPage = 10,\n    Page = 1\n});\n\n// Get stories with specific tags\nvar taggedStories = await _client.GetStoriesAsync\u003cBlogPost\u003e(new StoryQueryParameters \n{\n    WithTag = \"featured\",\n    SortBy = \"created_at:desc\"\n});\n\n// Get all stories (handles pagination automatically)\nvar allStories = await _client.GetAllAsync\u003cBlogPost\u003e(\n    \"cdn/stories\",\n    new StoryQueryParameters \n    {\n        StartsWith = \"blog/\"\n    }\n);\n```\n\n### Query Parameters\n\nThe `StoryQueryParameters` class provides various filtering and sorting options:\n\n```csharp\nvar parameters = new StoryQueryParameters\n{\n    // Version control\n    Version = \"published\", // or \"draft\"\n    \n    // Pagination\n    PerPage = 10,\n    Page = 1,\n    \n    // Filtering\n    StartsWith = \"blog/\",\n    WithTag = \"featured\",\n    SearchTerm = \"tutorial\",\n    ExcludingFields = \"body,image\",\n    \n    // Sorting\n    SortBy = \"created_at:desc\",\n    \n    // Language\n    Language = \"en\",\n    FallbackLang = \"de\",\n    \n    // Relations\n    ResolveLinks = \"1\",\n    ResolveRelations = new[] { \"author\", \"categories\" },\n    ResolveLevel = 2\n};\n```\n\n### Working with Content Types\n\nYou can create strongly-typed models for your content:\n\n```csharp\npublic class BlogPost\n{\n    public string Title { get; set; }\n    public string Slug { get; set; }\n    public RichTextField Content { get; set; }\n    public Asset FeaturedImage { get; set; }\n    public DateTime PublishedDate { get; set; }\n    public string[] Tags { get; set; }\n}\n\n// Use the typed model\nvar response = await _client.GetStoryAsync\u003cBlogPost\u003e(\"my-blog-post\");\nvar title = response.Story.Content.Title;\nvar content = response.Story.Content.Content;\n```\n\n## Basic Usage\n\n```csharp\n// Configure services\nservices.AddStoryblokClient(builder =\u003e builder\n    .WithAccessToken(\"your_access_token\")\n    .WithCache(options =\u003e options\n        .WithType(CacheType.Memory)\n        .WithDefaultExpiration(TimeSpan.FromMinutes(5)))\n    .WithMaxRetries(3)\n    .WithRateLimit(5));\n\n// Inject and use the client\npublic class MyService\n{\n    private readonly IStoryblokClient _client;\n\n    public MyService(IStoryblokClient client)\n    {\n        _client = client;\n    }\n\n    public async Task\u003cStory\u003cT\u003e\u003e GetStoryAsync\u003cT\u003e() where T : class\n    {\n        var parameters = new StoryQueryParameters\n        {\n            Version = \"published\",\n            Language = \"en\"\n        };\n\n        var response = await _client.GetStoryAsync\u003cT\u003e(\"home\", parameters);\n        return response.Story;\n    }\n}\n```\n\n## Advanced Configuration\n\nThe client can be configured with various options:\n\n```csharp\nservices.AddStoryblokClient(builder =\u003e builder\n    .WithAccessToken(\"your_access_token\")\n    .WithOAuthToken(\"your_oauth_token\") // For management API\n    .WithRegion(Region.EU)\n    .WithHttps()\n    .WithMaxRetries(3)\n    .WithTimeout(30)\n    .WithRateLimit(5)\n    .WithHeaders(new Dictionary\u003cstring, string\u003e\n    {\n        [\"Custom-Header\"] = \"Value\"\n    })\n    .WithCache(options =\u003e options\n        .WithType(CacheType.Memory)\n        .WithDefaultExpiration(TimeSpan.FromMinutes(5)))\n    .WithCustomCache(new YourCustomCacheProvider())\n    .WithRichTextSchema(new YourCustomSchema())\n    .WithComponentResolver((type, props) =\u003e $\"\u003cdiv\u003e{type}\u003c/div\u003e\")\n    .WithResponseInterceptor(async response =\u003e\n    {\n        // Custom response handling\n        return response;\n    }));\n```\n\n## Rich Text Rendering\n\nThe library includes a powerful rich text renderer with support for custom resolvers:\n\n```csharp\nservices.Configure\u003cRichTextOptions\u003e(options =\u003e\n{\n    options.OptimizeImages = true;\n    options.KeyedResolvers = true;\n    options.InvalidNodeHandling = InvalidNodeStrategy.Remove;\n    options.MarkSortPriority = new[]\n    {\n        MarkTypes.Bold,\n        MarkTypes.Italic,\n        MarkTypes.Link\n    };\n});\n```\n\n## Blazor Integration\n\nStoryblokSharp provides seamless integration with Blazor components:\n\n```csharp\n// Register Blazor component resolver\nservices.AddBlazorComponentResolver();\n\n// Register a component\nservices.AddStoryblokComponent\u003cHeroComponent\u003e(\"hero\");\n\n// Create a Blazor component\npublic class HeroComponent : ComponentBase, IComponent\n{\n    [Parameter]\n    public string Title { get; set; }\n\n    [Parameter]\n    public string Subtitle { get; set; }\n\n    protected override void BuildRenderTree(RenderTreeBuilder builder)\n    {\n        builder.OpenElement(0, \"div\");\n        builder.AddAttribute(1, \"class\", \"hero\");\n        \n        builder.OpenElement(2, \"h1\");\n        builder.AddContent(3, Title);\n        builder.CloseElement();\n        \n        builder.OpenElement(4, \"p\");\n        builder.AddContent(5, Subtitle);\n        builder.CloseElement();\n        \n        builder.CloseElement();\n    }\n}\n\n// Use in Storyblok content\n@{\n    var story = await StoryblokClient.GetStoryAsync\u003cdynamic\u003e(\"home\");\n    var component = story.Story.Content.hero;\n}\n\n\u003cStoryblokComponent Type=\"hero\" Props=\"@component\" /\u003e\n```\n\n## Image Optimization\n\nConfigure image optimization options:\n\n```csharp\nservices.Configure\u003cRichTextOptions\u003e(options =\u003e\n{\n    options.OptimizeImages = true;\n    options.ImageOptions = new ImageOptimizationOptions\n    {\n        Width = 800,\n        Height = 600,\n        Loading = \"lazy\",\n        Class = \"optimized-image\",\n        SrcSet = new[]\n        {\n            new SrcSetEntry { Width = 400 },\n            new SrcSetEntry { Width = 800 },\n            new SrcSetEntry { Width = 1200 }\n        },\n        Sizes = new[]\n        {\n            \"(max-width: 400px) 100vw\",\n            \"(max-width: 800px) 50vw\",\n            \"800px\"\n        },\n        Filters = new ImageFilters\n        {\n            Quality = 80,\n            Format = \"webp\",\n            Grayscale = false\n        }\n    }\n});\n```\n\n## Security\n\nThe library includes built-in HTML sanitization:\n\n```csharp\nservices.Configure\u003cHtmlSanitizerOptions\u003e(options =\u003e\n{\n    options.AllowedTags.Add(\"custom-tag\");\n    options.AllowedAttributes[\"a\"].Add(\"rel\");\n    options.AllowedProtocols.Add(\"tel\");\n});\n```\n\n## Contributing\n\nContributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Support\n\nYou can customize story queries with various parameters:\n\n```csharp\nvar parameters = new StoryQueryParameters\n{\n    Version = \"draft\",                    // 'draft' or 'published'\n    Language = \"en\",                      // Language code\n    ResolveLinks = \"story\",              // How to resolve links\n    ResolveRelations = new[] { \"author\" },// Relations to resolve\n    ExcludingFields = \"long_text\",       // Fields to exclude\n    Sort_by = \"position:desc\",           // Sort order\n    StartsWith = \"blog/\",                // Filter by path\n    WithTag = \"featured\",                // Filter by tag\n    Page = 1,                            // Page number\n    PerPage = 10                         // Items per page\n};\n\nvar stories = await _storyblok.GetStoriesAsync\u003cBlogPostContent\u003e(parameters);\n```\n\n\n## Security\n\nThe library includes built-in HTML sanitization:\n\n```csharp\nservices.Configure\u003cHtmlSanitizerOptions\u003e(options =\u003e\n{\n    options.AllowedTags.Add(\"custom-tag\");\n    options.AllowedAttributes[\"a\"].Add(\"rel\");\n    options.AllowedProtocols.Add(\"tel\");\n});\n```\n\n## Contributing\n\nContributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Support\n\n- [Documentation](https://github.com/juryrigcoder/storybloksharp/wiki)\n- [Issue Tracker](https://github.com/juryrigcoder/storybloksharp/issues)\n- [Storyblok API Reference](https://www.storyblok.com/docs/api/content-delivery)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuryrigcoder%2Fstorybloksharp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuryrigcoder%2Fstorybloksharp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuryrigcoder%2Fstorybloksharp/lists"}