A quick observation...

Startable facility and hosting base4

Ivan's pretty keen :) last week he demonstrated hosting the base4 service in ASP.Net ... cool stuff.



Now here he has placed the implementation in the HttpApplication class, and as a quick observation this would be a good candidate for moving into it's own component... and as luck would have it, Castle has just the thing, via the Startable facility.

Startable... (and the implied, stoppable ;o)

Now the startable facility is a very simple beast, basically... you create a class, that implements this interface:


///
/// Interface for components that wish to be started by the container
///

public interface IStartable
{
void Start();
void Stop();
}


Which lives in the Castle.Core assembly... though the facility itself lives in the Castle.MicroKernel assembly... you should already have references to both, otherwise I doubt your container is working at all :P



And you then register the startable facility in the container:



type="Castle.Facilities.Startable.StartableFacility, Castle.MicroKernel" />


And thats it... now whenever you register a component in the container, the startable facility will check it, and if it's found to implement IStartable it will add it to a list of components "awaiting startup"... once any of these awaiting components has had all it's dependencies satisfied it will be started (so it may be started as soon as the component is registered, if it doesn't have any dependencies - this will be the case for our component)...



Disposing of the container will cause the components to be stopped in turn... perfect for hosting our base4 server...

Reworking the hosting code to be a startable component...

...so taking what Ivan's done, we could rework it a little... and be able to host base4 in web apps, as well as say your business logic's test harness... here's my quick 'n dirty reworked implementation as a component that I did this afternoon... my final version probably won't have any dependencies on the HttpContext as I'll just rely on explicit declaration of all the settings as parameters in the container configuration... I tend to like keeping all the config in the container, one place to look 'n all that.


public class Base4Host : IStartable
{
private const string _machineName = "localhost";
private const string _provider = "SQL2005";
private string _appName;
private int _port;
private string _root;
private ServerConfiguration _configuration;
private IServerProxy _proxy;

public ServerConfiguration Configuration
{
get { return _configuration; }
}

public IServerProxy ServerProxy
{
get { return _proxy; }
}

public Base4Host(string appName, int port)
{
if (string.IsNullOrEmpty(appName)) throw new ArgumentNullException("appName");
if (port <= 1024)="" throw="" new="" argumentoutofrangeexception("port",="" "port="" should="" be="" greater="" then="">
_appName = appName;
_port = port;
}

public Base4Host(string appName, int port, string root)
: this(appName, port)
{
if (string.IsNullOrEmpty(root)) throw new ArgumentNullException("root");
_root = root;
}

#region IStartable Members

public void Start()
{
_configuration = CreateConfiguration();
_proxy = ServerFactory.StartServer(_configuration, false);
string base4Context = string.Format("tcp://Server:@localhost:{0}/{1}", _port, _appName);
StorageContext.SetDefault(base4Context);
}

public void Stop()
{
_proxy.Stop();
_proxy = null;
_configuration = null;
}

#endregion

#region Support methods

private ServerConfiguration CreateConfiguration()
{
ServerConfiguration configuration = new ServerConfiguration();

DiscoverApplicationName();
InsertApplicationRoot(configuration);
InsertConnectionStrings(configuration);
InsertConnectivityInformation(configuration);

if (!Directory.Exists(configuration.Store.Root))
{
Directory.CreateDirectory(configuration.Store.Root);
}

return configuration;
}

private void InsertConnectivityInformation(ServerConfiguration configuration)
{
configuration.Store.Name = _appName;
configuration.Store.Provider = _provider;
configuration.Store.Port = _port;
configuration.Store.MachineName = _machineName;
}

private void DiscoverApplicationName()
{
if (string.IsNullOrEmpty(_appName))
{
if (HttpContext.Current.Application["AppName"] == null)
{
Assembly assembly = Assembly.GetCallingAssembly();
HttpContext.Current.Application["AppName"] = (AssemblyTitleAttribute.GetCustomAttribute(assembly, typeof(AssemblyTitleAttribute)) as AssemblyTitleAttribute).Title;
}

_appName = HttpContext.Current.Application["AppName"].ToString();
}
}

private void InsertApplicationRoot(ServerConfiguration configuration)
{
string root = string.IsNullOrEmpty(_root) ? HttpContext.Current.Server.MapPath("~/") : _root;
configuration.Store.Root = root.EndsWith("") ? root + "App_DataBase4" : root + "App_DataBase4";
}

private void InsertConnectionStrings(ServerConfiguration configuration)
{
string connectionStringName = ConfigurationManager.AppSettings["DefaultConnection"];

if (string.IsNullOrEmpty(connectionStringName) && ConfigurationManager.ConnectionStrings != null
&& ConfigurationManager.ConnectionStrings.Count > 0)
{
connectionStringName = ConfigurationManager.ConnectionStrings[0].Name;
}

configuration.Store.ConnectionString = string.IsNullOrEmpty(connectionStringName) ? ConfigurationManager.AppSettings["Store.ConnectionString"] :
ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;

SqlConnectionStringBuilder connStrBuilder = new SqlConnectionStringBuilder(configuration.Store.ConnectionString);

connStrBuilder.InitialCatalog = "master";
configuration.MasterConnectionString = connStrBuilder.ToString();
}

#endregion
}



Notice we don't expose a default constructor... how can the container create it?



Well, it can't unless we supply the parameters it requires... which is just what we'll do:


<>
id="base4host.default"
type="MyProject.Base4Host, MyProject"> GoatsAndBoats 11888




Simple... a couple of useful extensions to our existing base4 facility may well be to:
  • Ensure the startable facility is installed... so we can fail-early and with a meaningful error during application
    startup.
  • Add some additional "optional" parameters for configuring the facility, so that it can take care of registering the Base4Host for us.

For the optional parameters, I'm thinking of doing something like this:


type="SomeProject.Core.Base4StorageFacility, SomeProject.Core"
host="true"
appName="GoatsAndBoats"
port="11888"
connectionString="local"
/>


At which point it's trivial to shift between accessing base4.net as a remote service vs. hosting it in your own app... unlike the remote setup you can skip providing the base4 url (context string) as it'll be implied from the app name and port.
Written on October 17, 2006