Interface implementations using Kentico's built-in classes
28th of February, 2017 0 comments

Interface implementations using Kentico's built-in classes

A lot of important aspects in object-oriented programming are often ignored when integrating new functionality into a system. It all comes from a fear of overcomplicating and over-engineering. When given a task to extend the functionality of Kentico by writing a custom module or component, developers often jump onto writing code and getting things "to work". Problems arise when that functionality needs to be changed, extended or tested.

Interfaces provide a great way of "detaching" modules from the application.

How can interfaces help?

They are increasingly more common within Kentico, as they provide a neat way to do customisations. Customised providers are easily registered in the system with assembly attributes . Implementing interfaces allows modifications such as changing the way a preferred payment method is determined in the shopping cart checkout process.

The most powerful aspect is that the functionality is publicly exposed in the system, and it can be used for custom features that are not necessarily related to built-in Kentico features.

A practical example

Let's assume that your application needs to integrate an SMS gateway for sending notifications. Here's a rough representation of a class that does just that:

public class SmsGatewayConnector
{
    public void SendSms(string phoneNumber, string message)
    {
        // Connect to the SMS gateway provider
        // ... and send the message!
    }       
}

When the time comes, you need to send an SMS message:

public class SomethingHappenedNotifier
{
    public void SomethingHappened(string phoneNumber)
    {
        var smsGateway = new SmsGatewayConnector();
        smsGateway.SendSms(phoneNumber, "Something happened!");
    }
}

This is fine, however:

  • What if you need to implement a 'test mode' for the SMS gateway?
  • What if the SMS gateway changes, and a new implementation is needed?

If we implement an interface, swapping out the SMS gateway implementation becomes much easier.

public interface ISmsGateway
{
    void SendSms(string phoneNumber, string message);
}

public class SmsGatewayConnector : ISmsGateway
{
    public void SendSms(string phoneNumber, string message)
    {
        // Connect to the SMS gateway provider
        // ... and send the message!
    }       
}

Using the power of Kentico to get our implementation

Since our goal is to dynamically obtain an implementation of the SMS gateway class, we can use the CMS.Core.Service class, in combination with the RegisterImplementation attribute.

The RegisterImplementation attribute will ensure that when we ask for an instance of a class that implements the ISmsGateway interface, we get the SmsGatewayConnector class.

[assembly: RegisterImplementation(typeof(ISmsGateway), typeof(SmsGatewayConnector))]
public class SmsGatewayConnector : ISmsGateway
{
    public void SendSms(string phoneNumber, string message)
    {
        // Connect to the SMS gateway provider
        // ... and send the message!
    }       
}

Now, the class that 'consumes' the SMS gateway can use the CMS.Core.Service class to ask for an SMS gateway implementation.

public class SomethingHappenedNotifier
{
    public void SomethingHappened(string phoneNumber)
    {
        var smsGateway = Service<ISmsGateway>.Entry();
        smsGateway.SendSms(phoneNumber, "Something happened!");
    }
}

The Service<>.Entry() method returns a static singleton instance of an ISmsGateway - in this case, an SmsGatewayConnector, since that is the class we registered an implementation for.

Manually registering an implementation

Another alternative to using the RegisterImplementation assembly attribute is to use the Service<>.Use<>() method, to manually "register" an implementation. For example, this could be done during the initialisation of a custom module:

public class MyModule : Module
{
    public MyModule() : base("MyModule") { }

    protected override void OnInit()
    {
        Service<ISmsGateway>.Use<SmsGatewayConnector>();
    }
}

This gives us the flexibility to register different implementations for different cases. One of the possibilities is to register a 'dummy' implementation for test purposes, if a particular web.config key is set.

public class DummySmsGateway : ISmsGateway
{
    public void SendSms(string phoneNumber, string message)
    {
        EventLogProvider.LogInformation("SMSGateway", "Test", message);
    }       
}

public class MyModule : Module
{
    public MyModule() : base("MyModule") { }

    protected override void OnInit()
    {
    	var testMode = SettingsHelper.AppSettings["SmsGatewayTestMode"];
        
        if (testMode == "true")
        {
            Service<ISmsGateway>.Use<DummySmsGateway>();
        }
        else
        {
            Service<ISmsGateway>.Use<SmsGatewayConnector>();
        }
    }
}

I don't want a singleton

The Service class returns a singleton when the Entry() method is invoked. Every time you call Entry(), you get the same instance. The whole concept explained for the Service class can be applied to the CMS.Core.ObjectFactory class in a similar fashion.

The ObjectFactory class can provide you with a non-singleton instance.

// Register an implementation
ObjectFactory<ISmsGateway>.SetObjectTypeTo<SmsGatewayConnector>();

// Get a new instance
var smsGateway = ObjectFactory<ISmsGateway>.New();

Why do all this?

Some benefits of this approach are:

  • It's simple to create a 'dummy' SMS gateway that could perform in a different manner, or be detached from the live gateway
  • You avoid 'hacking' an existing class when the implementation needs to change. You create a new one, and delete the old one. If you're lucky enough to use a source control system, the old implementation can still be retrieved from history, if necessary.
  • You're able to have multiple implementations, and dynamically load the right one in different scenarios

Bonus tip with the ObjectFactory class

The recommended way of customising providers in Kentico mentions the use of the RegisterCustomProvider assembly attribute, to register the provider as a replacement for the built-in providers.

Using the ObjectFactory class, you can have more control over the registration of custom providers. This opens up possibilities such as enabling and disabling custom providers by changing web.config settings. Of course, this is something that you still have to perform during the initialisation phase of the application.

public class MyModule : Module
{
	public MyModule() : base("MyModule") { }
    
    protected override void OnInit()
    {
    	// If the 'LoadCustomProvider' web.config setting is set to 'true', override the provider
        if (SettingsHelper.AppSettings["LoadCustomProvider"] == "true")
        {
        	var provider = (ICustomizableProvider)ObjectFactory.New(typeof(CustomShippingOptionInfoProvider));
            provider.SetAsDefaultProvider();
        }
        else
        {
            // Custom provider is not loaded - default provided is used
        }
    }
}

Isn't it smarter to just use IoC containers and dependency injection?

Probably, yes. However, if you're not already doing that, a great start would be to utilise Kentico's built-in features, to improve the architecture of your application.

Written by Kristian Bortnik


Tags

Comments