Skip to content

Context not injected correctly in asp.net 5 #168

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

Closed
Gillardo opened this issue Jan 13, 2016 · 28 comments
Closed

Context not injected correctly in asp.net 5 #168

Gillardo opened this issue Jan 13, 2016 · 28 comments
Labels

Comments

@Gillardo
Copy link

I am in the process of writing an api using asp.net 5, and i got so far and my context was being injected. I have striped and gutted the code to a bare minimum and it seems to be a problem with SimpleInjector (or the way i have done something).

If i commented out the following 2 lines (think you can do the first one only) and use asp.net DI, then it works.

The lines to comment out are on the startup.cs file at line 67

services.AddScoped<IControllerActivator>(e => new SimpleInjectorControllerActivator(_container));

And line 81
app.UseSimpleInjector(_container, _configuration);

If you load up the project, it will display a simple page, with a page. If you are using asp.net DI, you will get a response of "Hi bob".

Once you switch to using SimpleInjector, you get an error message saying

Cannot access a disposed object.
Object name: 'ApplicationDbContext'.

Now this maybe something to do with it, but not sure. But if i also comment out the 31 on AppBuilderExtensions.cs file, which reads container.CrossWire<ApplicationDbContext>(app);, then upon starting the application, i get a SimpleInjector Verify error, saying

-[Disposable Transient Component] ApplicationDbContext is registered as transient, but implements IDisposable.

To get round this, i added the line that you just commented out.

What is wrong here? I cannot seem to work it out. Really appreciate any help.

Please find the sample project attached here

SimpleInjectorContextTest.zip

@qujck
Copy link
Collaborator

qujck commented Jan 13, 2016

Which version of SI are you using?

@Gillardo
Copy link
Author

dependencies are

    "SimpleInjector": "3.1.2",
    "SimpleInjector.Integration.AspNet": "3.1.2-beta4",

@Gillardo
Copy link
Author

I have attached project as well

@qujck
Copy link
Collaborator

qujck commented Jan 13, 2016

Ok, I cannot get the code to run on my PC at work, the app starts and immediately stops again. I will try at home later.

My first suspicion is that AddDbContext is registering ApplicationDbContext with a lifetime (as it should) but CrossWire resolves without first resolving any instance of a scope object (IServiceScope) and as there is no held reference to the instance of IServiceScope that ApplicationDbContext is resolved from they are both immediately disposed.

DISCLAIMER: I made that up from reading the code, I could very easily be wrong :-/

@Gillardo
Copy link
Author

The code requires you to use rc2 of asp.net5, so you will need to run dnvm upgrade -u

@dotnetjunkie
Copy link
Collaborator

I'm unable to take a look at the source code, but what is this UseSimpleInjector method you are callig? That's not something from the integration package.

I also see you're not following the integration guide; it dictates to register the IControllerActivator as singleton.

Furthermore, could it be you are using an old version of the integration package? Beta4 actually suppresses the IDisposable transient diagnostic warning. You shouldn't get this error at all.

Besides, it seems weird that you register your application's DbContext in ASP.NET and crosswire it in Simple Injector. That DbContext seems an application component to me, not some framework component.

@Gillardo
Copy link
Author

UseSimpleInjector is just an extension method i created so i could keep all my container code in without having it in the startup.cs file.

I have tried singleton with icontrolleractivator but didnt make a difference but that is my mistake and i will change that.

I believe i am using the correct version as beta4 is stated in my project.json file.

How can i ise DbContext without registering it with Asp.net? In EF6 u could simply pass in a connectionstring but EF7 doesnt seem to allow u to do this? I dont have any experience with EF7 and all examples seem to use what i have. Happy to do it dofferent way. In my EF6 code i would of done container.Register(() => new DbContext("connectionString"), Lifestyle.Scoped)

@TheBigRic
Copy link
Collaborator

I'm unable to help you with all the Asp.Net stuff, but configuring a connectionstring for a DbContext in EF7 seems doable, although different as in EF6.

It's quite extensively documented on the asp.Net\Entityframework repo. See Configuring a DbContext

So the EF7 equivalent of this:

container.Register(() => new DbContext("connectionString"), Lifestyle.Scoped) 

would be:

var optionsBuilder = new 2DbContextOptionsBuilder();
 optionsBuilder.UseSqlServer(@"connectionstring");
container.Register(
    () => new DbContext(optionsBuilder.Options),
    Lifestyle.Scoped); 

@qujck
Copy link
Collaborator

qujck commented Jan 13, 2016

I've not made a lot of progress but I have found something odd: if I comment out the module level variable private Container _container and adjust the code until it is compiling (by effectively removing Simple Injector) the code still fails with the same error.

@Gillardo
Copy link
Author

I have looked at the EF7 link you sent me, but a DbContext doesnt have a base constructor that accepts anything? Not sure why you guys cant compile the code.... It works fine if you dont use SimpleInjector on my machine, just not with

@Gillardo
Copy link
Author

Guys to confirm, commenting out the 2 lines to do with SI allows it all to work fine... Something is definitely amiss

@qujck
Copy link
Collaborator

qujck commented Jan 13, 2016

I get the error

200: Cannot access a disposed object.
Object name: 'ApplicationDbContext'.

when I click Call Auth Method on the web page that is displayed when I run this code (can you try it for me?):

using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Data.Entity;
using System.Linq;
using SimpleInjectorContextTest.Web.Models;

namespace SimpleInjectorContextTest.Web
{
    public class Startup
    {
        // rc2
        public static void Main(string[] args)
        {
            var application = new WebApplicationBuilder()
                .UseConfiguration(WebApplicationConfiguration.GetDefault(args))
                .UseStartup<Startup>()
                .Build();

            application.Run();
        }

        private IConfiguration _configuration;

        public Startup(IHostingEnvironment env)
        {
            // configuration is in the folder above the wwwroot
            var builder = new ConfigurationBuilder();

            builder
               .AddJsonFile("config.json")
               .AddEnvironmentVariables();

            _configuration = builder.Build();
        }

        // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            // Add MVC services to the services container.
            services.AddMvc();

            services.AddEntityFramework()
                .AddSqlServer()
                .AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(_configuration["Data:DefaultConnection:ConnectionString"]));

            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders()
                .AddOpenIddict(); // Add the OpenIddict services after registering the Identity services.
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            // logger support
            loggerFactory
                .AddConsole(_configuration.GetSection("Logging"))
                .AddDebug();

            // Handle errors when in development mode
            if (env.IsDevelopment())
            {
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }

            // use jwt bearer authentication
            app.UseJwtBearerAuthentication(options =>
            {
                options.AutomaticAuthenticate = true;
                options.AutomaticChallenge = true;
                options.RequireHttpsMetadata = false;
                options.Audience = "http://localhost:5000/";
                options.Authority = "http://localhost:5000/";
            });

            // Add all the external providers you need before registering OpenIddict:
            //app.UseFacebookAuthentication();

            app.UseOpenIddict();

            // Enable all static file middleware
            app.UseStaticFiles();

            // Enable Mvc for view controller, and 
            // default all routes to the Home controller
            app.UseMvc(options =>
            {
                options.MapRoute(
                    name: "default",
                    template: "{*url}",
                    defaults: new { controller = "Home", action = "Index" });
            });

            using (var context = app.ApplicationServices.GetRequiredService<ApplicationDbContext>())
            {
                context.Database.EnsureCreated();

                if (!context.Applications.Any())
                {
                    context.Applications.Add(new OpenIddict.Models.Application
                    {
                        Id = "myClient",
                        DisplayName = "My client application",
                        RedirectUri = "http://localhost:5000/signin-oidc",
                        LogoutRedirectUri = "http://localhost:5000/",
                        Secret = CryptoHelper.Crypto.HashPassword("secret_secret_secret"),
                        Type = OpenIddict.OpenIddictConstants.ApplicationTypes.Confidential
                    });

                    context.SaveChanges();
                }
            }
        }
    }
}

@Gillardo
Copy link
Author

Stupid question, have you rebuilt? Are you running using command dnx web or IIS Express? I have copied and pasted your startup.cs and i get no error....

@qujck
Copy link
Collaborator

qujck commented Jan 13, 2016

IIS Express

@Gillardo
Copy link
Author

Works fine for me using dnx and IISExpress.... What version of dnvm have you got installed? I am using 1.0.0-rc2-16357 x64

@qujck
Copy link
Collaborator

qujck commented Jan 13, 2016

I've 16 different runtimes in my profile, what tells me which one it is using?

dnx-clr-win-x64.1.0.0-beta4
dnx-clr-win-x64.1.0.0-beta5
dnx-clr-win-x64.1.0.0-rc1-update1
dnx-clr-win-x86.1.0.0-beta4
dnx-clr-win-x86.1.0.0-beta5
dnx-clr-win-x86.1.0.0-rc1-final
dnx-clr-win-x86.1.0.0-rc1-update1
dnx-clr-win-x86.1.0.0-rc2-16278
dnx-clr-win-x86.1.0.0-rc2-16357
dnx-coreclr-win-x64.1.0.0-beta4
dnx-coreclr-win-x64.1.0.0-beta5
dnx-coreclr-win-x64.1.0.0-rc1-update1
dnx-coreclr-win-x86.1.0.0-beta4
dnx-coreclr-win-x86.1.0.0-beta5
dnx-coreclr-win-x86.1.0.0-rc1-update1
dnx-coreclr-win-x86.1.0.0-rc2-16278

@Gillardo
Copy link
Author

Run Powershell command and type dnvm list You should see a list of all your installed runtimes. The one with a * under the active column is the runtime your using.

image

@Gillardo
Copy link
Author

That is when ur using the dnx command. Right click the project and go to properties for iis express i think

@qujck
Copy link
Collaborator

qujck commented Jan 13, 2016

1.0.0-rc2-16357 x86

@Gillardo
Copy link
Author

Can you try the x64 version?

@qujck
Copy link
Collaborator

qujck commented Jan 15, 2016

I've tried dnvm install 1.0.0-rc2-16357 -arch x64

The server returned a 404 (NotFound). This is most likely caused by the feed not having the version that you typed.
Check that you typed the right version and try again. Other possible causes are the feed doesn't have a DNX of the
right name format or some other error caused a 404 on the server.
At C:\Users\Peter.dnx\bin\dnvm.ps1:558 char:13

  •         throw "The server returned a 404 (NotFound). This is most ...
    
  •         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    • CategoryInfo : OperationStopped: (The server retu... on the server.:String) [], RuntimeException
    • FullyQualifiedErrorId : The server returned a 404 (NotFound). This is most likely caused by the feed not having
      the version that you typed. Check that you typed the right version and try again. Other possible causes are the fe
      ed doesn't have a DNX of the right name format or some other error caused a 404 on the server.

and dnvm install latest -arch x64 says

Determining latest version
'dnx-clr-win-x64.1.0.0-rc1-update1' is already installed in C:\Users\Peter.dnx\runtimes\dnx-clr-win-x64.1.0.0-rc1-update1.

I've also run dnvm update-self

@Gillardo
Copy link
Author

Rc2 is a nightly build so u need to run command with -u parameter. Try dnvm upgrade -u -arch x64

@qujck
Copy link
Collaborator

qujck commented Jan 16, 2016

Okay, I figured out that IIS Express appears to keep hold of the first assembly that it compiled and that's why I continued to get the error after removing Simple Injector.

The code without SI works fine, just as you said it did.

@Gillardo
Copy link
Author

@qujck This is good news!! At least your seeing the same issue now. So it is a problem with SI then?

@qujck
Copy link
Collaborator

qujck commented Jan 17, 2016

I have it working but I am uncomfortable with the solution. I have created a couple of new extension methods for registering stuff with a scoped lifestyle (which is, after all, what the DbContext should have):

public static void CrossWireDefaultScope<TService>(
    this Container container, 
    IApplicationBuilder applicationBuilder) where TService : class
{
    var registration = Lifestyle.Scoped.CreateRegistration(
        () => GetScopedInstance<TService>(container),
        container);

    container.AddRegistration(typeof(TService), registration);
}

private static TService GetScopedInstance<TService>(Container container) =>
    container.GetInstance<IServiceScope>()
        .ServiceProvider
        .GetRequiredService<TService>();

And updated the container registrations:

container.CrossWire<IServiceScopeFactory>(app);
container.Register<IServiceScope>(
    () => container.GetInstance<IServiceScopeFactory>().CreateScope(),
    Lifestyle.Scoped);
container.CrossWireDefaultScope<ApplicationDbContext>(app);

The problem I see here is that we are creating an instance of IServiceScope to keep the cross-wired items alive within the context of the ASP.NET scope but this is probably not the same instance of IServiceScope the ASP.NET container is using internally. It is entirely possible we could get 2 instances of ApplicationDbContext within a single request, which is not good at all!

Although this code works for your example I do not recommend using it as is ..

@Gillardo
Copy link
Author

@qujck Although this works on the sample i sent you, i am still facing the same issue on another method. Will come up with an example shortly

@Gillardo
Copy link
Author

@qujck I have been working on a sample, and thought i would ask the question on another site, if you could actually use another DI with their software. They said this, which i believe is incorrect, but wanted to double check with you, as this is not recommended on the documentation.

openiddict/openiddict-core#51 (comment)

@qujck
Copy link
Collaborator

qujck commented Jan 20, 2016

My interpretation of their remarks is that they are recommending you use an adapter which is not something you are likely to see for SimpleInjector. I also don't have any other solution to offer, it may be the case the SimpleInjector and OpenIdDict will not be easily integrated.

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

No branches or pull requests

4 participants