Written By Stefan Kamphuis (40F)
2025-11-18
NOTE 2025-11-26: I edited the code based on received feedback.
Over the past years, DNN has been deprecating a lot of API methods to services using Dependency Injection. Starting DNN 10, the first batch of "old" API's have actually been removed. these API's have been used for years (and some still are) in modules and other extensions. And as long as they work, there's not really a reason to change the coe. Unless, maybe, you need to change the code anyway, then you should probably fix the deprecated warnings as well.
This means a lot of the old modules that are still being used, will need to be updated to use those new DI-services. The good thing is that de DNN Docs explain how you can do that. Unfortunately, back in the day, static method were considered pretty fancy. No need to instanciate a class if you don't need it's state, right? Well, that's biting us now. But, there's no way back, so we need to have a quick and solid way to use the new services, using DI.
For example, in our SEORedirect module, we're using the PortalAliasController:
var portalAliasInfo = PortalAliasController.GetPortalAliasInfo(domainName);
That method has been removed. But we still need it.
I will certainly need more services, so I'm looking for a solution that can be easily extended with extra services, and preferrably also be easily applied in other modules in the future. I decided to make a ServiceHelper class, that will serve as a wrapper for the services I need. This makes sure I don't have to implement DI on every class that's using some kind of service, but instead give me a single class that provides them all. I'll be making this ServiceHelper class a Singleton.
But to be able to use DI in that class, I will need to add it to the services on Dnn Startup. To do that, we need a simple class that implements IDnnStartup:
public class DnnStartup : IDnnStartup
{
public void ConfigureServices(IServiceCollection services)
{
// IndexModel registration is required for
// constructor injection to work
services.AddScoped<ServiceHelper>();
}
}
Then, I need to create the ServiceHelper class itself:
using System;
using System.Web;
using DotNetNuke.Abstractions;
using DotNetNuke.Abstractions.Portals;
using DotNetNuke.Common;
using DotNetNuke.Common.Extensions;
public class ServiceHelper
{
// Use a unique key: this obviously comes from the SEORedirect module
private const string ReadyFlag = "SEORedirect_ServiceHelper_Ready";
public static ServiceHelper Instance => ResolveForRequest(HttpContextSource.Current);
internal static ServiceHelper ResolveForRequest(HttpContextBase context)
{
if (context == null)
throw new InvalidOperationException("ServiceHelper can only be resolved during an active HTTP request.");
var provider = context.GetScope()?.ServiceProvider;
if (provider == null)
throw new InvalidOperationException("Unable to locate the request service provider.");
// Prefer resolving the scoped instance registered in DI
if (provider.GetService(typeof(ServiceHelper)) is ServiceHelper diHelper)
{
MarkReady();
return diHelper;
}
// Fallback: resolve dependencies manually if the container has not been configured
var portalSvc = provider.GetService(typeof(IPortalAliasService)) as IPortalAliasService;
if (portalSvc != null)
{
MarkReady();
return new ServiceHelper(portalSvc, navSvc);
}
throw new InvalidOperationException("ServiceHelper could not be resolved from the current request.");
}
private static void MarkReady()
{
var app = HttpContext.Current?.Application;
if (app != null)
{
app[ReadyFlag] = true;
}
}
private IPortalAliasService _portalAliasService;
internal IPortalAliasService PortalAliasService => _portalAliasService;
public ServiceHelper(IPortalAliasService portalAliasService)
{
_portalAliasService = portalAliasService;
}
}
Now, we can change the line from the beginnning to
var portalAliasInfo = ServiceHelper.Instance.PortalAliasService.GetPortalAlias(domainName);
The ServiceHelper can easily be extended to also provider other services.
Have fun!