Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Question
Wednesday, August 19, 2009 8:22 PM
Hi.
I am in the process of rewriting my data provider using C# generics in order to rationalise my code.
The DataProviderManager class below is responsible for initializing the provider of type T. What I need to do is pass the relevant configuration section name (see GetSection("SectionName")):
public class DataProviderManager<T> where T:BaseProvider
{
private static T defaultProvider;
private static DataProviderCollection<T> providers;
static DataProviderManager()
{
Initialize();
}
private static void Initialize()
{
DataProviderConfiguration configuration =
(DataProviderConfiguration)
ConfigurationManager.GetSection("SectionName");
................
I implemented by own BaseProvider class that inherits from ProviderBase with a property SectionName. All data providers inherit from BaseProvider.
public abstract class BaseProvider : ProviderBase
{
public virtual string SectionName { get; set; }
}
public abstract class CustomDataProvider : BaseProvider
{
public override string SectionName
{
get { return "SampleProvider";}
}
// abstract methods
What I cant figure out is how to access the SectionName property from the DataProviderManager generic class. Do I need to cast type T as the BaseProvider type in order to access this property?
Am quite new to Generics in C# so any help would be appreciated.
Thanks.
All replies (8)
Thursday, August 20, 2009 4:05 PM âś…Answered
This sounds like a job for attribution.
[SectionName("NotDefined")]
public abstract class BaseProvider
{
}
[SectionName("SampleProvider")]
public class CustomProvider : BaseProvider
{
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class SectionNameAttribute: Attribute
{
public string SectionName{ get; private set;}
public SectionNameAttribute(string sectionName)
{
SectionName = sectionName;
}
}
public static class DataProviderManager<T> where T : BaseProvider
{
private static T defaultProvider;
private static DataProviderCollection<T> providers;
static DataProviderManager()
{
Initialize();
}
private static void Initialize()
{
DataProviderConfiguration configuration =
(DataProviderConfiguration)
ConfigurationManager.GetSection(GetSectionName<T>());
}
public static string GetSectionName<T>() where T : BaseProvider
{
return (typeof(T).GetCustomAttributes(typeof(SectionNameAttribute), true)
[0] as SectionNameAttribute).SectionName;
}
}
How's that?
Wednesday, August 19, 2009 10:24 PM
Within DataProviderManager a variable of type T should be able to provide you the SectionName, you should not need to cast as T is already of type Baseprovider (because of the constraint you specified). So, for example,
Console.WriteLine(defaultProvider.SectionName);
should be OK.
Thursday, August 20, 2009 9:48 AM
Try this:
public abstract class BaseProvider : ProviderBase
{
public virtual string SectionName { get; set; }
}
public abstract class CustomDataProvider : BaseProvider
{
public CustomDataProvider()
{
SectionName = "SampleProvider";
}
public override string SectionName
{
get { return base.SectionName; }
set { base.SectionName = value; }
}
Your're calling the SectionName property of the BaseProvider derived object explicitly casted as the BaseProvider class (since that's the contraint on the type parameter). Which directly invokes properties and methods of that base class.
Since you've defined the property in the BaseProvider class, use it as the data store for the value you want to retrieve.
Another way to do it would be:
public abstract class BaseProvider : ProviderBase
{
public virtual string SectionName
{
get { return GetSectionName(); }
set { SetSectionName(value); }
}
public abstract string GetSectionName();
public abstract void SetSectionName(string name);
}
public abstract class CustomDataProvider : BaseProvider
{
public override string GetSectionName()
{
return "SampleProvider";
}
public void SetSectionName(string value)
{
}
Hope that helps.
Thursday, August 20, 2009 11:01 AM
Thanks for both your emails. Unfortunately I still have the problem of how to access the SectionName value from the DataProviderManager.
The static defaultProvider represents the data specific provider e.g. SqlCustomProvider/MySqlCustomProvider and is not instantiated until this line:
defaultProvider = providers[configuration.Default];
If I know that T derives from BaseProvider can I not specify a static field of type BaseProvider and return base(T)?
Either that or I need a static constructor on the abstract provider classes (e.g. CustomDataProvider) that can initialize a static property/constant?
Many thanks,
Thursday, August 20, 2009 11:27 AM
Whenever you create your DataProviderManager<T> object, you should be able to access the base class properties from there:
DataProviderManager<YourCustomType> dpm = new DataProviderManager<YourCustomType>();
dpm.SectionName
Thursday, August 20, 2009 11:32 AM
The problem with static properties / fields in base classes is that it is specific to the base class.
baseClass.StaticSectionName = "The same regardless of the derived class";
If a derived class were to change it's base class static field, it will be changed for every class derived from that base class that may later try to access it. You also cannot have abstract static methods.
I guess I'm not fully understanding exactly what it is you want to do. Where is the line defaultProvider = providers[configuration.Default] in your code? I'm guessing your trying to get the value of SectionName before the default provider is instanciated?
You could use reflection and just have static methods declared in your derived classes, but you HAVE to remember to add them...
public class CustomDataProvider: BaseProvider
{
public static string SectionName {get {return "SectionName";}}
}
public class DataProviderManager<T> where T:BaseProvider
{
private static T defaultProvider;
private static DataProviderCollection<T> providers;
static DataProviderManager()
{
Initialize();
}
private static void Initialize()
{
string sectionName;
PropertyInfo prop = typeof(T).GetProperty("SectionName");
if (prop != null && prop.CanRead)
sectionName = prop.GetValue(null, null) as string;
DataProviderConfiguration configuration =
(DataProviderConfiguration)
ConfigurationManager.GetSection("SectionName");
I would NOT do this if this call is made thousands of times as reflection is relatively slow. However, to do it a quite a few times during the life of your application wouldn't be bad.
Thursday, August 20, 2009 12:18 PM
Whenever you create your DataProviderManager<T> object, you should be able to access the base class properties from there:
DataProviderManager<YourCustomType> dpm = new DataProviderManager<YourCustomType>();
dpm.SectionName
I will not be working with DataProviderManager in this way. I do not want to create multiple instances, it is effectively a static class responsible for initializing a concrete providers that inherit from abstract provider classes e.g. SqlCustomProvider : CustomProvider
I'm guessing your trying to get the value of SectionName before the default provider is instanciated?
This is exactly what I want to do.
Perhaps if I give a top level explanation of what I want to achieve. Currently I have a DataProviderManager class that is resposible for initializing providers of a specific type (DataProvider):
namespace SampleProvider
{
public class DataProviderManager
{
private static DataProvider defaultProvider;
private static DataProviderCollection providers;
static DataProviderManager()
{
Initialize();
}
private static void Initialize()
{
DataProviderConfiguration configuration =
(DataProviderConfiguration)
ConfigurationManager.GetSection("SampleProvider");
if (configuration == null)
throw new ConfigurationErrorsException
("SampleProvider configuration section is not set correctly.");
providers = new DataProviderCollection();
ProvidersHelper.InstantiateProviders(configuration.Providers
, providers, typeof(DataProvider));
providers.SetReadOnly();
defaultProvider = providers[configuration.Default];
if (defaultProvider == null)
throw new Exception("defaultProvider");
}
public static DataProvider Provider
{
get
{
return defaultProvider;
}
}
public static DataProviderCollection Providers
{
get
{
return providers;
}
}
}
}
I can then access my provider methods in the following way:
DataProviderManager.Provider.DoSomething()
All I want to do is use C# generics so that I can use the above DataProviderManager to initialize any provider. So instead I could have something like:
namespace SampleProvider
{
public abstract class DataProviderManager<T> where T : BaseProvider
{
private static T defaultProvider;
private static DataProviderCollection<T> providers;
static DataProviderManager()
{
Initialize();
}
private static void Initialize()
{
DataProviderConfiguration configuration =
(DataProviderConfiguration)
ConfigurationManager.GetSection("SectionName");
if (configuration == null)
throw new ConfigurationErrorsException
("SampleProvider configuration section is not set up correctly");
providers = new DataProviderCollection<T>();
ProvidersHelper.InstantiateProviders(configuration.Providers,
providers, typeof(T));
providers.SetReadOnly();
defaultProvider = providers[configuration.Default];
if (defaultProvider == null)
throw new Exception("defaultProvider");
}
#region Properties
public static T Provider
{
get
{
return defaultProvider;
}
}
public static DataProviderCollection<T> Providers
{
get
{
return providers;
}
}
#endregion
}
}
And access like so:
DataProviderManager<CustomDataProvider>.Provider.DoSomething();
The only part I can not figure out is how to pass the correct configuration section name:
DataProviderConfiguration configuration =
(DataProviderConfiguration)
ConfigurationManager.GetSection("SectionName");
for each provider. Really this should be set on the abstract class that the providers inherit from:
namespace SampleProvider
{
public abstract class CustomDataProvider : BaseProvider
{
// need to set section name on abstract provider class
// so it can be accessed from DataProviderManager
public abstract void DoSomething();
}
}
I really hope someone can help as this will save me so much time in the long run. Using the provider model for data access offers enormous flexibility but creating the specific providers can result in alot of duplicated code where the only difference is the types I am trying to initialize. To me, despite being quite new to it, this is a perfect job for C# generics.
Thanks.
Thursday, August 20, 2009 7:35 PM
This sounds like a job for attribution.
Thank you so much - this works perfectly.
So that this may help others I below is the complete implementation of a generic data provider manager that you can use to instantiate your application specific providers. It is based on the example from Keyvan Nayyeri's article How to Write a Provider Model so for a detailed explanation of what each class does please do check out that article.
SectionNameAttribute.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
/// <summary>
/// Represents a configuration section name
/// </summary>
namespace Retro.ProviderSamples.DataAccess
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class SectionNameAttribute : Attribute
{
#region Ctor
public SectionNameAttribute(string sectionName)
{
SectionName = sectionName;
}
#endregion
#region Properties
public string SectionName { get; private set; }
#endregion
}
}
BaseDBProvider.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Configuration.Provider;
namespace Retro.ProviderSamples.DataAccess
{
/// <summary>
/// Provides a base class for all providers
/// </summary>
[SectionName("NotDefined")]
public abstract class BaseDBProvider : ProviderBase
{
}
}
DBProviderConfiguration.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Configuration;
namespace Retro.ProviderSamples.DataAccess
{
public class DBProviderConfiguration : ConfigurationSection
{
[ConfigurationProperty("providers")]
public ProviderSettingsCollection Providers
{
get
{
return (ProviderSettingsCollection)base["providers"];
}
}
[ConfigurationProperty("default", DefaultValue = "SqlProvider")]
public string Default
{
get
{
return (string)base["default"];
}
set
{
base["default"] = value;
}
}
}
}
DBProviderCollection.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Configuration.Provider;
namespace Retro.ProviderSamples.DataAccess
{
public class DBProviderCollection<T> : ProviderCollection where T : BaseDBProvider
{
// Returns a provider instance
// for a specified provider name
new public T this[string name]
{
get { return (T)base[name];}
}
}
}
DBProviderManager.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Configuration;
using System.Configuration.Provider;
using System.Web.Configuration;
namespace Retro.ProviderSamples.DataAccess
{
public abstract class DBProviderManager<T> where T : BaseDBProvider
{
private static T defaultProvider;
private static DBProviderCollection<T> providers;
static DBProviderManager()
{
Initialize();
}
private static void Initialize()
{
DBProviderConfiguration configuration =
(DBProviderConfiguration)
ConfigurationManager.GetSection(GetSectionName<T>());
if (configuration == null)
throw new ConfigurationErrorsException
("SampleProvider configuration section is not set up correctly");
providers = new DBProviderCollection<T>();
ProvidersHelper.InstantiateProviders(configuration.Providers,
providers, typeof(T));
providers.SetReadOnly();
defaultProvider = providers[configuration.Default];
if (defaultProvider == null)
throw new Exception("defaultProvider");
}
#region Utilities
/// <summary>
/// Returns the appropriate provider configuration section name
/// </summary>
public static string GetSectionName<T>() where T : BaseDBProvider
{
return (typeof(T).GetCustomAttributes(typeof(SectionNameAttribute),
true)[0] as SectionNameAttribute).SectionName;
}
#endregion
#region Properties
public static T Provider
{
get
{
return defaultProvider;
}
}
public static DBProviderCollection<T> Providers
{
get
{
return providers;
}
}
#endregion
}
}
With the above implemented, creating your abstract and concrete provider classes is really easy. Below I create a CustomerProvider, resposible for customer information:
DBCustomer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Retro.ProviderSamples.DataAccess
{
/// <summary>
/// Represents a customer
/// </summary>
public class DBCustomer
{
#region Ctor
/// <summary>
/// Creates a new instance of the customer class
/// </summary>
public DBCustomer()
{
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the first name
/// </summary>
public string FirstName { get; set; }
/// <summary>
/// Gets or sets the last name
/// </summary>
public string LastName { get; set; }
#endregion
}
}
DBCustomerProvider.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Configuration.Provider;
namespace Retro.ProviderSamples.DataAccess
{
[SectionName("CustomerProvider")]
public abstract class DBCustomerProvider : BaseDBProvider
{
/// <summary>
/// Gets a customer
/// </summary>
/// <param name="customerID">Customer identifier</param>
/// <returns>Customer</returns>
public abstract DBCustomer GetCustomerByID(int customerID);
}
}
Concrete provider class (for MSSQL):
SqlCustomerProvider.cs
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Web;
using System.Configuration;
using Retro.ProviderSamples.DataAccess;
namespace Retro.ProviderSamples.DataAccess.SqlServer
{
public class SqlCustomerProvider : DBCustomerProvider
{
private string _connectionString = string.Empty;
public override void Initialize(string name, NameValueCollection config)
{
base.Initialize(name, config);
this._connectionString = config["connectionString"];
if (string.IsNullOrEmpty(this._connectionString))
throw new ConfigurationErrorsException
("connectionString must be set to the appropriate value");
}
public override DBCustomer GetCustomerByID(int customerID)
{
// retrieve data from sql server
return new DBCustomer() { FirstName = "John", LastName = "Smith" };
}
}
}
Configuration of provider.
web.config:
<section name ="CustomerProvider" type="Retro.ProviderSamples.DataAccess.DBProviderConfiguration"/>
.....................
<CustomerProvider>
<providers>
<add name="SqlProvider" type="Retro.ProviderSamples.DataAccess.SqlServer.SqlCustomerProvider" connectionString="MyAppConnectionString"/>
</providers>
</CustomerProvider>
Testing the provider:
protected void Page_Load(object sender, EventArgs e)
{
DBCustomer customer = DBProviderManager<DBCustomerProvider>.Provider.GetCustomerByID(1);
Response.Write(customer.FirstName + " " + customer.LastName);
}
That's it.
What I like about this model is that my abstract provider classes can be really lightweight since they do not contain the initialization like they did before. The above classes could easily be wrapped up in an assembly to be used in any project where the provider model is required.
I hope this helps others as much as it helps me. Thanks again to everyones suggestions.