Welcome to Bangladesh Microsoft Technology Community Sign in | Join | Help

Programatically Loading Providers by Avoiding web / app config

Background
Recently I was in a situation where I had to expose two of my .NET Libraries as COM, to invoke via VBScript (Late Binding) from one of the Legacy Application. One of the .NET Library uses .NetTiers and the other one uses Subsonic as the DAL, so as you can understand all the configurations are declared in the app.config / web.config file as something like this:

Nettiers Config
<netTiersService defaultProvider="SqlNetTiersProvider">
  <providers>   
    <add
name="SqlNetTiersProvider"
type="Locum.Net.Data.SqlClient.SqlNetTiersProvider, Locum.Net.Data.SqlClient"
connectionStringName="netTiersConnectionString"
providerInvariantName="System.Data.SqlClient"
entityFactoryType="Locum.Net.Entities.EntityFactory"
useEntityFactory="true"
enableEntityTracking="false"
enableMethodAuthorization="false"
useStoredProcedure="false"
  />   
  </providers>
</netTiersService>

Subsonic Config
<SubSonicService  defaultProvider="Northwind">
  <providers>
    <clear/>
    <add 
      name="Northwind" 
      type="SubSonic.SqlDataProvider, 
      SubSonic" connectionStringName="Northwind"
      generatedNamespace="Northwind"/>
  </providers>
</SubSonicService>
 
Investigation 

I started playing with it.
Step1: I wrote my special layer (library) on top of my .Net (BLL) Business Logic Layer, 
which will expose only the necessary methods for COM. This is important as we can keep the
exposure clean by using the special COM related Attributes. Example:
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IStudentMessageContextClass
{...}

[ClassInterface(ClassInterfaceType.None)]
public class StudentMessageContextClass : IStudentMessageContextClass
{...}













Step2: I made my .Net ClassLibrary Com visible.
*VS2005 right click the ClassLibrary Project and click Properties.
*In the Application Tab click "Assembly Information" Button.
*Check the "Make Assembly COM Visible" checkbox.

Step3: Used RegAsm to register it in VS Command Prompt.
>regasm filename.dll /tlb filename.tlb /codebase filepath
remember to add the codebase with the path of dll.
Step4: Started writing the VB Class to test if its working. Soon the fun begins and
I realised that the configs has to be provided to load the correct provider details etc.
As I do not know which application will be calling my Libraries and as I do not have,
any controls on them I thought lets pass the configs programatically.



Step5: Initially I tried to pass the providers programatically by extending the NetTiersServiceSection class 
and allowing to set the property. (Note: I did similiar with SubSonicSection)

public class ExtendedNetTiersServiceSection : NetTiersServiceSection
{
  [ConfigurationProperty("providers")]
  public new ProviderSettingsCollection Providers
  {
    get { return (ProviderSettingsCollection)base["providers"]; }
    set { base["providers"] = value; }
  }
}

 

//Create an instance of the extended section
ExtendedNetTiersServiceSection section = new ExtendedNetTiersServiceSection();
ProviderSettingsCollection providers = new ProviderSettingsCollection();
ProviderSettings provider = new ProviderSettings("SqlNetTiersProvider", "Sms.Net.Data.SqlClient.SqlNetTiersProvider, Sms.Net.Data.SqlClient");

//Pass the parameters that we normally put in the web/app config file
provider.Parameters.Add("useStoredProcedure", "false");
provider.Parameters.Add("connectionString", "YourConnectionString");

...//other parameters as necessary

providers.Add(provider);
section.Providers = providers;
DataRepository.NetTiersSection = section;

It was all good but I soon realized that the Sms.Net.Data.SqlClient.dll needs
to be in the same folder of the host application else it comes back with error,
Sms.Net.Data.SqlClient.dll could not be found/loaded or one of its dependencies
could not be loaded.
The reason is it will try to instantiate the provider using
System.Web.Configuration.ProvidersHelper.InstantiateProviders(NetTiersSection.Providers, _providers, typeof(NetTiersProvider));
and it will look for the dlls at the same path of the host application.
Step6: So the above solution did not work for me and I scraped Step5, as I mentioned earlier that I do not know 
who will be calling my dlls, and I do not want to force a requirement that my dlls
should be in the same folder. But its a workable solution too...also by this time you might
be asking why I am not writing a Webservice and ask the Legacy Application to consume that...
don't ask me why but I can't do it....its defined as part of the project requirement
that I cannot have a webservice....:(
Step7: So I looked further in to the API and discovered its not that difficult as I thought, 
the (.NetTiers) DataRepository class comes with LoadProvider() function and (Subsonic) comes
with AddProvider method in the DataService class where I can easily pass my providers programmatically.



.Nettiers DataRepository class
public static void LoadProvider(NetTiersProvider provider, bool setAsDefault)
{
....
}
Subsonic DataService class

public static void AddProvider(DataProvider provider)
{
}


But soon I also realised that there is no way to pass a Name of a provider
as the Name property is Readonly.

public virtual string Name { get; }
So the trick is to use the Initialize method 
of the providers that implements abstract class System.Configuration.Provider.ProviderBase.
public virtual void Initialize(string name, NameValueCollection config);
So to replicate the Nettiers config section that I mentioned above at the starting 
of this article, I finally wrote the following block of code.

//Instantiate the provider
NetTiersProvider provider = new SqlNetTiersProvider();
//Pass all the parameters of the provider here
NameValueCollection config = new NameValueCollection();
config.Add("useStoredProcedure", "false");
config.Add("connectionString", "YourConnectionString");
config.Add("connectionStringName", "netTiersConnectionString");
config.Add("providerInvariantName", "System.Data.SqlClient");
config.Add("useEntityFactory", "true");
config.Add("enableEntityTracking", "false");
config.Add("enableMethodAuthorization", "false");
//Initialize the provider with a proper name and config
provider.Initialize("SqlNetTiersProvider", config);
DataRepository.LoadProvider(provider, true);

 
And for SubSonic after initializing the correct provider with the correct parameters,
we can use SubSonic.DataService.AddProvider(DataProvider provider) method
to add a provider to the providers list. We have to also set the
SubSonic.DataService.Provider with the initialized provider so it assigns it as the default provider.

Conclusion
Here we have seen how we can load a provider programatically and avoid any dependencies on
web/app config. Here I have demonstrated with the providers of .NetTiers and SubSonic,
but its prettymuch the same for all other providers that implements System.Configuration.Provider.ProviderBase.
The trick is to use the Initialize method with correct NameValueCollection and a name
to instantiate a provider that implements the System.Configuration.Provider.ProviderBase.


Hope this helps.
 
Published Wednesday, September 26, 2007 4:58 AM by Shahed

Comments

No Comments

Anonymous comments are disabled