JasperFx / lamar

Fast Inversion of Control Tool and Successor to StructureMap
https://jasperfx.github.io/lamar
MIT License
563 stars 118 forks source link

.Net5 app with Lamar not able to cast IServiceCollection to ServiceRegistry #272

Closed gswartz777 closed 2 years ago

gswartz777 commented 3 years ago

I just installed .net5 and created a new .net core app to do some learning. In trying to get structuremap set up for it I came across Lamar. Installed that with the nuget package manager and tried to follow along with this - https://jasperfx.github.io/lamar/documentation/ioc/aspnetcore/

When I run the code I get the error "Unable to cast object of type 'Microsoft.Extensions.DependencyInjection.ServiceCollection' to type 'Lamar.ServiceRegistry" Here's my startup class.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureContainer(ServiceRegistry services)
    {
        services.AddMvc();
        services.AddLogging();

        services.Scan(s =>
        {
            s.TheCallingAssembly();
            s.WithDefaultConventions();
        });

        services.AddScoped<IDbClass, DbClass>();
    }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                "default",
                "{controller=Home}/{action=Index}/{id?}");
        });
    }
}
CodingGorilla commented 3 years ago

What does your Program.cs look like, do you have the appropriate .UseLamar() as shown here: https://jasperfx.github.io/lamar/documentation/ioc/aspnetcore/

gswartz777 commented 3 years ago

Ah thanks. Yeah I missed the program update. Got that updated but after doing that it's not resolving one of the services that should be injected into my controller. It's complaining about not being able to resolve the IInterceptor class defined in another project in the solution, so it's not in the same assembly. Guess I'm not clear on how to do that. From reading here - https://jasperfx.github.io/lamar/documentation/ioc/registration/auto-registration-and-conventions/ - I thought I had to specify the assembly name in s.Assembly("Net5Test2.BLL") Guess it must be something else?

using Lamar;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Net5Test2.BLL;

namespace Net5Test2
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureContainer(ServiceRegistry services)
        {
            services.AddMvc();
            services.AddLogging();

            services.Scan(s =>
            {
                s.Assembly("Net5Test2.BLL");
                s.TheCallingAssembly();
                s.WithDefaultConventions();
            });

            services.AddScoped<IDbClass, DbClass>();
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    "default",
                    "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Net5Test2.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Net5Test2.BLL;

namespace Net5Test2.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private readonly IInterceptor _interceptor;
        public HomeController(ILogger<HomeController> logger, IInterceptor interceptor)
        {
            _logger = logger;
            _interceptor = interceptor;
        }

        public IActionResult Index()
        {
            _interceptor.Save();
            return View();
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

And in another project I have the following.

using System;

namespace Net5Test2.BLL
{
    public interface IInterceptor
    {
        void Save();
    }

    public class MyInterceptor : IInterceptor
    {
        private readonly IDbClass _dbClass;

        public MyInterceptor(IDbClass dbClass)
        {
            _dbClass = dbClass;
        }

        public void Save()
        {
            _dbClass.Save();
        }
    }

    public interface IDbClass
    {
        void Save();
    }

    public class DbClass : IDbClass
    {
        public void Save()
        {
        }
    }
}
CodingGorilla commented 3 years ago

Just a off the top of my head guess, but this is probably the culprit (typo):

s.Assembly("Net5Test2.BLL");
gswartz777 commented 3 years ago

Don't think so. Double checked it, copied what you had and still doesn't work. I pushed it to my gitlab repo if you want to take a look - https://gitlab.com/gswartz/net5test2

CodingGorilla commented 3 years ago

Oh, I thought the "B" in .BLL was a typo for .DLL. 😄 Try using the .WhatDoIHave() and .WhatDidIScan() diagnostics, maybe those will give you a clue. See: https://jasperfx.github.io/lamar/documentation/ioc/diagnostics/

FYI, you can get to those methods by casting the app.ApplicationServices to IContainer. I usually do this in the Startup.Configure method like so:

if(IsDebugMode)
{
    var container = (IContainer)app.ApplicationServices;
    Log.Logger.Verbose(container.WhatDidIScan());
    Log.Logger.Verbose(container.WhatDoIHave());
}
gswartz777 commented 3 years ago

WhatDidIScan seemed to come up with the right assembly.

Assemblies
----------
* Net5Test2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
* Net5Test2.BLL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Conventions
--------
* Default I[Name]/[Name] registration convention

No problems were encountered in exporting types from Assemblies

However I don't see the IInterceptor in the WhatDoIHave log. I'm assuming I should?

CodingGorilla commented 3 years ago

I would expect that you should, do you see all the other registrations from that assembly?

Also, try rearranging the scan method a little, I can remember if this would make a difference or not, but it's worth a shot:

services.Scan(s =>
{
    s.WithDefaultConventions();
    s.Assembly("Net5Test2.BLL");
    s.TheCallingAssembly();
});

If that doesn't help, then I'm out of ideas. Everything else you have going on looks correct to me.

gswartz777 commented 3 years ago

I can add additional interfaces and classes to the referenced project and everything loads fine. The only one it doesn't seem to find is the MyInterceptor class and interface. I'm wondering if the word interceptor is causing some problem. If I rename it it picks it up just fine. Very odd.

FinalBreaker commented 3 years ago

i'd say to move services.AddControllersWithViews(); into public void ConfigureContainer(ServiceRegistry services) tack on .AddControllersAsServices() to the end of AddControllersWithViews so mvc knows to build controllers from the DI (it seams weird to me that this isn't the default)

next your MyInterceptor doesn't follow the default convention. either

  1. add either s.SingleImplementationsOfInterface(); or s.RegisterConcreteTypesAgainstTheFirstInterface();
  2. change the name of MyInterceptor or IInterceptor
    • Interceptor : IInterceptor
    • MyInterceptor : IMyInterceptor

next, maybe preference here but i prefer selecting assemblies from a known type. s.AssemblyContianingType<IDbClass>(); or w/e you think is the best entry type to that project/dll