views:

296

answers:

3

I have an asp.net web app that uses forms-based authentication, a SqlMembershipProvider (using an encrypted password format), and a SqlRoleProvider. I need to know if it's possible to administer the users (create new users, assign them to roles, etc.) from a windows application - the powers that be don't want any administrative functionality in the web app itself.

Here is the membership provider definition from web.config:

<membership defaultProvider="MyProvider">
  <providers>
    <add name="MyProvider"
         type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
         connectionStringName="MyConnectionString"
         enablePasswordRetrieval="false"
         enablePasswordReset="true"
         requiresQuestionAndAnswer="true"
         applicationName="/MyWebApp"
         requiresUniqueEmail="true"
         passwordFormat="Encrypted"
         maxInvalidPasswordAttempts="5"
         minRequiredPasswordLength="7"
         minRequiredNonalphanumericCharacters="1"
         passwordAttemptWindow="10"
         passwordStrengthRegularExpression=""/>
  </providers>
</membership>

And the role manager definition:

<roleManager enabled="true" defaultProvider="MyRoleManager">
  <providers>
    <add name="MyRoleManager"
         type="System.Web.Security.SqlRoleProvider"
         connectionStringName="MyConnectionString"
         applicationName="/MyWebApp" />
  </providers>
</roleManager>

And here is the machineKey definition (necessary to be able to use encrypted passwords):

<machineKey
    validationKey="BC50A82A6AF6A015C34C7946D29B817C00F04D2AB10BC2128D1E2433D0E365E426E57337CECAE9A0681A2C736B9779B42F75D60F09F142C60E9E0E8F9840DB46"
      decryptionKey="122035576C5476DCD8F3611954C837CDA5FE33BCDBBF23F7"
      validation="SHA1"
      decryption="AES"/>

So, obviously, I have a Sql Server database that contains the users and roles for the web app. I'd like to create a separate windows app that references the web app assembly, and use the configured MembershipProvider, RoleProvider, and machineKey to create users, assign users to roles, etc. If that's not possible, I can duplicate the configuration settings from web.config within the windows app. But I don't know how to do this either.

Am I way out of line thinking that this is possible? I've tried googling for a solution, but the signal-to-noise ratio is really bad.

+1  A: 

I'm not sure what "best-practice" would be here, but a simple way that should work is this.

  • Make a new windows app
  • Add an Application Config file (app.config)
  • Copy the appropriate settings into the app.config (settings from above ^)
  • Add a reference to System.Web
  • And copy the code from your web app that uses the above settings to connect to the database

That should do what you want.

TJMonk15
I can instantiate a SqlMembershipProvider and a SqlRoleProvider and set *almost* all necessary properties, but how do I give them a connection string? And how would I provide the SqlMembershipProvider with validation and decryption keys?
fre0n
According to http://msdn.microsoft.com/en-us/library/system.web.security.sqlmembershipprovider_members.aspx, there is no property or method to set the connection string of a SqlMembershipProvider.
fre0n
You should be able to do all of that in the same way as you are doing in your web app. I assume you are using similar code in your web app for users to login?
TJMonk15
In a web app the initialization is all done automagically. You never have to manually call the `Initialize` method of the `SqlMembershipProvider`. Plus, I was using a Login Control, so I never even touched the `SqlMembershipProvider`.
fre0n
+2  A: 

Some options:

  • You could use the Web Site Administration Tool, which isn't Windows-Forms-based, but isn't part of your Web app, either. It comes with Visual Studio and can be accessed by clicking the ASP.NET Configuration icon in the Solution Explorer.
  • It's possible to directly manipulate the provider database used by a SqlMembershipProvider from a Windows Forms app, but you might have to be careful not to break things.
  • If you were to create a custom membership provider, you'd be in control of how membership and role data is persisted. If you did that you could create a reusable library that could be used in the Web app and a Windows Forms app, too.

I don't think trying to use a SqlMembershipProvider from a Windows Forms app is a practical approach.

Andy West
It works from VS, but can you use the Web Site Administration Tool in a production environment?
fre0n
@fre0n: Technically yes, but it's not simple. Description here: http://forums.asp.net/p/932674/1155428.aspx I would look into the other options I mentioned for that.
Andy West
This is a good suggestion... your best bet to do this easily. Otherwise, you're better off creating your own Membership and Role provider.
Bryan
+1  A: 

I've come up with a solution, based on the other answers (who both got +1), and some other sites out there.

First, I created Application Config file (app.config). It mirrors exactly what is found in web.config from the web app, with the exception of how the connection string was handled:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <configSections>
    <section name="connectionStrings" type="System.Configuration.ConnectionStringsSection, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" requirePermission="false" />
  </configSections>

  <connectionStrings>
    <add name="MyConnectionString"
         connectionString ="SERVER=abc;UID=def;PWD=hij;Initial Catalog=klm;MultipleActiveResultsets=True"/>
  </connectionStrings>

  <system.web>

    <membership defaultProvider="MySqlMembershipProvider">
      <providers>
        <add name="MySqlMembershipProvider"
             type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
             connectionStringName="MyConnectionString"
             enablePasswordRetrieval="false"
             enablePasswordReset="true"
             requiresQuestionAndAnswer="true"
             applicationName="/MyWebApp"
             requiresUniqueEmail="true"
             passwordFormat="Encrypted"
             maxInvalidPasswordAttempts="5"
             minRequiredPasswordLength="7"
             minRequiredNonalphanumericCharacters="1"
             passwordAttemptWindow="10"
             passwordStrengthRegularExpression=""/>
      </providers>
    </membership>

    <roleManager enabled="true" defaultProvider="MySqlRoleManager">
      <providers>
        <add name="MySqlRoleManager"
             type="System.Web.Security.SqlRoleProvider"
             connectionStringName="MyConnectionString"
             applicationName="/MyWebApp" />
      </providers>
    </roleManager>

    <machineKey
      validationKey="BC50A82A6AF6A015C34C7946D29B817C00F04D2AB10BC2128D1E2433D0E365E426E57337CECAE9A0681A2C736B9779B42F75D60F09F142C60E9E0E8F9840DB46"
      decryptionKey="122035576C5476DCD8F3611954C837CDA5FE33BCDBBF23F7"
      validation="SHA1"
      decryption="AES"/>
  </system.web>

</configuration>

Then I created a helper class that provides access to two singletons: a MembershipProvider and a RoleProvider. This turned out to be easier than I thought, once I knew how to do it:

using System.Configuration;
using System.Reflection;
using System.Web.Security;

namespace WebAdminViaWindows
{
    internal static class Provider
    {
        private static readonly string assemblyFilePath = Assembly.GetExecutingAssembly().Location;

        static Provider()
        {
            Membership = CreateMembershipProvider();
            Role = CreateRoleProvider();
        }

        public static MembershipProvider Membership { get; private set; }
        public static RoleProvider Role { get; private set; }

        private static MembershipProvider CreateMembershipProvider()
        {
            var config = ConfigurationManager.OpenExeConfiguration(assemblyFilePath);

            var systemWebGroup = config.SectionGroups["system.web"];
            if (systemWebGroup == null)
            {
                throw new ConfigurationErrorsException("system.web group not found in configuration");
            }

            var membershipSection = systemWebGroup.Sections["membership"];
            if (membershipSection == null)
            {
                throw new ConfigurationErrorsException("membership section not found in system.web group");
            }

            var defaultProviderProperty = membershipSection.ElementInformation.Properties["defaultProvider"];
            if (defaultProviderProperty == null)
            {
                throw new ConfigurationErrorsException("defaultProvider property not found in membership section");
            }

            var defaultProviderName = defaultProviderProperty.Value as string;
            if (defaultProviderName == null)
            {
                throw new ConfigurationErrorsException("defaultProvider property is not a string value");
            }

            var providersProperty = membershipSection.ElementInformation.Properties["providers"];
            if (providersProperty == null)
            {
                throw new ConfigurationErrorsException("providers property not found in membership section");
            }

            var providerCollection = providersProperty.Value as ProviderSettingsCollection;
            if (providerCollection == null)
            {
                throw new ConfigurationErrorsException("providers property is not an instance of ProviderSettingsCollection");
            }

            ProviderSettings membershipProviderSettings = null;

            foreach (ProviderSettings providerSetting in providerCollection)
            {
                if (providerSetting.Name == defaultProviderName)
                {
                    membershipProviderSettings = providerSetting;
                }
            }

            if (membershipProviderSettings == null)
            {
                if (providerCollection.Count > 0)
                {
                    membershipProviderSettings = providerCollection[0];
                }
                else
                {
                    throw new ConfigurationErrorsException("No providers found in configuration");
                }
            }

            var provider = new SqlMembershipProvider();
            provider.Initialize("MySqlMembershipProvider", membershipProviderSettings.Parameters);
            return provider;
        }

        private static RoleProvider CreateRoleProvider()
        {
            var config = ConfigurationManager.OpenExeConfiguration(assemblyFilePath);

            var systemWebGroup = config.SectionGroups["system.web"];
            if (systemWebGroup == null)
            {
                throw new ConfigurationErrorsException("system.web group not found in configuration");
            }

            var roleManagerSection = systemWebGroup.Sections["roleManager"];
            if (roleManagerSection == null)
            {
                throw new ConfigurationErrorsException("roleManager section not found in system.web group");
            }

            var defaultProviderProperty = roleManagerSection.ElementInformation.Properties["defaultProvider"];
            if (defaultProviderProperty == null)
            {
                throw new ConfigurationErrorsException("defaultProvider property not found in roleManager section");
            }

            var defaultProviderName = defaultProviderProperty.Value as string;
            if (defaultProviderName == null)
            {
                throw new ConfigurationErrorsException("defaultProvider property is not a string value");
            }

            var providersProperty = roleManagerSection.ElementInformation.Properties["providers"];
            if (providersProperty == null)
            {
                throw new ConfigurationErrorsException("providers property not found in roleManagerSection section");
            }

            var providerCollection = providersProperty.Value as ProviderSettingsCollection;
            if (providerCollection == null)
            {
                throw new ConfigurationErrorsException("providers property is not an instance of ProviderSettingsCollection");
            }

            ProviderSettings roleProviderSettings = null;

            foreach (ProviderSettings providerSetting in providerCollection)
            {
                if (providerSetting.Name == defaultProviderName)
                {
                    roleProviderSettings = providerSetting;
                }
            }

            if (roleProviderSettings == null)
            {
                if (providerCollection.Count > 0)
                {
                    roleProviderSettings = providerCollection[0];
                }
                else
                {
                    throw new ConfigurationErrorsException("No providers found in configuration");
                }
            }

            var provider = new SqlRoleProvider();
            provider.Initialize("MySqlRoleManager", roleProviderSettings.Parameters);
            return provider;
        }
    }
}

At this point all that's needed is to access the Membership and Role properties of the Provider class. As an example, the following prints out the first 10 users and their roles:

int total;
foreach (MembershipUser user in Provider.Membership.GetAllUsers(0, 10, out total))
{
    var sb = new StringBuilder();
    sb.AppendLine(user.UserName);
    foreach (var role in Provider.Role.GetRolesForUser(user.UserName))
    {
        sb.AppendLine("\t" + role);
    }

    Console.WriteLine(sb.ToString());
}
fre0n
Impressive. I didn't realize it could be that simple.
Andy West