This blog contains reflections and thoughts on my work as a software engineer

mandag den 26. januar 2009

SubSonic - day #2

Did you ever see this errormessage: "Can't find the SubSonicService section of the application config file" when trying to get your autogenerated Subsonic DAL to work across multiple projects? Well, I did - and I took the liberty to blog about my bad experiences after trying SubSonic for the first time. Today I decided to tinker a little with the sourcecode of the SubSonic project to see if I could get my ideal configuration scheme set up. My idea was to have a separate configuration file (subsonic.config) placed in the project together with your autogenerated code. Configuration stuff related to database belongs to the DAL layer, I believe, so that's where I would like it to place it physically. I would then add the configuration file to the projects needing dataaccess using the "Add as link" feature in Visual Studio. This way I would have only one configurationfile for my dataaccess which could be shared across multiple projects regardless of their nature (console apps, webapps etc).

I solved it within an hour or two - this is how it was done:

I downloaded the code and quickly discovered that the code basicly traverses the application directory for either web.config or app.config. Afterwards the ConfigurationManager is used to read the various sections into wrapper objects inheriting from System.Configuration.ConfigurationSection - nothing new here.

I decided to attack the DataService class, specifically the static method LoadProviders(). It generates a SubSonicSection class which wraps the SubSonicService section of your app.config or web.config. This is how the code looks:

 

public static void LoadProviders()

{
// Avoid claiming lock if providers are already loaded
if(defaultProvider == null)
{
lock(_lock)
{
// Do this again to make sure DefaultProvider is still null
if(defaultProvider == null)
{
//we allow for passing in a configuration section
//check to see if one's been passed in
if(section == null)
{
section = ConfigSectionSettings ?? (SubSonicSection)ConfigurationManager.GetSection(ConfigurationSectionName.SUB_SONIC_SERVICE);


//if it's still null, throw an exception
if (section == null)
throw new ConfigurationErrorsException("Can't find the SubSonicService section of the application config file");
}

...[snip]...
}
}
}
}


 



I modified it method to create a SubSonicSection based on the contents of subsonic.config if it had failed creating a section based on web/app.config:



 



public static void LoadProviders()



{
// Avoid claiming lock if providers are already loaded
if(defaultProvider == null)
{
lock(_lock)
{
// Do this again to make sure DefaultProvider is still null
if(defaultProvider == null)
{
//we allow for passing in a configuration section
//check to see if one's been passed in
if(section == null)
{
section = ConfigSectionSettings ?? (SubSonicSection)ConfigurationManager.GetSection(ConfigurationSectionName.SUB_SONIC_SERVICE);

if (section == null)
{
string configPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.PrivateBinPath, "subsonic.config");

if (!File.Exists(configPath))
throw new ConfigurationErrorsException(string.Format("Unable to read configuration file {0}", configPath));

var execfg = new ExeConfigurationFileMap();
execfg.ExeConfigFilename = configPath;
var cfg = ConfigurationManager.OpenMappedExeConfiguration(execfg, ConfigurationUserLevel.None);
section = (SubSonicSection)cfg.GetSection(ConfigurationSectionName.SUB_SONIC_SERVICE);
}

//if it's still null, throw an exception
if (section == null)
throw new ConfigurationErrorsException("Can't find the SubSonicService section of the application config file");
}

...[snip]...
}
}
}
}

I then created a subsonic.config resembling the following:







<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="SubSonicService" type="SubSonic.SubSonicSection, SubSonic" requirePermission="false"/>
</configSections>

<connectionStrings>
<add name="ExceptionLog" connectionString="Data Source=localhost\SQLExpress; Database=ExceptionLog; Integrated Security=true;"/>
</connectionStrings>

<SubSonicService defaultProvider="ExceptionLog">
<providers>
<clear/>
<add name="ExceptionLog" type="SubSonic.SqlDataProvider, SubSonic" connectionStringName="ExceptionLog" generatedNamespace="DataAccess"/>
</providers>
</SubSonicService>
</configuration>

...and placed it in my ErrorLoggerAPI project which contains all the autogenerated SubSonic DAL code in the folder DataAccess:







Untitled




Next to follow was to add the subsonic.config using Add As Link in my webproject - like this:







Untitled


Very important: Change the property "Copy to output directory" from "Do not copy" to "Copy when newer" on your linked copy of subsonic.config - otherwise it won't be copied into your buildfolder when you build your project and the following code will fail.









That's it! I now have complete separation between my web project and the corresponding storage working behind the scenes. The only dependency is the linked configuration file I named subsonic.config. When changes occur in my configuration (i.e. a database migration or relocation of the database server) I only have to alter the subsonic.config, rebuild the code in ErrorLoggerAPI and distribute the new subsonic.config to whatever application that's using it.


Last, but not less important: Since the configuration settings are now stored in a file called subsonic.config you will have to create a bat-file to execute the generation of your files. I haven't found a better way to do this so I created a file called deploy.bat and placed it in ErrorLoggerAPI:







Untitled



The Subsonic documentation (referring the command "sonic.exe help" from the command line)dictates that if you haven't got a web.config or an app.config you will have to provide the database and databasename in the arguments when calling sonic.exe. You can still point to a configuration file where the remaining SubSonic configuration stuff can be placed.


The final deploy.bat looks like this:







"%programfiles%\SubSonic\SubSonic 2.1 Final\SubCommander\sonic.exe" generate /out DataAccess /db ExceptionLog /server "localhost\SQLExpress" /config subsonic.config






Until next time... :o)







Regards K.

1 kommentar:

Anonym sagde ...

i don't know file deploy.bat

Could you share code file deploy.bat??

Thank you very much