Staying DRY with the Kentico API

Peter Cranston
Distinction
Published in
6 min readApr 1, 2019

Developers love acronyms! DRY (Don’t Repeat Yourself) is one of the favourites in the programming world, first introduced by Andrew Hunt and David Thomas in their book: The Pragmatic Programmer. It prompted the tongue-in-cheek counter acronym of WET — ‘Write Everything Twice’, ‘We Enjoy Typing’ or my personal favourite: ‘Waste Everyone’s Time!’

The point is serious though: writing code according to DRY principles reduces the size of your code-base. This makes for less code to trawl through and quicker updates — saving time for you and your company. I’ve had my fair share of inheriting bloated code-bases, with business logic repeated in many places. This wastes hours of developer time (as well as being dull and monotonous) and costs both company and client.

In this blog post, I’ll show you how a good understanding of the Kentico API can transform a rigid, single purpose method into a reusable function.

Note: This article is only concerned with manipulating data (module) classes. The principles can also be applied to custom tables (the api methods will vary — see Kentico’s documentation for more details https://docs.kentico.com/api12/content-management/custom-table-data). On the other hand, page types are handled very differently by the api and may have workflow or versioning applied which makes the process of creating/updating more complicated.

Let’s start off with a simple method:

This is the sort of code you might expect to find behind an ‘edit my profile’ page. It will look for a user and update some fields, or create a new user with those details. Although this may serve the immediate purpose, you can see how this method can quickly become restrictive. Suppose we want to set the first name, last name? Suppose we’ve added custom fields like phone number or address details? Either you have to manually update this method, or write new methods to cover these extra fields your adding.

We can instead update our function to be more flexible. All of Kentico’s built in classes extend the ‘BaseInfo’ class. This means that they have .SetValue(columnName, value) and .GetValue(columnName) methods. You may have used these for getting and setting data in custom tables or custom fields of an out-of-the-box class. By leveraging SetValue, we can ensure our method can update any fields we need it to:

As you can see, I’ve replaced the arguments ‘fullName’ and ‘email’ with a dictionary of key-value pairs. All data provided in the dictionary can now be saved against the user.

Public health warning: I’m keeping these code examples as simple as possible and excluding any validation or error handling. If you are implementing this approach in a production environment, you will need to ensure that you validate the data coming in to catch any errors.

So, our method is generic enough that we can update any field on the user. Next, lets look at how we identify which user to update. We’re passing an id value of the user into our method, but what if we only have their email instead? In our present scenario, we would have to do a separate lookup to get their id, and then call our method. Alternatively, we could make the method accept any identifier:

We’ve removed the int userId argument and replaced it with 2 new arguments: string identifier and object identifierValue. Identifier expects the column you’re going to be searching against and identifierValue the value to search for. I’ve updated the method call on UserInfoProvider to implement this.

We now have the flexibility to call the method with whatever identifiers we need, such as:

UpdateUserDetails(“UserID”, 125, dictionaryData);UpdateUserDetails(“Email”, “pete@example.com”, dictionaryData);UpdateUserDetails(“UserGuid, Guid.Parse(“dfa510c1–0cd5–4173–8136-dccb6f5a016a”), dictionaryData);UpdateUserDetails(“FirstName”, “Pete”, dictionaryData);

Another public health warning: You may have spotted that the last example using ‘FirstName’ as an identifier could cause some problems, as there is likely to be more than one user with the firstname of ‘Pete’. It is up to you to ensure that you only pass unique identifiers and values.

Already we have ensured that our code is much more flexible and re-usable for various scenarios. We could stop there, but there is one more step that we can take to ramp it up to another level in re-usability.

As it currently stands, our method is tightly bound to the UserInfo class. If we wanted to update fields on the SkuInfo class, we would need to copy this code and simply replace UserInfo with SkuInfo. That’s 95.8% of the code being repeated (yes, I did the maths!) which seems ridiculous! As any fan of DRY coding should know, if you find yourself writing the same code twice, you should be seriously asking yourself if you can abstract the code to avoid duplication.

This was the question we found ourselves asking when building the Distinction Import Module. We wanted to be able to configure it to import into any data class that the user wanted. There are 235 out-of-the-box data classes in Kentico (yes, I counted!), and that’s before we get to any custom classes! Writing separate methods was never a consideration.

Instead, we were able to harness the power of Kentico’s API with the following classes:

1. ObjectQuery

If you’ve done any development in Kentico 8 or above you should be very familiar with querying tables with InfoProviders (such as UserInfoProvider as we’ve used so far in our examples). These rely on the ObjectQuery class to return strongly typed results. This ObjectQuery class is itself accessible to be used for queries. All you need to do is provide it with the class name that you want to query.

2. InfoObjectFactory

The InfoObjectFactory class allows you to create info objects based on a given class name. It is a little-known class that the Kentico API exposes, and in my opinion, it is one of Kentico’s best hidden treasures since it opens the door to a huge amount of flexibility for developing reusable components. Again, all it requires is a class name to initialise a new object of that type.

Let’s see this in practice as we update our method for the final time:

We have renamed it to be more generic and added classname as a parameter (reminder: this is the full class name of the object, e.g. ‘cms.user’ or ‘ecommerce.sku’). Leveraging ObjectQuery is as simple as replacing UserInfoProvider.GetUsers() with ObjectQuery(className). This returns a BaseInfo class.

For creating a new object, there’s an extra line needed. We initialise our InfoObjectFactory using the classname. Then we call CreateNewObject(), casting the result to a BaseInfo so that we can assign it. Our code is already using the .SetValue(columnName, value) method from the BaseInfo class, so we don’t need to do any more updates for this.

And there you have it. We are now left with a method that can be used to update any data class in Kentico. If you’re like me, you’ll be amazed at how simple it is, which it’s a testament to the development team at Kentico.

Does this mean that should go instantly and re-write all your Kentico code to use this approach? Not necessarily.

Although (as you can probably tell) I’m a huge fan of going as far as you can to make your code as clean and elegant as possible, you have to judge each situation as comes. I’ve demonstrated how you can push the Kentico API as far as it will go, but if you are just building a simple, single-use feature this will be overkill. DRY is a good principle to follow, but it must be held in balance with the other factors that influence your coding style (such as performance or security).

I hope this has been helpful to you and inspires you to go and develop some great reusable code for your Kentico site.

A final health warning:

License: Creative Commons 4.0 BY-NC

Needless to say, there are many of the 235 Kentico data classes that you do not want to be messing with. Trying to do so will at best, fail. At worst it will overwrite vital data in the database and irretrievably break your site. If you are implementing code like this on your site make sure that you have the necessary restrictions in place to control how it is used to avoid making trouble for yourself.

--

--