menu
Logo
  • Why azing?
  • Blog
  • Help
ENarrow_drop_down
  • DE
  • EN
Suche in Checklisten
search
azing Logo ENarrow_drop_down
  • DE
  • EN
  • Why azing?
  • Help
DNN Community
drive_folder_upload
  • homeChecklist Templates
  • south

folder_sharedDevelop

  • check_circle Convert to checklist
  • insert_drive_file Convert to boilerplate
  • homeChecklist Templates
  • south

folder_sharedDevelop

Folders and checklists

  • infoConstructor Injection in DNN 10 WebForms Modules
  • check_circleDocFx - Setup in Visual Studio to Auto-Document your APIs
  • check_circleMS SQL – Remove Log File Size Restrictions
  • check_circleVS Code - Configure DNN files to be treated as XML

Parts of this checklist (0) expand_more

These are small document-parts which are used in other documents. They are not a starting point for any real activity. Because of this, they will be listed further down and the search will not list them unless requested. 

DNN 10 supports real constructor injection in WebForms module controls. Dependencies should be declared explicitly as constructor parameters instead of being resolved through Globals.DependencyProvider, PortalModuleBase.DependencyProvider, or another service-locator pattern.

Requirements

This approach requires:

  • DNN Platform 10.0 or later.
  • A compiled WebForms module.
  • Services registered through IDnnStartup.
  • A public module control with a public constructor.

DNN 10 registers its service provider with HttpRuntime.WebObjectActivator. When ASP.NET creates a WebForms control, DNN uses the current request scope and ActivatorUtilities to resolve its constructor dependencies.

The module does not need to configure HttpRuntime.WebObjectActivator or register the module control itself.

1. Define the service

Keep the interface independent of WebForms and DNN UI classes.

public interface IArticleService
{
    Article GetArticle(int articleId);
}
public sealed class ArticleService : IArticleService
{
    private readonly IArticleRepository articleRepository;

    public ArticleService(IArticleRepository articleRepository)
    {
        this.articleRepository = articleRepository;
    }

    public Article GetArticle(int articleId)
    {
        return this.articleRepository.GetArticle(articleId);
    }
}

Constructor injection should be used throughout the dependency graph. ArticleService therefore receives IArticleRepository instead of resolving it from an IServiceProvider.

2. Register the services

Create an IDnnStartup implementation in the module assembly.

using DotNetNuke.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

public sealed class Startup : IDnnStartup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IArticleRepository, ArticleRepository>();
        services.AddScoped<IArticleService, ArticleService>();
    }
}

DNN discovers IDnnStartup implementations during application startup and calls ConfigureServices.

The Startup class itself should:

  • Be concrete and discoverable in the deployed module assembly.
  • Have a public parameterless constructor.
  • Only register services.
  • Not resolve services or perform application work during startup.

3. Inject the service into the WebForms module

Declare every required dependency in the module control constructor.

using System;
using DotNetNuke.Entities.Modules;

public partial class View : PortalModuleBase
{
    private readonly IArticleService articleService;

    public View(IArticleService articleService)
    {
        this.articleService = articleService
            ?? throw new ArgumentNullException(nameof(articleService));
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (this.IsPostBack)
        {
            return;
        }

        var articleId = this.ModuleId;
        var article = this.articleService.GetArticle(articleId);

        this.TitleLabel.Text = article.Title;
        this.ContentLiteral.Text = article.Content;
    }
}

The module control does not need to be registered explicitly:

// Not required:
services.AddTransient<View>();

If the control type is not registered, DNN uses ActivatorUtilities.CreateInstance to construct it and resolve its constructor parameters from the current request scope.

Do not use a service locator

The following is not constructor injection:

public partial class View : PortalModuleBase
{
    private readonly IArticleService articleService;

    public View()
    {
        this.articleService =
            this.DependencyProvider.GetRequiredService<IArticleService>();
    }
}

The class still has a hidden dependency on the global container. This pattern:

  • Hides the actual dependencies of the class.
  • Allows missing dependencies to fail later at runtime.
  • Couples the class to DNN’s service-provider infrastructure.
  • Makes isolated unit testing more difficult.
  • Encourages dependencies to be resolved from arbitrary locations.
  • Makes service lifetimes and scope ownership less clear.

Injecting IServiceProvider and resolving services from it has the same disadvantages:

public View(IServiceProvider serviceProvider)
{
    this.articleService =
        serviceProvider.GetRequiredService<IArticleService>();
}

Prefer:

public View(IArticleService articleService)
{
    this.articleService = articleService;
}

IServiceProvider should normally appear only in infrastructure code that must create a service selected dynamically at runtime, not in regular module, application, or domain classes.

Constructor best practices

Use one public constructor

A module control should normally have exactly one public constructor:

public View(
    IArticleService articleService,
    IPermissionService permissionService,
    ILogger<View> logger)
{
    this.articleService = articleService;
    this.permissionService = permissionService;
    this.logger = logger;
}

Avoid keeping a parameterless constructor for compatibility. Multiple constructors can make activator selection ambiguous and may allow the module to be created without its required dependencies.

Keep constructors simple

The constructor should only:

  • Validate constructor arguments.
  • Assign dependencies to fields.
  • Establish simple object invariants.

Do not perform database queries, logging workflows, redirects, or other request processing in the constructor.

DNN assigns module-specific context after constructing the control. Properties such as ModuleConfiguration, ModuleId, TabId, and other request-related state should therefore be used during the WebForms lifecycle, such as OnInit, Page_Load, or later—not in the constructor.

Prefer explicit dependencies

Inject the narrowest useful abstraction:

public View(IArticleService articleService)

Avoid broad dependencies that expose unrelated functionality:

public View(IServiceProvider serviceProvider)
public View(IDnnContext contextContainingEverything)

A large constructor often indicates that the control has too many responsibilities. Move business logic into focused application services instead of hiding dependencies behind a service locator.

Choose the correct service lifetime

Use the normal Microsoft dependency injection lifetimes:

services.AddTransient<IValueFormatter, ValueFormatter>();
services.AddScoped<IArticleService, ArticleService>();
services.AddSingleton<IApplicationCache, ApplicationCache>();

Transient

Use Transient for lightweight, stateless services that can be created each time they are requested.

Scoped

Use Scoped for services whose lifetime belongs to the current web request. This is usually the appropriate default for application services, repositories, and request-related operations.

DNN resolves WebForms constructor dependencies from the current request scope.

Singleton

Use Singleton only for thread-safe services that do not depend on request, portal, user, module, HTTP context, or scoped services.

A singleton must not capture a scoped service:

// Invalid lifetime relationship:
services.AddScoped<IArticleRepository, ArticleRepository>();
services.AddSingleton<IArticleCache, ArticleCache>();

If ArticleCache depends on IArticleRepository, the scoped repository may effectively be retained for the lifetime of the application.

Testing

Constructor injection allows dependencies to be supplied directly in tests without configuring DNN’s global service provider.

[Test]
public void View_UsesInjectedArticleService()
{
    var articleService = new FakeArticleService();
    var view = new View(articleService);

    // Exercise the relevant behavior.
}

Business behavior should preferably live in independently testable services, leaving the WebForms control responsible for adapting WebForms and DNN lifecycle events to those services.

Tests should not need to modify:

Globals.DependencyProvider

Changing global DI state makes tests order-dependent and can cause state to leak between tests.

Extension points that are not WebForms controls

HttpRuntime.WebObjectActivator applies to objects created through the ASP.NET WebForms activation pipeline. It does not automatically add constructor injection to every legacy DNN extension point.

Schedulers, search providers, business controllers, portable interfaces, navigation providers, Prompt commands, connectors, and similar extension points use their own DNN activation pipelines. Constructor injection is available only when that specific pipeline creates instances through DNN’s DI container.

Do not assume that an arbitrary type named in a DNN manifest supports constructor injection. Check the API for that extension point.

Do not replace DNN’s WebObjectActivator

DNN 10 owns the application-level registration of:

HttpRuntime.WebObjectActivator

A module must not replace it with its own container adapter. Doing so affects every WebForms object in the application and can break DNN or other extensions.

Modules should integrate with the existing DNN container exclusively through IDnnStartup.

Migration from the service-locator pattern

Before:

public partial class View : PortalModuleBase
{
    private readonly IArticleService articleService;

    public View()
    {
        this.articleService =
            this.DependencyProvider.GetRequiredService<IArticleService>();
    }
}

After:

public partial class View : PortalModuleBase
{
    private readonly IArticleService articleService;

    public View(IArticleService articleService)
    {
        this.articleService = articleService;
    }
}

Migration steps:

  1. Identify every call to DependencyProvider.GetService, GetRequiredService, Globals.DependencyProvider, or another global resolver.
  2. Add the resolved type as an explicit constructor parameter.
  3. Store it in a readonly field.
  4. Ensure the dependency and its complete dependency graph are registered in IDnnStartup.
  5. Remove the service-provider access from the class.
  6. Verify that the selected service lifetimes are compatible.
  7. Add an isolated unit test that constructs the class with test implementations.

Troubleshooting

Unable to resolve service

An exception similar to the following means the dependency or one of its transitive dependencies is not registered:

Unable to resolve service for type 'IArticleService'
while attempting to activate 'View'.

Check the complete dependency chain, not only the first constructor.

Constructor is not used

Verify that:

  • The site is running DNN 10 or later.
  • The control and its constructor are public.
  • The control is created by the WebForms pipeline.
  • The module has not replaced HttpRuntime.WebObjectActivator.
  • The deployed assembly contains the current implementation.

Startup registration is not executed

Verify that:

  • Startup implements IDnnStartup.
  • The class is concrete and has a public parameterless constructor.
  • The assembly is deployed to the site’s bin directory.
  • No exception is thrown from ConfigureServices.
  • The application has restarted after deployment.

Summary

For WebForms modules on DNN 10 and later:

  • Register services through IDnnStartup.
  • Declare module dependencies as constructor parameters.
  • Use a single public constructor.
  • Resolve dependencies from the current request scope through DNN’s built-in activation pipeline.
  • Keep request and module context out of constructors.
  • Do not call DependencyProvider.GetService from application code.
  • Do not inject IServiceProvider merely to resolve other services.
  • Do not replace DNN’s HttpRuntime.WebObjectActivator.

References:

  • DNN issue #4097 – Dependency Injection in WebForms
  • DNN PR #4098 – Dependency Injection in WebForms
  • DNN 10 release notes
  • Microsoft: ASP.NET dependency injection in .NET Framework 4.7.2
Logo
Legal | Content Copyright CC-BY 4.0
bug_reportReport Bug
  • info
  • Links
  • Permissions
code Share
code
URL copied to clipboard.
Embed Checklist close
Copy Copy
Content Copyright

CC-BY 4.0

Translations

None

DNN Community Logo

DNN Community

QR-Code
azing.org/dnn-community
/r/XzFLoE_K
View & Use

Public (can be used by everybody)

Edit & Admin

Default (all members can edit)

This catalog has a simple permission model, where all members have the same permissions. For advanced permissions, upgrade to Enterprise.

Here you can see how this document is used and linked by other documents

Used (0)

Others referencing this

Uses these (0)

Documents linked from this document

How it Behaves

How this document is categorized changes how it behaves.

This is a main document, it is listed normally and appears in search result.

Type

This is an Information. Lists are treated as information and not as checkboxes'

Get something to say?

Comment to start a discussion or make a note
send

please log on to chat

close

Durchsuche ganz Azing