I have an MVC5, which uses ASP.NET Identity for users. I have a class named Business which inherits from ApplicationUser, then I populate the database with the entries in my CSV files, but then in the database they don't have a SecurityStamp and I cannot seem to be able to log in. I tried something like this in my Configuration.cs file, but it doesn't seem to work:
var userManager = new UserManager<Business>(new UserStore<Business>(context));
foreach (Business b in context.Businesses)
{
userManager.UpdateSecurityStampAsync(b.Id);
}
context.SaveChanges();
Please note that initially their SecurityStamp is null in the database. Any idea, how to add the security stamps from Configuration.cs?
You should always use the non-async versions of methods that are not intended to be awaited.
userManager.UpdateSecurityStamp(b.Id);
Change context.Businesses to context.Businesses.ToList()
The error you were getting There is already an open DataReader associated with this Connection which must be closed first. is probably because you are iterating a set which is streaming objects from your DB and at the same time trying to issue additional commands through UpdateSecurityStamp
Related
I've read microsoft article about resource based authorization with IAuthorizatinService, but it allows to autorize only one resource. For example i have a User class and File class. File has an owner and can be public or not, so the file can be viewed only if its public or the user is owner of this file. I need to display a list of all files for different users, so each user will see all public files and all owned files.
In authorization handler i have this:
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
OwnerOrPublicRequirement requirement,
File resource)
{
if (resource.IsPublic || context.User.Identity?.Name == resource.Owner)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
Then in controller i had to do like this:
List<File> authorizedFiles = new List<File>();
foreach (var file in _dbContext.Files)
{
var result = await _authorizationService
.AuthorizeAsync(User, file, new OwnerOrPublicRequirement());
if (result.Success)
{
authorizedFiles.Add(file);
}
}
But it looks ugly cause i have to load all the files from DB and then filter them one by one. What if i have like millions of files and most of them are nor public not owned by user? I will not be able to load all of them and filter like this due to out of memory. I can rewrite it to LINQ query and let DB will do all the job:
var authorizedFiles = _dbContext.Files
.Select(f => f)
.Where(f.IsPublic || f.User.Identity?.Name == f.Owner)
.ToList();
But then i will have two places with code that does same thing, so whenever i need to change authorization logic i have to fix two different parts of code. So what will be the propper way of doing this?
Don't use the custom authorization provider too much extra cost and complexity.
Have one place to get the list of files and let the database do the heavy work of filtering and sorting by filename.
Death by a thousand cuts of having to know dozens/hundreds of special features of the ASP.NET framework costs. Each special knowledge item costs minutes per year to support for you and future developers and adds risk to the project.
Combined together, hundreds of small extra features/specialized knowledge needed, will add man days (months?) to the cost of keeping your production system alive and enhancing it. Microsoft seemed to forget the keep it simple and keeps adding dozens of specialized knowledge needed features with each new version of ASP.NET.
A developer should be able to read the application main program, then trace how each piece of code in the entire application code base is called without needing to know internals/extensibility hell micro-trivia of the ASP.NET framework.
Background
I have a central database my MVC EF web app interacts with following best practices. Here is the offending code:
// GET: HomePage
public ActionResult Index()
{
using (var db = new MyDbContext())
{
return View(new CustomViewModel()
{
ListOfStuff = db.TableOfStuff
.Where(x => x.Approved)
.OrderBy(x => x.Title)
.ToList()
});
}
}
I also modify the data in this database's table manually completely outside the web app.
I am not keeping an instance of the DbContext around any longer than is necessary to get the data I need. A new one is constructed per-request.
Problem
The problem I am having is if I delete a row or modify any data from this table manually outside the web app, the data being served by the above code does not reflect these changes.
The only way to get these manual edits of the data to be picked up by the above code is to either restart the web app, or use the web app to make a modification to the database that calls SaveChanges.
Log Results
After logging the query being executed and doing some manual tests there is nothing wrong with the query being generated that would make it return bad data.
However, in logging I saw a confusing line in the query completion times. The first query on app start-up:
-- Completed in 86 ms with result: CachingReader
Then any subsequent queries had the following completion time:
-- Completed in 0 ms with result: CachingReader
What is this CachingReader and how do I disable this?
Culprit
I discovered the error was introduced elsewhere in my web app as something that replaced the underlying DbProviderServices to provide caching, more specifically I am using MVCForum which uses EF Cache.
This forum's CachingConfiguration uses the default CachingPolicy which caches everything unless otherwise interacted with through the EF which was the exact behavior I was observing. More Info
Solution
I provided my own custom CachingPolicy that does not allow caching on entities where this behavior is undesirable.
public class CustomCachingPolicy : CachingPolicy
{
protected override bool CanBeCached(ReadOnlyCollection<EntitySetBase> affectedEntitySets, string sql, IEnumerable<KeyValuePair<string, object>> parameters)
{
foreach (var entitySet in affectedEntitySets)
{
var table = entitySet.Name.ToLower();
if (table.StartsWith("si_") ||
table.StartsWith("dft_") ||
table.StartsWith("tt_"))
return false;
}
return base.CanBeCached(affectedEntitySets, sql, parameters);
}
}
With this in place, the database logging now always shows:
-- Completed in 86 ms with result: SqlDataReader
Thanks everyone!
I am looking for a good way to perform logs change/audit trail on EF5 database first.
The main problem i'm having is that currently an old application is ruining and it creates logs using Triggers, but on that application the database connection uses a specific user for each user on the application (every user on the application has his own database user), so when they do a log they use a lot of the connection properties as default values like userID, and Host, also many logged tables doesn't have an userID row so if i use EF, the entity i want to update/insert/delete doesn't have any user data.
but my application (MVC4) has only 1 string connection using only 1 user (same database user for each) so the triggers will store the userId of the database user from the connection string.
so what will be a good way to create logs using EF? is there a way to do it using triggers?(and passing userID and others?).
i have being reading about override onUpdate functions but also they say it wont work on EF5
In the DatabaseContext it is possible to override the SaveChanges function.
You can test the changeset for entries that needs to be logged.
Maybe it's to low-level i.e. to close to the datalayer, but it will work in EF.
You'll get something like this:
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries())
{
if (entry.State == EntityState.Added)
{
var needToLogAdd = entry.Entity as INeedToLogAdd;
if (needToLogAdd != null)
DoLogAdd(needToLogAdd);
}
}
base.SaveChanges();
}
I make a Mvc4 application from defaults I've been given at the beginning.
I need to store the modified UserProfile in my DB, so I have changed the UserProfile class for fulfilling my needs and also changed the RegisterModel class. Now when I register someone, I have a correct view with all the necessary fields for it, however, when I open Server Explorer for check, I get a UserProfile table with UserId and UserName only(but by the model there should also be firstname,lastname,email and so on).
What should be modified more for storing them correctly?
I was in the same spot a few months ago. One of the issues is that the registration story isn't exactly completed out of the box. For example, if someone register first THEN link their external account, they can wind up with multiple profiles.
I pull the user id from the webpages_OAuthMembership table on successful login.
if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: true))
{
var oAuthMembership = new EndeavorODataEntities().webpages_OAuthMembership
.Where(u => u.ProviderUserId == result.ProviderUserId)
.FirstOrDefault(x => x.Provider == result.Provider) ??
new webpages_OAuthMembership
{
Provider = result.Provider,
ProviderUserId = result.ProviderUserId,
};
TempData.Add("OAuthMembership", oAuthMembership);
HttpContext.Response.Cookies.Add(new HttpCookie("UserId", oAuthMembership.UserId.ToString(CultureInfo.InvariantCulture)));
return RedirectToAction("Summary", new { Controller = "Member", id = oAuthMembership.UserId.ToString(CultureInfo.InvariantCulture) });
From here, I make a separate call to my custom 'Membership' table on another controller, as I store all of my application data in a separate db from the this OAuthMember table. In my prior experience in using the ASP.NET Membership provider database, I always kept that as a separate db from my applications, reusing it across multiple apps. Of course, if you wished to modify the UserProfile or other tables, as you can see from the code above, this is just a LINQ statement. There is nothing to say you couldn't perform a join to the UserProfiles table here too.
In this example above I had created an edmx file OAuthMembership.edmx and imported my tables from SQL just like any other database. EndeavorODataEntities is the name of my connection string, and webpages_OAuthMembership is the name of the actual membership table.
I've added in some other resources that I've used which may help you.
http://blogs.msdn.com/b/webdev/archive/2012/08/22/extra-information-from-oauth-openid-provider.aspx
http://blogs.msdn.com/b/pranav_rastogi/archive/2012/08/23/plugging-custom-oauth-openid-providers.aspx
http://blogs.msdn.com/b/webdev/archive/2012/08/24/customizing-the-login-ui-when-using-oauth-openid.aspx
http://blogs.msdn.com/b/webdev/archive/2012/09/12/integrate-openauth-openid-with-your-existing-asp-net-application-using-universal-providers.aspx
http://blogs.msdn.com/b/webdev/archive/2012/09/19/configuring-your-asp-net-application-for-microsoft-oauth-account.aspx
http://blogs.msdn.com/b/rickandy/archive/2012/08/15/initializesimplemembership-attribute-and-simplemembership-exceptions.aspx
I currently have a site where different users can login, and depending on their sub domain, are presented with different data from different databases. I am overriding the SqlMembershipProvider to use a "temp" connection string, that I dynamically set during the Initialize Method, per this post's instructions:
http://forums.asp.net/p/997608/2209437.aspx
public override void Initialize(string name, NameValueCollection config)
{
// intercept the setting of the connection string so that we can set it ourselves...
string specifiedConnectionString = config["connectionStringName"];
ConnectionStringSettings connectionString = ConfigurationManager.ConnectionStrings[specifiedConnectionString];
var fi = typeof(ConfigurationElement).GetField("_bReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);
fi.SetValue(connectionString, false);
connectionString.ConnectionString = WakeflyClientHelper.GetClientConnectionStringByCurrentSubdomain();
config["connectionStringName"] = connectionString.Name;
// Pass doctored config to base classes
base.Initialize(name, config);
}
The problem is that the SqlMembershipProvider class seems "static" in that when multiple users connect from different sub domains, they end up seeing the User Accounts from ONE of the databases, not each of their own. It looks like the Initialize is called by the application, and not on a per request basis.
So my question to you is... What would be the easiest way to implement a solution to this?
I have not written a custom provider before, so I'm not sure how that works, or what the limitations are. And if I write it myself, there is always the possibility of security holes if I overlook something (not to mention the time it will take). Any ideas?
Update 1:
I could have a single database, however, the user's within a given sub domain need the ability to add/edit/delete user's within their own sub domain, without seeing other sub domains' users. Would that be possible under this scheme?
The code for the SQLMembershipProvider is provided here. You could extend/change what you think is necessary by adding the project to your solution and changing the namespace references in web.config.
I have done exactly this using a single membership database to handle all the logins, and then hook them up to the appropriate database depending on the domain in which they logged in.