The entry point exited without ever building an IHost - c#

I try to run integration tests and get this error
System.InvalidOperationException: The entry point exited without ever building an IHost.
at Microsoft.Extensions.Hosting.HostFactoryResolver.HostingListener.CreateHost()
at Microsoft.Extensions.Hosting.HostFactoryResolver.<>c__DisplayClass10_0.b__0(String[] args)
at Microsoft.AspNetCore.Mvc.Testing.DeferredHostBuilder.Build()
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory1.CreateHost(IHostBuilder builder) at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory1.ConfigureHostBuilder(IHostBuilder hostBuilder)
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory1.EnsureServer() at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory1.CreateDefaultClient(DelegatingHandler[] handlers)
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory1.CreateDefaultClient(Uri baseAddress, DelegatingHandler[] handlers) at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory1.CreateClient(WebApplicationFactoryClientOptions options)
at Ptco.System.IntegrationTests.Infrastructure.IntegrationTestsWebFactory.CreteManagedClient() in C:\Users\nemes\Documents\GitHub\ptco.app\System\Ptco.System.IntegrationTests\Infrastructure\IntegrationTestsWebFactory.cs:line 249
Row 249 is
private HttpClient CreteManagedClient() =>
CreateClient(new WebApplicationFactoryClientOptions
{
BaseAddress = new Uri(_configuration.GetValue<string>("IntegrationServerBaseUri"))
});
That is called like this
public IntegrationTestsWebFactory()
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
{
_configurationPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "appsettings.json");
_configuration = BuildConfiguration();
ManagedHttpClient = CreteManagedClient();
}
How I can solve this error?

It happens to me because I use serilog with CreateBootstrapLogger().
Here is the detail discussion https://github.com/serilog/serilog-aspnetcore/issues/289
To me, it works fine with CreateLogger. Check if really need CreateBootstrapLogger here https://nblumhardt.com/2020/10/bootstrap-logger/#why-cant-we-just-do-both

Related

Unknown command: --environment=Development during testing

I created an ASP.NET project and wrote some integration tests for it. But when I tried to run dotnet test this shows up:
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
The active test run was aborted. Reason: Test host process crashed : Unknown command: --environment=Development
Test Run Aborted with error System.Exception: One or more errors occurred.
---> System.Exception: Unable to read beyond the end of the stream.
at System.IO.BinaryReader.Read7BitEncodedInt()
at System.IO.BinaryReader.ReadString()
at Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.LengthPrefixCommunicationChannel.NotifyDataAvailable()
at Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.TcpClientExtensions.MessageLoopAsync(TcpClient client, ICommunicationChannel channel, Action`1 errorHandler, CancellationToken cancellationToken)
--- End of inner exception stack trace ---.
As I understand something tries to run dotnet executable with --environment=Development but this argument is invalid even though it is used in Microsoft docs.
I tried creating new ASP.NET project (no controllers, services, database etc. just API that does nothing and an empty test) but I couldn't reproduce the error again.
Initially I created my project and solution in the same folder by accident and had to manually move project to subfolder. Everything worked fine after I did that so I assumed it's fine. Maybe that is the reason.
Here's how I access application during testing:
// TestingApplication.cs
public class TestingApplication : WebApplicationFactory<Program>
{
private readonly Guid _appId = Guid.NewGuid();
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
// Add mock/test services to the builder here
builder.ConfigureServices(services =>
{
services.AddMvcCore().AddApplicationPart(typeof(Program).Assembly);
services.AddScoped(sp => new DbContextOptionsBuilder<EfDbContext>()
.UseSqlServer(
$"DATABASE CONNECTION STRING")
.UseApplicationServiceProvider(sp)
.Options);
});
}
protected override IHost CreateHost(IHostBuilder builder)
{
var host = base.CreateHost(builder);
using (var serviceScope = host.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetRequiredService<EfDbContext>();
context.Database.EnsureCreated();
}
return host;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
using (var serviceScope = Server.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetRequiredService<EfDbContext>();
context.Database.EnsureDeleted();
}
}
}
// BaseTest.cs
public class BaseTest : IDisposable, IClassFixture<TestingApplication>
{
protected readonly TestingApplication Application;
private HttpClient? _client;
protected HttpClient Client => _client ??= Application.CreateClient();
public BaseTest(TestingApplication testingApplication)
{
Application = testingApplication;
}
public void Dispose()
{
Application.Dispose();
}
}
Some more info:
Unit tests work just fine
Initially I forgot to add <InternalsVisibleTo Include="NameOfTestsProject" /> to the main project file, but it doesn't work either way.
.NET 6, OS - Linux, IDE - Jetbrains Rider
Rebuilding solution does not work
Creating new project for unit tests doesn't help either
Does anyone know what the problem is?
UPD I figured it out
Okay, So this is just another example of copy-pasting someone else's code without checking. I had copied something along the lines of:
if (args[0] == "something") {
...
} else if (args[0] == "something else") {
...
} else {
// exit with code 1 here and print error
}
in my Program.cs. It worked fine by itself but when testing it caused this problem.

ASP.Net 6 custom WebApplicationFactory throws exception

I am migrating my existing ASP.Net 5 web app to ASP.Net 6 and bump into the final hurdles of getting the integration tests to pass.
I customize WebApplicationFactory and it throws exception: Changing the host configuration using WebApplicationBuilder.WebHost is not supported. Use WebApplication.CreateBuilder(WebApplicationOptions) instead.
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "IntegrationTests");
builder.ConfigureServices(services => {
// Create a new service provider.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase().AddLogging()
.BuildServiceProvider();
// Add a database context (AppDbContext) using an in-memory database for testing.
services.AddDbContextPool<AppDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryAppDb");
options.UseInternalServiceProvider(serviceProvider);
options.EnableSensitiveDataLogging();
options.EnableDetailedErrors();
options.LogTo(Console.WriteLine);
});
services.AddDbContextPool<AppIdentityDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryIdentityDb");
options.UseInternalServiceProvider(serviceProvider);
options.EnableSensitiveDataLogging();
options.EnableDetailedErrors();
options.LogTo(Console.WriteLine);
});
services.AddScoped<SignInManager<AppUser>>();
services.AddScoped<ILogger<UserRepository>>(provider => {
ILoggerFactory loggerFactory = provider.GetRequiredService<ILoggerFactory>();
return loggerFactory.CreateLogger<UserRepository>();
});
services.AddDistributedMemoryCache();
// Build the service provider.
var sp = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database contexts
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var appDb = scopedServices.GetRequiredService<AppDbContext>();
var identityDb = scopedServices.GetRequiredService<AppIdentityDbContext>();
var logger = scopedServices.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
// Ensure the database is created.
appDb.Database.EnsureCreated();
identityDb.Database.EnsureCreated();
try
{
// Seed the database with test data.
SeedData.PopulateTestData(identityDb);
SeedData.PopulateTestData(appDb);
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred seeding the " +
$"database with test messages. Error: {ex.Message}");
}
}
});
}
}
Exception:
Message: 
System.NotSupportedException : The content root changed from "C:\Projects\C#\AspNetCoreApi\src\Web.Api\" to "C:\Projects\C#\AspNetCoreApi\test\Web.Api.IntegrationTests\bin\Debug\net6.0\". Changing the host configuration using WebApplicationBuilder.WebHost is not supported. Use WebApplication.CreateBuilder(WebApplicationOptions) instead.
Stack Trace: 
ConfigureWebHostBuilder.UseSetting(String key, String value)
HostingAbstractionsWebHostBuilderExtensions.UseContentRoot(IWebHostBuilder hostBuilder, String contentRoot)
Program.<Main>$(String[] args) line 58
--- End of stack trace from previous location ---
HostingListener.CreateHost()
<>c__DisplayClass8_0.<ResolveHostFactory>b__0(String[] args)
DeferredHostBuilder.Build()
WebApplicationFactory`1.CreateHost(IHostBuilder builder)
WebApplicationFactory`1.ConfigureHostBuilder(IHostBuilder hostBuilder)
WebApplicationFactory`1.EnsureServer()
WebApplicationFactory`1.CreateDefaultClient(DelegatingHandler[] handlers)
WebApplicationFactory`1.CreateDefaultClient(Uri baseAddress, DelegatingHandler[] handlers)
WebApplicationFactory`1.CreateClient(WebApplicationFactoryClientOptions options)
WebApplicationFactory`1.CreateClient()
MyControllerIntegrationTests.ctor(CustomWebApplicationFactory`1 factory) line 15
Any advice and insight is appreciated.
The error happens due to this line in Program.cs:
builder.WebHost.UseContentRoot(Path.GetFullPath(Directory.GetCurrentDirectory())); // Changing the host configuration using WebApplicationBuilder.Host is not supported. Use WebApplication.CreateBuilder(WebApplicationOptions) instead.
I added this as I want to preserve args and therefore I used WebApplication.CreateBuilder(args). Thanks to #davidfowl I used the following code snippet instead:
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
ApplicationName = typeof(Program).Assembly.FullName,
ContentRootPath = Path.GetFullPath(Directory.GetCurrentDirectory()),
WebRootPath = "wwwroot",
Args = args
});
and removed the faulting line of code. Note that builder.WebHost.UseContentRoot will throw exception whenever the input parameter differs from the default value. In my case, it throws exception whenever running the integration tests but NOT when running the application proper.
I have came across the same issue when I tried to create .NET6 api as window service,
I solved with the below code
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
ContentRootPath = WindowsServiceHelpers.IsWindowsService() ? AppContext.BaseDirectory : default
});

Autofac registers components multiple times

In a previous question about how I visualize the graph of my dependencies I got the foundation for the code I now use to visualize my dependency graph as it is resolved by Autofac.
Running the code I get a tree that results in code like the following.
Usd.EA.Bogfoering.WebApi.Controllers.BogfoerController (3851,7 ms. / 0,0 ms.) Depth: 0
Usd.EA.Bogfoering.WebApi.Controllers.BogfoerController (3851,7 ms. / 0,4 ms.) Depth: 1
Usd.Utilities.WebApi.Controllers.UnikOwinContext (0,1 ms. / 0,0 ms.) Depth: 2
Usd.Utilities.WebApi.Controllers.UnikOwinContext (0,1 ms. / 0,0 ms.) Depth: 3
In the start I thought there was a problem with the code, and that it for some reason resulted in the components getting resolved multiple times. As Steven points out, this could happen when a component is registered as InstancePerDependency. But as several of my components are registered as InstancePerLifetime or SingleInstance dependencies, those dependencies shouldn't be resolved twice in the graph.
Steven does mention that "the first resolve of the InstancePerDependency dependency seems to have more dependencies than the next resolve, because this graph only shows resolves. Perhaps this is what's going on." But as I'm seeing InstancePerLifetime components being registered multiple times, on several occasions throughout the graph, I have the feeling that there's something else going on here.
What could be going on here?
How the dependencies are registered
The following code is the one we use to register our assemblies:
public static void RegisterAssemblies(this ContainerBuilder containerBuilder, IList<Assembly> assemblies, params Type[] typesToExclude)
{
if (containerBuilder != null && assemblies.Any())
{
var allTypes = assemblies.SelectMany(assembly => assembly.GetTypes()).Where(t => !typesToExclude.Any(t2 => t2.IsAssignableFrom(t))).ToList();
RegisterAllClassesWithoutAttribute(containerBuilder, allTypes);
RegisterClassesThatAreSingleton(containerBuilder, allTypes);
RegisterClassesThatAreInstancePerLifetimeScope(containerBuilder, allTypes);
RegisterGenericInterfaces(containerBuilder, allTypes);
RegisterRealOrTestImplementations(containerBuilder, allTypes);
RegisterAutofacModules(containerBuilder, allTypes);
containerBuilder.Register(c => UnikCallContextProvider.CurrentContext).As<IUnikCallContext>();
}
}
private static void RegisterAutofacModules(ContainerBuilder containerBuilder, List<Type> allTypes)
{
var modules = allTypes.Where(type => typeof(IModule).IsAssignableFrom(type) && type.GetCustomAttribute<DoNotRegisterInIocAttribute>() == null);
foreach (var module in modules)
{
containerBuilder.RegisterModule((IModule) Activator.CreateInstance(module));
}
}
private static void RegisterRealOrTestImplementations(ContainerBuilder containerBuilder, List<Type> allTypes)
{
if (StaticConfigurationHelper.UseRealImplementationsInsteadOfTestImplementations)
{
var realTypes = allTypes.Where(type => type.GetCustomAttribute<RealImplementationAsInstancePerLifetimeScopeAttribute>() != null).ToArray();
containerBuilder.RegisterTypes(realTypes).AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
else
{
var testTypes = allTypes.Where(type => type.GetCustomAttribute<TestImplementationAsInstancePerLifetimeScopeAttribute>() != null).ToArray();
containerBuilder.RegisterTypes(testTypes).AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}
private static void RegisterGenericInterfaces(ContainerBuilder containerBuilder, List<Type> allTypes)
{
var typesAsGenericInterface = allTypes.Where(type => type.GetCustomAttribute<RegisterAsGenericInterfaceAttribute>() != null).ToArray();
foreach (var type in typesAsGenericInterface)
{
var attribute = type.GetCustomAttribute<RegisterAsGenericInterfaceAttribute>();
containerBuilder.RegisterGeneric(type).As(attribute.Type);
}
}
private static void RegisterClassesThatAreInstancePerLifetimeScope(ContainerBuilder containerBuilder, List<Type> allTypes)
{
var typesAsInstancePerDependency = allTypes.Where(type => type.GetCustomAttribute<InstancePerLifetimeScopeAttribute>() != null).ToArray();
containerBuilder.RegisterTypes(typesAsInstancePerDependency).InstancePerLifetimeScope().AsImplementedInterfaces();
}
private static void RegisterClassesThatAreSingleton(ContainerBuilder containerBuilder, List<Type> allTypes)
{
var typesAsSingleton = allTypes.Where(type => type.GetCustomAttribute<SingletonAttribute>() != null).ToArray();
containerBuilder.RegisterTypes(typesAsSingleton).SingleInstance().AsImplementedInterfaces();
}
private static void RegisterAllClassesWithoutAttribute(ContainerBuilder containerBuilder, List<Type> allTypes)
{
var types = allTypes.Where(type => !typeof(IModule).IsAssignableFrom(type) &&
type.GetCustomAttribute<DoNotRegisterInIocAttribute>() == null &&
type.GetCustomAttribute<SingletonAttribute>() == null &&
type.GetCustomAttribute<RealImplementationAsInstancePerLifetimeScopeAttribute>() == null &&
type.GetCustomAttribute<TestImplementationAsInstancePerLifetimeScopeAttribute>() == null &&
type.GetCustomAttribute<InstancePerLifetimeScopeAttribute>() == null &&
type.GetCustomAttribute<RegisterAsGenericInterfaceAttribute>() == null).ToArray();
containerBuilder.RegisterTypes(types).AsSelf().AsImplementedInterfaces();
}
Where the assemblies that are delivered to the RegisterAssemblies method could be fetched like this:
private List<Assembly> GetAssemblies()
{
var assemblies = AssemblyResolveHelper.LoadAssemblies(AppDomain.CurrentDomain.BaseDirectory,
new Regex(#"Usd.EA.*\.dll"),
SearchOption.TopDirectoryOnly);
assemblies.AddRange(AssemblyResolveHelper.LoadAssemblies(AppDomain.CurrentDomain.BaseDirectory,
new Regex(#"Usd.Utilities.*\.dll"),
SearchOption.TopDirectoryOnly));
assemblies.Add(GetType().Assembly);
return assemblies.Distinct().ToList();
}
The attributes
The attributes used in RegisterAllClassesWithoutAttribute are custom attributes that we manually assign to individual classes
using System;
[AttributeUsage(AttributeTargets.Class)]
public class DoNotRegisterInIocAttribute : Attribute
{
}
Used like this
[ExcludeFromCodeCoverage]
[DoNotRegisterInIoc]
public sealed class TestClass : ITestClass
When I'm not overwriting Autofacs MaxResolveDepth I get the following error
Failed An error occurred when trying to create a controller of type
'BogfoerController'. Make sure that the controller has a parameterless
public constructor. An exception was thrown while activating λ:Usd.EA
.Bogfoering.WebApi.Controllers.BogfoerController ->
Usd.EA.Bogfoering.WebApi.Controllers.BogfoerController -> ......
Probable circular dependency between factory-scoped components. Chain
includes 'Activator = DomainWrapper (DelegateActivator), Services =
SomeService, Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime,
Sharing = None, Ownership = ExternallyOwned'
Short answer:
This is casused by the Autofac behaviour when resolving services from a child ILifetimeScope created by calling BeginLifetimeScope(Action<ContainerBuilder> configurationAction).
Long answer:
I have set up a simple test to prove above statement. I have generated a 51 test classes referencing themselves.
public class Test0
{
public Test0() { }
}
public class Test1
{
public Test1(Test0 test) { }
}
(...)
public class Test50
{
public Test50(Test49 test) { }
}
Registered them in a newly created container and tried to resolve the "Test50" class directly from the container. As you already found out. There is hard coded limit of 50 dependencies depth in the Autofac library, which you can see it on the GitHub page. After reaching this limit the DependencyResolutionException is thrown stating "Probable circular dependency between factory-scoped components." And this is exactly what happened in my first test.
Now you have asked, why are you seeing multiple registrations of the same dependencies. So here comes the fun part. When you are trying to resolve your instance, you are probably gonna use the BeginLifetimeScope function to create new ILifetimeScope. This would be still ok, unless you are going to add some new registrations to the child scope using one of the overloads. See example below:
using (var scope = container.BeginLifetimeScope(b => { }))
{
var test = scope.Resolve<Test49>();
}
I'm resolving only 50 dependencies (which have previously worked), but now, it yields an exception:
As you can see, this is exactly the same behaviour as you previously described. Each dependency is now showed 2 times. On that image, you can also see that the dependency graph has only reached the Test25 class. This has effectively reduced the previous max depth by a half (whole 25 dependencies!). We can test this by successuflly resolving Test24 class, but exception is thrown when trying to resolve the Test25. This goes even funnier, how do you think, what happens if we add another scope?
using (var scope1 = container.BeginLifetimeScope(b => { }))
{
using (var scope2 = scope1.BeginLifetimeScope(b => { }))
{
var test2 = scope2.Resolve<Test49>();
}
}
You probably guessed it, now you can only resolve the dependencies of depth 50 / 3 = ~16.
Conclusion: Creating nested scopes is limiting the actual available maximum depth of the dependencies graph N times, where the N is the depth of the scope. To be honest, scopes created without extending the container builder do not affect this number. In my opinion, this is a huge absurd, to have hard-coded magic number, which is nowhere in the documentation, cannot be easily configured, doesn't even represent the actual maximum depth and when overflowed, it throws misleading exception stating that you have circular dependencies in the graph somewhere.
Solutions: As a resolution to this issue you could not use this overload of this function. This could be not possible due to architecture limitations, or even the 3rd party framework which could be using the Autofac as DI container.
Another solution that you have already mentioned is overwriting the MaxResolveDepth using dirty reflection.
string circularDependencyDetectorTypeName = typeof(IContainer).AssemblyQualifiedName.Replace(typeof(IContainer).FullName, "Autofac.Core.Resolving.CircularDependencyDetector");
Type circularDependencyDetectorType = Type.GetType(circularDependencyDetectorTypeName);
FieldInfo maxResolveDepthField = circularDependencyDetectorType.GetField("MaxResolveDepth", BindingFlags.Static | BindingFlags.NonPublic);
maxResolveDepthField.SetValue(null, 500);
On the Autofac's GitHub you can also read that they are already planning to change the behaviour of the CircularDependencyDetector, so it could handle the infinite depth of dependencies, but those plans were mentioned in 2018 and they even couldn't change that exception message by this date.

Faking Confluent .NET Kafka consumers

Environment: .NET 4.5.
FakeItEasy : 4.0.0
I am trying to create a fake object of Kafka consumer, below is the syntax I use:
var fakeconsumer = A.Fake<Consumer<Null, string>>((x => x.WithArgumentsForConstructor(() => new Consumer<Null, string>(A.Dummy<IEnumerable<KeyValuePair<string, object>>>(), A.Dummy<IDeserializer<Null>>(), A.Dummy<StringDeserializer>()))));
The code for Kafka client is here: https://github.com/confluentinc/confluent-kafka-dotnet/blob/master/src/Confluent.Kafka/Consumer.cs
As you can see, I am invoking the Fake call with correct parameters required for the constructor. However I keep getting the follow error message : "No constructor matches the passed arguments for constructor.".
Any help is greatly appreciated.
Thank you
Edit:
at FakeItEasy.Creation.CastleDynamicProxy.CastleDynamicProxyGenerator.CreateProxyGeneratorResult(Type typeOfProxy, ProxyGenerationOptions options, IEnumerable1 additionalInterfacesToImplement, IEnumerable1 argumentsForConstructor, IFakeCallProcessorProvider fakeCallProcessorProvider) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\CastleDynamicProxy\CastleDynamicProxyGenerator.cs:line 125
at FakeItEasy.Creation.CastleDynamicProxy.CastleDynamicProxyGenerator.GenerateProxy(Type typeOfProxy, ProxyGenerationOptions options, IEnumerable1 additionalInterfacesToImplement, IEnumerable1 argumentsForConstructor, IFakeCallProcessorProvider fakeCallProcessorProvider) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\CastleDynamicProxy\CastleDynamicProxyGenerator.cs:line 86
at FakeItEasy.Creation.FakeObjectCreator.GenerateProxy(Type typeOfFake, IProxyOptions proxyOptions, IEnumerable1 argumentsForConstructor) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\FakeObjectCreator.cs:line 113
at FakeItEasy.Creation.FakeObjectCreator.CreateFake(Type typeOfFake, IProxyOptions proxyOptions, DummyCreationSession session, IDummyValueResolver resolver, Boolean throwOnFailure) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\FakeObjectCreator.cs:line 36
at FakeItEasy.Creation.DefaultFakeAndDummyManager.CreateFake(Type typeOfFake, Action1 optionsBuilder) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\DefaultFakeAndDummyManager.cs:line 41
at FakeItEasy.A.Fake[T](Action`1 optionsBuilder) in C:\projects\fakeiteasy\src\FakeItEasy\A.cs:line 47
I believe I've reproduced your problem. Here's the full exception that I see:
FakeItEasy.Core.FakeCreationException :
Failed to create fake of type Confluent.Kafka.Consumer`2[Confluent.Kafka.Null,System.String] with the specified arguments for the constructor:
No constructor matches the passed arguments for constructor.
An exception of type System.ArgumentException was caught during this call. Its message was:
'group.id' configuration parameter is required and was not specified.
at Confluent.Kafka.Consumer..ctor(IEnumerable`1 config)
at Confluent.Kafka.Consumer`2..ctor(IEnumerable`1 config, IDeserializer`1 keyDeserializer, IDeserializer`1 valueDeserializer)
at Castle.Proxies.Consumer`2Proxy..ctor(IInterceptor[] , IEnumerable`1 , IDeserializer`1 , IDeserializer`1 )
at FakeItEasy.Core.DefaultExceptionThrower.ThrowFailedToGenerateProxyWithArgumentsForConstructor(Type typeOfFake, String reasonForFailure)
at FakeItEasy.Creation.FakeObjectCreator.AssertThatProxyWasGeneratedWhenArgumentsForConstructorAreSpecified(Type typeOfFake, ProxyGeneratorResult result, IProxyOptions proxyOptions)
at FakeItEasy.Creation.FakeObjectCreator.CreateFake(Type typeOfFake, IProxyOptions proxyOptions, DummyCreationSession session, IDummyValueResolver resolver, Boolean throwOnFailure)
at FakeItEasy.Creation.DefaultFakeAndDummyManager.CreateFake(Type typeOfFake, Action`1 optionsBuilder)
at FakeItEasy.A.Fake[T](Action`1 optionsBuilder)
Kafka.cs(14,0): at FakeItEasyQuestions2015.Kafka.MakeConsumer()
You can see that FakeItEasy itself encountered an exception while calling the Consumer class's constructor:
An exception of type System.ArgumentException was caught during this call. Its message was:
'group.id' configuration parameter is required and was not specified.
This was thrown from the Consumer constructor on line 756:
if (config.FirstOrDefault(prop => string.Equals(prop.Key, "group.id", StringComparison.Ordinal)).Value == null)
{
throw new ArgumentException("'group.id' configuration parameter is required and was not specified.");
}
It seems that
Consumer(IEnumerable<KeyValuePair<string, object>> config,
IDeserializer<TKey> keyDeserializer,
IDeserializer<TValue> valueDeserializer)`
Has some requirements on its inputs that aren't being met. In particular, it seems it needs config to contain one element with the key "group.id". If I change your code to
var fakeconsumer = A.Fake<Consumer<Null, string>>(
(x => x.WithArgumentsForConstructor(
() => new Consumer<Null, string>(new [] { new KeyValuePair<string, object>("group.id", "hippo")},
A.Dummy<IDeserializer<Null>>(),
A.Dummy<StringDeserializer>()))));
The fake is created.
I notice that you cross-posted to FakeItEasy Issue 1176. I'll make a note there to come here for this answer.
Not 100% related to the original question but in my library (Silverback: https://github.com/BEagle1984/silverback) I have a mocked in-memory implementation of the Confluent.Kafka library, that allows for kinda sophisticated integration tests. See some simple examples: https://silverback-messaging.net/concepts/broker/testing.html.
Just to give you an idea:
[Fact]
public async Task SampleTest()
{
// Arrange
var testingHelper = _factory.Server.Host.Services
.GetRequiredService<IKafkaTestingHelper>();
var producer = testingHelper.Broker
.GetProducer(new KafkaProducerEndpoint("tst-topic"));
// Act
await producer.ProduceAsync(new TestMessage { Content = "abc" });
await testingHelper.WaitUntilAllMessagesAreConsumedAsync();
// Assert
testingHelper.Spy.OutboundEnvelopes.Should().HaveCount(1);
testingHelper.Spy.InboundEnvelopes.Should().HaveCount(1);
testingHelper.Spy.InboundEnvelopes[0].Message.As<TestMessage>
.Content.Should().Be("abc");
}
The implementation is not that complex but it supports partitions and a simulation of the rebalance mechanism.
See the implementation: https://github.com/BEagle1984/silverback/tree/master/src/Silverback.Integration.Kafka.Testing/Messaging/Broker/Kafka
I am no expert on the Consumer class for Kafka, but it looks like your are invoking it like:
Consumer<Null, string>
But the only constructors I can find in the code are:
public Consumer(
IEnumerable<KeyValuePair<string, object>> config)
public Consumer(
IEnumerable<KeyValuePair<string, object>> config,
IDeserializer<TKey> keyDeserializer,
IDeserializer<TValue> valueDeserializer)
So there is no match. It looks like you want to use the first one, so you are missing the IEnumerable part.

Autofac WCF registration exception with svc-less service

I'm trying to set up Autofac as my DI container for a new WCF project I am working on. We're working with a svc-less configuration and self hosting. Without Autofac and simply using poor-man's DI, everything is working exactly as desired, but when I add Autofac into the mix, something is going awry.
The code I am using to set everything up is:
public class CustomServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var container = InitializeDIContainer();
var customHost = new CustomServiceHost(serviceType, baseAddresses);
// Exception is being thrown on the line below
customHost.AddDependencyInjectionBehavior(serviceType, container);
return customHost;
}
private static IContainer InitializeDIContainer()
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof (AccountRepository).Assembly)
.Where(t => t.Name.EndsWith("Repository"))
.As(t => t.GetInterfaces().FirstOrDefault(
i => i.Name == "I" + t.Name));
builder.RegisterAssemblyTypes(typeof (AccountService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.As(t => t.GetInterfaces().FirstOrDefault(
i => i.Name == "I" + t.Name));
builder.RegisterType<tktktktkDbContext>().As<IDataContextAsync>();
builder.RegisterType<UnitOfWork>().As<IUnitOfWorkAsync>();
var container = builder.Build();
return container;
}
}
When this runs, I am getting the following exception:
An exception of type 'System.ArgumentException' occurred in Autofac.Integration.Wcf.dll but was not handled in user code.
Additional information: The service contract type 'tktktktk.Services.AccountClassService' has not been registered in the container.
When I put a breakpoint in the code and inspect the container, I can see all of my services, including the 'tktktktk.Services.AccountClassService' object, in the ComponentRegistry.Registrations collection.
I've tried reworking my web.config file to use the "Autofac.Integration.Wcf.AutofacServiceHostFactory" factory, but then my application fails before it even gets to this point.
I'm thinking that I missed a step somewhere, but am at a loss for what. Any assistance would be greatly appreciated!
UPDATE
I modified my web.config to use "Autofac.Integration.Wcf.AutofacServiceHostFactory" as indicated. I am now getting the following error:
WebHost failed to process a request.
Sender Information: System.ServiceModel.ServiceHostingEnvironment+HostingManager/45653674
Exception: System.ServiceModel.ServiceActivationException: The service '/Account/AccountClassService.svc' cannot be activated due to an exception during compilation. The exception message is: The AutofacServiceHost.Container static property must be set before services can be instantiated.. ---> System.InvalidOperationException: The AutofacServiceHost.Container static property must be set before services can be instantiated.
UPDATE 2
I tried what is suggested in the answer below, and I got the same error as above. One thing I noted, When I added AutofacServiceHost.Container = container to my code, that would not compile. I switched that to AutofacHostFactory.Container and it compiled fine. Also, with the web.config changed to use Autofac.Integration.Wcf.AutofacServiceHostFactory, I no longer hit any breakpoints in this code, suggesting that it is being bypassed completely now.
I also attempted svc-less configuration. I followed the steps in the documentation and received this error message:
The service 'MyService' configured for WCF is not registered with the Autofac container.
It turns out everything was registered correctly however the "add factory" element in web.config needs the assembly name of the service just as the .svc file would if it existed:
<add factory="Autofac.Integration.Wcf.AutofacServiceHostFactory" relativeAddress="~/MyService.svc" service="NameSpace.Service, AssemblyName" />
The Autofac documentation does not mention this or show it in the example. Also, Visual Studio will complain and underline the attribute however the service will run correctly.
Here's the situation you're using your custom host, on the other hand the Autofac have it's builtin ServiceHost provided as out of the box feature.
You need to set the property Container of AutofacServiceHost class:
public class CustomServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var container = InitializeDIContainer();
// Add this line and try again.
AutofacServiceHost.Container = container;
var customHost = new CustomServiceHost(serviceType, baseAddresses);
customHost.AddDependencyInjectionBehavior(serviceType, container);
return customHost;
}
private static IContainer InitializeDIContainer()
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof (AccountRepository).Assembly)
.Where(t => t.Name.EndsWith("Repository"))
.As(t => t.GetInterfaces().FirstOrDefault(
i => i.Name == "I" + t.Name));
builder.RegisterAssemblyTypes(typeof (AccountService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.As(t => t.GetInterfaces().FirstOrDefault(
i => i.Name == "I" + t.Name));
builder.RegisterType<tktktktkDbContext>().As<IDataContextAsync>();
builder.RegisterType<UnitOfWork>().As<IUnitOfWorkAsync>();
var container = builder.Build();
return container;
}
}
I ran into this problem myself. I managed to solve this by using the Named style registration
```
builder.RegisterType<YourServiceType>()
.Named<object>("myservice");
//and in your config:..
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" >
<serviceActivations>
<add relativeAddress="~/serviceurl.svc" service="myservice" factory="Autofac.Integration.Wcf.AutofacServiceHostFactory"/>
</serviceActivations>
</serviceHostingEnvironment>
```
Note that in my case i am not using apsnetcompatibilitymode.
I think its a bug in the AutofacHostFactory, its using the wrong servicename identifier to resolve the wcfservice type.
Using the Named registration style, your basically making sure that the type the registration is known under and the name used by the AutofacHostFactory match up

Categories

Resources