How do I stop a custom EF-based RoleProvider from caching results? - c#

I have an ASP.NET MVC 4 application which was required to use a pre-existing membership model of users/roles. The way I did this was to implement a custom ASP.NET RoleProvider to manage access, this uses Entity Framework repositories to read user data from the database. The method to read user roles is shown below, but all the method implementations follow this pattern:
public class OurRoleProvider : RoleProvider
{
private IUserRepository _userRepository;
public OurRoleProvider() : this(Container.Resolve<IUserRepository>())
{
}
public OurRoleProvider(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public override string[] GetRolesForUser(string username)
{
var user = _userRepository.GetUserByUserName(username);
if (user.Roles.IsNullOrEmpty())
return new string[0];
return user.Roles.Select(r => r.RoleName).ToArray();
}
}
I have now come across the problem described in this post. Because a single instance of a RoleProvider is re-used for the lifetime of the application, and all other functionality creates it's own per-request DbContext to persist data, changes made to a User profile are not reflected by the RoleProvider until a restart in the application, because it's underlying DbContext is not being refreshed. This means you can remove a User from a Role, and they will still have access to that Role's functionality until an app restart.
I have tried creating a new repository instance within the RoleProvider methods, i.e. in GetRoleForUser():
var user = _userRepository.GetUserByUserName(username);
becomes
var userRepository = Container.Resolve<IUserRepository>();
var user = userRepository.GetUserByUserName(username);
This fixes the issue but breaks unit tests which don't use the DI container and inject a mock repository via the constructor. There would be a lot of unit tests to re-write.
I'd like to stick with a custom RoleProvider if possible to make use of features such as the Authorize atrribute. What I really need to do is re-instantiate the RoleProvider on a per-request basis OR force the EF repository to always update from the database. So far I haven't found a way to do this. Is this possible? Or is there a better solution?

You shouldn't define EF Services' life time as a singleton (it should be per-request life time). Because DbContext is not thread safe and also it's designed to have a short life. Activating the caching of a role provider is done somewhere else, in the web.config file:
<roleManager defaultProvider="CustomRoleProvider" cacheRolesInCookie="true" enabled="true">
<providers>
<clear />
<add name="CustomRoleProvider" type="Security.CustomRoleProvider" />
</providers>
</roleManager>
The above custom role provider is cached, beacuse it's using cacheRolesInCookie="true" here.

Related

Calling Entity Framework db context from WCF Service

We have developed an ASP Net MVC application using the Repository pattern.
We are creating a db context instance by using a context provider class like:
public class ContextProvider
public static DBEntities GetContext()
{
return HttpContext.Current.Items["_EntityContext"] as DBEntities;
}
}
Here we are making sure that the DBEntities db call exists only during the existence of the request - we are putting an instance into a Session map - HttpContext.Current.Items["_EntityContext"] in this example.
We are using this in our entire Asp Net Mvc Project as following:
public class TeamRepository
{
#region Members
private DBEntities storeDB = null;
#endregion Members
#region Constructors
public TeamRepository()
{
storeDB = ContextProvider.GetContext();
}
#endregion Constructors
#region Methods
...
Now we need to create a WCF service to enable access to some features to other vendors.
Since all the Repository classes are a part of a project - they were not excluded to a separated DLL I made a reference to the entire project in my new WCF project so that I could use already existing DB Repository method calls.
Here I am facing an issue since I am not able to access to the Session variable HttpContext.Current.Items["_EntityContext"] - method call public static DBEntities GetContext() is always returning null when called from WCF Service.
I tried to make HttpContext.Current Available Within a WCF Service available by placing
[ServiceBehavior(AddressFilterMode=AddressFilterMode.Any)]
[AspNetCompatibilityRequirements(RequirementsMode= AspNetCompatibilityRequirementsMode.Required)]
on my Service class,
and tweaking the serviceHostingEnvironment section of web.config, which now looks like this:
<serviceHostingEnvironment aspNetCompatibilityEnabled="true">
<baseAddressPrefixFilters>
<add prefix="http://localhost” />
</baseAddressPrefixFilters>
</serviceHostingEnvironment>
but with no results.
I am using Windows 10.
Do you know a way I can access HttpContext.Current.Items["_EntityContext"]... contained within Asp Net Mvc project from my WCF Project?
Regards
The issue is resolved using the following steps:
I decorated my service implementation with the AspNetCompatibilityRequirements attribute:
[AspNetCompatibilityRequirements(RequirementsMode= AspNetCompatibilityRequirementsMode.Required)]
public class Service : IService {
. . .
}
The last thing I had to do was necessitated by WCF not supporting multiple host headers; I had to hard-wire the WCF endpoint to listen on a specific hostname. In this case, this involved tweaking the serviceHostingEnvironment section of web.config, which now looks like this:
<serviceHostingEnvironment aspNetCompatibilityEnabled="true">
<baseAddressPrefixFilters>
<add prefix=http://services.mydomain.com” />
</baseAddressPrefixFilters>
</serviceHostingEnvironment>
And then adding another attribute to the service implementation class and initializing the HttpContext.Current.Items session:
[ServiceBehavior(AddressFilterMode=AddressFilterMode.Any)]
[AspNetCompatibilityRequirements(RequirementsMode= AspNetCompatibilityRequirementsMode.Required)]
public class Service : IService {
HttpContext.Current.Items["_EntityContext"] = new DBEntities();
...
}

SignalR and ASP.NET Identity's UserManager class lifetime

In my MVC application, I user SignalR for communication between users. Basically, a client calls a method on the hub, which calls a method on a repository, which then saves the message to the database and the hub notifies the other client of the new message.
I had used the GetOwinContext() method during these calls from the client to get the current instance of UserManager and ApplicationDbContext, by using the GetUserManager<UserManager>() and Get<ApplicationDbcontex>() extension methods, respectively. However, I have noticed that calls from the same connection use the same context, which is, obviously, not a very good thing. I went ahead and changed my repository so it is like this now:
public XyzRepository() //constructor
{
db = ApplicationDbContext.Create(); //static method that generates a new instance
}
private ApplicatonDbContext db { get; set; }
private UserManager UserManager
{
get
{
return new UserManager(new UserStore<ApplicationUser>(db)); //returns a new UserManager using the context that is used by this instance of the repository
}
}
Since I reference the ApplicationUser objects using the UserManager (using FindByIdAsync(), etc, depending on the design), it is extremely important to use the context I currently work with for the UserStore of the UserManager's current instance. The repository is created once per request, which seems to apply to each SignalR calls as intended. While I have experienced no problems with this design so far, after reading about the issue (in this article), particularly this line:
"In the current approach, if there are two instances of the UserManager in the request that work on the same user, they would be working with two different instances of the user object.", I decided to ask the community:
Question: what is the preferred way to use ASP.NET Identity's UserManager class with SignalR, if it is imperative that I use the same instance of DbContext for my repository's methods that the UserManager's UserStore uses?
I think the preferred way is to use an Inversion of Control container and constructor-inject dependencies with some kind of lifetime scope. Here is another question that you might want to look into:
Using Simple Injector with SignalR
It is preferable that your DbContext instance live as long as the current web request. IoC containers have facilities that let you register DbContext instances with per web request lifetimes, but you need to set up the IoC container so that it can manage the construction of the Hub classes to achieve this. Some IoC containers (like SimpleInjector) will also automatically dispose of the DbContext at the end of the web request for you, so you don't need to wrap anything in a using block.
As for the UserManager, XyzRepository, etc, I think those can also have per-web-request lifetime, or even transient lifetimes. Ultimately, I don't see why you wouldn't be able to achieve something like this:
public class MyXyzHub : Hub
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly MessageRepository _messageRepository;
public MyXyzHub(UserManager<ApplicationUser> userManager,
MessageRepository messageRepository)
{
_userManager = userManager;
_messageRepository= messageRepository;
}
public void sendMessage(string message)
{
var user = _userManager.FindByIdAsync(...
_messageRepository.CreateAndSave(new Message
{
Content = message, UserId = user.Id
});
Clients.All.receiveMessage(message, user.Name);
}
}
If you wire up your IoC container the right way, then every time the Hub is constructed, it should reuse the same ApplicationDbContext instance for the current web request. Also with your current code, it looks like XyzRepository is never disposing of your ApplicationDbContext, which is another problem that an IoC container can help you out with.

System.Web.Http.AuthorizeAttribute not recognizing custom role provider

In my MVC 4 Web API project, I have a custom role provider that works as designed via System.Web.Mvc.Authorize attribute on my Home System.Web.Mvc.Controller.
On any System.Web.Http.ApiController with System.Web.Http.Authorize the custom role provider never gets called, always returning false. Is there a way to specify that the Web API AuthorizeAttribute pick up my custom role provider like the MVC AuthorizeAttribute?
Role Provider:
public class CustomRoleProvider : RoleProvider
{
//Overriden methods
public override string[] GetRolesForUser(string username)
{
//Always return "Master" for testing purposes
return new string[] { "Master" };
}
public override bool IsUserInRole(string username, string roleName)
{
//Always return true for testing purposes
return true;
}
//Other overridden method stubs...
}
Web.config:
<roleManager defaultProvider="CustomRoleProvider" enabled="true" cacheRolesInCookie="false" >
<providers>
<clear />
<add name="CustomRoleProvider" type="MyApp.SecurityExtensions.CustomRoleProvider, MyApp" />
</providers>
</roleManager>
This is not really an answer, but this might help:
Both attributes work by querying the current pricipal. The MVC attribute uses HTTPContent.User, while the System.Web.http version uses Thread.CurrentPrincipal, but that difference is minor.
I'm not really familar with Web API, but I suspect that the RoleManagerModule is not running by the time the attribute fires, or you have not yet reached the PostAuthenticateRequest event, because in that event the Module replaces the Pricipal.
Are you sure you have some form of ASP authentication required for your WebAPI usage? If you don't have your WebAPI project configured to require some form of authentication, then obviously you will never reach the PostAuthenticateRequest event, and thus the RoleManagerModule will never kick-in.
The last possibility that comes to mind is that someting else is replacing the Principal after the RoleManagerModule does so. If possible, temporarally remove the System.Web.Http.AuthorizeAttribute, set a breakpoint in the controller, and detemine what class Thread.CurrentPrincipal has. That might give you a hint as to where it went wrong.
You would need to use System.Web.Http.AuthorizeAttribute for Web API's controllers. Sample: http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/

Providing custom database functionality to custom asp.net membership provider

I am creating custom membership provider for my asp.net application. I have also created a separate class "DBConnect" that provides database functionality such as Executing SQL statement, Executing SPs, Executing SPs or Query and returning SqlDataReader and so on...
I have created instance of DBConnect class within Session_Start of Global.asax and stored to a session. Later using a static class I am providing the database functionality throughout the application using the same single session. In short I am providing a single point for all database operations from any asp.net page.
I know that i can write my own code to connect/disconnect database and execute SPs within from the methods i need to override. Please look at the code below -
public class SGI_MembershipProvider : MembershipProvider
{
......
public override bool ChangePassword(string username, string oldPassword, string newPassword)
{
if (!ValidateUser(username, oldPassword))
return false;
ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, newPassword, true);
OnValidatingPassword(args);
if (args.Cancel)
{
if (args.FailureInformation != null)
{
throw args.FailureInformation;
}
else
{
throw new Exception("Change password canceled due to new password validation failure.");
}
}
.....
//Database connectivity and code execution to change password.
}
....
}
MY PROBLEM -
Now what i need is to execute the database part within all these overriden methods from the same database point as described on the top. That is i have to pass the instance of DBConnect existing in the session to this class, so that i can access the methods.
Could anyone provide solution on this. There might be some better techniques i am not aware of that. The approach i am using might be wrong. Your suggessions are always welcome.
Thanks for sharing your valuable time.
Understanding the lifecycle of the membership provider would help clear this up.
An instance of the membership provider is spun up when the application is started and remains active for the lifetime of the application AppDomain, which in practice equates to the application lifecycle. e.g. If for some reason the AppDomain cycles, the application is disposed and a new instance is spun up. A new instance of the registered membership provider is spun up on first use.
You need to either instantiate an instance of you data access class within your membership provider implementation or access static methods from within your provider. I prefer to use an instance.
Separating the membership provider from it's data access by creating singletons or stashing it in application is a hack in my opinion and will lead to nothing but pain, sorrow, lost sleep and credibility amongst your peers.
Cheers and good luck.
Dont keep a seperate instance of the DBConnect class in session, you will end up creating a class for each user! This will seriously affect scalability.
You can do one of the following :
Place the class in Application state
Use the singleton pattern
Make the class and all the methods in the class static.
My recommendation is to go for number 3. You dont usually need to create an instance of a class that does database crud operations eg
public static class DBConnect
{
public static ChangePassword(string userId, string password)
{
//Implementation here
}
}
Then you can simply call this code in your provider without creating an instance:
DBConnect.ChangePassword(x,y);

Can I create a custom roleprovider through a WCF service?

I have a web application that accesses a database through a wcf service. The idea is to abstract the data from the web application using the wcf service. All that works fine but I am also using the built in roleprovider using the SqlRoleManager which does access the aspnetdb database directly. I would like to abstract the roleprovider by creating a custom roleprovider in a wcf service and then accessing it through the wcf service.
I have created the custom role provider and it works fine but now I need to place it in a wcf service.
So before I jump headlong into trying to get this to work through the WCF service, I created a second class in the web application that accessed the roleprovider class and changed my web config roleprovider parameters to use that class. So my roleprovider class is called, "UcfCstRoleProvider" and my web.config looks like this:
<roleManager
enabled="true"
defaultProvider="UcfCstRoleProvider">
<providers>
<add
name="UcfCstRoleProvider"
type="Ucf.Security.Wcf.WebTests.UcfCstRoleProvider, Ucf.Security.Wcf.WebTests"
connectionStringName="SqlRoleManagerConnection"
applicationName="SMTP" />
</providers>
</roleManager>
My class starts like this:
public class UcfCstRoleProvider : RoleProvider
{
private readonly WindowsTokenRoleProvider _roleProxy = new WindowsTokenRoleProvider();
public override string ApplicationName
{
get
{
return _roleProxy.ApplicationName;
}
set
{
_roleProxy.ApplicationName = value;
}
}
As I said, this works fine. So the second class is called BlRoleProvider that has identical properties and parameters as the roleprovide but does not implement RoleProvider. I changed the web.config to point to this class like this:
<roleManager
enabled="true"
defaultProvider="BlRoleProvider">
<providers>
<add
name="UcfCstRoleProvider"
type="Ucf.Security.Wcf.WebTests.BlRoleProvider, Ucf.Security.Wcf.WebTests"
connectionStringName="SqlRoleManagerConnection"
applicationName="SMTP" />
</providers>
</roleManager>
But I get this error.
"Provider must implement the class 'System.Web.Security.RoleProvider'."
I hope I have explained well enough to show what I am trying to do. If I can get the roleprovider to work through another class in the same application, I am sure it will work through the WCF service but how do I get past this error?
Or maybe I took a wrong turn and there is a better way to do what I want to do??
I think your best bet is to create a custom role provider and implement each method. In the implementation of each method, call the WCF service to do the data access. Eg:
public class WcfRoleProvider: RoleProvider
{
public bool IsUserInRole(string username, roleName)
{
bool result = false;
using(WcfRoleService roleService = new WcfRoleService())
{
result = roleService.IsUserInRole(username, roleName);
}
return result;
}
}
No, you have to have a class that must implement RoleProvider. That will not work. If you can't have this class inherit from RoleProvider directly, consider creating a RoleProvider wrapper class that implements RoleProvider's props/methods, but utilizes whatever you need to do with this second class.
This error isn't specific to WCF, but is specific to the role provider framework.
HTH.
Looking at your code it appears that you already have your configuration using a custom role provider.
If you want to be able to authentiacate users mking calls through your web service you should implement a custom header that authenticates each request against your configured role provider.
Things work slightly differently in WCF, it's not like you have access to session and application states since each call is considered to be a stateless one, a custom header however will offset that by handling this stuff as the call is made.

Categories