WEBINAR:
On-Demand
Building the Right Environment to Support AI, Machine Learning and Deep Learning
The Test Provider
With an abstract class in place, and a configuration class to read the configuration information, you can write a test provider called TestPaymentProcessingProvider that implements the PaymentProcessingProvider base class. This class will set the patterns and standard for subsequent providers.
The new class inherits from PaymentProcessingProvider, which defines two abstract methods called
ProcessPayment and
ProcessReturn, which the TestPaymentProcessingProvider class must implement.
In addition the class overrides the
Initialize method. If you were to design a provider-based feature that does not rely on any configuration information in the provider definition besides the
name and
type attributes, you wouldn't need this method. In this case, though, because the configuration stores the login and password used to sign on to a payment gateway and perform a transaction, you must override the
Initialize method defined in the ProviderBase class, which serves as the base for PaymentProcessingProvider. The Initialize method arguments are
name and
config. The
name argument corresponds to the
name attribute of the provider as defined in the configuration, while the
config argument is a NameValueCollection type that contains all other attributes in the configuration for that provider (except
type).
Here, the
Initialize method performs several tasks. First, it loops through, the expected attributes by looking for them in the
config collection argument and performs specific validation on each. It uses the .NET ProviderException class to report validation problems, including missing expected values. It also widens the scope of each value in the collection so you can access them from the two method implementations later. Remember, the PaymentProcessingProvider abstract class defines protected variables that hold configuration values.
Finally, the Initialize method checks for unexpected configuration attributes. This is not required, but is good practice. You can opt to simply ignore such values if you like. Here, the code first eliminates all expected and valid attributes from the
config argument. Then, if the collection contains anything else, those values are due to unexpected attributes in the configuration.
The rest of the information that this provider needs is customer-specific: the arguments of
ProcessPayment and
ProcessReturn. These two method implementations are specific to each payment gateway. Because this particular provider is simply a test, I'm going to create a dummy TransactionResult object and return it from each method. See
Listing 6 (C#) and
Listing 7 (VB) for the complete code.
At this point, you have a test provider written and a custom configuration section in which to define it. Here's a more complete explanation of the instantiation process in the context of the code I've just written.
Instantiating the Provider
Using the configuration pattern explained earlier, you can define the test provider like this:
<paymentProcessing defaultProvider="TestProvider">
<providers>
<add
name="TestProvider"
type="Acme.Providers.TestPaymentProcessingProvider,
ProviderFeature"
login="11223344" password="dotnet"/>
</providers>
</paymentProcessing>
Instantiating this provider causes the PaymentProcessingSection class to read this configuration section. That grabs the value of the
defaultProvider attribute and uses it to access that provider in the
Providers collection (remember,
Providers is a property in the configuration class). After determining which
<add> item in the configuration corresponds to the provider I want, the code uses the
type attribute to instantiate that class, using the Activator class as shown below:
Activator.CreateInstance(
Type.GetType("class, assembly"))
That seems like a lot of work. It's also work I don't want to do each and every time I use my provider. For this reason, the final step to complete the provider model is to write a factory class that will perform all this work for me. This class will be analogous to the Membership static class I showed you earlier, but will be called PaymentProcessing.
The Factory Class
The PaymentProcessing class will take care of all the plumbing code that reads the configuration information, grabs the provider list, identifies the default provider, instantiates it, and calls the appropriate method. All this will take place whenever you access the PaymentProcessing class and call one of its methods. Like its counterpart, the Membership class, the PaymentProcessing class will have static methods that mimic those defined in the provider base class, the PaymentProcessingProvider class. From a usage perspective, when you want to process a credit card payment, you should have to write only code such as this:
PaymentProcessing.ProcessPayment(
credit card, exp month, exp year, amount)
If you later write another provider and install it in the configuration, the preceding code would NOT change whatsoever—and that's the whole point of all the work you've done so far.
I'll take the code statement above apart to show you how each step works. The first time you access the PaymentProcessing static class, you'll hit the static constructor. The constructor will read the configuration and set up the correct provider. Here's where the process differs slightly from a conventional strategy pattern, such as the one I described in a previous article (see the sidebar "
Design for Extensibility").
A provider-based feature modeled by the ASP.NET Provider Model is expected to have in-memory instances of all the providers listed in its corresponding configuration section, and the
default provider must be directly accessible in its own variable; the others, of course, go into some sort of collection. This is so you can request any one of the installed providers, regardless of which one is identified as the default provider.
Reading the configuration section uses conventional code like this:
// In C#:
PaymentProcessingSection section =
(PaymentProcessingSection)
WebConfigurationManager.GetSection(
"system.web/paymentProcessing");
' In VB:
Dim section As PaymentProcessingSection = _
CType(WebConfigurationManager.GetSection( _
"system.web/paymentProcessing"), _
PaymentProcessingSection)
The resulting
section variable contains the object model that corresponds to the configuration section. From this point on, you have a lot of help from a .NET class called ProvidersHelper. Before discussing that class, I want to follow up on differentiating the ASP.NET Provider Model from a conventional Strategy Pattern.
The PaymentProcessing class will have two properties, one called
Provider, of type PaymentProcessingProvider, which represents the default provider. The other is called
Providers and is of type ProviderCollection. ProviderCollection class is a .NET class that will contain instances of all the providers defined in my configuration section. Both these properties will expose a member variable of the same type and will be read-only, because the outside world should not be able to change them. With that out of the way, let's get back to the ProvidersHelper class.
As mentioned earlier, part of the PaymentProcessing static class's job is to read the configuration file, instantiate the providers installed, and make the default provider available to whichever method I call. The ProvidersHelper class does all of this work for me. The
InstantiateProviders method in this class receives two arguments: a ProviderCollection type, and the
Providers property from the custom configuration section.
//In C#:
ProvidersHelper.InstantiateProviders(
section.Providers, _Providers,
typeof(PaymentProcessingProvider));
' In VB:
ProvidersHelper.InstantiateProviders( _
section.Providers, _Providers, _
GetType(PaymentProcessingProvider))
After this code executes, the
_Providers variable contains instances of all the providers defined in the configuration section. The
_Providers variable is the member variable behind the
Providers static property, which is of type ProviderCollection.
The rest of the initialization involves checking the
DefaultProvider property of my configuration section object and if it's not empty, setting the
_Provider variable to the item in the
_Providers collection, specified by the DefaultProvider value.
// In C#:
_Provider = (PaymentProcessingProvider)
_Providers[section.DefaultProvider];
' In VB:
_Provider = DirectCast(_Providers.Item( _
section.DefaultProvider), _
PaymentProcessingProvider)
The
_Provider variable is the member variable of type PaymentProcessingProvider (the provider base class) exposed by the
Provider property.
At this point, the
Provider property of the static class exposes the current default provider cast to the abstract type PaymentProcessingProvider. The
Providers property exposes all the providers listed in the configuration section if you wanted to access any of them directly.
After all this initialization complets, the method called when you originally accessed the PaymentProcessing class gets executed—in this case, ProcessPayment. All the code in that method does is make a call to the
ProcessPayment method of the Providers property, which holds the current default provider instance.
Listing 8 (C#) and
Listing 9 (VB) show the complete code for the PaymentProcessing factory class.
All the code written so far resides in one assembly that will be referenced by any additional payment processing provider(s) I might write.