Unable to hook up interceptor on classes/interfaces in asp.net core 3.1 application - autofac

Packages: Autofac.Extensions.DependencyInjection (6.0.0), Autofac.extras.DynamicProxy(4.5.0)
I am trying to prototype an interceptor for all of our service classes/interface serving the Web API controllers in our application, by following the ASP.NET Core 4.0 and the Interceptor documentation. The Interceptor is just a simple Log action using Serlog:
public MethodCallInterceptor()
{
}
public void Intercept(IInvocation invocation)
{
var text = invocation.Method.Name;
Log.Logger.Debug($"Interceptor (Method): {text}");
invocation.Proceed();
}
Our main application sets up Autofac with ComApiApplication as the "Startup" class:
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<ComsApiApplication>());
}
In ComsApiApplication, I register most of ASP.NET MVC services in ConfigureServices (e.g. Authentication, Authorization etc). Then, I hook up the Interceptor for classes in a couple of our service assembles in its ConfigureContainer function:
public void ConfigureContainer(ContainerBuilder builder)
{
foreach (var assembly in Options.Assemblies)
{
// Add application services: IService => Service.
var registrations = assembly.GetExportedTypes()
.Where(type => type.Namespace != null && type.Namespace.Contains(".Services") && type.GetInterfaces().Any() && !type.IsAbstract)
.Select(type => new
{
Service = type.GetInterfaces().Single(inf => inf.Name.Contains(type.Name)),
Implementation = type
});
foreach (var registration in registrations)
{
//builder.RegisterType(registration.Implementation).As(registration.Service).InstancePerDependency().EnableInterfaceInterceptors().InterceptedBy(typeof(MethodCallInterceptor));
builder.RegisterType(registration.Implementation).As(registration.Service).EnableInterfaceInterceptors().InstancePerDependency().InterceptedBy(typeof(MethodCallInterceptor));
}
builder.Register(c => new MethodCallInterceptor());
}
}
However, when I run the application with requests leading to call the interface functions in those services (inheritance exists in our interfaces and service classes), I get the following exception and would be grateful for any help in identifying my issue:
Autofac.Core.DependencyResolutionException: An exception was thrown while executing a resolve operation. See the InnerException for details.
---> Castle.DynamicProxy.ProxyGenerationException: This is a DynamicProxy2 error: Target type for the proxy implements Castle.DynamicProxy.IProxyTargetAccessor which is a DynamicProxy infrastructure interface and you should never implement it yourself. Are you trying to proxy an existing proxy?
at Castle.DynamicProxy.Generators.BaseProxyGenerator.HandleExplicitlyPassedProxyTargetAccessor(ICollection1 targetInterfaces, ICollection1 additionalInterfaces)
at Castle.DynamicProxy.Generators.InterfaceProxyWithTargetGenerator.GetTypeImplementerMapping(Type[] interfaces, Type proxyTargetType, IEnumerable1& contributors, INamingScope namingScope)
at Castle.DynamicProxy.Generators.InterfaceProxyWithTargetGenerator.GenerateType(String typeName, Type proxyTargetType, Type[] interfaces, INamingScope namingScope)
at Castle.DynamicProxy.Generators.InterfaceProxyWithTargetGenerator.<>c__DisplayClass6_0.<GenerateCode>b__0(String n, INamingScope s)
at Castle.DynamicProxy.Generators.BaseProxyGenerator.ObtainProxyType(CacheKey cacheKey, Func3 factory)
at Castle.DynamicProxy.Generators.InterfaceProxyWithTargetGenerator.GenerateCode(Type proxyTargetType, Type[] interfaces, ProxyGenerationOptions options)
at Castle.DynamicProxy.DefaultProxyBuilder.CreateInterfaceProxyTypeWithTarget(Type interfaceToProxy, Type[] additionalInterfacesToProxy, Type targetType, ProxyGenerationOptions options)
at Castle.DynamicProxy.ProxyGenerator.CreateInterfaceProxyTypeWithTarget(Type interfaceToProxy, Type[] additionalInterfacesToProxy, Type targetType, ProxyGenerationOptions options)
at Castle.DynamicProxy.ProxyGenerator.CreateInterfaceProxyWithTarget(Type interfaceToProxy, Type[] additionalInterfacesToProxy, Object target, ProxyGenerationOptions options, IInterceptor[] interceptors)
at Castle.DynamicProxy.ProxyGenerator.CreateInterfaceProxyWithTarget(Type interfaceToProxy, Type[] additionalInterfacesToProxy, Object target, IInterceptor[] interceptors)
at Autofac.Extras.DynamicProxy.RegistrationExtensions.<>c__DisplayClass9_03.<EnableInterfaceInterceptors>b__0(Object sender, ActivatingEventArgs1 e)
at Autofac.Core.Registration.ComponentRegistration.RaiseActivating(IComponentContext context, IEnumerable1 parameters, Object& instance)
at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable1 parameters, Object& decoratorTarget)
at Autofac.Core.Resolving.InstanceLookup.Execute()
at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest request)
at Autofac.Core.Resolving.ResolveOperation.ResolveComponent(ResolveRequest request)
at Autofac.Core.Resolving.ResolveOperation.Execute(ResolveRequest request)
--- End of inner exception stack trace ---
at Autofac.Core.Resolving.ResolveOperation.Execute(ResolveRequest request)
at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(ResolveRequest request)
at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable1 parameters, Object& instance)
at Autofac.ResolutionExtensions.ResolveOptionalService(IComponentContext context, Service service, IEnumerable1 parameters)
at Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType, IEnumerable`1 parameters)
at Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType)
at Autofac.Extensions.DependencyInjection.AutofacServiceProvider.GetService(Type serviceType)
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.b__0(ControllerContext controllerContext)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass5_0.g__CreateController|0(ControllerContext controllerContext)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.SystemTextJsonResultExecutor: Information: Executing JsonResult, writing value of type 'System.String'.

The following is from Google Translate:
Coincidentally, I just solved this problem, but this is not the correct solution.
Just need to downgrade the Autofac.Extensions.DependencyInjection package to 5.0.1 to solve the problem.

Related

An exception of type 'System.InvalidOperationException' occurred in Microsoft.EntityFrameworkCore.dll but was not handled in user code

I'm trying to call a method from IActionResult using Task.Run. This is the Exception I am getting.
An exception of type 'System.InvalidOperationException' occurred in
Microsoft.EntityFrameworkCore.dll but was not handled in user code:
'An exception was thrown while attempting to evaluate a LINQ query
parameter expression. To show additional information call
EnableSensitiveDataLogging() when overriding DbContext.OnConfiguring.'
Inner exceptions found, see $exception in variables window for more
details. Innermost exception System.NullReferenceException : Object
reference not set to an instance of an object.
Initialization of EntityContext instance:
private readonly EntityContext _context;
public ApiController (
UserManager<User> userManager,
SignInManager<User> signInManager,
ILoggerFactory loggerFactory,
IMemoryCache memoryCache,
EntityContext context,
IRepository repository,
Context session,
IEmailService emailService,
IHostingEnvironment environment,
IHttpContextAccessor contextAccessor, ViewRender view, IStringLocalizer<SharedResources> localizer) : base (userManager, signInManager, loggerFactory, memoryCache, context, repository, session, contextAccessor) {
//_view = view;
_emailService = emailService;
this.environment = environment;
_localizer = localizer;
this._context = context;
}
Startup.cs
services.AddEntityFrameworkNpgsql ()
.AddDbContext<EntityContext> (
options => options.UseNpgsql (connectionString)
);
Calling method from controller:
if(updated){
Task t1 = Task.Run(()=>SendEmailAsync(entity,true,responsible,_context));
}else{
Task t1 = Task.Run(()=>SendEmailAsync(entity,false,responsible,_context));
}
Method I am calling:
public void SendEmailAsync (Activity entity, bool updated, User responsible, EntityContext ctx) {
List<string> emailList = new List<string> ();
var mail = new MailComposer (_emailService, environment, _localizer);
if (responsible.IsSubscriber) {
emailList.Add (responsible.Email);
}
if (entity.Participants.Count > 0) {
foreach (var item in entity.Participants) {
var p = ctx.Users.Where(c=>c.Id==item.Participant.Id).FirstOrDefault(); //This is where I am getting an exception.
if (p.IsSubscriber) {
emailList.Add (p.Email);
}
}
}
if (emailList.Count != 0) {
var emailArray = emailList.ToArray ();
if (updated) {
mail.SendActivityUpdate (entity, emailArray);
} else {
mail.SendActivityCreated (entity, emailArray);
}
}
}
For your issue, this is caused by that you are reference a scoped service EntityContext from another thread. For EntityContext, it will be disposed when the request returned from Controller.
As the suggestion from Chris, you may call t1.Wait(); to complete the t1 task before the request return back to client. By calling t1.Wait();, the EntityContext _context will not be disposed and then you won't get any error.
For another option, you may try pass IServiceProvider to create a new EntityContext instead of referencing the existing EntityContext which is created by Controller
public class HomeController : Controller
{
private readonly ApplicationDbContext _context;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<HomeController> _logger;
public HomeController(ApplicationDbContext context
, IServiceProvider serviceProvider
, ILogger<HomeController> logger)
{
_context = context;
_serviceProvider = serviceProvider;
_logger = logger;
}
public IActionResult TestTask()
{
Task t1 = Task.Run(() => SendEmailAsync(_serviceProvider));
//t1.Wait();
return Ok();
}
private void SendEmailAsync(IServiceProvider serviceProvider)
{
var context = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService<ApplicationDbContext>();
var result = context.Student.ToList();
_logger.LogInformation(JsonConvert.SerializeObject(result));
}
}
Task.Run will start a new thread, and unless you await it, the existing thread where the action is running will keep going, eventually returning and taking the context with it, which your method running in the new thread depends on. If you do await it, then there's no point in running in a separate thread; you're just consuming additional resources for no good reason.
In short, you should not be using Task.Run for this at all. It's not the same as "running the background". Instead, you should schedule the email to be sent on different process or at the very least an IHostedService. You can use QueuedBackgroundService. There's an implementation available at https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2&tabs=visual-studio#queued-background-tasks.

How to Mock or Fake MongoDB 's static method FindAsync() in .Net Unit test cases using MOQ or FakeItEasy or Pose

I have been trying to mock/fake the static method FindAsync() in my Unit test cases using Wrappers, some concepts of Pose.
As the static methods cannot be mocked or faked normally.
It is not successful.
The code in the repository layer which I want to unit test points to the IMongoCollectionExtension.FindAsync() method.
This is the method I am trying to mock
public async Task<MyClass> GetItem(Guid id)
{
var filter = Builders<MyClass>.Filter.Eq(m => m.Id, id);
var result = await _context.MyCollection.FindAsync(filter);
return result.FirstOrDefault();
}
This FindAsync() is pointing to IMongoCollectionExtensions STATIC class
public static Task<IAsyncCursor<TDocument>> FindAsync<TDocument>(this IMongoCollection<TDocument> collection, FilterDefinition<TDocument> filter, FindOptions<TDocument, TDocument> options = null, CancellationToken cancellationToken = default(CancellationToken));
So as it is pointing to Static class and a static method I started writing wrapper to mock,
First Method tried using Wrapper:
This is wrapper I have created.
public interface IMongoCollectionExtensionsWrapper
{
Task<IAsyncCursor<MyClass>> FindAsync<MyClass>(IMongoCollection<MyClass> collection, FilterDefinition<MyClass> filter, FindOptions<MyClass, MyClass> options = null, CancellationToken cancellationToken = default(CancellationToken));
}
public class MongoCollectionExtensionsWrapper : IMongoCollectionExtensionsWrapper
{
public Task<IAsyncCursor<MyClass>> FindAsync<MyClass>(IMongoCollection<MyClass> collection, FilterDefinition<MyClass> filter, FindOptions<MyClass, MyClass> options = null, CancellationToken cancellationToken = default(CancellationToken))
{
return collection.FindAsync(filter, options, cancellationToken);
}
}
public static class FakeExtensions
{
public static IMongoCollectionExtensionsWrapper defaultmcExtWrapper = new MongoCollectionExtensionsWrapper();
public static Task<IAsyncCursor<MyClass>> FindAsync(this IMongoCollection<MyClass> collection, FilterDefinition<MyClass> filter, FindOptions<MyClass, MyClass> options = null, CancellationToken cancellationToken = default(CancellationToken))
{
return defaultmcExtWrapper.FindAsync(collection, filter, options, cancellationToken);
}
}
As the wrapper was not working properly i checked out free framework Pose to mock static methods. That was not successful too.
Second trial using Pose
Shim findshim = Shim.Replace(() => IMongoCollectionExtensions.FindAsync(Is.A<IMongoCollection<MyClass>>(), filter, null, CancellationToken.None)).With(delegate (IMongoCollection<MyClass> mc, FilterDefinition<MyClass> f, FindOptions<MyClass, MyClass> o, CancellationToken ca) { return Task.FromResult(_fakeOutput.FakedObject); });
NOTE: _fakeOutput is a faked Cursor holding an IEnumerable. It works fine.
PoseContext.Isolate(() =>
{
Task.Run(() =>
{
var exp = Task.FromResult(item1);
var myres = _Repo.GetItem(Id);
Assert.Equal(exp, myres);
});
}, findshim);
var myres = _Repo.GetItem(Id);
In both the trials, I have tried mocking IMongoCollectionExtensions.FindAsync() but result (output of the method i want to unit test after setting up mock/fake) in both cases are null
and when I tried below Assertion if the FindAsync() method of IMongoCollectionExtension has Happened or not, but it didn't hit. I dont understand when the method i want to unit test is pointing to IMongoCollectionExtension.FindAsync() only but it is not hitting.
fakeIMongoCollExt.CallsTo(x => x.FindAsync(A<IMongoCollection<MyClass>>.Ignored, A<FilterDefinition<MyClass>>.Ignored, null, CancellationToken.None)).MustHaveHappened();
(Method signature has MongoCollections as first parameter - Extension Method)
is showing that it didn't hit that method.
So I tried checking MustHaveHappened() for IMongoCollection.FindAsync() (It is interface method not the static class method which we are discussing above) which also tells that "The target of this call is not the fake object being configured."
I am not sure how FindAsync() is pointing. How to proceed with unit test cases. Please let me know if you have any idea.. Thanks in Advance..
IMongoCollections.FindAsync() Mocking

External login fails asp.net mvc core 2.2

Did scaffold some identity pages (Login, Logout, Register) for translation purposes and added Google as an external provider. It works, but when I hit Register on the Identity/Account/ExternalLogin page I get the following error:
InvalidOperationException: The instance of entity type 'IdentityUserLogin' cannot be tracked because another instance with the same key value for {'LoginProvider', 'ProviderKey'} is already being tracked.
Did scaffold ExternalLogin to check what might be wrong: here is the (standard) code:
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
...
The await _userManager.AddLoginAsync(user, info) gives the exception mentioned.
InvalidOperationException: The instance of entity type 'IdentityUserLogin<string>' cannot be tracked because another instance with the same key value for {'LoginProvider', 'ProviderKey'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap<TKey>.ThrowIdentityConflict(InternalEntityEntry entry)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap<TKey>.Add(TKey key, InternalEntityEntry entry, bool updateDuplicate)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap<TKey>.Add(TKey key, InternalEntityEntry entry)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NullableKeyIdentityMap<TKey>.Add(InternalEntityEntry entry)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, bool acceptChanges)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, bool acceptChanges, Nullable<EntityState> forceStateWhenUnknownKey)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode node, bool force)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph<TState>(EntityEntryGraphNode node, TState state, Func<EntityEntryGraphNode, TState, bool> handleNode)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState entityState, bool forceStateWhenUnknownKey)
Microsoft.EntityFrameworkCore.DbContext.SetEntityState(InternalEntityEntry entry, EntityState entityState)
Microsoft.EntityFrameworkCore.DbContext.SetEntityState<TEntity>(TEntity entity, EntityState entityState)
Microsoft.EntityFrameworkCore.DbContext.Add<TEntity>(TEntity entity)
Microsoft.EntityFrameworkCore.Internal.InternalDbSet<TEntity>.Add(TEntity entity)
Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserOnlyStore<TUser, TContext, TKey, TUserClaim, TUserLogin, TUserToken>.AddLoginAsync(TUser user, UserLoginInfo login, CancellationToken cancellationToken)
Microsoft.AspNetCore.Identity.UserManager<TUser>.AddLoginAsync(TUser user, UserLoginInfo login)
Project.Areas.Identity.Pages.Account.ExternalLoginModel.OnPostConfirmationAsync(string returnUrl) in ExternalLogin.cshtml.cs
+
result = await _userManager.AddLoginAsync(user, info);
How to solve this? Thanks in advance for any help!

ExecuteReader requires the command to have a transaction

We get a strange exception when passing a transaction to EF:
Exception thrown: 'System.InvalidOperationException' in
System.Data.dll
Additional information: ExecuteReader requires the command to have a
transaction when the connection assigned to the command is in a
pending local transaction. The Transaction property of the command
has not been initialized.
this.DbContext = this.DbContextFactory.CreateContext<TContext>(connection);
this.DbContext.Database.UseTransaction(transaction);
This exception is caught by EF because it is shown only when 'Break when thrown' is on. Is it expected behavior or are we doing something potentially wrong?
Here is how call stack looks like:
System.Data.dll!System.Data.SqlClient.SqlCommand.ValidateCommand(string method, bool async)
System.Data.dll!System.Data.SqlClient.SqlCommand.RunExecuteReader(System.Data.CommandBehavior cmdBehavior, System.Data.SqlClient.RunBehavior runBehavior, bool returnStream, string method, System.Threading.Tasks.TaskCompletionSource<object> completion, int timeout, out System.Threading.Tasks.Task task, bool asyncWrite)
System.Data.dll!System.Data.SqlClient.SqlCommand.RunExecuteReader(System.Data.CommandBehavior cmdBehavior, System.Data.SqlClient.RunBehavior runBehavior, bool returnStream, string method)
System.Data.dll!System.Data.SqlClient.SqlCommand.ExecuteReader(System.Data.CommandBehavior behavior, string method)
EntityFramework.dll!System.Data.Entity.Infrastructure.Interception.InternalDispatcher<System.Data.Entity.Infrastructure.Interception.IDbCommandInterceptor>.Dispatch<System.Data.Common.DbCommand, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader>, System.Data.Common.DbDataReader>(System.Data.Common.DbCommand target, System.Func<System.Data.Common.DbCommand, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader>, System.Data.Common.DbDataReader> operation, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader> interceptionContext, System.Action<System.Data.Entity.Infrastructure.Interception.IDbCommandInterceptor, System.Data.Common.DbCommand, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader>> executing, System.Action<System.Data.Entity.Infrastructure.Interception.IDbCommandInterceptor, System.Data.Common.DbCommand, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader>> executed)
EntityFramework.dll!System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.Reader(System.Data.Common.DbCommand command, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext interceptionContext)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlVersionUtils.GetServerType(System.Data.Common.DbConnection connection)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlProviderServices.QueryForManifestToken(System.Data.Common.DbConnection conn)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlProviderServices.GetDbProviderManifestToken.AnonymousMethod__9(System.Data.Common.DbConnection conn)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlProviderServices.UsingConnection.AnonymousMethod__32()
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute.AnonymousMethod__0()
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute<object>(System.Func<object> operation)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlProviderServices.GetDbProviderManifestToken(System.Data.Common.DbConnection connection)
EntityFramework.dll!System.Data.Entity.Core.Common.DbProviderServices.GetProviderManifestToken(System.Data.Common.DbConnection connection)
EntityFramework.dll!System.Data.Entity.Utilities.DbProviderServicesExtensions.GetProviderManifestTokenChecked(System.Data.Entity.Core.Common.DbProviderServices providerServices, System.Data.Common.DbConnection connection)
mscorlib.dll!System.Collections.Concurrent.ConcurrentDictionary<System.Tuple<System.Type, string, string>, string>.GetOrAdd(System.Tuple<System.Type, string, string> key, System.Func<System.Tuple<System.Type, string, string>, string> valueFactory)
EntityFramework.dll!System.Data.Entity.Utilities.DbConnectionExtensions.GetProviderInfo(System.Data.Common.DbConnection connection, out System.Data.Entity.Core.Common.DbProviderManifest providerManifest)
EntityFramework.dll!System.Data.Entity.DbModelBuilder.Build(System.Data.Common.DbConnection providerConnection)
EntityFramework.dll!System.Data.Entity.Internal.LazyInternalContext.CreateModel(System.Data.Entity.Internal.LazyInternalContext internalContext)
EntityFramework.dll!System.Data.Entity.Internal.RetryLazy<System.Data.Entity.Internal.LazyInternalContext, System.Data.Entity.Infrastructure.DbCompiledModel>.GetValue(System.Data.Entity.Internal.LazyInternalContext input)
EntityFramework.dll!System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
EntityFramework.dll!System.Data.Entity.Internal.LazyInternalContext.GetObjectContextWithoutDatabaseInitialization()
EntityFramework.dll!System.Data.Entity.Database.UseTransaction(System.Data.Common.DbTransaction transaction)
Transaction is created within Open method of the session:
public virtual void Open(IsolationLevel isolation, string user)
{
ValidateState(false);
this.m_user = user;
this.m_connection = this.m_database.CreateConnection();
this.m_connection.Open();
if (IsolationLevel.Unspecified != isolation)
{
this.m_transaction = this.m_connection.BeginTransaction(isolation);
}
}
Then this method is overreiden in the class which supports EF:
public override void Open(System.Data.IsolationLevel isolation, string user)
{
if (isolation == System.Data.IsolationLevel.Unspecified)
{
throw new InvalidOperationException("Isolation level 'Unspecified' is not supported");
}
base.Open(isolation, user);
this.DbContext = this.DbContextFactory.CreateContext<TContext>(this.Connection);
this.DbContext.Database.UseTransaction(this.Transaction);
}
During its initialization, DdContext will determine the version of SQL Server. This process will attempt to connect to the underlying server and query select cast (serverproperty ('EngineEdition') as int) using the provided connection, or in the absence of that, creating a new one from the configured connectionstring.
This will happen only once, after its first initialization.
Therefore, if your first use of DbContext in your application life cycle is from within a transaction, this will cause DbContext to try to use this connection and result in the observed error.
If you assure a first non-transactional call to DbContext (DbContext construction and usage for querying), you will avoid this behavior.
private static object _syncRoot = new object();
private static bool _Initialized = false;
private MyDbContext(string connectionString) : base(connectionString)
{
Database.SetInitializer<MyDbContext>(null);
}
private MyDbContext(DbTransaction dbTransaction) : base(dbTransaction.Connection, false)
{
Database.SetInitializer<MyDbContext>(null);
Database.UseTransaction(dbTransaction);
}
public static MyDbContext Factory()
{
return new MyDbContext(Tools.GetDefaultConnectionString());
}
public static MyDbContext Factory(DbTransaction dbTransaction)
{
if(_Initialized==false)
{
lock(_syncRoot)
{
if(_Initialized==false)
{
using (MyDbContext tempDbContext = new MyDbContext(dbTransaction.Connection.ConnectionString))
using (System.Data.Common.DbTransaction temptransaction = tempDbContext.BeginTransaction())
{
var mySampleData = tempDbContext.OneRowTable.AsNoTracking().ToList();
temptransaction.Commit();
_Initialized = true;
}
}
}
}
MyDbContext finalContext = new MyDbContext(dbTransaction);
return finalContext;
}
This is a solution for a scenario where you can not or do not want to use a TransactionScope in your software.
The issue appears on first access to to the underling ObjectContext (attempting to set the transaction accesses the underlying context). Using a TransactionScope is fine; this issue is when using a connection that already has a local transaction associated with it.
While a bit ugly, and approach such as the follow does work around this issue. Specific implementation details such as determining notCreateYet are left as an exercise.
if (notCreatedYet)
{
// Create a second session/connection to not touch the incoming connection.
// Using a TransactionScope works and proper enlistment is done.
// Attempts to open a manual transaction prior to creation will fail.
using (new TransactionScope(TransactionScopeOption.RequiresNew))
using (var initConn = new SqlConnection(connection.ConnectionString))
{
initConn.Open();
var tempContext = this.DbContextFactory.CreateContext<TContext>(connection);
// Touch the object context.
if (tempContext is IObjectContextAdapter contextAdapter)
{
_ = contextAdapter.ObjectContext;
}
}
}
// Then later on this is fine.
this.DbContext = this.DbContextFactory.CreateContext<TContext>(connection);
this.DbContext.Database.UseTransaction(transaction);

Why can't I get signalr to work with autofac dependency injection?

I'm new to AutoFac, but I thought this would be pretty straight forward. I've got a simple Hub:
public class Chat : Hub
{
private readonly IHomeTasks _homeTasks;
public Chat(IHomeTasks homeTasks)
{
_homeTasks = homeTasks;
}
public void Send(string text)
{
_homeTasks.DoNothing();
Clients.addMessage(text);
var user = new UserDocument { Username = text };
}
}
In my Global.ascx.cs I have:
protected void Application_Start()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces();
builder.RegisterType<Helpers.AutofacDependencyResolver>().As<SignalR.IDependencyResolver>(); // Not sure if I need this or...
var container = builder.Build();
RouteTable.Routes.MapHubs(new Helpers.AutofacDependencyResolver(container));
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
and my AutofacDependencyResolver I just copied from here:
public class AutofacDependencyResolver : DefaultDependencyResolver, IRegistrationSource
{
private readonly ILifetimeScope _lifetimeScope;
public AutofacDependencyResolver(ILifetimeScope lifetimeScope)
{
_lifetimeScope = lifetimeScope;
_lifetimeScope.ComponentRegistry.AddRegistrationSource(this);
}
public override object GetService(Type serviceType)
{
object result;
if (_lifetimeScope.TryResolve(serviceType, out result))
{
return result;
}
return null;
}
public override IEnumerable<object> GetServices(Type serviceType)
{
object result;
if (_lifetimeScope.TryResolve(typeof(IEnumerable<>).MakeGenericType(serviceType), out result))
{
return (IEnumerable<object>)result;
}
return Enumerable.Empty<object>();
}
public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
var typedService = service as TypedService;
if (typedService != null)
{
var instance = base.GetServices(typedService.ServiceType);
if (instance != null)
{
return instance.Select(i => RegistrationBuilder.ForDelegate(i.GetType(), (c, p) => i).As(typedService.ServiceType)
.InstancePerMatchingLifetimeScope(_lifetimeScope.Tag)
.CreateRegistration());
}
}
return Enumerable.Empty<IComponentRegistration>();
}
bool IRegistrationSource.IsAdapterForIndividualComponents
{
get { return false; }
}
}
My HomeTasks looks like this:
public class HomeTasks : Sandbox.Tasks.IHomeTasks
{
public void DoNothing()
{
}
}
Doesn't look like anything is happening as I'm getting a error like this inside my SignalR.Hosting.AspNet.HttpTaskAsyncHandler:
Could not load type 'SignalR.Hosting.IRequest' from assembly 'SignalR,
Version=0.5.1.10625, Culture=neutral, PublicKeyToken=null'.
Edit:
I removed all SignalR dll's and then re-added due to dfowler's answer and everything comes up great but now when I actually invoke the Send(text) on my Chat Hub I get this:
[MissingMethodException: No parameterless constructor defined for this object.]
System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck) +0
System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache) +98
System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipVisibilityChecks, Boolean skipCheckThis, Boolean fillCache) +241
System.Activator.CreateInstance(Type type, Boolean nonPublic) +69
System.Activator.CreateInstance(Type type) +6
SignalR.Hubs.DefaultHubActivator.Create(HubDescriptor descriptor) +141
SignalR.Hubs.DefaultHubManager.ResolveHub(String hubName) +71
SignalR.Hubs.HubDispatcher.CreateHub(IRequest request, HubDescriptor descriptor, String connectionId, TrackingDictionary state, Boolean throwIfFailedToCreate) +679
SignalR.Hubs.HubDispatcher.OnReceivedAsync(IRequest request, String connectionId, String data) +870
SignalR.<>c__DisplayClass6.<ProcessRequestAsync>b__4(String data) +73
SignalR.Transports.ForeverTransport.ProcessSendRequest() +141
SignalR.Transports.ForeverTransport.ProcessRequestCore(ITransportConnection connection) +128
SignalR.Transports.ForeverTransport.ProcessRequest(ITransportConnection connection) +37
SignalR.PersistentConnection.ProcessRequestAsync(HostContext context) +1018
SignalR.Hubs.HubDispatcher.ProcessRequestAsync(HostContext context) +216
SignalR.Hosting.AspNet.AspNetHandler.ProcessRequestAsync(HttpContextBase context) +676
SignalR.Hosting.AspNet.HttpTaskAsyncHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +125
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8968180
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184
I think I'm just missing one piece here I just can't figure out what the heck it is.
What leads me to thinking that the wiring for SignalR has problems is because I also have a simple controller that works fine.
public class HomeController : Controller
{
private IHomeTasks _homeTasks;
public HomeController(IHomeTasks homeTasks)
{
_homeTasks = homeTasks;
}
public ActionResult Index()
{
_homeTasks.DoNothing(); // No problems resolving HomeTasks!
}
Update with answer
protected void Application_Start()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces();
builder.RegisterType<Chat>().InstancePerLifetimeScope();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
RouteTable.Routes.MapHubs(new AutofacSignalrDependencyResolver(container));
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
Got it to work with the above AppStart method. The key line being the builder.RegisterType().InstancePerLifetimeScope();. I honestly don't know if this is the best way as this was sort of a learning experience for me as I figured this out using what I knew better [Windsor] but figured I had spent enough time that I wanted to at least see something from my labors. Hope this helps someone :D
It Looks like a version mismatch, that's probably why it can't load the type. Make sure you are using binary compatible versions of your dependencies.