Search code examples
asp.netwpfdependency-injectionninjectninject-conventions

How to correctly bind types by convention?


The projects is planned to target multiplatform, so I pull the maximum of code in class libraries so that it can be easily reused.

The architecture is based on the Model-View-Presenter principle.

The project structure is the following:

Solution
    -> Domain
    -> Domain.Tests
    -> MVP
    -> MVP.Tests
    -> Web
    -> Web.Tests
    -> Windows
    -> Windows.Tests

MVP

This project contains the presenters and the views of the project. For example:

public interface IApplicationView : IView, IHasUiHandler<IApplicationUiHandler> {
}

public class ApplicationPresenter : Presenter<IApplicationView>
    , IApplicationUiHAndler {
    public ApplicationPresenter(IApplicationView view) : base(view) {
        View.Handler = this;
    }
}

Windows

This project contains the WPF GUI of the application, and the so-called Composition Root. For example:

public class ApplicationWindow : Window, IApplicationView {
}

public class App : Application {
    protected override void OnStartUp(StartupEventArgs e) {
        IKernel kernel = new StandardKernel();
        kernel.Bind(x => x
            .FromThisAssembly()
            .SelectAllClasses().EndingWith("Window")
            .BindAllInterfaces().EndingWith("View") // Here's what I'd like to do.
        );
    }
}

Web

This project contains the ASP.NET GUI pages for the application, and the so-called Composition Root.

public class ApplicationPage : Page, IApplicationView {
}

public class MvcApplication : HttpApplication {
    protected override void Application_Start() {
        IKernel kernel = new StandardKernel();
        kernel.Bind(x => x
            .FromThisAssembly()
            .SelectAllClasses().EndingWith("Page")
            .BindAllInterfaces().EndingWith("View") // Here's what I'd like to do.
    }
}

Well, I guess you get the idea...

I'm quite new to Dependency Injection, and even newer in convention binding.

I'd like to know how to configure the bindings using the conventions, with Ninject.

Any idea how to bind those views with Windows (WPF) and Pages (Web)?

EDIT

After trying what @BatteryBackupUnit suggested, I guess my problem is all about my search of assemblies.

using (var kernel = new StandardKernel()) 
    kernel.Bind(scanner => scanner
        .From(AppDomain.CurrentDomain
            .GetAssemblies()
            .Where(a => (a.FullName.Contains("MyProject.MVP") 
                || a.FullName.Contains("Windows"))
                && !a.FullName.Contains("Tests")))
        .SelectAllClasses()
        .EndingWith("Window")
        .BindSelection((type, baseType) =>
            type.GetInterfaces().Where(iface => iface.Name.EndsWith("View"))));

As stated previously, the View interfaces aren't located in the same assembly as the Window classes. The above code, base on @BatteryBackupUnit's answer, works great.


Solution

  • How about this:

    using FluentAssertions;
    using Ninject;
    using Ninject.Extensions.Conventions;
    using System.Linq;
    using Xunit;
    
    public interface ISomeView { }
    public interface ISomeOtherView { }
    public interface INotEndingWithViewWord { }
    
    public class SomePage : ISomeView, ISomeOtherView, INotEndingWithViewWord
    {
    }
    
    public class Demo
    {
        [Fact]
        public void Test()
        {
            using (var kernel = new StandardKernel())
            {
                kernel.Bind(x => x
                    .FromThisAssembly()
                    .SelectAllClasses()
                    .EndingWith("Page")
                    .BindSelection((type, baseType) => 
                         type.GetInterfaces()
                         .Where(iface => iface.Name.EndsWith("View"))));
    
                kernel.Get<ISomeView>().Should().BeOfType<SomePage>();
    
                kernel.Get<ISomeOtherView>().Should().BeOfType<SomePage>();
    
                kernel.Invoking(x => x.Get<INotEndingWithViewWord>())
                    .ShouldThrow<ActivationException>();
            }
        }
    }
    

    Note: I've been using nuget packages

    • Ninject
    • Ninject.Extensions.Conventions
    • xunit.net
    • FluentAssertions

    of those xunit.net and FluentAssertions are only there to run the test and not be used in production.

    Or you could also use .BindWith<T : IBindingGenerator> or .BindUsingRegex(...).