Our blog contains the activity stream of Orchard Dojo: general news, new resources or tutorials are announced here.

Featured tags

IIS
API
All tags >

How to change the idle logout time in Orchard Core - Orchard Core Nuggets

Keeping user accounts secure is important. One aspect of this is to constrain how long people can remain logged in: If they share a device, especially a public one, then it's better to be on the safe side and automatically log them out after some time of inactivity. Here is how you can do it in Orchard Core! Since an Orchard Core-using app is in the end just an ASP.NET Core app, you can use the standard Identity options to configure how user sessions work. What governs how long users remain logged in is mostly the cookie settings. This is how you can set it from the Program file of your web app: builder.Services .AddOrchardCms() .ConfigureServices(services => services.ConfigureApplicationCookie(options => options.ExpireTimeSpan = TimeSpan.FromMinutes(30))); This logs users out after 30 minutes of inactivity. You can also set options.SlidingExpiration = false if you'd like this to be counted from the time they logged in, as opposed to the last time they did something in the app. While the above example shows how to do this in the root web app, you can similarly set this from the Startup class of a module or theme. That's it! It's worth checking out the official Orchard Core docs on configuring other Identity options too. Did you like this post? It's part of our Orchard Core Nuggets series where we answer common Orchard questions, be it about user-facing features or developer-level issues. Check out the other posts for more such bite-sized Orchard Core tips and let us know if you have another question!

How to do a security scan of an Orchard Core app - Orchard Core Nuggets

You don't want malicious people to crack your web apps to use them for spamming, cryptocurrency mining, and spreading malware, nor do you want them to get access to your users' personal data (if you actually do want to cooperate with criminals, you don't need to read further). Thus, you want your app to be secure. One aspect of achieving this is to do penetration testing on your app. Thankfully, much of this can be automated, and with the help of Lombiq UI Testing Toolbox for Orchard Core and Zed Attack Proxy (ZAP) you can conveniently do this for your Orchard Core app. Let's see how! First, install v8.2.1-alpha.6.osoe-351 or greater of the UI Testing Toolbox from NuGet because that's the one that added security scanning. There are a couple of minor breaking changes that should affect nobody, really, in this, so it'll be part of the upcoming v.9.0.0 (but for that, we're waiting for Orchard Core 1.8). Set up UI testing as explained in the UI Testing Toolbox's documentation. While we're focusing on security scanning here, the UI Testing Toolbox can do a lot, and I really mean a lot more, including one-liners to test if the basic Orchard Core features still work in your app, or unleashing automated monkey testing to try to break your app. We never work on an Orchard Core app without its safety net! Add one or more test cases to run ZAP's security scan. Since all the configuration of ZAP is available to you, customization is unlimited, but to give you a glimpse, this is how a basic security scan that's already a good start would look like: [Fact] public Task BasicSecurityScanShouldPass() => ExecuteTestAfterSetupAsync(context => context.RunAndAssertBaselineSecurityScanAsync()); And that's it! OK, I might have omitted the last step here: 4. Fix all the security issues ZAP finds, because it'll definitely find at least a couple of them! This was just a short teaser, but be sure to check out the UI Testing Toolbox's security scanning documentation, because we tried to summarize everything necessary to get you going there, including samples that you can just copy-paste. Do you want to see security scanning in action? Check out the demo video too! Also, security starts with quality code. Check out our Lombiq .NET Analyzers project to get automated checks for your code too, including pointing out potential security issues even before running the app. Did you like this post? It's part of our Orchard Core Nuggets series where we answer common Orchard questions, be it about user-facing features or developer-level issues. Check out the other posts for more such bite-sized Orchard Core tips and let us know if you have another question!

How to migrate an Orchard 1 application to Orchard Core

How long have you been using Orchard 1 for your website? How satisfied are you with its features and performance? Have you encountered any limitations or challenges with Orchard 1 that you hope to address? If you are looking for a modern and improved version of Orchard 1, you might want to consider migrating to Orchard Core which is a better version of Orchard 1 in many aspects. Orchard Core is not just a port of Orchard 1, but a new and improved system that offers many benefits over its predecessor. What are the benefits of migrating to Orchard Core? You can enjoy numerous remarkable benefits by migrating to Orchard Core. First of all: Performance. As the saying goes: “The faster the better.” Orchard Core is so fast that an output cache module like for Orchard 1 is not required. For example, you can set up a site with the blog recipe in less than half the time that Orchard 1 needs for the same task. It is portable: it can run on Windows, Linux, and macOS, as well as on Docker containers. It has a more flexible and extensible modular framework that allows building modular and multi-tenant applications more easily. Orchard Core also supports NuGet packages for modules and themes, thus creating a new website with it is actually as simple as referencing a single meta package from the NuGet gallery. It is built on ASP.NET Core. This means that it can use any C# language version without any limitations, while Orchard 1 uses .NET Framework 4.8 which means it’s restricted to C# 7.3. It has new features that are not available in Orchard 1, such as GraphQL API, OpenID Connect, Liquid templates support, and more. It supports all major site building strategies: Full CMS, Decupled CMS, Headless CMS. This is just the tip of the iceberg, to read more about Orchard Core check out its documentation. Preparations The first thing to know is that there is no easy or fully automated way to migrate your website and content. The two systems are different in terms of data schema, modules, themes, and features. However, in general, we can say, that proficiency in Orchard 1 puts you in a good position to develop in Orchard Core too. There are a lot of similarities for example menu points on the admin UI, or how migrations work, etc. A good first step would be to take a look at your current application. Do you want to replicate what you have there, or do you also want to improve it? Migration is a good opportunity to renew your site, change design, and functionality, get rid of obsolete elements on the site, etc. You'll also need to know Orchard Core before attempting a migration. If you are new to Orchard Core development, we recommend that you start with our Dojo Course 3 - the full Orchard Core tutorial. This course covers the fundamentals of Orchard Core for both users and developers. New CMS, familiar features Before starting the migration, it is recommended to check your most used, most important features in Orchard 1 and list them. There is a good chance that there is an equivalent feature in Orchard Core, but it changed and has been upgraded, so you will need to do some research. For example, if you want to migrate your custom form, instead of Orchard.DynamicForms you can use OrchardCore.Forms to achieve the same result. Or instead of Orchard.Taxonomies you can use OrchardCore.Taxonomies. The Workflows and Audit Trail modules were ported to Orchard Core and improved too. Migrating content types Content types are composed of content parts and fields, which provide different functionalities and data types for your content. If you want to migrate your content types, you will need to recreate them in the new system. Luckily, as with features most of Orchard 1 content parts and fields have their equivalent in Orchard Core. Some examples are BooleanField, NumericField, TitlePart, or CommonPart. In this case, you can recreate your content type from the admin UI, from a recipe, or with a migration. Let’s say you have a BlogPost content type in Orchard 1, which has the following parts and fields: TitlePart: Provides a title for the blog post. AutoroutePart: Provides a URL for the blog post. BodyPart: Provides a rich text editor for the blog post content. MediaLibraryPickerField: Provides a way to select an index image for the blog post. Now take a look at what we have in Orchard Core. We have TitlePart and AutoroutePart in Orchard Core too, so that is handy, but there is no BodyPart and MediaLibraryPickerField. But after some research, we can find the equivalent of them: HtmlBodyPart and MediaField. We have all the parts and fields in the new CMS to recreate the BlogPost content type. In this example, we are creating it with the help of a migration file BlogPostMigration.cs: using OrchardCore.Autoroute.Models; using OrchardCore.ContentManagement.Metadata; using OrchardCore.ContentManagement.Metadata.Settings; using OrchardCore.Data.Migration; using OrchardCore.Html.Models; using OrchardCore.Media.Settings; using OrchardCore.Title.Models; namespace MyProject.Migrations; public class BlogPostMigration : DataMigration { private readonly IContentDefinitionManager _contentDefinitionManager; public BlogPostMigration(IContentDefinitionManager contentDefinitionManager) => _contentDefinitionManager = contentDefinitionManager; public int Create() { // Define a part called BlogPost with some fields. _contentDefinitionManager.AlterPartDefinition("BlogPost", part => part .WithField("Image", field => field .WithDisplayName("Index Image") .WithPosition("0") .WithSettings(new MediaFieldSettings { Multiple = false, })) ); // Define a type called BlogPost with some parts. _contentDefinitionManager.AlterTypeDefinition("BlogPost", type => type .DisplayedAs("Blog Post") .Creatable() .Listable() .Draftable() .Versionable() .Securable() // Add the Title part to provide a title for the blog post. .WithPart(nameof(TitlePart), part => part .WithPosition("0")) // Add the BlogPost part to provide some fields for the blog post. .WithPart("BlogPost", part => part .WithPosition("1")) // Add Autoroute part and configure it to use a pattern based on the title. .WithPart(nameof(AutoroutePart), part => part .WithPosition("2") .WithSettings(new AutoroutePartSettings { Pattern = "{{ ContentItem | display_text | slugify }}", })) // Add the HTMLBody part to provide a rich text editor for the blog post content. .WithPart(nameof(HtmlBodyPart), part => part .WithPosition("3") .WithEditor("Wysiwyg")) ); return 1; } } …and don’t forget to add your migration to the Startup.cs file to register it. However, if you have a content type with custom fields and parts, with custom functionality, and features, you will also have to recreate those fields and parts by reimplementing your custom code in the new project. Migrating content items The best way to add numerous content items is with a recipe. To migrate the content items, first, make sure that you have the content type created in the new system. After that, you will need to export your content items. The file formats of the recipes are different in the new CMS: Orchard 1 uses XML, while Orchard Core uses JSON, however, their functionality and use cases are similar. You will need to create an Orchard Core recipe: This can be done either manually, or Lombiq has a feature for this in the open-source Helpful Extensions module called Lombiq Helpful Extensions - Orchard 1 Recipe Migration. It has built-in functionality for content items with the most used content parts, but you can extend the built-in functionality to support more parts. Conclusion Migrating from Orchard 1 to Orchard Core is a challenging but rewarding process that can bring many benefits to your site. However, it requires careful planning, preparation, and testing to ensure a smooth transition. We have extensive experience and expertise in migrating sites from Orchard 1 to Orchard Core, and we can help you migrate your project. Lombiq has recently renewed and migrated its main site http://lombiq.com to Orchard Core too, and we are very happy with the results. You can check out our site and see how Orchard Core can power your site too. Happy migrating!

Delivering a Node.js asset pipeline as a NuGet package

How long does it take you to set up a Node.js-based frontend asset pipeline? That lints and compiles your SCSS, JavaScript, Markdown? That makes code style inconsistencies a tale of the past? In .NET? With Lombiq Node.js Extensions, it'll take you under a minute. In this post, we will provide an overview of what we’ve squeezed into Lombiq Node.js Extensions, how we packaged it for NuGet, why we ditched Gulp, and some gems on MSBuild integration. If you want to see a demo of Lombiq Node.js Extensions in action, check out our recent This week in Orchard post. Web Development in 2023 needs Node.js Even in the .NET world, Node.js has become a first-class citizen for all matters frontend. Grunt, Gulp, Babel, Webpack – all Node.js-based tools – have conquered the frontend landscape. Orchard Core uses Gulp to build its modules’ assets. At Lombiq, we used to use Gulp to build our assets. But it's... Time to say goodbye While Gulp provides a great development experience, there are downsides: Gulp wrappers around NPM packages were not always available or up to date. Sometimes, they caused unexpected errors after updates. Lack of support for Node.js 14 and above became an issue. Plain NPM and Node.js scripts can do the same work as a Gulp script. One less moving part in our tooling ecosystem means less friction. Based on experience with our Gulp Extensions project, we've built a tool that eliminates its weaknesses. Building a reusable frontend asset pipeline on top of Node.js Why? - So you can simply plug it into your projects. Like we do! How? - Read on! What? - Here's an excerpt of what Lombiq Node.js Extensions does: lints SCSS, compiles, autoprefixes and minifies it, generating source maps on the way lints JS, minifies it, generates source maps lints Markdown provides sensible defaults for Orchard-based web development, i.e. needs no manual configuration in the default scenario! can copy assets to a webroot folder, like image files and frontend libraries is usable from a NuGet package integrates tightly with MSBuild adds generated assets to the project's assembly allows to keep generated assets excluded from version control uses PNPM to efficiently install packages and execute scripts (if you don't know it, PNPM is a Node.js package manager with a lot of advantage over NPM) The important pieces Let's look at some of the magic sauce that makes all this possible! MSBuild takes care of the build We use our Lombiq NPM MSBuild Targets project to hook the necessary Node.js work into the MSBuild build pipeline. All the asset manipulation happens before a project's Compile target executes. That allows us to generate frontend assets and embed them into the project's assembly as part of the .NET build. There was a challenge here, though: Files that are generated during the build need to be added manually to the EmbeddedResource item group, else they won't be embedded in the DLL. Here's the relevant piece from the AddGeneratedFilesToEmbeddedResourceList target in Lombiq.NodeJs.Extensions.targets: <ItemGroup> <EmbeddedResource Include="@(NodeJsExtensionsTargetFiles)" WithCulture="false" Type="Non-Resx" /> </ItemGroup> Node.js complexities are handled under the hood Setting up Stylelint, ESLint, Markdownlint and other NPM packages can be time-consuming and complex. Lombiq Node.js Extensions has it all set up and configured out of the box! When your project integrates Lombiq Node.js Extensions and you accept the defaults, you are done. Installation and execution of the SCSS and JavaScript pipelines happen automagically. You even get live ESLint support in Visual Studio with our preconfigured settings! And if you need, you are free to extend them. Packing it up with NuGet Configuring NuGet packaging can be tedious, but here's our successful configuration. Most of all, we include all the files that are necessary to later install this project as an NPM package in any consuming project. As the documentation states: A package [can be] a folder containing a program described by a package.json file. Using Lombiq Node.js Extensions from a NuGet package is the simplest way to get started. When using it from a Git submodule, you will need to edit your project file. Outsource your frontend asset pipelines Are you still writing CSS, because you don't want to invest in setting up the necessary tooling to compile SCSS as part of your build? Do you still argue over your JavaScript files' code styling? Is inconsistent Markdown formatting plaguing you? Let Lombiq Node.js Extensions handle all that for you. Get the NuGet package today! Should you need any further help, file an issue or contact us for commercial-grade support.

Our full Orchard Core tutorial series, the Dojo Course 3 is here!

After a long wait, the new Orchard Core version of our legendary Dojo Course tutorial series is here, the Dojo Course 3! Are you a newcomer and want to learn Orchard Core from the ground up, both from a user's and a developer's perspective? Are you somewhat familiar with Orchard Core but would like to get up to speed and become an Orchard pro? Look no further, check out Dojo Course 3! Dojo Course 3 guides you from the very basics of Orchard Core all up to be able to write your own themes and modules, utilizing various APIs of Orchard. We're publishing a tutorial video every day for 40 days starting on 1 December. So, this is your 40 days of Orchard :).

How to debug a NuGet-based Orchard Core solution - Orchard Core Nuggets

How can you debug Orchard Core code when you’re working with a solution that loads Orchard packages from NuGet? Orchard’s packages are built with symbols so you can actually use them as source too! First, in Visual Studio be sure to uncheck “Enable Just My Code” under Tools → Options → Debugging → General. Then you can debug almost as usual: If you want to step into Orchard’s code from your own code then just put a breakpoint into your code as usual and hit Step Into (F11). It’ll open the Orchard source and debugging will work as usual. If you want to place a breakpoint anywhere in the Orchard Code that you can’t step into from yours (like a controller action) then do the following: Add a reference to the type anywhere in your own code. E.g. just write ItemController somewhere and import the namespace for OrchardCore.Contents.Controllers.ItemController. Hit Go To Definition (F12) on the type. Now you’re at the decompiled source of the type. This won’t be perfect but at least you’ll be able to place a breakpoint at the beginning of the method or other member you want to debug. Be sure to tick “Allow the source code to be different from the original” under the breakpoint’s settings. Run the app with the debugger attached. Your breakpoint will be hit and then the nice symbol sources will be used, just as when you step into Orchard’s code. Note that with “Enable Just My Code” you’ll also potentially see exceptions from the libraries you use or from .NET, even if they’re swallowed down the line. Depending on your work it might be better to keep the option ticked most of the time. Alternatively, you can also try opening a full Orchard source solution (with the Orchard version closest to what you’re using) and attach it to your own app. This sometimes works too but you can only debug Orchard’s own code in that case, not yours. Did you like this post? It's part of our Orchard Core Nuggets series where we answer common Orchard questions, be it about user-facing features or developer-level issues. Check out the other posts for more such bite-sized Orchard Core tips and let us know if you have another question!

How to access services from another tenant in Orchard Core - Orchard Core Nuggets

The multi-tenancy feature of Orchard Core is great: A tenant is basically a subsite with its own independent content and configuration, under its own domain or URL prefix. You can use tenants to e.g. host websites for multiple customers of yours from a single Orchard Core app. The sites won't know anything about each other but they'll run from the same app built from the same codebase, and have access to the same modules and themes. This makes maintaining such sites very efficient, both for hosting and for development. What if you want tenants to be not that isolated though? What if there is certain content or configuration that you actually want to share among tenants or some functionality that you want to centralize on one tenant? You can use the APIs we show below to cross tenant boundaries and use any service from another tenant! Back in the day, this was also possible with Orchard 1. What we'll see here is a simple controller (just for the sake of easy demonstration, you can do the same thing anywhere). In the Index action, we'll fetch content items from another tenant with the IContentManager service that you already know. This is just an example though, really you can access any other service as well. public class CrossTenantServicesController : Controller { private readonly IShellHost _shellHost; // We'll need IShellHost to access services from a currently running shell's dependency injection container // (Service Provider). public CrossTenantServicesController(IShellHost shellHost) => _shellHost = shellHost; // A simple route for convenience. You can access this from under /CrossTenantServices?contentItemId=ID. Here // ID needs to be a content item ID that you can get e.g. from the URL when you open an item to edit from the // admin (it looks something like "4da2sme18cc2k2rpdgwx3d4cwj" which is NOT made by a cat walking across the // keyboard!). [Route("CrossTenantServices")] public async Task<string> Index(string contentItemId) { // Even if you don't create tenants, there will still be a single tenant in an Orchard app, the Default // tenant. For all other tenants you create you can provide the technical name. // In this example, we'll access content items from the Default tenant but this works for any tenant of // course. Create a tenant in your app (enable the Tenants feature and then create it from under // Configuration / Tenants), enable the Training Demo on it too and check out how this works there! // First you have to retrieve the tenant's shell scope that contains the shell's Service Provider. Note // that there is also an IShellSettingsManager service that you can use to access the just shell settings // for all tenants (shell settings are a tenant's basic settings, like its technical name and its URL). var shellScope = await _shellHost.GetScopeAsync("Default"); // We'll just return the title of the content item from this action but you can do anything else with the // item too, like displaying it. string title = null; // With UsingAsync() we'll create a block where everything is executed within the context of that other // tenant. It's a bit similar to being inside a controller action, but remember that all of this is running // on the Default tenant, even if you're looking at it from another tenant you've created. await shellScope.UsingAsync(async scope => { // You can resolve any service from the shell's Service Provider. This serves instead of injecting // services in the constructor. var contentManager = scope.ServiceProvider.GetRequiredService<IContentManager>(); // We can use IContentManager as usual, it'll just work. // Note that for the sake of simplicity there is no error handling for missing content items here, or // any authorization. It's up to you to add those :). var contentItem = await contentManager.GetAsync(contentItemId); // DisplayText is what you've already learned about in PersonPartHandler. title = contentItem.DisplayText; }); return title; } } Pretty neat, right? If you'd like to play with the code and see it in action check it out in our Training Demo module! Note that a shortcut to achieving this is now part of our Helpful Libraries too! Did you like this post? It's part of our Orchard Core Nuggets series where we answer common Orchard questions, be it about user-facing features or developer-level issues. Check out the other posts for more such bite-sized Orchard Core tips and let us know if you have another question!

How to fix "InvalidDataException: Form value count limit 1024 exceeded." in Orchard Core - Orchard Core Nuggets

Let's suppose you're building your shiny new Orchard Core website. In there you're also building a shiny new page, with Flow Part obviously. You throw together a lot of widgets to get all the bells and whistles on the page, then you save it and BAM! Internal Server Error: "InvalidDataException: Form value count limit 1024 exceeded." What's this, did I break Orchard? Is Orchard trying to break me? Let's fix this! The exception happens because the structure you've built with Flow Part is simply too large for the default ASP.NET Core limits. This is not something that would be too extreme to achieve, actually: If you have more complex widgets (with a lot of fields each) and you put several of them into a complex nested structure you can quite possibly build something by hand that would fail like this. Ask how I know! You can increase the limits restricting posted form value sizes, and in this particular case you'd need to change the ValueCountLimit value of FormOptions. Put this into the Startup class of your Orchard-based web app project (it won't work in a module or theme!): services.Configure This increased the limit to 4096, plenty more than the default. However, keep in mind that these limits have their uses: Malicious users could try to post large pieces of data to your server, trying to overwhelm it for example. So only increase this as much you only need! Note BTW that we're using the ASP.NET Core configuration API here, something which is also demonstrated in detail in our Training Demo Module, so follow up learning there! Did you like this post? It's part of our Orchard Core Nuggets series where we answer common Orchard questions, be it about user-facing features or developer-level issues. Check out the other posts for more such bite-sized Orchard Core tips and let us know if you have another question!

How to add a breadcrumb menu in Orchard Core - Orchard Core Nuggets

A breadcrumb menu is a simple but effective navigation aid that shows the user where they are in the site's content structure and which path they can take back. It's also part of the web accessibility guidelines. However, there's no feature built into Orchard Core for this. Nevertheless, how to create a breadcrumb menu? It's actually really easy, you just have to copy a piece of code from Orchard Dojo! Oh wait, we're Orchard Dojo. So here you go: @inject OrchardCore.ContentManagement.IContentAliasManager ContentAliasManager; @inject OrchardCore.ContentManagement.IContentManager ContentManager; @inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor; @using OrchardCore.ContentManagement; @using OrchardCore.Menu.Models; @{ // Retrieving the menu we want to build the breadcrum menu for, in this case the one with the alias "main-menu". var menu = await ContentManager.GetAsync(await ContentAliasManager.GetContentItemIdAsync("alias:main-menu")); // We'll need the current URL to be able to check which menu item corresponds to the page. var currentRelativeUrl = HttpContextAccessor.HttpContext.Request.Path; var breadcrumbItems = new Stack<ContentItem>(); // Building the path in the menu tree to the current item. bool SearchActiveItem(IEnumerable<ContentItem> menuItems) { if (menuItems == null) return false; // Note that for the sake of simplicity this will work only with Link Menu Items, not with Conten Menu Items. foreach (var menuItem in menuItems.Select(item => item.As<LinkMenuItemPart>()).Where(item => item != null)) { if (!string.IsNullOrEmpty(menuItem.Url)) { var url = menuItem.Url; if (url.StartsWith("~")) url = url.Substring(1); if (url.Equals(currentRelativeUrl, StringComparison.OrdinalIgnoreCase)) { breadcrumbItems.Push(menuItem.ContentItem); return true; } else { if (SearchActiveItem(menuItem.ContentItem.As<MenuItemsListPart>()?.MenuItems)) { breadcrumbItems.Push(menuItem.ContentItem); return true; } } } } return false; } SearchActiveItem(menu.As<MenuItemsListPart>().MenuItems); } @* Using the Bootstrap breadcrumb classes. *@ <ul class="breadcrumb"> <li class="breadcrumb-item"> <a href="@Url.Content("~/")">@T["Home"]</a> </li> @while (breadcrumbItems.TryPop(out var menuItem)) { var linkItem = menuItem.As<LinkMenuItemPart>(); <li class="breadcrumb-item"> <a href="@Url.Content(linkItem.Url)">@linkItem.Name</a> </li> } </ul> Wait, wait, slow a bit down. What is this? This is the code that would render a simple breadcrumb menu for the menu with the alias "main-menu". If you've set up your Orchard site with any of the built-in recipes you already should have such a menu. But do take a look at the code in detail, we have a lot of helpful comments in there! Once you're clear on what this piece of code does you can copy it somewhere in your theme to display it. For example, you can just put it into the Layout shape template. A cleaner approach would be to put it into its own separate template, like BreadcrumbMenu.cshtml, and display it in one of the many ways possible. Pretty much that's it! Now let's suppose you built e.g. a menu like this: With the above snippet you can have a breadcrumb menu like this when you open the Module Development page: This just uses the default Bootstrap breadcrumbs styling. And that's it, we're done with our breadcrumbs, enjoy! To be fair, this could use some work to make it bulletproof. We could e.g. optimize away the whole lookup when we're on the homepage. Then, it only supports only a single menu item for a given page. Nevertheless, it should get you going. If you'd like to see a generic and more advanced breadcrumb menu feature in Orchard check out this issue. Did you like this post? It's part of our Orchard Core Nuggets series where we answer common Orchard questions, be it about user-facing features or developer-level issues. Check out the other posts for more such bite-sized Orchard Core tips and let us know if you have another question!

How to add a culture URL segment for localization in Orchard Core - Orchard Core Nuggets

So you're building a localized Orchard Core site and want all URLs to be in the form of /culture-name/rest/of/the/url, e.g. /hu-HU/my-page. (Figure out what "hu-HU" is! Hint: It's not an owl, neither a rock band from Mongolia!) What do you need to achieve this? Well, we've already seen how to localize content items to achieve this, but that's just for content pages. There is one small piece missing though: How to get the same functionality for controller actions, i.e. coded pages? There is nothing built into Orchard Core for this, actually. And the reason is that all you need is available in ASP.NET Core MVC already. First, you'll need to set up RouteDataRequestCultureProvider so the URL segment indicating the culture will be used to set the culture of the current request. Just add this to your module's or theme's Startup class (if you don't yet know how to build a module, check out our Training Demo!): services.Configure So far so good. Next, you'll need the controller actions you want to be culture-aware to be routed in a way that the culture name is included in the URL: public class CultureAwareController : Controller { [Route("{culture}/culture-aware"] public ActionResult CultureAwareAction() { // Build the result here. } } So now you'll be able to reach this action from under /hu-HU/culture-aware for example. There's one final part missing: Building URLs for these actions. This is quite simple too, you'd e.g. create a link for this action like following: <a asp-action="CultureAwareAction" asp-controller="CultureAware" asp-area="CultureAwareModule" asp-all-route-data="routeParams">Click here</a> That's it! Of course, this can get more complex. You can make route configuration as well as URL generation easier by centralizing this culture parameter handling, which is useful if you have loads of such controllers and links. Did you like this post? It's part of our Orchard Core Nuggets series where we answer common Orchard questions, be it about user-facing features or developer-level issues. Check out the other posts for more such bite-sized Orchard Core tips and let us know if you have another question!