News module - How to combine data from default and subtenant

Dec 17, 2010 at 3:16 AM
Edited Jan 6, 2011 at 2:58 AM

Hi,

 

I am new to Orchard but I have already read all available documentation, also http://www.orchardproject.net/docs/Creating-1-n-and-n-n-relations.ashx

I have to implement News module (very similar to Blog)  which base functionality is to combine data from default tenant and current sub tenant. For example:

I have tenants with host names: news.net (default tenant), city1.news.net (subtenant), city2.news.net (subtenant).

Now default tenant have to contain news about something and each of subtenants has to combine self managed news and news from default tenant.

 

How Can I achieve this? I assume that I have the same database but with prefixes for each subtenant.

I know that I should create Service but does simple IRepository in subtenant has access only to prefixed tables or to all tables in database?

Can you provide me any advise or/and example?

 

Regards,
Daniel Skowroński

Coordinator
Dec 17, 2010 at 3:26 AM

Tenants don't share data. To build that kind of model, don't use tenants, but instead do something similar to what blogs are doing and isolate them by container and manage yourself who can modify what using custom permissions.

Dec 17, 2010 at 1:58 PM
Edited Dec 17, 2010 at 4:24 PM

Bertrand,

Thank you for your answer, but I don't agree with you as a result that you don't have full view of the story, it is more complicated that may sounds.

And yes I totally agree with you that if our only requirement would be to create content similar to blog but categorized by city it could be done by permissions, but please read user story:

 

Project that I am currently working on is a social network for small cities and villages. Core functionalities requires that each city that is connected to the network has possibility to create they own site in the system.

So as I wrote before I have situation when we have base portal (default tenant) and a lot of sub-portals for specific cities (subtenants).

Each portal (both default and sub) has different URL, ex  app.net (default tenant), city1.app.net (subtenant), city2.app.net (subtenant).
Each portal has to contain: own blog (maybe multiple blogs), own different layout, own set of permissions (you may be an admin in one of them and standard user in different), etc.
Each of the portal has to got their own news module but also synchronized with main portal (as I wrote before).
- Other functionalities etc. 

I know that I could host separate instances of Orchard but I have also restrictions:
Each sub portal has to be easily deployed by admin of main portal
Data analysis - We need analytics concerning how many articles, etc we have, but on the level of each portal and whole network (tenants sounds perfectly as a solution)

 

And Bertrand this portal (its first version) is not abut how well it should be designed, or what architecture principles it should follow.

Our main issue here is time, and we are really in great rush.

Do you really believe that in this situation we have other (faster) option than Multi-tenancy? 

And in this point I still don't have answer to question from my first post.

I would really appreciate any help.

 

Regards,
Daniel Skowroński

Coordinator
Dec 17, 2010 at 6:27 PM

The problem is that this is not what the multi-tenancy module was designed for. Multi-tenancy as it was designed will simply not allow you to share data between tenants unless you completely handle that through your own code. You may choose to re-implement your own version of multi-tenancy, implement custom data services for shared data, or implement the system as a blog-like module. I don't see any of those options as fast.

Dec 18, 2010 at 1:51 PM

Bertrand,

 

"Multi-tenancy as it was designed will simply not allow you to share data between tenants unless you completely handle that through your own code. You may choose to re-implement your own version of multi-tenancy, implement custom data services for shared data, or implement the system as a blog-like module."

And that answer I appreciate. I now know that I have 2 options:
1. Implement JOB / ETL that would replicated data from main tenant.
2. Add SOA capability between tenants, restricted to the same domain.

I know and understand that it is not with multi-tenancy architecture principles, but if it save my "a**" it is worth to break the rules.

 

Regards,
Daniel Skowroński 

Coordinator
Dec 18, 2010 at 5:02 PM

Sure. Actually if you are going to break the rules you could also dive into the multi tenancy module's code that isolates tenant data and replicate what it's doing while relaxing sone of the constraints. A sort of backdoor into the main tenant data.

Jan 5, 2011 at 8:15 AM
Edited Jan 6, 2011 at 12:40 AM

Hi after Xmas and New Year's Eve

 

I dived in to find workaround in Multi-tenancy Module but it looks like Data Access for Tenants in resolved always by Environment not by this module...

I spend 2 days analyzing Data Access Layers and Environment and I am still confused...

What I would like to achieve for simple test is to create instance of IContentManager but with settings of default Tenant.

Ex. If I am in subtenant then set _contentManagerDefault to default tenant.

Unfortunately following code bring in infinitive loop of transactions and ends in Stack Overflow...

Can Any one give hint how to create IContentManager with settings of default Tenant inside subtenant?

 

 

        // TESTING ONYL //
        private readonly IOrchardHost _orchardHost;
        private readonly IShellSettingsManager _shellSettingsManager;
        private readonly ShellSettings _shellSettings;
        private readonly IProcessingEngine _processingEngine;
        private readonly IContentManager _contentManagerDefault;

        public BlogService(IContentManager contentManager, IBlogSlugConstraint blogSlugConstraint, IOrchardHost orchardHost, IShellSettingsManager shellSettingsManager, ShellSettings shellSettings, IProcessingEngine processingEngine)
        {
            _contentManager = contentManager;
            _blogSlugConstraint = blogSlugConstraint;
            _orchardHost = orchardHost;
            _shellSettingsManager = shellSettingsManager;
            _shellSettings = shellSettings;
            _processingEngine = processingEngine;

            var allSettings = _shellSettingsManager.LoadSettings();
            var defaultTenatSettings = allSettings.SingleOrDefault(x => x.Name == "Default");

            // TESTING ONLY //
            if (_shellSettings.Name != defaultTenatSettings.Name)
            {
                // TESTING ONLY //
                //defaultTenatSettings.State = new TenantState("Running");
                using (var environment = _orchardHost.CreateStandaloneEnvironment(defaultTenatSettings))
                {
                    try
                    {
                        _contentManagerDefault = environment.Resolve<IContentManager>();
                    }
                    catch
                    {
                        throw;
                    }
                }

                // in effect "pump messages" see PostMessage circa 1980
                while (_processingEngine.AreTasksPending())
                    _processingEngine.ExecuteNextTask();
            }
        }

 

Regards,
Daniel Skowroński 

Jan 5, 2011 at 12:36 PM

It is extreamy important for me.

I will really appreciate any help.

Jan 5, 2011 at 6:31 PM

I would create a quick module if I were you.  The module would attach to the blog published event on the root site, and could then push the entry to any number of child sites.  You could do this either by directly writing to the database, calling the service layer, or if you want to be fancy, updating the blog using the same tech Live Writer uses.

Jan 6, 2011 at 12:11 AM
Edited Jan 6, 2011 at 12:41 AM
tkunstek wrote:

I would create a quick module if I were you.  The module would attach to the blog published event on the root site, and could then push the entry to any number of child sites.  You could do this either by directly writing to the database, calling the service layer, or if you want to be fancy, updating the blog using the same tech Live Writer uses.

What I have provided it is only test code. It will be new module and module will be a copy of blog. But I still don't know how to access database with tenant prefix and serialize records to IContentManager entries that will provide mi functionaly to "list" news and manipulate them by user, merged from both subtenant and default tenant...

How can I do that? How can I access data layer (with specific prefix, other tenant than running) and serialize items back to IContentManager?

What is wrong with the code that I have provided?

Regards,
Daniel Skowroński 

Jan 6, 2011 at 1:02 AM

Ok, so as he said, tenants can't access each others data directly.  That is the whole point.  BUT, you can have a module that listens to a blog published event, and then do something.  Think of it like a database trigger.  Now, when that code gets kicked off, you could call a WCF service on another tenant, or call the same code that live writer uses to insert the blog posts into a blog on another tenant. 

Another option is to use JQuery from one tenant to read RSS feed of a blog on another tenant. 

Yet a third option is to use modules to grab RSS feeds of blogs from another tenant and merge it into the current one.

Jan 6, 2011 at 1:31 AM
tkunstek wrote:

Ok, so as he said, tenants can't access each others data directly.  That is the whole point.  BUT, you can have a module that listens to a blog published event, and then do something.  Think of it like a database trigger.  Now, when that code gets kicked off, you could call a WCF service on another tenant, or call the same code that live writer uses to insert the blog posts into a blog on another tenant. 

Another option is to use JQuery from one tenant to read RSS feed of a blog on another tenant. 

Yet a third option is to use modules to grab RSS feeds of blogs from another tenant and merge it into the current one.

tkunstek,

Thanks but this might resolve only situation when I treat all custom modules like blogs and / or give them all RSS feed what don't really save me. I have other functionality that will not be similar to blog but have to be shared between tenants.

"Ok, so as he said, tenants can't access each others data directly."
- When application starts up, Setup module turns on, later initializing Environment and Scopes using injected by IoC ShellSettings, then Environment initialize IContentManager that is of DB Scope by ShellSettings.DatabasePrefix
- What I need to know is how I can simulate the same process, he doesn't said "tenants can't access each others data directly" (meaning it is not possible), he said "it is not implemented, you must handle it by your code", here comes my questions

"Now, when that code gets kicked off, you could call a WCF service on another tenant"
- If you have any snippet that would allow me to access different tenant by WCF that will expose IContentManager it could save me, I will really appreciate that
- If you advice me to simply access DB but it won't be possible later to serialize records to  IContentManager entries then it doesn't give me any help because I can't use them in User Interface (render them)

It must be possible to access other tenant data (by, I need only clues how to do that...).

If you believe that it is possible to expose IContentManager by WCF I would be glad to see any example.

Regards,
Daniel Skowroński 

Jul 8, 2011 at 9:33 AM

I wrote this code to get blog from Orchard farm.

using (var tran = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Suppress))
{
    List<BlogPart> blogs = new List<BlogPart>();

    foreach(var tentant in ShellSetingsManager.LoadSettings())
    {
        var env = Host.CreateStandaloneEnvironment(tentant);
        using (var ts = new TransactionScope(TransactionScopeOption.RequiresNew, 
                    new TransactionOptions
                    {
                        IsolationLevel = IsolationLevel.ReadCommitted
                    }))
        {

            using (env.Resolve<ITransactionManager>() as IDisposable)
            {
                IBlogService blogService = null;
                if (env.TryResolve(out blogService))
                {
                    blogs.AddRange(blogService.Get());
                }
            }
            ts.Complete();
        }
    }

    tran.Complete();
    return View(blogs);                
}