Single website multiple databases, database switching - asp.net

I have created a content management system (CMS) for my company’s product databases. The CMS is based on asp.net scaffolding with many custom pages and actions mixed in. We have 7 products currently, all of which share the same database schema (Entity Framework model-first) and all run perfectly in the CMS. The issue is that every time we get a new product we must clone the CMS and change the connection string in the app.config to point to the correct database in order to work with the new database. While this works, it’s becoming bothersome to maintain and will fail us completely as we acquire more products.
What I would like to do is have a centralized landing page where a user is directed to log in, then given the option to connect to and edit a specific product based on their selection. The idea is that we would have one CMS site which would be able to switch between the databases depending on the user. It is not an option to combine all of the product database in to a single master product database.
I am not sure where to start to achieve this goal, or if this is even the correct plan to achieve my goal of having a single CMS to maintain, and am looking for some guidance in this.

Assuming that your database structures are identical, you could use a factory method anywhere you get an instance of your entity context and put logic in there to grab the correct connection string (or calculate it if there's a naming convention that you could use). Something like this might work for example:
public static MyDatabaseEntities CreateEntityContext(string productName)
{
string connectionString = null;
switch (productName.Trim().ToLower())
{
case "apples":
connectionString = ConfigurationManager.ConnectionStrings["MyDatabase_Apples"].ConnectionString;
break;
case "pears":
connectionString = ConfigurationManager.ConnectionStrings["MyDatabase_Pears"].ConnectionString;
break;
default:
connectionString = ConfigurationManager.ConnectionStrings["MyDatabase"].ConnectionString;
break;
}
return new MyDatabaseEntities(connectionString);
}
Then use this method anywhere you need an instance of your CRM data context passing in the product name that you've calculated on your landing page.

Create another database for user to database mapping. The structure would be like so:
database UserMap
table Users
username (composite primary key)
dbID (composite primary key, foreign key to "Databases" table)
table Databases
dbID (primary key)
connectionString
Then,
populate the list of database in table "Databases"
Do your SQL work to copy the users from the other websites into this "UserMap" database
Write a trigger in each CMS database to add or remove a user as they are created or removed in their respective CMS so it updates the "UserMap" database
Modify your code on the CMS(s) to use this single database for lookup to what connection string should be used.
This would allow you to rely on the single database for the lookup and switch between them in a managed fashion going forward. It requires some up front work but after the triggers are there, you don't have to do anything more.

Related

Asp.net Multi Tenancy implementation on existing solution

I have an asp.net MVC solution, Entity Framework code first, which has dozens of database tables all designed around a single company using the solution.
The requirement has come up to allow multiple companies to use the solution, so what we have done is add "CompanyID" as a column to all database tables and set a default value. There is a company table with the various company names and CompanyID's. On login the user selects the company they are logging in as which stores the CompanyID in the session.
At the moment every Entity Framework call now has to be updated to include the CompanyID, for example when selecting Employees I am doing:
List<Employee> employees = db.Employees.Where(x => x.CompanyID = Session.CompanyID).ToList();
As you can see it will be tedious to do this on thousands of calls to the db. Any update, save, and fetch has to change.
Surely I am doing it the long way and there is a way at runtime, globally to append all DB calls to include the CompanyID stored in the logged in users Session? Something that dynamically appends the CompanyID when fetching values or storing etc? Perhaps a package I can use to do this task at runtime?
In my opinion, there is no need to add CompanyID to EVERY table in the database. I would select just "root" tables/entities for that. For example, Employee or Department clearly sounds like a many-to-one relationship with a company - so adding CompanyID there sounds right. But, for example, EmployeeEquipment which is a many-to-one relationship with Employee does not have to have CompanyID column since it can be filtered by the joined Employee table.
Regarding your request to filter by CompanyID globally, I'm not aware of anything that can do that per request. There are global filters for Entity Framework, but I'm not sure how you can apply them per-request. Take a look on .HasQueryFilter() during model creation if you are using Entity Framework Core.

AddOrUpdate() throws error: Modifying a column with the 'Identity' pattern - how should I be handling this?

I've been working through Adrian Hall's book on integrating Xamarin and Azure Mobile Apps. In Chapter 3 he adds a User table to facilitate "Friends" data. In his implementation, the client authenticates the user and then makes a request to a custom endpoint that either adds the user to the database or updates their record. Here's an abridged version of the method in the custom controller:
[HttpGet]
public async Task<IHttpActionResult> Get()
{
// ...Obtain user info
User user = new User()
{
Id = sid,
Name = name,
EmailAddress = email
};
dbContext.Users.AddOrUpdate(user);
dbContext.SaveChanges();
// ...
}
The trouble is, the 2nd time the same user logs in to the app, this code throws an exception saying
Modifying a column with the 'Identity' pattern is not supported. Column: 'CreatedAt'. Table: 'CodeFirstDatabaseSchema.User'.
This StackOverflow Answer explains that this is because the AddOrUpdate() method nulls out any properties not set on the entity, including CreatedAt, which is an identity column. This leaves me with a couple of questions:
What is the right way to Add or Update an entity if the CreatedAt value cannot be edited? The same SO thread suggests a helper method to look up the existing CreatedAt and apply it to the entity before trying to save it. This seems cumbersome.
Why is this implemented as a custom auth controller that returns a new Auth token when it only needs to add or update a User in a database? Why not use a normal entity controller to add/update the new user and allow the client to continue using the Auth token it already has?
For the CustomAuthController.cs code, see here.
When you focus on what you are trying to do from SQL perspective it would be like:
update dbo.some_table set some_primary_key = new_primary_key where some_primary_key = ...
which would result in cannot update identity column some_primary_key which makes sense.
But if you do have a reason to update the PK you still can do it if you set the identity insert
SET IDENTITY_INSERT dbo.some_table ON;
Then after you made an insert you set it off using similar syntax.
But this is rather exceptional scenario.
Usually there is no need to manually insert PKs.
Now going back to EF.
The error you are getting is telling you that you cannot modify a column with PK, most likely user_id and/or some other columns if you have composite PK.
So, first time round a new user gets created. Second time round, because you are suing GetOrUpdate a user gets udpated but because you are passing PK it breaks.
Solution?
AddOrUpdate was meant to help with seeding the migrations only.
Given its destructive nature I would not recommend using GetOrUpdate anywhere near production.
You can replace GetOrUpdate with two operations Get and Update
Fetch user and then
if not exists then create a new one
or if it does exist then update it

Can i add new custom tables inside my aspnet database inside asp.net MVC-5 project

I have the following:-
Visual Studio 2013.
i created a new asp.net MVC-5 web project.
the project is using asp.net identity 2.2.
for the authentication method i chose "Individual user accounts"
this process created a new database named aspnet-OurProjectNanme-number
inside the automatically generated database, i have a table named AspNetUSers which store the user info.
now i am working on building an ERP system. and inside the ERP system i want to add the following:-
a table named "Asset" to store the asset info.
the "Asset" table will have 2 columns named "CreatedBy" + "ModifiedBy" which should store the userId who created and modified the asset item.
now i am not sure how i need to achieve this? as i need to add a foreign key between my custom table "Asset" and the "AspNetUsers" table which have been created automatically.. so can i add my custom table "Asset" inside the automatically generated database, and build the foreign key between the Asset.CreatedBy and AspNetUsers.Id ??
if the answer is Yes then can this relation break in the future if we want to upgrade our aspnet identity version ? as upgrading the identity might result in creating new tables or renaming existing ones etc.. which might break the relation between the Asset table and the AspNetUsers table?
If the answer is No (i should not add custom tables inside the automatically generated database ) then how i can build the foreign key ?? and where i need to add the Asset table in this case??
The most common approach to what you want to do is simply to add your additional model as a DbSet in your ApplicationDbContext.
public class Asset
{
public string CreatedBy { get; set; }
public string UserId { get; set; }
public ApplicationUser User { get; set; }
}
public class ApplicationUser : IdentityUser
{
public IList<Asset> Assets { get; set; }
}
public class ApplicationDbCotext : IdentityDbContext<ApplicationUser>
{
public DbSet<Asset> Assets { get; set; }
}
As I mentioned this is the most common approach as updating the Identity packages should have no impacting affects on your schema. That said you should always test updates before pushing to production.
UPDATE:
Note that when you're working with One to Many relationship's you will see in our Asset model a property for the User Id foreign key as well as the User object. Because of the relationship we are then able to create a List<Asset> in our User to complete the One to Many relationship. This will then allow us to directly query Assets belonging to a User.
As for Code First vs Database First the difference really comes down to how you define the mapping between Entity Framework and the Database.
As I mentioned below there is no one size fits all answer to should you separate the Identity context from your business context, or should you separate them into separate databases. The reality is that only you can answer that question for your needs. It is far more common to have all of the data in a single database. That said, there is something to be said for the security of having identifying information about a user such as their name, email and password hash separated from information like their address or payment information. The trade off is that you can find yourself trying to maintain objects that are supposed to be tied together but are only loosely related because they reside in different databases. Also you would then need to make sure you're using different users/passwords to connect to the different databases, and it's better to have the databases on different servers because if the server gets compromised you went through the entire exercise for nothing. The trade off to get the theoretical security ends up being so impractical with there consistently being another thing you have to do, that you end up seeing everything in one database where you can focus all of your hardening efforts.
Both the ApplicationDbContext and ApplicationUser objects should typically be created for you when you File -> New a project with Individual Authentication. You can add as many properties and relationships to your User as you require.
-- Update --
The answer is growing and growing and so is the discussion. I think I've shown all kinds of variations, which may not have helped to make it understandable. So here is a summary. For explanation, read the full answer and discussion.
Out of the box you have two contexts, identity and business. These are decoupled, so you can change your security without interfering with your business. This way, upgrading security won't break your application or other models. Since the contexts are seperate, changes to either one of them won't affect the other.
As a sidenote: you are not intended to directly access the AspNet identity tables. Implement the UserManager and use the avaiable methods of the manager to perform actions.
Now it comes to logic, where should information be stored? As a simple rule just ask yourself the question: is it part of security or business?
In both contexts you have users. For your requirement this is a logical 1:1 relation. But they are actually seperate. You can create people without supplying a login or delete a login, without deleting a user (people), e.g. for historical reasons.
All you want is to find all information for the current user. So all you need is the People.Id.
Without having to change the IdentityUser you can create the 1:1 relation by just overriding the AspNetUser.Id.
var appUser = new IdentityUser
{
UserName = model.Email,
Email = model.Email,
Id = Convert.ToString(People.Id)
};
var identityResult = await userManager.CreateAsync(appUser, model.Password);
You do not need the identity context for your business. All you need is People.Id. The identity context is only used when tokens are issued and users are created / modified.
To obtain the id use something like this:
var peopleId = int.Parse(Request.User.Identity.GetUserId());
Now you can query your business model using the Id.
When registering, extend the View and ViewModel with the People information you want to store. This will allow you to add both People and AspNetUser at the same time. Though this is not one transaction. But I think it is highly unlikely that creating either one would fail if you perform checks first.
You can validate the username and password (use the methods in the UserManager) and check the ModelState of the viewmodel before creating the user. Use attributes to force Required fields to be filled.
-- Original answer --
In order not to repeat myself, read my answer here.
In short, keep identity and business seperated.
Just in case the identity logic is removed from the same database, like when implementing IdentityServer.
It seems you have business information in AspNetUser. If so, create a Person table and move the information to that table. Relate to that table in your model. In table Person you can add a reference to AspNetUser.
-- update --
I think you understand correctly, but I will just add the details to this answer.
In most cases all tables are defined in one database. But that doesn't mean they are all part of the same model. There can be multiple contexts. In this case one for Identity and one (or more) for Business.
Now why seperate those two? The most important difference between the Business model and Identity model is that Identity tables are not to be called directly. We use the Owin context to call the UserManager / RoleManager.
That is why we cannot add these tables to the business model. Things can be altered in a way that is not secure. Also we do not want the business to have any knowledge about authorization. It shouldn't matter how this is done, as long as a user is identified and authorized.
Also you may want to implement OpenId and claim based authorization. In that case information doesn't have to be available in the database.
The idea is to create a 1:1 relation of the identity table AspNetUsers and business table People. There can be some redundancy, like email or (user)name. But that isn't a problem. The People table should contain all information you want to use in your business model. And the business tables should only relate to People, not AspNetUsers.
Now about the link between AspNetUsers and People. There are four options:
Set People.Id = AspNetUser.Id. Please note that AspNetUser.Id doesn't have to be a GUID. You can add your own value as key.
Set AspNetUser.Id = People.Id.
Add column AspNetUserId to People. No modifications to Identity are needed. You can add People to the Identity Model as well, but I don't think you can create both records in one transaction. You can use User.Identity.GetId() to get AspNetUser.Id. You may however ask yourself if the business should have knowledge about this information.
Add column PeopleId to AspNetUsers. You'll need to extend the IdentityUser to add PeopleId. An advantage is that you don't need the AspNetUser Id, but you can use the actual Id of People. When using OpenId or claims you can get People.Id from claims and you won't have to add AspNetUser.Id to the business. Optionally you can add People to the Model and as navigation property of the extended IdentityUser. When creating the user, you can do this in one transaction.
In case you are creating the user in seperate contexts, you'll need to handle the rollback yourself. But before adding a record to People, you can already test if an AspNetUser can be added: has a valid name/email and password.
Since your business model relates to the People table, you can query all assets and join with the People table for additional information. Or you can get all assets for the current user.
o yes, there are two contexts. The identity model, which contains the AspNet... tables + optionally People. And the business model, which contains all ERP tables + Asset + People.
You may consider to use code first for identity framework 2 and database first for the business model.
I hope that this helps. If not, let's continue in chat.
-- update --
The answer focused on seperation of domains: identity and business. That is why I didn't discuss one possible alternative concerning the AspNetUsers table.
The two models are representations of the database, which means that the database doesn't have to be an exact match. You are free to map tables and fields as you like, as long as they don't break database logic.
Since AspNetusers and People has a 1:1 relation and when both tables are present in the same database, you may as well merge the two into the AspNetUsers table. You can also add relations to the AspNetUsers table, though you may want to add an extra Id (int) column instead of using the current Id (string).
This does not mean the People class can be discarded, except that we have to change the table mapping: AspNetUsers.
Example:
[Table("AspNetUsers")]
public class People
{
[Required]
[StringLength(128)]
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
As you can see, the sensitive fields are not mapped. We need however the Id field. You can now read and update the mapped fields.
You don't have to extend IdentityUser. You can add an AspNetUser and then update the fields using People in the other context. But if you want to add a user in one single transaction it may be easier to extend the IdentityUser (make sure you'll define the new fields in both People and ApplicationUser):
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
There are multiple advantages:
There is only one transaction to add the user.
You cannot expose the sensitive fields since they are not mapped in People.
You cannot add People to the database, since some required fields are not mapped in People.
Please note that this may not work for all types of models (code first/database first + migrations).

How to use ASP.NET Membership for User Defined Permissions

I have a simple ASP.NET MVC 3 application. I have the database I designed for the application, which corresponds to a domain model (in an App.Domain assembly).
I auto-generated the Application Services Membership tables and added them to the application database. Membership creation is working fine.
I created a 'Permission' table which has a composite PK made up of UserId (from the auto-generated aspnet_Users table) and ContentId (from the Content table holding the application content).
The idea is to allow users to allocate permissions to other users for the content they create.
My plan is to then place logic in the Controllers that goes something like:
Collapse | Copy Code
Guid currentUser = (Guid)Membership.GetUser().ProviderUserKey;
int[] accessible = (from p in context.Permissions
where p.UserId == currentUser
select p.ContentId).toArray();
Then to get the content for the current user, something like this:
Collapse | Copy Code
IEnumerable<content> content = context.Content
.Where(x => x.PublicAccess < 3
|| accessible.Contains(x.ContentId));</content>
If I have made any sense, can anyone tell me if this is a normal way to handle user defined permissions.
Also, this code doesn't work because it won't cast the linq to an int[]. Any help with that?
Typically permissions are handled by the role subystem. You do something like this:
if (User.IsInRole("RoleName")) {
DoWhateverTheUserIsAllowedToDo();
}
You can combine this with dynamically assigned content permissions by looking up what roles or users are assigned to the content, and checking if the user is in that role, or if the users is specifically allowed.
Role based permission scales better than assigning users to specific pages. As the number of pages grow, assigning users to pages becomes a nightmare. So you typically assign roles to pages, then assign users to roles.
Your code seems to want to do a lot of work, returning lots of different content items. Typically, you know what item you want to control access to. So you might do something like:
var roles = Roles.GetRolesForUser()
var content = from p in context.Permissions where p.ContentID == contentID
&& roles.Any(x => p.Roles.Contains(x)) select p;
But, there are so many ways to do this, you will have decide what works best for you.
I don't understand your last bit about not casting to an int[]. I assume .toArray() is a typo, it should be .ToArray(). And if ContentID is an int, .ToArray should create an int[].

Best way to check a setting in all profiles

Using ASP.net, is there a recommended best way to access a particular field of the profile in code. I was looking in my old Problem-Design-Solution 2.0 book, and it does it by pulling all members in the DB and then iterating through each one's profile (see code below). Is there a better way?
for each (MembershipUser user in Membership.GetAllUsers())
{
ProfileCommon userProfile = profile.GetProfile(user.UserName);
if (userProfile.mysetting == desiredValue)
{
//do something
}
}
Edit 1
I found that it can be done a little more efficiently than pulling members and then pulling profiles. It is possible that not all members have a profile, so if you use the following code, you'll pull all the profiles (which may be fewer in number than members, and then can iterate across it:
for each (ProfileInfo theProfile in ProfileManager.GetAllProfiles (ProfileAuthenticationOption.All)
{
ProfileCommon pc = ProfileBase.Create(theProfile.UserName)
if (pc.mysetting == desiredValue)
{
//do something
}
}
It still round trips the DB for each profile, but it may not do it as many as if we used the members...
With built-in Profiles, no there isn't a better way. One option as provided by Tim is Table Profile provider or writing your own profile provider.
Or you can go completely other route i.e. storing profile information in your own custom table.
You could use the Table Profile Provider and build custom queries to get your desired settings.
You could probably do better with linq, I don't have VS with me right now, but pseudo code would look something like this:
var users = from MembershipUser user in Membership.GetAllUsers()
where user.mysetting == desiredValue
select user
then iterate over the users,
foreach(MembershipUser u in users) {
// do something
}
that should only contain the ones of interest. Linq should handle executing the SQL for you correctly, but you can check to see what it's doing with profiler.
EDIT
Actually that probably won't get you anything from a performance perspective, the GetAllUsers is going to bring back everything. You might want to create a linq2sql dbml map for the users table and use that instead of Membership class for querying against a custom property.
EDIT 2
ASP.NET Roles and Profiles: best way to query for collection of users who match custom profile property?
If you're using the table profile provider you may be able to use the linq query against that table:
http://weblogs.asp.net/kencox/archive/2010/09/05/using-the-sql-table-profile-provider-in-asp-net-4-web-applications-c-amp-vb.aspx

Resources