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

Featured tags

IIS
API
SMS
SEO
All tags >

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!

This week in Orchard - 11/22/2019

This week the community behind Orchard Core was soo productive again, that means we can ship you a lot of news around the CMS. You could read about the new UI for tenants, an updated Trumbowyg editor, updated script and style tag helpers and a nice demo about how to add tags using taxonomies! On Orchard Core Insert image in HTML instead of Liquid in the WYSIWYG editor In the past, when you insert an image using the HTML editor, it adds Liquid code. It can be a bit weird for some users that they would expect to be able to see the image in the WYSIWYG editor. The only advantage seems to be that it will resize the image to the expected size, but we are losing something important for "standard" users. A relatively low-risk option would be to render the media URL with the prefix only. The only case it would break the site is if the production instance uses a different prefix. The best solution would be to add a setting in the editor to either render media as img tags or Liquid tags. This way the end-user won't have to make the decision, and the admin knows how the site is published. To show you how this setting works, let's set up a site with the Blog recipe and edit the Blog Post content type. Add two new HTML Fields to it with the Trumbowyg editor type. For the second one, put a tick in the Insert Media with URL checkbox. Now upload an image to the Media Library then create a new Blog Post. Insert the same media item to both HTML fields and view the HTML of the fields. You could see that by default the Liquid tag is used to insert the image. In the case of the second HTML Field, you could see the img tag is used and when editing the content of the field, you could see the rendered image. Add view button to media app grid and field container The media app always appends ? to the query string, when building resized thumbnail URLs. With a custom secured blob media file store, in a project, this causes the query string to be built badly. This means the attached media field thinks the file doesn't exist, so tries to delete it every time. Dean Marcussen removed the cache busting support, but kept the check for an existing query string in the media app, this check fixes an issue using a custom secure Azure Blob implementation that appends a blob secure access key to the media URL. And there is a new View button to the media grid to each media library item in order to preview the file and easily copy the URL. Tenants new UI The Tenants page has a new UI based on the content items UI. You have quick filters to show only the running tenants, only the disabled tenants or only the uninitialized tenants. You can filter the tenants by the state or sort them by name or state. You can also disable or enable multiple tenants in a row. Prevent ConnectionResetException on SelectedContentType change Sometimes there was a ConnectionResetException when changing the content type selection under the content items index page. It doesn't stop the app but errors are logged. Microsoft.AspNetCore.Connections.ConnectionResetException: 'The client has disconnected'0x800704CD "An operation was attempted on a nonexistent network connection." It was first failing in FormValueRequiredMatcherPolicy and Jean-Thierry Kéchichian could fix it by checking httpContext.IsRequestAborted but then it was failing elsewhere in Asp.NET Core. We need to commit the following changes to fix the issue: Removed an event that seems to be never triggered. Then, when changing the content type selection, he kept the update of the action attribute but he removed the form submission that seems to be done in the following event. So he thinks the form was submitted twice and we were processing the second one while the page was refreshing on the client-side. Allow script/style tag helpers to add dependencies Now you have the ability for the script and style tag helpers to add to the dependencies defined on a ResourceManifest. A lot of the script tags uses depends-on="admin" for no good reason. However, that doesn't work when the resource is registered with a ResourceManifest. A ResourceManifest can set its own dependencies, but until now a script tag helper cannot add to them. It makes sense that a resource manifest entry should define its own dependencies, however not being able to add to them is confusing (and has led to some 30-40 entries using depends-on="admin" in the Orchard Core Razor code, that are useless currently). The best actual use case for wanting to add to dependencies on the fly is for the bootstrap-select. It's manifest depends on jQuery which is good, but it also needs Bootstrap. But Bootstrap is compiled into the admin.js. So the manifest can't refer to that, or it will fail when used elsewhere without the admin. So it makes sense to define the admin dependency on the fly. Noting that it currently works, in the correct order, because the jQuery dependency also exists in the admin, and it gets lucky. Demos Tags using taxonomies We don't have a tags module in Orchard Core that is using taxonomies. The decision has been made that the community wants to have a tags module that will use taxonomies. The idea is to use taxonomies in a way to create a custom editor for the taxonomy that would just reflect something like a tags editor and store the taxonomy terms as any other taxonomy fields. There are pros and cons for both solutions using taxonomies for tags and not using taxonomies for tags. Pro: Reusing what we already have. Con: Might be the performance, because we store term IDs, instead of storing the data, but there are ways to optimize that by also storing the data we want as a tag. Let's set up a site using the Blog recipe. Head to Configuration -> Features and enable the Taxonomies module. Then create a new content type and call it Site Tags. For now, only add the Title Part for this content type. Go ahead and create a new Taxonomy called Site tags and select the previously created content type as the term content type. Finally, add some sample term content types to this taxonomy. Now edit the definition of the Blog Post content type and add a Taxonomy Field to it. Edit that field and see that here you can select the Tags editor type to use. Now create a new blog post or edit an existing one. Here you can see that we added two taxonomy fields to the blog post, one with the standard editor (called Taxonomy) and one with the tags editor (called Tags). When adding tags, the green background shows that the selected tag hasn't been selected yet and the red background shows that the tag has been already added to the list. If you type something you can create a new tag that will be added to the list of tags (under development, but will be available soon). This feature is still under development, but you can check the progress in this branch. And if you checkout to the latest commit of this branch and set up your site using the Blog recipe you will find that the blog posts have a taxonomy field attached with the tags editor using some sample tags. Furthermore, the styling of the tags has been added too! It's a great sample for you to show how to add styling to your tags! If you want to see the full demo of using tags check out the video too! On Lombiq Poll to upload recordings of demos in separate videos as well Last week we created a poll on Twitter about uploading the recordings of demos from the weekly meeting in separate videos as well, so they can be found easier. We had 24 votes and everyone said that you want us to upload these demos separately. :) So from now, if you check the playlists of the YouTube channel of Lombiq you will find a new one, called Orchard Core Demos. Every new demo will be added to this playlist and the name of the video will be {name of the demo} - Orchard Core Demo. And (as you could see) from now we embed the recordings of the demos in our This week in Orchard posts too. Searching for performance-intensive codes Do you write a performance-intensive code? Then help us build the nerdiest .NET thing! Drop us a line to [email protected] and we'll ask a few questions about the challenges you encounter, and in exchange, we'll show you how to make a chip out of your programs! Orchard Dojo Newsletter Now we have 103 subscribers of the Lombiq's Orchard Dojo Newsletter! We have started this newsletter to inform the community around Orchard with the latest news about the platform. By subscribing to this newsletter, you will get an e-mail whenever a new post published to Orchard Dojo, including This week in Orchard of course. Do you know of other Orchard enthusiasts who you think would like to read our weekly articles? Tell them to subscribe here! If you are interested in more news around Orchard and the details of the topics above, don't forget to check out the recording of this week's Orchard meeting!

Advanced Orchard: accessing other tenants' services

Many using Orchard's multi-tenancy feature sooner or later want to share data between tenants or generally, execute some operations on other tenants from one tenant. This is possible! Let's see how. First some core Orchard fundamentals: Orchard creates so called shells for tenants. These shells describe that sub-application what a tenant is, among others the shell also has its own Autofac IoC container. This container deals with all the dependencies that are injected or otherwise requested. The interesting thing is that you can access the shell - the shell context - of all running tenants and through it also their IoC container. Now if you have the container you can make dependency resolution calls on it - what means that you can resolve services from that shell, i.e. that tenant. public class TenantAccessDemo { // ShellSettingsManager lets you access the shell settings of all the tenants. private readonly IShellSettingsManager _shellSettingsManager; // OrchardHost is the very core Orchard service running the environment. private readonly IOrchardHost _orchardHost; public TenantAccessDemo(IShellSettingsManager shellSettingsManager, IOrchardHost orchardHost) { _shellSettingsManager = shellSettingsManager; _orchardHost = orchardHost; } public void UseServicesFromTenant() { // Fetching the shell settings for a tenant. This is more efficient if you have many tenants with the Lombiq Hosting Suite, // see below. var tenantShellSettings = _shellSettingsManager.LoadSettings().Where(settings => settings.Name == "TenantName").Single(); var shellContext = _orchardHost.GetShellContext(tenantShellSettings); // Creating a new work context to run our code. Resolve() needs using Autofac; using (var wc = shellContext.LifetimeScope.Resolve<IWorkContextAccessor>().CreateWorkContextScope()) { // You can resolve services from the tenant that you would normally inject through the constructor. var tenantSiteName = wc.Resolve<ISiteService>().GetSiteSettings().SiteName; // ... } } } If you're operating a multi-tenant environment you may also want to look at the Lombiq Hosting Suite: among others it has a Maintenance feature that you can use to run code in the context of tenants in a safe and scheduled way. Also it has an extended ShellSettingsManager that stores shell settings in the database: this means you can have any number of tenants, you can even fetch their settings by name or you can page when listing them and you don't have to take care about all the Settings.txt files in App_Data.