I created a project with EF6 + Ninject + Owin.
I realized that Ninject InRequestScope doesn't work, infact, in a single Web Api request the constructor of my DBContext derived class fires more than once.
The Startup file of my web api project is like:
public void Configuration(IAppBuilder app)
{
ConfigureOAuth(app);
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
app.UseNinjectMiddleware(CreateKernel).UseNinjectWebApi(config);
}
private static IKernel CreateKernel()
{
// I have my Ninject module in a separate assembly
var kernel = new StandardKernel(new Core.Ioc.AutoBotMapper());
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
return kernel;
}
My Module:
public class AutoBotMapper : NinjectModule
{
private readonly ILog _logger = LogManager.GetLogger("CORE");
public override void Load()
{
// Contesto is my DBContext
Bind<Contesto>().ToSelf().InRequestScope();
// Other Binds
Bind<ITournamentServiceFactory>().ToFactory();
Bind<ITournamentCloser>().To<TournamentCloser>();
...
}
}
I don't use the Bootstrapper "NinjectWebCommon" since i found this approach on the net.
Does exist a way to bypass this bug?
Related
I've read a lot of articles regarding database migration on startup and no matter what approach I use my efforts aren't going anywhere. My main problem that i'm getting is no parameterless constructor defined for type startup
I have my DataContext class
public class DataContext : DbContext
{
public DataContext(DbContextOptions options) : base(options)
{
}
public DataContext()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
if (options.IsConfigured)
{
//means that context has been added during dependency injection and no further action required.
}
else
{
//means context is being accessed during Add-Migration therefore we need to set the options. The whole DI/Configuration process won't have run yet, so need some other way to get connection string.
//probably below is a bit too fancy, just hardcoding would be fine. But anyway it seems to work and transfers to different developers machines
//you must have {Values: { SqlConnectionString : xyz}} in local.settings.json in Unite.FunctionApp project dir
var localSettingsJson =
Path.Combine(local.settings.json");
var config = new ConfigurationBuilder()
.AddJsonFile(localSettingsJson, false)
.Build();
options.UseSqlServer(config["Values:SqlConnectionString"]);
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{... }
My Startup Class
// register assembly
[assembly: FunctionsStartup(typeof(Startup))]
{
// inherit FunctionsStartup
public class Startup : FunctionsStartup
{
private DataContext _context;
public Startup(DataContext context)
{
_context = context;
}
public override void Configure(IFunctionsHostBuilder builder)
{
var executionContextOptions = builder.Services.BuildServiceProvider()
.GetService<IOptions<ExecutionContextOptions>>().Value;
var config = new ConfigurationBuilder()
.SetBasePath(executionContextOptions.AppDirectory)
.AddJsonFile("local.settings.json", true)
.AddUserSecrets(Assembly.GetExecutingAssembly(), false)
.AddEnvironmentVariables()
.Build();
builder.Services.AddSingleton<IConfiguration>(config);
var sqlConnection = config["SqlConnectionString"] ??
throw new Exception("SQL Connection String Not Defined");
builder.Services.AddDbContext<DataContext>(options => options.UseSqlServer(sqlConnection));
_context.Database.MigrateAsync();
}
}
}
If I have my paramaterless DataContext method in my class why am i still getting this issue that it isn't defined?
Add your parameterless constructor before the other constructor in your DataContext class.
public sealed class SessionContext
{
private ISession httpContext;
public SessionContext(ISession httpContext)
{
this.httpContext = httpContext;
}
public string UserType
{
get
{
return httpContext.GetString("_UserType");
}
set
{
httpContext.SetString("_UserType", value);
}
}
...... More properties .....
}
public class HomeController : Controller
{
private AppSettings _appSettings;
private SessionContext session = null;
private readonly IHttpContextAccessor _httpContextAccessor;
private ISession httpContext => _httpContextAccessor.HttpContext.Session;
//I don't like this constructor as it is getting initialize or every controller call.
public HomeController(IOptions<AppSettings> myAppSettings, IHttpContextAccessor httpContextAccessor)
{
_appSettings = myAppSettings.Value;
_httpContextAccessor = httpContextAccessor;
appSettings = new AppSettings(_appSettings); //Should initialize only once.
session = new SessionContext(httpContext);
}
}
I have questions regarding ...
How to initialize and use Custom / Support classes in MVC 6 with Asp.Net Core 2.0
When I Initialize these classes, they getting initialize or every controller call. That is very redundant.
my SessionContext class is getting re-initialize every time. So I am loosing the values when I call this class from another controller.
I tried this approach but, not much of use.
services.AddSingleton();
Move from question to answer:
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.AddSingleton<SessionContext, SessionContext>();
//calling the extension class to instantiate the classes which we require earlier.
services.AddMyProjectHelper(Configuration)
}
Created a extension class... where it initializes the support classes
public static class MyProjectHelperExtensions
{
public static IServiceCollection AddMyProjectHelper(this IServiceCollection services, IConfiguration configuration)
{
var section = configuration.GetSection("AppSettings");
// we first need to create an instance
var settings = new AppSettings();
// then we set the properties
new ConfigureFromConfigurationOptions<AppSettings>(section).Configure(settings);
var session = services.BuildServiceProvider().GetService<SessionContext>();
// then we register the instance into the services collection
services.AddSingleton<MyProjectHelper>(new MyProjectHelper(settings, session));
return services;
}
}
finally controller ctor uses the DI for the required class. Now I have avoided redundant initialization of support classes.
public SecurityController(MyProjectHelper objHelper, SessionContext sessionContext)
{
session = sessionContext;
projectHelper = projectHelper ?? objHelper;
}
Now, I am able to share the session variables which I have set in my support classes
private SessionContext session = null;
public HomeController(SessionContext sessionContext)
{
session = sessionContext;
}
[Authorize]
public IActionResult Index()
{
if (session.CurrEmployee != null)
{
ViewBag.Name = (session.CurrEmployee.FirstName + " " + session.CurrEmployee.LastName);
return View();
}
}
I have 2 project, Data and Data.test, I use ef core and .net core for both of them, for Data project I have ExpenseDb like this:
public class ExpenseDb: DbContext
{
private IConfigurationRoot _config;
public ExpenseDb(DbContextOptions<ExpenseDb> options, IConfigurationRoot config) : base(options)
{
_config = config;
}
public DbSet<Account> Accounts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(_config["Data:ConnectionString"]);
}
}
And I have a repository for Account like this:
private ExpenseDb _db;
public AccountRepository(ExpenseDb db)
{
_db = db;
}
public IEnumerable<Account> All(Guid userId)
{
return (_db.Accounts.AsNoTracking().Where(a => a.UserId == userId).ToList());
}
I use ms IOC for injectiong dependencies like this :
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json");
_config = builder.Build();
}
IConfigurationRoot _config;
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(_config);
services.AddDbContext<ExpenseDb>(ServiceLifetime.Scoped);
}
These all are in my Data project, and for Data.Test I would like to test All method, I realized I must Mock my ExpenseDb so I got Moq from Nuget Package and now I have test class like this :
[TestClass]
public class AccountRepositoryTest
{
private readonly Mock<ExpenseDb> _dbMock = new Mock<ExpenseDb>();
private readonly AccountRepository _repo;
public AccountRepositoryTest()
{
_repo = new AccountRepository(_dbMock.Object);
}
[TestMethod]
public void AllForInvalidUser()
{
var fakeaccount = new Account() { Name="cat2",OpenDate=DateTime.Now,StartBalance=100};
Mock < DbSet < Account >> acMock = DbSetMock.Create(fakeaccount);
var results = _repo.All(Guid.Parse("cf15c6c9-f688-47ee-892e-297e530be053"));
Assert.IsNotNull(results);
}
}
Obviously my test is failed, because I must pass config and options to my ExpenseDb somehow, but I don't know How?!
I searched and I found out all answer are saying "You must have an inteface for your service" but i don't want to create an unnecessary interface.
Since DbContextOptions and config are not being used in the actual test code. You could create a constructor in your db context marked as protected to allow the instantiation of the ExpenseDb object without any params.
I am using dapper with the repository pattern in a WebApi Application and I have the following problem.
The Repository Class is as follows
public class Repository : DataConnection, IRepository
{
public Repository(IDbConnection connection)
: base(connection)
{
}
public T GetFirst<T>(object filters) where T : new()
{
//Creates the sql generator
var sqlGenerator = new MicroOrm.Pocos.SqlGenerator.SqlGenerator<T>();
//Creates the query
var query = sqlGenerator.GetSelect(filters);
//Execute the query
return Connection.Query<T>(query, filters).FirstOrDefault();
}
The IRepository Interface has only one method, the GetFirst. A Controller that uses this repository is as follows
public class UsersController : ApiController
{
private IRepository Repository;
public UsersController(IRepository repository)
{
Repository = repository;
}
public User Get(int id)
{
return Repository.GetFirst<User>(new { id });
}
}
I use autofac as DI and in the Application_Start method in Global.asax I use the following code
string connString = ConfigurationManager.ConnectionStrings["DapperDemo"].ConnectionString;
SqlConnection connnection = new SqlConnection(connString);
var builder = new ContainerBuilder();
builder.RegisterType<Repository>().As<IRepository>();
builder.RegisterType<UsersController>().InstancePerRequest();
var container = builder.Build();
var resolver = new AutofacWebApiDependencyResolver(container);
GlobalConfiguration.Configuration.DependencyResolver = resolver;
But it seems that I am missing something cause I get the following error:
An error occurred when trying to create a controller of type 'UsersController'. Make sure that the controller has a parameterless public constructor.
You need to overwrite default controller activator, because it has no knowledge of your DI container.
Add a service class:
public class ServiceActivator : IHttpControllerActivator
{
public ServiceActivator(HttpConfiguration configuration) { }
public IHttpController Create(HttpRequestMessage request
, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
var controller = ObjectFactory.GetInstance(controllerType) as IHttpController;
return controller;
}
}
Then on Application_Start():
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new ServiceActivator(GlobalConfiguration.Configuration));
I'm using structure map in this example, so just replace it with which ever container you are using.
I foolishly decided to try something new on a Friday job!
So I have used NuGet to add Ninject.Web.Mvc 2.2.x.x to my .Net MVC2 project.
I've altered my Global.asax.cs
using System.Web.Mvc;
using System.Web.Routing;
using IntegraRecipients;
using Mailer;
using Ninject;
using Ninject.Web.Mvc;
using Ninject.Modules;
namespace WebMailer
{
public class MvcApplication : NinjectHttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("favicon.ico");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Mail", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected override void OnApplicationStarted()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
protected override IKernel CreateKernel()
{
return new StandardKernel(new INinjectModule[] { new MailModule()});
}
internal class MailModule : NinjectModule
{
public override void Load()
{
Bind<IMailing>().To<Mailing>();
Bind<IMailingContext>().To<MailingContext>();
Bind<IRecipientContext>().To<RecipientContext>();
}
}
}
}
and I've created a controller like so...
using System.Linq;
using System.Web.Mvc;
using WebMailer.Models;
namespace WebMailer.Controllers
{
[ValidateInput(false)]
public class MailController : Controller
{
private readonly IMailingContext _mailContext;
private readonly IRecipientContext _integraContext;
public MailController(IMailingContext mail,IRecipientContext integra)
{
_mailContext = mail;
_integraContext = integra;
}
public ActionResult Index()
{
return View(_mailContext.GetAllMailings().Select(mailing => new MailingViewModel(mailing)).ToList());
}
}
}
But the controller is still insisting that
The type or namespace name 'IRecipientContext' could not be found (are you missing a using directive or an assembly reference?)
and
The type or namespace name 'IMailingContext' could not be found (are you missing a using directive or an assembly reference?)
My google-fu has failed me and I really hope this is just a silly typo/missing line thing
Thanks in advance
P
Ninject does not change the way assemblies are compiled! It deos not magically add references to other assemblies or add using directives. If you are using interfaces from other assemblies you have to add a using directive and a reference to this assembly.
All Ninject is about is to wire up your application at runtime.
I am have what appears to be a similar problem.
I have a simple WPF Window project with the compiled Ninject.dll linked in. However, the following is giving me errors...
using Ninject;
namespace CatalogueManager
{
public class ServiceLocator
{
public IMainWindowViewModel GetMainWindowViewModel()
{
return Kernel.Get<IMainWindowViewModel>();
}
static IKernel Kernel;
static ServiceLocator()
{
Kernel = new StandardKernel(new NinjectConfiguration());
}
}
}
In particular, "Ninject" namespace and IKernel are prompting the compile time message "type or name space 'X' not found..."