The Kentico Xperience 13 Betas let us, the development community, get access to the latest upcoming features in the next version of Kentico Xperience.
Beta 3 is now available. So let's dive in ๐คฝ๐พโโ๏ธ!
How to Get the Kentico Xperience 13 Beta
To get access to the beta we need to have a DevNet account. If you don't have one, you can register here.
After logging into DevNet with our account, we can download the beta in the downloads section of the site.
The beta has instructions ๐ on how to get it up and running, what features are new in this version, the list of known issues, and what the focus should be for developers and users of Kentico Xperience when trying out this version of the beta.
Data Access in Kentico Xperience 13
There are many new cool ๐ features and changes coming in Kentico Xperience 13, and I've already covered the support for ASP.NET Core and some of the new Page Builder architecture when targeting .NET Core...
Kentico EMS 2020 Beta - Beta 2 on .NET Core
Sean G. Wright ใป Mar 2 '20 ใป 11 min read
Kentico Xperience 13 Beta 3 - Page Builder View Components in ASP.NET Core
Sean G. Wright ใป Jun 24 '20 ใป 5 min read
As excited ๐ as I am to be finally building Kentico Xperience sites with ASP.NET Core, I don't want to overlook some of the biggest low-level improvements to Xperience - changes to DataQueryBase
/ObjectQueryBase
and Xperience's data access APIs.
Interfaces vs Static Methods
Up until now, the standard pattern for accessing strongly typed data in Xperience has been to use the various *InfoProvider
class static methods ๐ง.
For example, to get the collection of all Sites running in the application we would write the following:
IEnumerable<SiteInfo> sites = SiteInfoProvider
.GetSites()
.AsEnumerable();
foreach (SiteInfo site in sites)
{
Debug.WriteLine(site.SiteName);
}
The static
nature of GetSites()
means we don't have to instantiate a SiteInfoProvider
to query the database.
This means we can query it wherever we want ๐!
This was great in a WebForms world where object construction often relied on factory patterns and data access code was commonly found directly in the presentation layer (.aspx.cs
and .ascx.cs
files).
In ASP.NET Core, Dependency Injection is the law of the land.
The framework is designed to be testable, pushing volatile dependencies to outside of the application and instead relying on abstractions supplied via constructor parameters ๐ช๐ฝ.
To accommodate this architectural shift, Xperience has changed the way data is accessed:
IEnumerable<SiteInfo> sites = SiteInfo
.Provider
.Get()
.AsEnumerable();
foreach (SiteInfo site in sites)
{
Debug.WriteLine(site.SiteName);
}
This looks very similar, but there is a key different - we are using the .Provider
property on SiteInfo
๐คจ.
This property is typed as:
public interface ISiteInfoProvider :
IInfoProvider<SiteInfo>,
IInfoByIdProvider<SiteInfo>,
IInfoByGuidProvider<SiteInfo>,
IInfoByNameProvider<SiteInfo>
And, just for reference, IInfoProvider<TInfo>
is defined as:
public interface IInfoProvider<TInfo>
where TInfo : AbstractInfoBase<TInfo>, new()
{
void Delete(TInfo info);
ObjectQuery<TInfo> Get();
void Set(TInfo info);
}
This means Kentico is exposing abstractions, like ISiteInfoProvider
, to give us access to data querying, instead of concrete or static types ๐ค.
When using these APIs in places where Dependency Injection is not available, our code won't look much different (though Unit Testing might be easier!).
However, when we need to perform data access in parts of the application where Dependency Injection is supported and configured, we can rely on the abstractions for querying.
The code below, if run in an ASP.NET Core app, doesn't require us to register the ISiteInfoProvider
type with the Dependency Injection container - Xperience handles that for us ๐๐พ:
public class SiteController
{
private readonly ISiteInfoProvider provider;
public SiteController(ISiteInfoProvider provider) =>
this.provider = provider;
public IActionResult GetSites()
{
var sites = provider.Get().AsEnumerable();
foreach (SiteInfo site in sites)
{
// ...
}
return View(...);
}
}
This code is far easier to test and requires fewer of Xperience's unit testing hacks ... I mean, APIs ๐
, compared to using the static
*InfoProvider
methods.
It should be noted that most of the CRUD (Create, Read, Update, Delete) static
methods have been marked as Obsolete
๐ฎ and it is recommended to use the new *Info.Provider.Get/Set/Delete
methods instead.
New DataQueryBase/ObjectQueryBase Methods
In addition to changing how we perform queries against the database, Xperience has also added new ways to get the results of those queries.
In previous versions, to force evaluation of the ObjectQuery<T>
being built up, we would either start iterating over the query or call .Result
, .TypedResult
, or .ToList()
to force an evaluation.
Since
ObjectQueryBase
implementsIEnumerable
any query we build up, with all the various query extension methods, can be enumerated ๐ค, which forces the query to be executed and resultingDataSet
be populated.
This approach was fine, but most of the time what we really wanted was to:
- Force evaluation
- Choose between typed/untyped results
- Expose an
IEnumerable
since it's the simplest API for consumers-
.Result
returns aDataSet
and.TypedResult
returns anInfoDataSet<T>
which has lots of properties we typically don't need ๐
-
Xperience now gives us some convenient methods to cut down on boilerplate and give us what we want:
IEnumerable<IDataRecord> GetEnumerableResult();
IEnumerable<TInfo> GetEnumerableTypedResult();
GetEnumerableResult()
gives us access to the System.Data.IDataRecord
which is a bit friendlier than trying to get access to, or work with DataRow
:
// Before
IEnumerable<int> siteIds = SiteInfo.Provider.Get()
// Forces immediate execution
.Result
.Tables[0]
// Exposes an Enumerable
.AsEnumerable()
.Select(r =>
ValidationHelper.GetInteger(r[nameof(SiteInfo.SiteID)], 0)
);
// After
IEnumerable<int> siteIds = SiteInfo.Provider.Get()
// Forces immediate execution and exposes and Enumerable
.GetEnumerableResult()
.Select(r =>
r.GetInt32(r.GetOrdinal(nameof(SiteInfo.SiteID)))
);
For the strongly typed variation, GetEnumerableTypedResult()
, we get to clean up a few lines as well ๐:
// Before
IEnumerable<SiteInfo> sites = SiteInfo.Provider.Get()
// Forces immediate execution
.TypedResult
// Exposes an Enumerable
.AsEnumerable();
// After
IEnumerable<SiteInfo> sites = SiteInfo.Provider.Get()
// Forces immediate execution and exposes and Enumerable
.GetEnumerableTypedResult();
These changes are small, but I like them because they improve the developer experience ๐ค and encourage consistent patterns.
Async Querying!
Update: 2020-08-13
I should have double checked and tested my code ๐คฆโโ๏ธ.
While the async querying works fine forObjectQuery
, it throws exceptions when used withDocumentQuery
.
This is even noted in the comment for the method onDocumentQueryBase<T>
:
// The method is not supported for document query and always throws a System.NotSupportedException.
I have contacted Kentico support about this, as I think it's a bad design decision to add a base class method that always throws an exception in a child class.
I was told the team is working on getting async querying implemented by the time of Xperience 13's release.
Let's hope they get it done ๐ค๐ค!
Back when Web Forms was developed, concurrency on the web wasn't a primary concern, but 10 years later, when async
/await
was introduced in C# 5, concurrent programming had become the best way to get web applications to scale.
Kentico Xperience's data access APIs were built for the world of Web Forms, so they always lacked Async integration and would block the HTTP request thread when querying the database ๐.
The lack of Async data access has been resolved in Xperience 13 and we can now use async
/await
all the way from our Controller Actions to the database call and back ๐.
The two APIs we care most about are defined on DataQueryBase
/ObjectQueryBase
and since DocumentQueryBase
inherits ObjectQueryBase
and DataQueryBase
, we can Async query for documents as well:
.GetEnumerableResultAsync();
.GetEnumerableTypedResultAsync();
These are the Async equivalents of the methods we looked at already.
So what does Async querying look like in Xperience 13?
public class SiteController
{
private readonly ISiteInfoProvider provider;
public SiteController(ISiteInfoProvider provider) =>
this.provider = provider;
public async Task<IActionResult> GetSites()
{
var sites = await provider
.Get()
.GetEnumerableTypedResultAsync();
// ...
return View(...);
}
}
Providers as Repositories
With the new IInfoProvider<TInfo>
(and related interfaces) APIs, we now have a consistent set of methods we use for query, independent of the type:
We no longer have to use SiteInfoProvider.GetSites()
for sites but UserInfoProvider.GetUsers()
for users.
Instead, both look like this:
SiteInfo.Provider.Get();
UserInfo.Provider.Get();
Here's a list of all the new, standard, methods:
ObjectQuery<TInfo> Get();
TInfo Get(int id);
TInfo Get(string name);
TInfo Get(Guid guid);
Task<TInfo> GetAsync(int id, CancellationToken token);
Task<TInfo> GetAsync(string name, CancellationToken token);
Task<TInfo> GetAsync(Guid guid, CancellationToken token);
void Delete(TInfo info);
void Set(TInfo info); // handles both create and update
There's an implication here that might not be immediately obvious... ๐ค
Xperience is now giving us a set of Repositories through a generic interface, instead of a bunch of data access initialization points.
Generic interfaces are great opportunities for cross-cutting concerns through Decoration.
I've covered some powerful ways to use Decoration with Xperience in my Design Patterns blog series, and I'm a huge proponent of the pattern for any cross-cutting, like logging or caching โ.
Kentico 12: Design Patterns Part 12 - Database Query Caching Patterns
Sean G. Wright ใป Aug 26 '19 ใป 13 min read
Kentico 12: Design Patterns Part 17 - Centralized Cache Management through Decoration
Sean G. Wright ใป Nov 4 '19 ใป 10 min read
There are currently some technical limitations around decorating Xperience's built-in types due to some uses of type casting internally, but this could be fixed in the future.
Another implication of this repository pattern is that we don't need to make another layer of repositories in our applications.
If you find you want to encapsulate common read/write scenarios, try the Command / Query Pattern instead.
My Design Pattern posts I linked above also cover the topic of Command / Query vs Repository ๐.
Xperience hasn't (as of yet) updated all the data access APIs, and some are still the same as before:
// No Async, no new methods
int settingValue = SettingsKeyInfoProvider.GetIntValue("keyName");
But that doesn't mean we can't opt-into the new awesomeness seen elsewhere, if needed:
SettingsKeyInfo setting = (await SettingsKeyInfoProvider
.GetSettingsKeys()
.WhereEquals(nameof(SettingsKeyInfo.KeyName), "someSetting")
.TopN(1)
.GetEnumerableTypedResultAsync())
.FirstOrDefault()
Conclusion
Kentico Xperience has always made it easy for developers to work with data created by the application and stored in the database.
We've been using the same powerful and flexible data access APIs for several years now.
With Xperience 13 we are gaining access to new ways of querying for data, including:
- Interfaces instead of static methods
- New
DataQueryBase
/ObjectQueryBase
methods to clean up boilerplate - Async querying
- Standardized interfaces and methods following a repository pattern
All of these new features are opt-in, though with some existing APIs being deprecated we might want to opt-in a little more aggressively for some of them.
When using Xperience 13 with ASP.NET Core, we will see the full benefit of these changes with Dependency Injection of providers and concurrent requests using async
/await
.
I find all of this very exciting (yes, I'm excited by new APIs ๐คทโโ๏ธ), and I'd love to hear your thoughts on how you might leverage the new features coming in Xperience 13.
As always, thanks for reading ๐!
We've put together a list over on Kentico's GitHub account of developer resources. Go check it out!
If you are looking for additional Kentico content, checkout the Kentico tag here on DEV:
Or my Kentico blog series:
Top comments (2)
Very interesting read. Can ObjectQuery GetEnumerableTypedResultAsync and GetEnumerableTypedResult be mocked with moq?
Lee,
The methods on
ObjectQueryBase<T>
are virtual, so assuming you somehow mock that, you should be able to override the methods.The new
Get()
methods onIInfoProvider<T>
are also virtual, so by mocking those and having them return a mockedObjectQuery<T>
(which inheritsObjectQueryBase<T>
) you should be able to fake out the whole data access layer.That said, I don't often see this kind of code being mocked because the internals of the querying code are very specific to getting data out of the database, and this seems like a much better opportunity for integration tests.
By putting code that interacts with
DocumentQuery<T>
andObjectQuery<T>
behind abstractions you can unit test your business logic, and integration test your data access implementation.