Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lamar & Alba ServiceRegistry not working in Release mode #98

Closed
Pondidum opened this issue Oct 21, 2018 · 8 comments
Closed

Lamar & Alba ServiceRegistry not working in Release mode #98

Pondidum opened this issue Oct 21, 2018 · 8 comments
Milestone

Comments

@Pondidum
Copy link
Contributor

For the record, I am not certain if this is a bug in Alba or Lamar.

The problem occurs when an object is registered outside the Startup class in Alba, using .ConfigureServices, and a ServiceRegistrywithin the application makes use of that registered type to register another type. This works fine when run in Debug mode, but under Release, the second type registration is not found. Probably better explained with a test...

  • If you run this in Debug mode, the test passes, however, if run in Release, it fails.
  • If you remove the use of ServiceRegistries, and make the services.For... call in ConfigureContainer, it works in both Release and Debug

Package versions used:

<PackageReference Include="Alba.AspNetCore2" Version="1.4.3" />
<PackageReference Include="Lamar" Version="1.1.2" />
<PackageReference Include="Lamar.Microsoft.DependencyInjection" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.1.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />

I can upload the test project if that would help.

[Fact]
public Task<IScenarioResult> Test1()
{
	var storage = new InMemoryStorage();
	var system = SystemUnderTest.ForStartup<Startup>();

	system.Configure(builder => builder.UseLamar());
	system.ConfigureServices(services =>
	{
		services.AddSingleton<IStorage>(storage);
	});

	return system.Scenario(_ =>
	{
		_.Get.Url("/Toggles");
		_.StatusCodeShouldBe(HttpStatusCode.OK);
		_.ContentShouldBe("{\"message\":\"LamarBug.InMemorySession\"}");
	});
}

public interface IStorage
{
	IStorageSession CreateSession();
}

public interface IStorageSession
{
}

public class InMemoryStorage : IStorage
{
	public IStorageSession CreateSession() => new InMemorySession();
}

public class InMemorySession : IStorageSession
{
}

public class Startup
{
	public void ConfigureContainer(ServiceRegistry services)
	{
		services.AddMvc();

		services.Scan(_ =>
		{
			_.TheCallingAssembly();
			_.WithDefaultConventions();
			_.LookForRegistries();
		});
		
		//services.For<IStorageSession>().Use(c => c.GetInstance<IStorage>().CreateSession()).Scoped();
	}

	public void Configure(IApplicationBuilder app, IHostingEnvironment env)
	{
		app.UseMvc();
	}
}


[Route("Toggles")]
public class TogglesController : Controller
{
	private readonly IStorageSession _session;

	public TogglesController(IStorageSession session)
	{
		_session = session;
	}

	[Route("")]
	[HttpGet]
	public async Task<IActionResult> Get()
	{
		return new JsonResult(new { Message = _session.GetType().ToString() });
	}
}

public class RestRegistry : ServiceRegistry
{
	public RestRegistry()
	{
		Scan(a =>
		{
			a.TheCallingAssembly();
			a.WithDefaultConventions();
		});

		For<IStorageSession>().Use(c => c.GetInstance<IStorage>().CreateSession()).Scoped();
	}
}
@CodingGorilla
Copy link
Contributor

When you say it fails in Release mode, what is the actual failure?

@Pondidum
Copy link
Contributor Author

🤦‍♂️ I really should have included that!

System.InvalidOperationException : Unable to resolve service for type 'LamarBug.IStorageSession' while attempting to activate 'LamarBug.TogglesController'.
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
   at lambda_method(Closure , IServiceProvider , Object[] )
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider.<>c__DisplayClass4_0.<CreateActivator>b__0(ControllerContext controllerContext)
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass5_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at Alba.SystemUnderTestExtensions.Scenario(ISystemUnderTest system, Action`1 configure)
   at Alba.SystemUnderTestExtensions.Scenario(ISystemUnderTest system, Action`1 configure)
--- End of stack trace from previous location where exception was thrown ---

@CodingGorilla
Copy link
Contributor

My initial guess is that it has to do with the scan of TheCallingAssembly. I would bet that the calling assembly is actually Alba (or maybe XUnit) and not the assembly that contains the actual service registry, so the service registry never actually gets picked up. The thing that makes me scratch my head is: Why does it work in debug mode?

The services.For<>.Use() syntax will always work because you're directly adding the registration, so that makes sense.

@Pondidum
Copy link
Contributor Author

Pondidum commented Oct 22, 2018

You might be onto something there.

If I change the Startup to explicitly use the RestRegistry, everything works too:

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

	services.Scan(_ =>
	{
		_.TheCallingAssembly();
		_.WithDefaultConventions();
		//_.LookForRegistries();
	});

	services.IncludeRegistry<RestRegistry>();
}

What I want to do is see the output of .WhatDidIScan(), but I can't figure out how to get at the container instance in the test 😞

@CodingGorilla
Copy link
Contributor

You can do it in the Startup.Configure() method by casting the app.ApplicationServices to a Container. This is what I do in my start up class(es):

if(IsDebugMode)
{
	var container = (IContainer)app.ApplicationServices;
	Log.Logger.Verbose(container.WhatDidIScan());
	Log.Logger.Verbose(container.WhatDoIHave());
}

@Pondidum
Copy link
Contributor Author

Well that is a helpful snippet even if we don't figure this out! Anyway the output of WhatDidIScan() shows the difference between Debug and Release:

Debug:

2018-10-22 21:17:54 [Verbose] All Scanners
================================================================

Assemblies
----------
* LamarBug, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Conventions
--------
* Default I[Name]/[Name] registration convention
* Lamar.Scanning.Conventions.FindRegistriesScanner

Assemblies
----------
* LamarBug, 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

And Release

2018-10-22 21:17:02 [Verbose] All Scanners
================================================================

Assemblies
----------
* Microsoft.AspNetCore.Hosting, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60

Conventions
--------
* Default I[Name]/[Name] registration convention
* Lamar.Scanning.Conventions.FindRegistriesScanner

No problems were encountered in exporting types from Assemblies

The remaining question I guess is...why the initial scan only finds the Microsoft.AspNetCore.Hosting in Release, but my actual assembly in Debug?

@CodingGorilla
Copy link
Contributor

For the record, I am not certain if this is a bug in Alba or Lamar.

Given all the information we have, I would venture to guess this has something to do with the way Alba is running the tests in the different configurations. As I said, the _.TheCallingAssembly() is only going to check the assembly which is actually executing the code.

So you can work around this, for both modes by adding into your scan: _..AssemblyContainingType<Startup>();. This will force it to scan the assembly which contains your StartupClass. Alternatively, if your registries are in a different assembly, then you can simply use one of your registry types as the generic argument.

There are a bunch of other methods on the scanner that you can use to add different assemblies to the scanning if neither of those is perfectly suitable, but the bottom line is that you have to make sure you are scanning all the right assemblies.

@Pondidum
Copy link
Contributor Author

Yes, I am aware of how the scanning works, so have updated my test to get around this. I've "moved" this issue to the Alba repo as that seems to be the more likely cuplrit, so will shut this issue.

Thanks for your help in diagnosing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants