Multiple CMS instances

Topics: Developer Forum
Apr 22, 2008 at 3:46 PM
Hi!

I need some advice about N2, what is the best or easiest way to modify the system so it can be used for many CMS instances.
I will try to define it more exactly: every instance has independent tree of sites, users bound to the instance and allowed to edit and administer only that instance.

I see next ways to implement it:\
1. allow to coexists many roots, so each root will represent an instance.
2. one root, many sites, users bound to the sites.

In both ways we need to hide tree items for users from the other instances.
Any suggestions?
Coordinator
Apr 22, 2008 at 9:24 PM
I'm assuming it's imporant each customer can't see other customer's pages e.g. through clever url usage. In this case I'd probably use separate tables for each customer. I'd start looking into using the windsor container configuration to replace the IConfigurationBuilder and have it use dynamically generated mappings for each customer.
Apr 24, 2008 at 2:40 PM
Edited Apr 24, 2008 at 2:41 PM
Yes, it's important to not see other customer's pages, but separate table it's not a good decision i guess. It's hard to manage separate tables for many customers. Also, customer can register byself - in such case we will create some basic item tree for him from template he choosed. And also i want to see all customer sites at one place.

At the time i've created new IUrlParser and ISitesProvider - i'm iteratating roots from database and automatically provides them, so there is no need to configure every site in configuration file.
Next step will be to hide tree items for other customers.

If you are interested in such changes, let me know, so i will share them.

Thank you for a great work you made, it's really cool!
Coordinator
Apr 25, 2008 at 10:43 AM
Hi, I would really like to see these changes. Maybe they could be included, or available as an addon.
May 6, 2008 at 2:31 PM
Edited May 6, 2008 at 2:39 PM
Hi!

Here is the changes i've made (posting as a comment, because can't attach files here):

1. To enable root items enumerating through the Finder, I've extended interface IQueryBuilder with new property:

/// <summary>Finds root items.</summary>
ICriteria<bool> Root { get; }

It's implemented inside the QueryBuilder class:

public ICriteria<bool> Root
{
get
{
return new PropertyRootCriteria(this);
}
}

Two new classes added:

/// <summary>
/// The criteria building block of a query. Compares a property to null value
/// </summary>
public class IsNullHqlProvider : IHqlProvider
{
string FieldName;
bool IsNull;
Operator op;

public IsNullHqlProvider(Operator op, string FieldName, bool IsNull)
{
this.op = op;
this.FieldName = FieldName;
this.IsNull = IsNull;
}

#region IHqlProvider Members

public void AppendHql(StringBuilder from, StringBuilder where, int index)
{
where.AppendFormat(" {0} {1} {2}",
GetOperator(),
FieldName,
IsNull ? "is null" : "is not null");
}

public void SetParameters(NHibernate.IQuery query, int index)
{
//query.SetParameter("v" + index, this.itemID);
}

#endregion

protected virtual string GetOperator()
{
if (op == Operator.None)
return string.Empty;
else
return op.ToString();
}
}

public class PropertyRootCriteria : ICriteria<bool>
{
Operator op;
QueryBuilder query;

public PropertyRootCriteria(QueryBuilder query)
{
this.query = query;
this.op = query.CurrentOperator;
}

#region IEqualityCriteria<ContentItem> Members

public IQueryAction Eq(bool value)
{
query.Criterias.Add(new IsNullHqlProvider(op, "ParentID", value));
return query;
}

public IQueryAction NotEq(bool value)
{
return Eq(!value);
}

public IQueryAction In(params bool[] anyOf)
{
throw new NotImplementedException("Sorry, In isn't supported.");
}
#endregion
}

2. Modifications within a DynamicSitesProvider:
GetSites enumerates all available root items, and returns sites for each one:

public virtual IEnumerable<Site> GetSites()
{
if (RecursionDepth < 0) throw new N2Exception("The DynamicSitesProvider requires the RecursionDepth property to be at least 0");

IItemFinder finder = Context.Current.Resolve<IItemFinder>();
IList<ContentItem> rootItems = finder.Where.Root.Eq(true).Select();

foreach (ContentItem rootItem in rootItems)
{
foreach (ISitesSource source in new RecursiveFinder().Find<ISitesSource>(rootItem, RecursionDepth))
{
foreach (Site s in source.GetSites())
{
yield return s;
}
}
}
}

3. Modification and fixes within MultipleHostsUrlParser:
Inside class added new field variable;

private ISitesProvider sitesProvider = null;

Within both constructors, removed Sites fetching to prevent constructors recursion (because DynamicSitesProvider.GetSites
call uses finder object which is requires already constructed CmsEngine). Instead save provider to further sites getting.
Here is constructors and Sites property of MultipleHostsUrlParser:

public MultipleHostsUrlParser(IPersister persister, IWebContext webContext, IItemNotifier notifier, int rootItemID,
ISitesProvider sitesProvider)
: base(persister, webContext, notifier, rootItemID)
{
this.sitesProvider = sitesProvider;
}

public MultipleHostsUrlParser(IPersister persister, IWebContext webContext, IItemNotifier notifier, Site defaultSite,
ISitesProvider sitesProvider)
: base(persister, webContext, notifier, defaultSite)
{
this.sitesProvider = sitesProvider;
}

public IList<Site> Sites
{
get
{
if (sites == null) // now get sites from provider
{
sites = new List<Site>();
foreach (Site s in sitesProvider.GetSites())
Sites.Add(s);
}
return sites;
}
set { sites = value; }
}

There is seems the bug in method MultipleHostsUrlParser.BuildUrl.
Some times, when working with multiple sites in edit mode, error 404 appeared. (even with original, not modified CMS).
I've changed line:

return "http://" + DefaultSite.Host + ToAbsolute(url, item);

to

return item.RewrittenUrl;

It's looks like fixed then. Don't sure this is right fix, but it is helped for me.

P.S.
Is it ok to submit code modifications through posts? I guess it's not easy to understand and integrate such changes.
May be some patches, or files would be better.

Thanks!
May 6, 2008 at 3:05 PM
Here is archive with modified and new files, based on SVN revision 185:

http://www.maqdev.com/file.axd?file=MultipleInstances.zip

Coordinator
May 6, 2008 at 5:50 PM
Cool, I'll check it out.