UPDATE: If you've come here looking for information about integrating PayPal into Community Server or about paid subscriptions for Community Server, I and several other Community Server technical evangelists have put together a product named Four Roads Commerce. You can find information about it at the Four Roads website.
Have you ever been intrigued by how many ways there are to map data from your application into a relational database? Options range from embedding your SQL and business logic into a web page to using a comprehensive architecture like Lhotka's CSLA.NET Framework.
A few years ago, I chose a pattern leaning towards the CSLA.NET side but it was a bit more relaxed. It doesn't take advantage of all the design-time features in the VS.NET IDE, but that's fine with me. It fits how I think.
And it's been interesting to see the pattern used in Community Server (CS).
Database access in CS is routed through data providers. They aren't the data providers that you see in ASP.NET 2.0. This is a custom implementation written before providers were available in ASP.NET.
CS data providers are organized by application, with an abstract provider class defined for each. For example, data common to all applications is supported via the abstract CommonDataProvider class. Data common to weblogs is supported via the abstract WeblogDataProvider class.
The abstract classes define abstract methods for reading, updating, and deleting data. The SqlDataProvider20 assembly contains the concrete implementations for the abstract methods.
We need to store payment settings somewhere. For now, the database looks like a good place. And if we want to follow the pattern in CS, using a data provider seems like a good idea.
What's the recipe for creating a custom data provider? Try this one.
Define an interface for the provider
You don't really have to do this and it's not done in CS. I just happen to really like interfaces. The interface should provide methods for all the ways in which you need to interact with the data. This interface will be named IPaymentDataProvider. We'll use the implementation to persist information about your subscribers and their purchases as well as payment settings.
public interface IPaymentDataProvider
{
string Name { get; }
void DeletePaymentSettings(IPaymentSettings settings);
void DeletePaymentSettings(int settingsID);
IPaymentSettings LoadPaymentSettings(int settingsID);
void SavePaymentSettings(IPaymentSettings settings);
}
Define an abstract class for the provider
This is simply a copy of the interface turned into a class.
public abstract class PaymentDataProvider : IPaymentDataProvider
{
public abstract string Name { get;}
public abstract void DeletePaymentSettings(IPaymentSettings settings);
public abstract void DeletePaymentSettings(int settingsID);
public abstract IPaymentSettings LoadPaymentSettings(int settingsID);
public abstract void SavePaymentSettings(IPaymentSettings settings);
}
List your provider in the CommunityServer.config file
Here begins the fun part. The file CommunityServer.config (located in your root CS directory) has a providers section. Within that section, you can identify your provider via an <add> element. Here's the one for the payment settings data provider.
<add
name = "PaymentDataProvider"
type = "Surehand.Subscriber.Providers.PaymentDataProviderSqlServer, SHSubscriber"
connectionStringName = "SiteSqlServer" databaseOwnerStringName = "SiteSqlServerOwner"
/>
add
name = "PaymentDataProvider"
type = "Surehand.Subscriber.Providers.PaymentDataProviderSqlServer, SHSubscriber"
connectionStringName = "SiteSqlServer" databaseOwnerStringName = "SiteSqlServerOwner"
/> The name attribute identifies the kind of provider. For this project, we call it a "PaymentDataProvider".
The type attribute specifies the namespace, classname, and assembly name of the concrete class that will do the work of persisting the data.
The connectionStringName and databaseOwnerStringName attributes tell CS to use the connection string and owner specified in the web.config file. Leave these as is if you want your provider to use the same database as CS. If you need to use a separate database, you could replace these attributes with connectionString and databaseOwner attributes.
Give the abstract class a way to instantiate the concrete class
In CS, the abstract data provider class is responsible for a) creating an instance of the concrete class listed in the providers section of CommunityServer.config and b) ensuring only one instance of that class is created.
I'm going to follow the pattern in CS. Going back to our abstract PaymentDataProvider class, let's add a static string that matches the name attribute in the <add> element shown in the last step. And let's add another static variable to hold the single instance of our concrete class.
public static readonly string PaymentProviderName = "PaymentDataProvider";
private static PaymentDataProvider _defaultInstance;
public static readonly string PaymentProviderName = "PaymentDataProvider";
private static PaymentDataProvider _defaultInstance; Next we need a method to read CommunityServer.config and instantiate the class. This code is blatantly stolen from other CS providers.
private static void CreateDefaultPaymentProvider()
{
// Get the Community Server configuration.
CSConfiguration config = CSConfiguration.GetConfig();
// Look for the payment provider information in the config info.
Provider providerInfo = null;
if (config.Providers.ContainsKey(PaymentProviderName))
providerInfo = (Provider)config.Providers[PaymentProviderName];
else
throw new CSException(CSExceptionType.DataProvider,
"Could not find PaymentProvider section in CommunityServer.config");
// Instantiate the class.
_defaultInstance = DataProviders.CreateInstance(providerInfo) as PaymentDataProvider;
}
private static void CreateDefaultPaymentProvider()
{
// Get the Community Server configuration.
CSConfiguration config = CSConfiguration.GetConfig();
// Look for the payment provider information in the config info.
Provider providerInfo = null;
if (config.Providers.ContainsKey(PaymentProviderName))
providerInfo = (Provider)config.Providers[PaymentProviderName];
else
throw new CSException(CSExceptionType.DataProvider,
"Could not find PaymentProvider section in CommunityServer.config");
// Instantiate the class.
_defaultInstance = DataProviders.CreateInstance(providerInfo) as PaymentDataProvider;
} It reads the CommunityServer.config file, looks for information matching our provider's name, and uses a utility method in the CS DataProviders class to instantiate the class. It does the dirty work of figuring out which connection string and database owner to use, based upon the attributes on our provider.
Finally, we need to throw in a static constructor and an Instance method. The static constructor makes sure only one instance is created. The Instance method give us access to the instance.
static PaymentDataProvider()
{
CreateDefaultPaymentProvider();
}
static PaymentDataProvider()
{
CreateDefaultPaymentProvider();
} public static PaymentDataProvider Instance()
{
return _defaultInstance;
}
public static PaymentDataProvider Instance()
{
return _defaultInstance;
} Implement the concrete class
In the concrete class, override and implement the abstract properties and methods. I won't list any code for that just yet.
One other thing...you must write a constructor that accepts two strings: databaseOwner and connectionString. This is the constructor called by the DataProviders class mentioned in the previous step.
Here's the example for this project:
public PaymentDataProviderSqlServer(string databaseOwner, string connectionString)
{
_dbOwner = databaseOwner;
_connectionString = connectionString;
}
public PaymentDataProviderSqlServer(string databaseOwner, string connectionString)
{
_dbOwner = databaseOwner;
_connectionString = connectionString;
} Enjoy the fruits of your labor
When you need to obtain an instance of your custom data provider, you can write code like the following:
public static void SavePaymentSettings(IPaymentSettings settings)
{
IPaymentDataProvider provider = PaymentDataProvider.Instance();
provider.SavePaymentSettings(settings);
}
public static void SavePaymentSettings(IPaymentSettings settings)
{
IPaymentDataProvider provider = PaymentDataProvider.Instance();
provider.SavePaymentSettings(settings);
} --
Sean Winstead
Tags: CommunityServer