I implemented a CustomMembershipProvider which is deriving from ExtendedMembershipProvider. IUserService is a dependency of CustomMembershipProvider which will be used to validate the given credentials.
This will be configured in Web.Config;
<membership defaultProvider="DefaultMembershipProvider">
<providers>
<add name="DefaultMembershipProvider" type="BorderExpress.AutoImport.Web.Security.CustomMembershipProvider" connectionStringName="DefaultConnection" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>
Injecting IUserService implementation via constructor not possible as CustomMembershipProvider require a parameterless constructor.
So thought of introducing Property injection. I made IUserService as a public property of CustomMembershipProvider.
public class CustomMembershipProvider : ExtendedMembershipProvider
{
public IUserService UserService { get; set; }
public CustomMembershipProvider()
{
}
...
public override bool ValidateUser(string username, string password)
{
var user = UserService.GetUser(username);
if (user != null && SaltedHash.Verify(user.Salt, user.Hash, password))
{
return true;
}
return false;
}
}
I wrote a separate installer only for this registration
public class WindsorMembershipInstaller:IWindsorInstaller
{
public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
{
container.Register(
Component.For<CustomMembershipProvider>()
);
}
}
That didn't work and UserService always NULL at the time of calling the ValidateUser.
And I tried contributors;
public class RequireUserServiceProperties : IContributeComponentModelConstruction
{
public void ProcessModel(Castle.MicroKernel.IKernel kernel, Castle.Core.ComponentModel model)
{
model.Properties
.Where(p => p.Dependency.TargetItemType == typeof(IUserService))
.All(p => p.Dependency.IsOptional = false);
}
}
And register the contributor where I bootstrap the container.
_container = new WindsorContainer()
.Install(FromAssembly.This());
_container.Kernel.ComponentModelBuilder.AddContributor(new RequireUserServiceProperties());
Please do let me know how to inject the IUserService property of CustomMembershipProvider
I found an answer within the SO.
#Mauricio Scheffer has done custom implementation to utilize windsor to inject dependencies to membership provider.
Original question
How do I control MembershipProvider instance creation/lifetime?
#Mauricio Scheffer 's blog post
http://bugsquash.blogspot.com/2010/11/windsor-managed-membershipproviders.html
Related
Within a signalR hub method, I'm trying to check if the user authenticated is within a specific role. In plane MVC, I can call user.IsInRole and the class overriding RoleProvider's GetRolesForUser method is called.
The CustomRoleProvider is set up in the web.config with:
<roleManager cacheRolesInCookie="false" defaultProvider="CustomRoleProvider" enabled="true">
<providers>
<clear />
<add name="CustomRoleProvider" type="CustomRoleProvider" />
</providers>
</roleManager>
When user.IsInRole is called CustomRoleProvider.GetRolesForUser(string username) is called.
Hub code
public IEnumerable<Items> GetList()
{
IList<Items> result;
var user = Context.User;
if (user.IsInRole("Test_Role"))
result = itemProvider.GetItems();
else
result = new[] { };
return result;
}
CustomRoleProvider
public class CustomRoleProvider:RoleProvider
{
public override string[] GetRolesForUser(string username)
{
return new[]{"Test_Role"};
}
}
Why is this not working in signal R? user.IsInRole does not call GetRolesForUser and always returns False.
Im implementing the ASP.NET Profile Provider alongside the Membership Provider using Jon Galloway's (slightly dated) example here:
and Im getting the error Provider must implement the class 'System.Web.Profile.ProfileProvider'
As per the example, I've created a custom UserProfile inheriting from ProfileBase
public class UserProfile : ProfileBase
{
public static UserProfile GetUserProfile(string username)
{
if (username != null)
{
var profile = Create(username) as UserProfile;
return profile;
}
}
public static UserProfile GetUserProfile()
{
var membershipUser = MembershipProvider.GetUser();
if (membershipUser != null)
return Create(membershipUser.UserName) as UserProfile;
}
[SettingsAllowAnonymousAttribute(true)]
public virtual string Name
{
get { return base["Name"] as string; }
set { base["Name"] = value; }
}
}
I'm calling above method from a Class library like this:
var profile = UserProfile.GetUserProfile(name);
In turn, Im calling that method from a Unit test project, which has an app.config:
<profile enabled="true" defaultProvider="MyProfileProvider" inherits="NAMESPACE.UserProfile, NAMESPACE">
<providers>
<clear/>
<add
name="MyProfileProvider"
connectionStringName="MembershipConnection"
applicationName="/"
type="NAMESPACE.UserProfile, NAMESPACE"
/>
</providers>
<properties>
<add name="Name" type="String"/>
</properties>
</profile>
Inheriting from ProfileBase (like Jon's example) should be enough right? I think i've used all variations on the 'type' and 'inherit' attributes, am I missing something here?
I've managed to at least save Profile values using a variation on this example
dynamic profile = ProfileBase.Create(UserName);
profile.Name = name;
profile.Save();
Not sure how to retrieve that same property yet tho, since ProfileBase has no such method...
Is there a way to call the custom MembershipProvider I have implemented in code without having to cast the Membership class to my custom provider every time? For example, I setup my web.config like so:
<connectionStrings>
<add name="TestDB"
connectionString="conn_str_here"
providerName="System.Data.SqlClient" />
</connectionStrings>
<membership defaultProvider="CustomSqlMembershipProvider">
<providers>
<clear/>
<add name="CustomSqlMembershipProvider" type="Common.CustomSqlMembershipProvider" connectionStringName="TestDB"
enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false"
maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
applicationName="/" />
</providers>
</membership>
And my custom provider class:
namespace Common
{
public class CustomSqlMembershipProvider : SqlMembershipProvider
{
public void ChangeUsername()
{
// ...
}
}
}
Now to call my new custom function, is there some way to allow Membership.ChangeUsername() versus having to do this:
CustomSqlMembershipProvider customMembership = Membership.Provider as CustomSqlMembershipProvider;
customMembership.ChangeUsername();
Doing the cast everywhere you want to use it starts to get annoying after a while. Thanks in advance.
You could add an extension method:
namespace System.Web.Membership
{
public static class MembershipProviderExtensions
{
public static void ChangeUsername(this MembershipProvider provider, string oldUsername, string newUsername)
{
var customProvider = provider as CustomSqlMembershipProvider;
if (customProvider == null) throw new Exception("Invalid provider type.");
customProvider.ChangeUsername(oldUsername, newUsername);
}
}
}
Usage:
Membership.Provider.ChangeUsername("bob", "jane");
I've a asp.net MVC app deployed to server it uses forms authentication and uses CustomSqlMembership provider basically I've not changed anything in the SqlMembershipPRovider just copied the source from MS and included the source in my project and renamed it from time to time this error comes up.
This happens on localhost and remotely deployed system and I can't figure out what could be the cause of it.
Server Error in '/' Application.
Configuration Error
Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.
Parser Error Message: Object reference not set to an instance of an object.
Source Error:
Line 50: <clear/>
Line 51: <!--<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="dq_systemConnectionString" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" passwordStrengthRegularExpression="" applicationName="/"/>-->
Line 52: `<add name="CustomSqlMembershipProvider" type="AcmeCorp.CustomSqlMembershipProvider, AcmeCorp, Version=1.0.0.0, Culture=neutral" connectionStringName="AcmeCorpConnectionString" enablePasswordRetrieval="False" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="True" passwordFormat="Hashed" maxInvalidPasswordAttempts="6" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" passwordStrengthRegularExpression="" applicationName="/" />`
Line 53: </providers>
Line 54: </membership>
this is the complete listing of membership object in web.config
<membership defaultProvider="CustomSqlMembershipProvider">
<providers>
<clear />
<!--<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="dq_systemConnectionString" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" passwordStrengthRegularExpression="" applicationName="/" />-->
<add name="CustomSqlMembershipProvider" type="AcmeCorp.CustomSqlMembershipProvider, AcmeCorp, Version=1.0.0.0, Culture=neutral" connectionStringName="dq_systemConnectionString" enablePasswordRetrieval="False" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="True" passwordFormat="Hashed" maxInvalidPasswordAttempts="6" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" passwordStrengthRegularExpression="" applicationName="/" />
public class CustomSqlMembershipProvider : AcmeCorp.SqlMembershipProvider
{
static public ConnectionStringSettings css {get; set;}
public override void Initialize(string name, NameValueCollection config)
{
config.Add("connectionString", css.ConnectionString);
base.Initialize(name, config);
}
}
public class CustomSqlRoleProvider : AcmeCorp.SqlRoleProvider
{
static public ConnectionStringSettings css { get; set; }
public override string GetConnectionString()
{
return css.ConnectionString;
}
public override void Initialize(string name, NameValueCollection config)
{
//config.Add("connectionString", css.ConnectionString);
base.Initialize(name, config);
}
}
public interface ISiteProvider
{
bool Initialise(string host);
Site GetCurrentSite();
}
public class SiteProvider : ISiteProvider
{
SystemMetaDataContext mDB;
Site mSite;
public SiteProvider(SystemMetaDataContext db)
{
mDB = db;
}
public bool Initialise(string host)
{
mSite = mDB.Sites.SingleOrDefault(s => s.Host == host);
if (null != mSite)
{
CustomSqlMembershipProvider.css = new ConnectionStringSettings();
CustomSqlMembershipProvider.css.ConnectionString = mSite.Connection;
CustomSqlMembershipProvider.css.ProviderName = "System.Data.SqlClient";
CustomSqlMembershipProvider.css.Name = "dq_systemConnectionString";
CustomSqlMembershipProvider.css.ConnectionString = mSite.Connection;
CustomSqlRoleProvider.css = new ConnectionStringSettings();
CustomSqlRoleProvider.css.ConnectionString = mSite.Connection;
CustomSqlRoleProvider.css.ProviderName = "System.Data.SqlClient";
CustomSqlRoleProvider.css.Name = "dq_systemConnectionString";
CustomSqlRoleProvider.css.ConnectionString = mSite.Connection;
return true;
}
else
{
return false;
}
}
public Site GetCurrentSite()
{
return mSite;
}
}
public class BaseController : Controller
{
ISiteProvider mSiteProvider;
protected IRepository mRepository { get; private set; }
protected int DefaultPageSize { get; set; }
public BaseController()
{
DefaultPageSize = 10;
mSiteProvider = new SiteProvider(new SystemMetaDataContext());
}
public BaseController(IDQRepository repository)
{
mRepository = repository;
DefaultPageSize = 10;
if (Session["ActiveView"] == null)
{
IList<RoleViewModel> roles = mRepository.GetAllRoles();
foreach (RoleViewModel rvm in roles)
{
if (Roles.IsUserInRole(rvm.Name))
{
Session["ActiveView"] = rvm.Name;
break;
}
}
}
}
protected override void Initialize(RequestContext requestContext)
{
string[] host = requestContext.HttpContext.Request.Headers["Host"].Split(':');
MetaInfo.PopulateMeta(host[0]);
if (!mSiteProvider.Initialise(host[0]))
RedirectToRoute("Default");
if (null == mRepository)
mRepository = new DQRepository();
base.Initialize(requestContext);
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
ViewData["Site"] = Site;
base.OnActionExecuting(filterContext);
}
public Site Site {
get {
return mSiteProvider.GetCurrentSite();
}
}
Where is initialized this static variable ?
static public ConnectionStringSettings css {get; set;}
Static variables are not thread safe.
You must initialize them in a thread safe way, especially if running in web farm mode.
If the server uses an application pool with more than 1 processor set in its config, it is running in web farm mode. In web farm mode, you will have 2 web applications running, but your static variable will be created only once as this memory space is shared (in fact there is more than that but you can write books on this subject).
You can disable web farm mode by setting the processor count to 1 in the application pool.
Where is the code initializing this css variable ?
Could you write it here ?
From where is it called ?
Check your custom code. Most likely, you are attempting to access a property without checking nullability of the object. This is probably due to asking for user name, or something, when membership has not found the person in question. Adding a null ref check to the code will at least rid the error so you can raise an appropriate exception (and then give the user a friendly error message).
Your configuration for this provider does have many redundant fields. What happens if you change <add name="CustomSqlMembershipProvider" type="AcmeCorp.CustomSqlMembershipProvider, AcmeCorp, Version=1.0.0.0, Culture=neutral" ... />
to <add name="CustomSqlMembershipProvider" type="AcmeCorp.CustomSqlMembershipProvider"/> ?
I want to have custom profile provider in my asp.net mvc 3 app. The problem is, that I don't want to use default DB that is generated by ASP.NET Membership/Role/Profile provider, mainly because authentication is already done with WebService and DBs already exist.
I want to user profile properties to populate them and use within different areas of the site.
I took a look at this example (How to assign Profile values?) but I am getting this error:
An attempt to attach an auto-named database for file
C:\Projects\FWManager\App_Data\aspnetdb.mdf failed. A database with
the same name exists, or specified file cannot be opened, or it is
located on UNC share.
Here is the web.config
<profile inherits="FWMembership.Membership.FWProfileProvider" defaultProvider="AspNetSqlProfileProvider" automaticSaveEnabled="false" enabled="true">
<providers>
<clear/>
<add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/" />
</providers>
</profile>
This is my custom class
public class FWProfileProvider : ProfileBase
{
[SettingsAllowAnonymous(false)]
public string FirstName
{
get { return base["FirstName"] as string; }
set { base["FirstName"] = value; }
}
[SettingsAllowAnonymous(false)]
public string LastName
{
get { return base["LastName"] as string; }
set { base["LastName"] = value; }
}
[SettingsAllowAnonymous(false)]
public int? UserID
{
get { return base["UserID"] as int?; }
set { base["UserID"] = value; }
}
[SettingsAllowAnonymous(false)]
public string UserCompany
{
get { return base["UserCompany"] as string; }
set { base["UserCompany"] = value; }
}
[SettingsAllowAnonymous(false)]
public string Email
{
get { return base["Email"] as string; }
set { base["Email"] = value; }
}
public StringCollection Entitlements
{
get { return base["Entitlements"] as StringCollection; }
set { base["Entitlements"] = value; }
}
public string username;
public FWProfileProvider()
{
}
public FWProfileProvider(string username)
{
this.username = username;
}
static public FWProfileProvider CurrentUser
{
get
{
return (FWProfileProvider)
(ProfileBase.Create("Joe"));
}
}
}
The key is to avoid using asp.net default membership tables.
Any ideas?
EDIT:
Forgot to add - this web application, but profile provider is placed in the class library project within same soulution:
Solution
|->FWProfile (class library project)
|->UI (asp.net mvc 3 web application)
I think you have to write your own MemberShip Provider as well. Your web.config refers to the default asp.net membership provider. How to write a Membership provider you can find here custom membership provider
The default membership provider uses a connection string into a locally installed SQL Express database and that causes your error.
Your web.config would look like this:
<membership defaultProvider="MyCustomMembershipProvider">
<providers>
<clear />
<add name="MyCustomMembershipProvider"
type="FWMembership.Membership.MyCustomMembershipProvider"
enablePasswordRetrieval="true"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
requiresUniqueEmail="true"
passwordFormat="Clear"/>
</providers>
</membership>
<profile defaultProvider="MyProfileProvider" enabled="true">
<providers>
<clear/>
<add name="MyProfileProvider" type="FWMembership.Membership.FWProfileProvider" connectionStringName="ApplicationServices" applicationName="/" />
</providers>
</profile>
Hope this helps.