I am trying to build a multi-tenant mvc 5 site that uses a single database and differentiates the tenants by schema in Sql Server.
I started with the default Mvc 5 template and updated the ApplicationDBContext that is provided to take a string specifying the schema to use for that tenant like so.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
private string _tenantSchema;
public ApplicationDbContext(string tenantSchema)
: base("Dev", throwIfV1Schema: false)
{
_tenantSchema = tenantSchema;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema(_tenantSchema);
base.OnModelCreating(modelBuilder);
}
public static ApplicationDbContext Create(string tenantSchema)
{
return new ApplicationDbContext(tenantSchema);
}
}
And then in the App_Start\IdentityConfig.cs I updated the Create Method of the ApplicationUserClass to use the first part of the Request.Host value to use as the tenantSchema like so
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var tenantSchema = context.Request.Host.Value.Split('.')[0];
var ctx = new ApplicationDbContext(tenantSchema);
var userStore = new UserStore<ApplicationUser>(ctx);
var manager = new ApplicationUserManager(userStore);
so if I were to log into site1.mysite.dev it would authenticate against the tables in the site1 schema is sql server.
When I start the site and access it using the site1 sub domain it correctly uses the site1 Schema to authenticate me. But then if I change url in the browser address bar and log in again it still validates against the site1 schema.
How can I configure the app to use to correct schema to check authentication for each request?
While the code you've shown should sort of work (but it's hard to tell since you left out other key parts, such as Startup.Auth), I would not do it in this way. I would instead change your ApplicationDbContext.Create method to this:
public static ApplicationDbContext Create(
IdentityFactoryOptions<ApplicationDbContext> options, IOwinContext context)
{
var tenantSchema = context.Request.Host.Value.Split('.')[0];
return new ApplicationDbContext(tenantSchema);
}
I would then alter my Startup.Auth as such:
app.CreatePerOwinContext<ApplicationDbContext>(ApplicationDbContext.Create); <---
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
And leave everything else the default. Specifically:
public static ApplicationUserManager Create(
IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(
new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
....
However, your real problem is most likely that your model builder is cached in the app domain the first time it is created. This is referenced in the documentation here:
http://msdn.microsoft.com/en-us/library/system.data.entity.dbcontext.onmodelcreating(v=vs.113).aspx
Typically, this method is called only once when the first instance of a derived context is created. The model for that context is then cached and is for all further instances of the context in the app domain. This caching can be disabled by setting the ModelCaching property on the given ModelBuidler, but note that this can seriously degrade performance. More control over caching is provided through use of the DbModelBuilder and DbContextFactory classes directly.
There is actually a nice discussion on this very topic relating to EF and multi-tenant usage (way too long to elaborate here) over at the EF codeplex site:
https://entityframework.codeplex.com/discussions/462765
Related
I'm trying to integrate the recently released ASP.NET Identity 2.0.0 into a 3-layer MVC application. I'm not sure I'm going in the correct direction. I can see two approaches to take.
In the first approach, I've gone with integrating Identity into each logical layer. Having some technical issues integrating but still developing.
In the second approach, go with a a self-contained encapsulated assembly dedicated for security.
I've gone with approach 1 at the moment, but questioning it all. Also, any other approaches to take?
Approach 1
Web
Startup.cs
/App_Start/Startup.Auth
/Controllers/Account
/Controllers/Manage
/Controllers/RolesAdmin
/Controllers/UserAdmin
/ViewModels
/Views
Business Logic
/Service/AccountService
/Service/ApplicationRoleManager
/Service/ApplicationUserManager
/Service/EmailService
/Service/SmsService
/Service/SignInHelper
Data
ApplicationDbContext
ApplicationUser
So, I've simply taken Identity and plugged it into each layer I see that fits.
I've put most of the log in the Business Logic layer, as it doesn't belong in the Web and there no 'real' database code for it to belong in the Data layer.
Side-issue: I'm a bit uncomfortable that in the Web/App_Start/Startup.Auth, I have to instantiate the Busness Logic object to call
app.CreatePerOwinContext(ApplicationDbContext.Create);
in the Data layer. I've yet to think about this more. This is another issue (but I see it is related to the architecture I've chosen).
Approach 2
Creating an assembly purely for Security which contains no layers, i.e. simply plug in Identity 2.0.0 into this one assembly. And my application can reference this. It goes against the layers though. But it encapsulates security. Given security objects can (or should) be resident throughout the application lifetime, this doesn't seem like a bad idea at all. Haven't thought about about scalability though.
I have taken approach 1, and when creating the context I have a helper class that I attempt to get the context from HttpContext.Current.["DbActiveContext"] and use it if it exists, if not create then new one, and use a single context for the entire application. So you do not end up with one context for aspnet idenity and another one for the rest of the app. It looks like you are trying to use a repository pattern in the first approach, if that is the case, then your model for your identity should be in the DB layer, and for full repository pattern, you should be using dependency injection when creating your objects, by doing so, you will not have a dependency until the object is created at run time.
namespace Data.Common
{
public class ConnectionHelper : IConnectionHelper
{
private ApplicationDbContext _context;
public ApplicationDbContext Context
{
get
{
if (_context == null && HttpContext.Current.Items["DbActiveContext"] != null)
{
_context = (ApplicationDbContext)HttpContext.Current.Items["DbActiveContext"];
}
else if (_context == null && HttpContext.Current.Items["DbActiveContext"] == null)
{
_context = new ApplicationDbContext();
HttpContext.Current.Items.Add("DbActiveContext", _context);
}
return _context;
}
set { _context = value; }
}
}
}
Also if you want to use the usermanager in the service layer with DI you can do something like:
public UserController()
: this(
new ApplicationUserManager(new UserStore<ApplicationUser>(new ConnectionHelper().Context)),
new UserService())
{
}
With the UserService signature like:
public class UserService
{
private readonly IRepository<ApplicationUser> _user;
private readonly UserManager<ApplicationUser> _userManager;
public UserService(IRepository<ApplicationUser> user,
UserManager<ApplicationUser> userManager)
{
_user = user;
_userManager = userManager;
}
public UserService()
: this(
new Repository<ApplicationUser>(new ConnectionHelper()),
new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ConnectionHelper().Context)))
{
}
I hope this helps you!
I'm not sure if the title correctly describes my problem. If someone could better describe my problem by reading the following description, please help me by editing the title to something more meaningful.
I'm trying to learn asp.net MVC with Entity Framework and Ninject.
I was having a look at NuGet Gallery application on GitHub and tried to implement a few parts in my project.
I followed the answer provided in this question [How do architect an ASP.Net MVC app with EF?] and designed my project with the following layered structure.
MyDemoApp
MyDemoApp.Domain (Contains POCO Classes)
MyDomain.Service (Contains references to Domain,EF. It contains only Interfaces)
MyDemoApp.Data (Contains references to EF, Domain, Service. It contains classes dealing with Entity Context and Repository)
MyDemoApp.Web (Contains references to ApplicationModel,Data,Domain,Service,Ninject)
MyDemoApp.ApplicationModel (Contains references to Data, Domain, Serivce. It implements the classes from Service project)
MyDemoApp.Web has no business logic and is acting like Humble Object, as mentioned in this answer
I have a Interface IConfiguration in MyDemoApp.Service project which is being implemented by Configuration class located in MyDemoApp.Web where I'm trying to read the connection string. I need to pass this connection string to the object of EntityContext being created in EntityContextFactory located in MydemoApp.Data
If I add a project reference of MyDemoApp.web to MyDemoApp.Data then Visual Studio Prompts me saying that it would cause a circular reference
In the following code return new EntitiesContext(""); How should I pass a parameter over here that would get the connection string that my bindings.cs gets ?
namespace MyDemoApp.Data
{
public class EntitiesContextFactory : IDbContextFactory<EntitiesContext>
{
public EntitiesContext Create()
{
//TO-DO : Get the Connnectionstring
return new EntitiesContext(""); //Need to pass connection string by calling property from Configuration class in MyDemoApp.Web project
}
}
public class EntitiesContext:DbContext,IEntitiesContext
{
public EntitiesContext(string connectionString) : base(connectionString)
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Provide mapping like foreign key
}
}
}
}
Configuration.cs:
namespace MydemoApp.Web
{
public class Configuration : IConfiguration
{
public string ConnectionString
{
get
{
return ConfigurationManager.ConnectionStrings['dev'].ConnectionString;
}
}
}
}
Bindings.cs:
namespace MydemoApp.Web.Bindings
{
public class MyModule : NinjectModule
{
public override void Load()
{
Bind<IConfiguration>().To<Configuration>();
var configuration = new Configuration(); //Gives me Connectionstring
Bind<IEntitiesContext>().ToMethod(context => new EntitiesContext(configuration.ConnectionString)); // This part would help me pass the connection string to the constructor
}
}
}
I don't quite get what problem you are facing. I assume that you need to access a class in Web assembly from Data assembly, but Data assembly already referencing Web assembly.
Can you just inject the configuration interface to your factory constructor, and use that to get the connection string?
public class EntitiesContextFactory : IDbContextFactory<EntitiesContext>
{
public EntitiesContextFactory(IConfiguration configuration){
this.configuration = configuration;
}
IConfiguration configuration;
public EntitiesContext Create()
{
return new EntitiesContext(configuration.ConnectionString);
}
}
I may misunderstand your question though.
Personally, I think you should leverage the ConfigurationManager in your EntitiesContextFactory.
This would look like:
return new EntitiesContext(System.Configuration.ConfigurationManager.ConnectionStrings["{connectionstringname}"].ConnectionString)
This class is agnostic of whether it is an app.config or web.config that is providing the configuration.
All it stipulates is that application that is hosting the dlls for running, must be configured with an (app/web).config that contains that connection string. your app can test for this at startup since it knows it has a dependency on a database connection for it to work.
I am creating web app in MVC 4 that is require custom Login, authentication and role based system. My intention is to use SimpleMembership and SimpleRole but struggling to start with and grasp its basic implication within my app.
I have already database say for example DB1.mdf and have created Users table with userId and UserName. I believe i need to initialize simplemembership with existing database??? and i dont want system create database itself if it doesn't find, as it is doing in default internet template created by visual studio 2012.
many thanks in advanced...
SimpleMembership was not developed for extensive customization or integrating to existing databases. To use an existing database you want to create your own membership provider just like you always have in ASP.NET. You can find direction on creating your own membership provider here.
You need to use WebSecurity.InitializeDatabaseFile tell MVC.NET that you already have a database and a Users table. Then, make sure that the provider uses it upon initialization. So create an ActionFilterAttribute that ensures the Simple Membership provider is initialized, for example:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Ensure ASP.NET Simple Membership is initialized only once per app start
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
try
{
if (!WebSecurity.Initialized)
{
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "Users", "UserId", "UserName", autoCreateTables: true);
}
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
}
Then you will need to register the filter:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new InitializeSimpleMembershipAttribute());
}
}
EDIT:
Thanks to constructive criticism I've removed the code that decorates controllers with the attribute since adding it globally already takes care of the problem. If you decide not to use it globally you will need to decorate other controllers with it since, as explained on this question, you will notice that users may be authenticated with cookies and redirected to other controllers without passing first by your AccountController.
Also, Jon Galloway shows in this post how you can point to your existing database by using:
WebSecurity.InitializeDatabaseFile("SecurityDemo.sdf", "Users", "UserID", "Username", true);
I have a simple asp.net MVC4 / EF 4.1 project created with VS 2011, with a layer for my domain model and one for my database that contains the DbContext. I have one basic domain class called Batch and a BatchController with the standard CRUD functionality using Index / Create / Edit actions. I add two default records with the overridden Seed method. All this works fine I can add / edit / delete records using the out of the box MVC template:
public class BatchController : Controller
{
private readonly MyContext _context = new MyContext();
public ActionResult Index()
{
return View(_context.Batches.ToList());
}
[HttpPost]
public ActionResult Create(Batch batch)
{
if (ModelState.IsValid)
{
this._context.Batches.Add(batch);
this._context.SaveChanges();
return RedirectToAction("Index");
}
return View(batch);
}
}
I added a new MVC4 Web api project to the solution with the intention of exposing the domain object so the data can be retrieved via json. This uses an api controller that I've called BatchesController, and I added a reference to my domain and database layers. I have two Get() methods, one to return all Batches and one to return a single batch given an id. I'm using IIS Express to host the main MVC app and the Web api. To retrieve all the Batches I run this in a browser:
http://localhost:46395/api/batches
Here's my Web api Controller :
public class BatchesController : ApiController
{
private readonly MyContext _context;
public BatchesController()
{
_context = new MyContext();
}
// GET /api/batches
public IEnumerable<Batch> Get()
{
var batches = _context.Batches.ToList();
if (batches == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
return batches;
}
// GET /api/batches/5
public Batch Get(int id)
{
var batch = _context.Batches.Find(id);
if (batch == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
return batch;
}
}
My problem is that when I add a new record and try to retrieve it via a browser, only the existing records aded with the Seed method are returned - I can't get any newly added record to be returned. The DbContext seems to be caching the initial records and not going to the database to get the latest...how do I return newly added records?
Just to clear out the obvious, you have surely rewired to Web API project to point to the same database, right? Because by default Web API will attach its own SQL Compact DB. Meaning that you could effectively be using 2 separate databases
There is an answer, which It doesn't solve my problem:
http://www.strathweb.com/2012/03/serializing-entity-framework-objects-to-json-in-asp-net-web-api/
Also, there is a same question at here:
http://forums.asp.net/t/1814377.aspx/1?Web+api+not+returning+records+from+EF+4+1+DbContext
and I find this useful:
ASP.Net Web API showing correctly in VS but giving HTTP500
BUT THE POINT IS:
You can not send the proxy object to webapi serializer. So it should be project to a new dynamic class or a predefined class which there is no virtual (or maybe IList, ICollection,...).
// GET api/ProfileGame
public dynamic GetProfileGames()
{
return db.ProfileGames.Select(pg => new
{
...
}).AsEnumerable();
}
I've got a new MVC 3 application which is showing some issues when modifying data manually in the Database.
The tool is still in development and once in a while I want to change my user's teamId. When I do so, I have to kill the Web development Server and run it again otherwise the queries don't pick the new teamId.
Same thing when I publish the tool to IIS, if I ever modify something on the database, I need to either copy over the 'bin' folder again or stop the application and re-run it.
When I modify data from the application itself, I have no problems.
This is how my Ninject looks like:
public class NinjectControllerFactory : DefaultControllerFactory
{
private IKernel kernel = new StandardKernel(new TrackerServices());
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
return null;
return (IController)kernel.Get(controllerType);
}
private class TrackerServices : NinjectModule
{
public override void Load()
{
var context = new TrackerEntities();
Bind<IUserRepository>().To<UserRepository>().WithConstructorArgument("context", context);
}
}
}
My Interface:
public interface IUserRepository : IRepository<User>
{
User GetByName(string name);
}
my Implementation:
public User GetByName(string login)
{
var userLogin = _misc.GetUsername(login);
return _context.Users.Where(x => x.Login == userLogin).Single();
}
And my Index Action
public ActionResult Index()
{
var teamid = (int)_users.GetByName("myName").TeamId;
This has never happened before, but this tool is the first one I'm using with Ninject. I'm wondering if there's a relation between my problem and using a repository?
There are two issues that are combining to create this problem:
The way you've created your context is causing it to effectively be a singleton.
Entity Framework will not automatically check for a new version of an entity which the context is already tracking.
To solve this, I would recommend that you recreate your repository once per request (there won't be a significant performance hit for this, as it's fairly lightweight), by using this binding:
Bind<IUserRepository>().To<UserRepository>().InRequestScope();
Ninject should be able to create your TrackerEntities context automatically, but if not (or if you want to make it clear), you can use the following binding:
Bind<TrackerEntities>().ToSelf().InRequestScope(); (The InRequestScope is not really required here, as the default transient scope should be okay).
You could also go down the road of forcing a refresh of the entity (using ObjectContext.Refresh()), but that's probably not a great idea because you'd have to do it explicitly for each entity.