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

Zoltán Lehóczky's avatar
Multi-tenancy, Orchard Core Nuggets, Orchard Core, OpenAPI, Swagger, Taxonomy Localization - This week in Orchard (12/06/2020)

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!

6 Comments

  • Raniel Quirante said Reply

    Is it possible to have a sample repository applying this service?

    • Raniel Quirante said Reply

      Sorry, I already saw the module, it is just stated below here.

  • Dave Scott said Reply

    A related idea- is there some way that Content types/parts can be defined either globally or for a specific tenant that then acts as a central repository? -- and then normal tenants would be able to use these types/parts as the basis of their own specific content. I get that types and parts can be imported to the tenants but for a large number tenants with same content types this seems a more natural approach?

  • Zoltán Lehóczky said Reply

    This is not possible out of the box but you could do something similar to what's explained in this blog post. Probably auto export-importing would be the way to go.

  • Jumanah Bukhari said Reply

    Dear Mr. Lehóczky,

    I would like to ask about the openId,
    How can I use it to get all the authenticated users from the default tenants to access the other tenants in Orchard core itself?


    Thank you

  • Zoltán Lehóczky said Reply

    Could you please ask this here: https://github.com/OrchardCMS/OrchardCore/discussions instead, Jumanah? It's a bit better to share code and else there. Then feel free to link it here and I'll chime in!

Add a Comment