Table of Contents

Dependency Provider Guide for MIB Consumers

This document explains how a consumer project can register custom services for the MIB FrontEnd server and how those services are resolved when MIB creates custom components through reflection.

Current scope

The current implementation covers:

  • custom components created through reflection by MIB
  • plugins/custom behaviors that are instantiated through the existing plugin loader flow and now reuse the same request scope for dependency resolution

It does not yet cover:

  • custom repositories loaded through RepositoryFactory
  • every reflection-based activation point in the solution

Why this exists

MIB creates several component instances internally. Because the consumer project does not control that new operation, constructor injection would normally not work for those components.

The dependency provider feature adds a registration point on the MIB side and allows MIB to resolve constructor dependencies when it creates a component dynamically.

The recommended setup is:

  1. a host web project that references MediaiBox.Cms.FrontEnd.Server
  2. one or more class libraries for:
    • custom components
    • custom services
    • shared contracts

Even if the consumer does not expose its own business Web API, it should still host MIB in an ASP.NET Core web project so the MIB server can run inside the normal HTTP pipeline.

Public entry point

Consumers register services through:

MediaiBox.Cms.FrontEnd.Server.DependencyInjection.MibServerDependencyBootstrap.ConfigureServices(...)

This registration is applied when the MIB server configures its internal service collection.

Registration API

The registration callback receives an IMibDependencyRegistrar.

Currently supported methods:

  • AddSingleton<TService, TImplementation>()
  • AddSingleton<TService>(instance)
  • AddScoped<TService, TImplementation>()
  • AddScoped<TService>()
  • AddTransient<TService, TImplementation>()
  • AddTransient<TService>()
  • AddHttpClient<TClient>()
  • AddHttpClient<TClient>(configureClient)
  • AddHttpClient<TClient, TImplementation>()
  • AddHttpClient<TClient, TImplementation>(configureClient)

Example: consumer host project

using MediaiBox.Cms.FrontEnd.Server.DependencyInjection;
using MyProject.Services;

MibServerDependencyBootstrap.ConfigureServices(services =>
{
    services.AddScoped<IMyTitleService, MyTitleService>();
    services.AddSingleton<IMyClock>(new MyClock());
    services.AddHttpClient<IMyApiClient, MyApiClient>(client =>
    {
        client.BaseAddress = new Uri("https://api.example.com");
        client.Timeout = TimeSpan.FromSeconds(15);
    });
});

The important point is that this configuration must run before the MIB host starts processing requests.

Example: custom service

using System.Net.Http;

public interface IMyTitleService
{
    string GetTitle();
}

public class MyTitleService : IMyTitleService
{
    private readonly HttpClient _httpClient;

    public MyTitleService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public string GetTitle() => _httpClient.BaseAddress?.Host ?? "Injected title from consumer";
}

Example: custom component with constructor injection

using MediaiBox.Cms.Api.Client.Model;
using MediaiBox.Cms.FrontEnd.Model.Mvc.UI.Component;
using MediaiBox.Cms.FrontEnd.Model.UI.Component;
using MediaiBox.Cms.FrontEnd.Model.UI.Context;
using MediaiBox.Cms.FrontEnd.Model.Workflow;
using Microsoft.AspNetCore.Http;

public class MyComponent : IRenderableComponent<ComponentConfiguration, object>
{
    private readonly IMyTitleService _titleService;

    public MyComponent(IMyTitleService titleService)
    {
        _titleService = titleService;
    }

    public ContextViewData Context { get; set; }
    public ComponentConfiguration Configuration { get; set; }
    public ComponentDisplayMode DisplayMode => ComponentDisplayMode.Edit;

    public Task Initialize(
        IMibApiClientLibrary apiClientLibrary,
        IPublicWorkflowFactory workflowFactory,
        IHttpContextAccessor httpContextAccessor)
    {
        return Task.CompletedTask;
    }

    public IInformationRequest GetInformationRequests() => null;

    public ComponentPermission GetPermissions(List<ResponseItemContent> responses)
    {
        return new ComponentPermission();
    }

    public Task<ComponentRenderData<object>> GetRenderData(
        List<ResponseItemContent> responseItemContents,
        CancellationToken cancellationToken)
    {
        return Task.FromResult(new ComponentRenderData<object>
        {
            Title = _titleService.GetTitle()
        });
    }

    public List<string> GetScripts() => [];
    public List<string> GetStyles() => [];
}

Optional access to the MIB dependency resolver

MIB also supports injecting:

MediaiBox.Cms.FrontEnd.Model.Mvc.DependencyInjection.IMibDependencyResolver

This is optional. Use it only when the component really needs dynamic or optional resolution.

Example:

public class MyComponent
{
    private readonly IMyTitleService _titleService;
    private readonly IMibDependencyResolver _resolver;

    public MyComponent(IMyTitleService titleService, IMibDependencyResolver resolver)
    {
        _titleService = titleService;
        _resolver = resolver;
    }
}

For most components, constructor injection of the required service types is enough.

How MIB resolves dependencies

When MIB creates a component through reflection, it now follows this strategy:

  1. inspect the available public constructors
  2. try the constructors with the highest parameter count first
  3. for each constructor parameter:
    • if the parameter type is IMibDependencyResolver, MIB injects its resolver wrapper
    • otherwise MIB tries to resolve the parameter from the current request service provider
  4. if all parameters of one constructor can be resolved, MIB uses that constructor
  5. if no constructor can be resolved, MIB falls back to the parameterless constructor

This also means typed clients registered through AddHttpClient(...) can be injected into services or components, as long as those services are available in the current request scope.

Where the services come from at runtime

Resolution is based on:

HttpContext.RequestServices

That means component dependencies are resolved from the current request scope.

This is important because:

  • Scoped services behave as expected per request
  • MIB does not use a detached global container for reflected components
  • the behavior stays aligned with the ASP.NET Core dependency injection model

Plugin and custom behavior support

The same request scope is now also available to plugin-based flows such as:

  • image URL resolver plugins
  • custom validators
  • custom business rules

This was implemented without changing plugin public contracts.

In practice, plugin loaders still keep their own built-in registrations (for example IMibLog and MIB API wrappers), but unresolved constructor dependencies may now be resolved from the same request scope used by components.

This means a plugin can keep its current interface contract and still receive consumer-registered services through constructor injection, as long as those services are available in the current request scope.

Backward compatibility

Existing components remain compatible.

If a component has a parameterless constructor, and MIB cannot resolve a dependency-aware constructor, it falls back to the old behavior.

So this feature is additive:

  • old components continue to work
  • new components may opt into constructor injection

Current limitations

At this stage:

  • reflected components are covered
  • plugin/custom behavior constructors can resolve services from the same request scope
  • repository overrides are still using the existing constructor flow

Practical recommendation

For new custom components:

  1. keep constructor dependencies explicit
  2. inject only the services you actually need
  3. use IMibDependencyResolver only for advanced or optional resolution
  4. keep the parameterless constructor only if you still want an easy fallback path

Summary

With the current implementation, a consumer can:

  • host MIB in its own ASP.NET Core host project
  • register services through MibServerDependencyBootstrap.ConfigureServices(...)
  • register standard services and typed clients through AddHttpClient(...)
  • create custom components that receive consumer-defined services through constructor injection
  • allow plugins/custom behaviors to resolve consumer-registered services from the same request scope without changing plugin contracts
  • rely on request-scoped resolution and backward-compatible fallback behavior