{"id":23050848,"url":"https://github.com/geta/epicategories","last_synced_at":"2025-08-15T03:31:46.280Z","repository":{"id":18863282,"uuid":"82556406","full_name":"Geta/EpiCategories","owner":"Geta","description":"An alternative to Episerver's default category functionality, where categories are instead stored as localizable IContent.","archived":false,"fork":false,"pushed_at":"2022-12-07T19:31:13.000Z","size":41254,"stargazers_count":15,"open_issues_count":26,"forks_count":13,"subscribers_count":8,"default_branch":"master","last_synced_at":"2023-10-20T19:59:01.879Z","etag":null,"topics":["cms"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Geta.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-02-20T12:39:54.000Z","updated_at":"2023-10-20T19:59:01.880Z","dependencies_parsed_at":"2023-01-11T20:30:00.060Z","dependency_job_id":null,"html_url":"https://github.com/Geta/EpiCategories","commit_stats":null,"previous_names":[],"tags_count":0,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2FEpiCategories","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2FEpiCategories/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2FEpiCategories/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2FEpiCategories/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Geta","download_url":"https://codeload.github.com/Geta/EpiCategories/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229890100,"owners_count":18140042,"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":["cms"],"created_at":"2024-12-15T23:38:55.170Z","updated_at":"2024-12-15T23:38:55.659Z","avatar_url":"https://github.com/Geta.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EpiCategories\n\n* Master\u003cbr\u003e\n![](http://tc.geta.no/app/rest/builds/buildType:(id:GetaPackages_EPiCategories_00ci_2),branch:master/statusIcon)\n\n## Description\nAn alternative to Episerver's default category functionality, where categories are instead stored as localizable IContent.\n\n## Features\n* Localization (no more language XML files)\n* More user friendly edit UI\n* Access rights support (some editors should perhaps have limited category access)\n* Shared and site specific categories in multisite solutions\n* Partial routing of category URL segments\n  \n## How to install\nInstall NuGet package from Episerver NuGet Feed:\n\n\tInstall-Package Geta.EpiCategories\n\t\n## CMS search providers (Episerver Find or Episerver Search)\nIf you want to install a CMS search provider you can choose from Geta.EpiCategories.Find or Geta.EpiCategories.Search package depending on if you have Episerver Find installed in your project or not:\n\n\tInstall-Package Geta.EpiCategories.Search\n\t\nor:\n\n\tInstall-Package Geta.EpiCategories.Find\n\n\n## How to use\nStart by creating a category content type that inherits from CategoryData. You can have multiple if you need. Note that CategoryData is included in the package, there is no need to create your own.\n\n\t[ContentType]\n\tpublic class BasicCategory : CategoryData\n\t{\n\t}\n\t\n\t[ContentType]\n\tpublic class ExtendedCategory : BasicCategory\n\t{\n\t\t[CultureSpecific]\n\t\tpublic virtual XhtmlString MainBody { get; set; }\n\t}\n\n### Edit categories\nInstead of going to admin mode to manage categories, you now do it in edit mode, under the \"Categories\" tab in the main navigation component to the left. You work with them like normal pages, and it's possible to translate them. You can create categories that are shared between multiple sites or you can create site specific categories.\n\n![ScreenShot](/docs/extended-category-tree.jpg)\n\n### ICategorizableContent interface\nImplement ICategorizableContent on your content type class to categorize your content.\n\n\tpublic class MyPageType : PageData, ICategorizableContent\n\t{\n\t\t[Categories]\n\t\tpublic virtual IList\u003cContentReference\u003e Categories { get; set; }\n\t}\n\t\nAbove property will look familiar if you have used standard Episerver categories before.\n\n![ScreenShot](/docs/category-selector.jpg)\n![ScreenShot](/docs/category-selector-dialog.jpg)\n\nThere is a context menu in the selector where you quickly can create and auto publish a new category and automatically get back to the selector with the new category selected:\n\n![ScreenShot](/docs/category-selector-dialog-create-new.jpg)\n\nIf you prefer to use the native content reference list editor for your categories you can skip the CategoriesAttribute:\n\n\t[AllowedTypes(typeof(CategoryData))]\n\tpublic virtual IList\u003cContentReference\u003e Categories { get; set; }\n\n![ScreenShot](/docs/content-reference-list.jpg)\n\nIf you want a single category on your content type just add a ContentReference property:\n\n\t[UIHint(CategoryUIHint.Category)]\n\tpublic virtual ContentReference MainCategory { get; set; }\n\n### IEnumerable\u0026lt;ContentReference\u003e extension methods\nThe following extension methods are included:\n\n1. MemberOf(this IEnumerable\u0026lt;ContentReference\u003e contentLinks, ContentReference contentReference)\n2. MemberOfAny(this IEnumerable\u0026lt;ContentReference\u003e contentLinks, IEnumerable\u0026lt;ContentReference\u003e otherContentLinks)\n3. MemberOfAll(this IEnumerable\u0026lt;ContentReference\u003e contentLinks, IEnumerable\u0026lt;ContentReference\u003e otherContentLinks)\n\n### ICategoryContentLoader interface\nThere is an implementation of ICategoryContentLoader (note that in 1.0.0 it is mistakenly named ICategoryContentRepository) that you can use to load categories:\n\n```\npublic interface ICategoryContentLoader\n{\n    T Get\u003cT\u003e(ContentReference categoryLink) where T : CategoryData;\n    IEnumerable\u003cT\u003e GetChildren\u003cT\u003e(ContentReference parentCategoryLink) where T : CategoryData;\n    IEnumerable\u003cT\u003e GetChildren\u003cT\u003e(ContentReference parentCategoryLink, CultureInfo culture) where T : CategoryData;\n    IEnumerable\u003cT\u003e GetChildren\u003cT\u003e(ContentReference parentCategoryLink, LoaderOptions loaderOptions) where T : CategoryData;\n    T GetFirstBySegment\u003cT\u003e(string urlSegment) where T : CategoryData;\n    T GetFirstBySegment\u003cT\u003e(string urlSegment, CultureInfo culture) where T : CategoryData;\n    T GetFirstBySegment\u003cT\u003e(string urlSegment, LoaderOptions loaderOptions) where T : CategoryData;\n    T GetFirstBySegment\u003cT\u003e(ContentReference parentLink, string urlSegment, LoaderOptions loaderOptions) where T : CategoryData;\n    IEnumerable\u003cT\u003e GetGlobalCategories\u003cT\u003e() where T : CategoryData;\n    IEnumerable\u003cT\u003e GetGlobalCategories\u003cT\u003e(CultureInfo culture) where T : CategoryData;\n    IEnumerable\u003cT\u003e GetGlobalCategories\u003cT\u003e(LoaderOptions loaderOptions) where T : CategoryData;\n    IEnumerable\u003cT\u003e GetSiteCategories\u003cT\u003e() where T : CategoryData;\n    IEnumerable\u003cT\u003e GetSiteCategories\u003cT\u003e(CultureInfo culture) where T : CategoryData;\n    IEnumerable\u003cT\u003e GetSiteCategories\u003cT\u003e(LoaderOptions loaderOptions) where T : CategoryData;\n    bool TryGet\u003cT\u003e(ContentReference categoryLink, out T category) where T : CategoryData;\n}\n```\n\nInject it in your controller as you are used to:\n\n```\npublic class MyController : Controller\n{\n    private readonly ICategoryContentLoader _categoryLoader;\n\n    public MyController(ICategoryContentLoader categoryLoader)\n    {\n        _categoryLoader = categoryLoader;\t\n    }\n}\n```\n\n### IContentInCategoryLocator interface\nYou can use IContentInCategoryLocator to find content in certain categories:\n\n```\npublic interface IContentInCategoryLocator\n{\n    IEnumerable\u003cT\u003e GetDescendents\u003cT\u003e(ContentReference contentLink, IEnumerable\u003cContentReference\u003e categories) where T : ICategorizableContent, IContent;\n    IEnumerable\u003cT\u003e GetDescendents\u003cT\u003e(ContentReference contentLink, IEnumerable\u003cContentReference\u003e categories, CultureInfo culture) where T : ICategorizableContent, IContent;\n    IEnumerable\u003cT\u003e GetDescendents\u003cT\u003e(ContentReference contentLink, IEnumerable\u003cContentReference\u003e categories, LoaderOptions loaderOptions) where T : ICategorizableContent, IContent;\n    IEnumerable\u003cT\u003e GetChildren\u003cT\u003e(ContentReference contentLink, IEnumerable\u003cContentReference\u003e categories) where T : ICategorizableContent, IContent;\n    IEnumerable\u003cT\u003e GetChildren\u003cT\u003e(ContentReference contentLink, IEnumerable\u003cContentReference\u003e categories, CultureInfo culture) where T : ICategorizableContent, IContent;\n    IEnumerable\u003cT\u003e GetChildren\u003cT\u003e(ContentReference contentLink, IEnumerable\u003cContentReference\u003e categories, LoaderOptions loaderOptions) where T : ICategorizableContent, IContent;\n    IEnumerable\u003cT\u003e GetReferencesToCategories\u003cT\u003e(IEnumerable\u003cContentReference\u003e categories) where T : ICategorizableContent, IContent;\n    IEnumerable\u003cT\u003e GetReferencesToCategories\u003cT\u003e(IEnumerable\u003cContentReference\u003e categories, CultureInfo culture) where T : ICategorizableContent, IContent;\n    IEnumerable\u003cT\u003e GetReferencesToCategories\u003cT\u003e(IEnumerable\u003cContentReference\u003e categories, LoaderOptions loaderOptions) where T : ICategorizableContent, IContent;\n}\n```\n\n## Routing\nTwo routes are mapped during initialization. One for site categories and one for global categories. This means you can create templates for your category content types. They are routed in a similar way as normal pages. You can set the root segment on the \"For This Site\" and \"For All Sites\" category nodes in the Categories tree.\n\n![ScreenShot](/docs/for-this-site.jpg)\n\nUsing above example, the URL \"/topics/sports/\" would be routed to the site category called \"Sports\".\n\n### ICategoryRoutableContent interface\nImplement this on your content type:\n\n    public class ArticleListPage: PageData, ICategoryRoutableContent \n\t{\n\t}\n\n  It will be possible to route category URL segments with the help of a partial router shipped in this package. Let's say you have an article list page with the URL \"/articles/\" on your site. If you have a category with the url segment of \"sports\", you can add it to the end of your list page URL, \"/articles/sports/\", and the category data will be added to the route values with the key \"currentCategory\". Your controller action method could look something like this:\n\n\tpublic ActionResult Index(ArticleListPage currentPage, CategoryData currentCategory)\n\t{\n\t}\n\nYou can also have multiple category URL segments separated with the configured category separator: /articles/entertainment__sports/.\n\n\tpublic ActionResult Index(ArticleListPage currentPage, IList\u003cCategoryData\u003e currentCategories) // currentCategories will now contain \"Sports\" and \"Entertainment\" categories.\n\t{\n\t}\n\nDefault category separator is \"__\" and you can change it by adding an appSetting in web.config:\n\n\t\u003cadd key=\"GetaEpiCategories:CategorySeparator\" value=\"__\" /\u003e\n\t\nThere is a couple of UrlHelper and UrlResolver extension methods included to get content URL with category segment added:\n\n\t@Url.CategoryRoutedContentUrl(/*ContentReference*/ contentLink, /*ContentReference*/ categoryContentLink) // Single category\n\t@Url.CategoryRoutedContentUrl(/*ContentReference*/ contentLink, /*IEnumerable\u003cContentReference\u003e*/ categoryContentLinks) // Multiple categories\n\n\t@UrlResolver.Current.GetCategoryRoutedUrl(/*ContentReference*/ contentLink, /*ContentReference*/ categoryContentLink) // Single category\n\t@UrlResolver.Current.GetCategoryRoutedUrl(/*ContentReference*/ contentLink, /*IEnumerable\u003cContentReference\u003e*/ categoryContentLinks) // Multiple categories\n\t\n## Show default Episerver category\nThis package hides the default Episerver category property. You can opt-out from this by adding an app setting:\n\n    \u003cadd key=\"GetaEpiCategories:ShowDefaultCategoryProperty\" value=\"true\" /\u003e\n\t\n## Hide disallowed root categories\nThe default behavior is to show all categories in the selector, while it's only possible to select based on [AllowedTypes] setting. If you want to hide all root categories that doesn't match AllowedTypes you can add this app setting:\n\n    \u003cadd key=\"GetaEpiCategories:HideDisallowedRootCategories\" value=\"true\" /\u003e\n\n## Sandbox App\nSandbox application is testing poligon for package new features and bug fixes.\n\nCMS username: epiadmin\n\nPassword: 3p!Pass\n\n## Package maintainer\nhttps://github.com/MattisOlsson\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeta%2Fepicategories","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeta%2Fepicategories","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeta%2Fepicategories/lists"}