ASP.net: MySQL Membership Provider – Clear Passwords

When you install the MySQL Connector it adds several configuration entries to your Machine.config. By default the newly installed Membership provider has been set up for a development environment.

The main reason I have come to this conclusion is that the MySQLMembershipProvider configuration entry has the passwordFormat attribute set to ‘Clear’ and the applicationName attribute set to ‘/’.

Just like in the ASP.net Membership provider the MySQL provider allows multiple applications to use the Membership database by keying off the applicationName attribute configured in the Provider. Being that the provider entry is added to the Machine.config file the default applicationName attribute of ‘/’ will be used for all applications using the provider. This is fine if you want the same users to have access across all of the applications using the Membership database. But if your plan is to use a single membership database across multiple applications but would like to keep the users segregated this setup will not work.

I also mentioned that the passwordFormat is set to ‘Clear’. This means that all passwords will be saved in the database in clear text. This of course is only to be used during development to you can pull test user’s passwords when you forget them. In a production environment that passwordFormat should be set to ‘Hashed’. This of course forces the password and the password recovery question’s answer to be hashed using a cryptographic algorithm, thus protecting the user’s password from anyone including rogue application and database administrators or hackers that manage to infiltrate the database.

To override the MySQL Membership Provider’s default settings you need to add the following to each of your application’s web.config files.

    <membership defaultProvider="MySQLMembershipProvider">

      <providers>

        <remove name="MySQLMembershipProvider"/>

        <add name="MySQLMembershipProvider" autogenerateschema="true" type="MySql.Web.Security.MySQLMembershipProvider, MySql.Web, Version=6.1.2.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" connectionStringName="LocalMySqlServer" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" applicationName="GndCoreMembership" requiresUniqueEmail="True" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1" passwordAttemptWindow="10" passwordStrengthRegularExpression="" />

      </providers>

    </membership>

    <profile>

      <providers>

        <remove name="MySQLProfileProvider"/>

        <add name="MySQLProfileProvider" type="MySql.Web.Profile.MySQLProfileProvider, MySql.Web, Version=6.1.2.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" connectionStringName="LocalMySqlServer" applicationName="/" />

      </providers>

    </profile>

    <roleManager enabled="true" defaultProvider="MySQLRoleProvider" >

      <providers>

        <remove name="MySQLRoleProvider"/>

        <add name="MySQLRoleProvider" type="MySql.Web.Security.MySQLRoleProvider, MySql.Web, Version=6.1.2.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" connectionStringName="LocalMySqlServer" applicationName="/" />

      </providers>

    </roleManager>

The ‘remove’ element is necessary before you add the provider because it already exists in your machine.config file. If you don’t the first attempt to access a protected resource will fail with a configuration error stating the provider already exists.

ASP.net: MySQL Membership Provider

If you are like me you can’t afford a production version of Microsoft SQL Server and even more crazy you can’t afford a license for each processor you have running in your production server. There is no way you are going to use SQL Server Express because you need to allow more than one application to hit the membership database and you can’t stand the file locking that occurs on all express database files.

That being said the best alternative and one I really really like is good old MySQL Community Server, it is amazing! An what is even more cool is that MySQL comes with a set of pre-built .Net connectors which you can download from their site and install. When you install the .Net connector it adds config entries to your Machine.config for all of the .Net Membership, Role and Profile providers they created for MySQL. Once this is done your system is ready to access all .Net membership using the MySQL DBMS. Simply amazing.

To set up your ASP.net website to use the MySQL membership providers you have a couple of config entries to add to your Web.config file. they are as follows:

1. Set up your Connection String

  <connectionStrings>

    <remove name="LocalMySqlServer"/>

    <add name="LocalMySqlServer"

         connectionString="Datasource=YOURSERVER;Database=YOURDATABASE;uid=YOURUSERNAME;pwd=YOURPASSWORD;"

         providerName="MySql.Data.MySqlClient"/>

  </connectionStrings>

2. Set your default Membership Provider and Role Provider

<membership defaultProvider="MySQLMembershipProvider" />

    <roleManager enabled="true" defaultProvider="MySQLRoleProvider" />

3. Optional but extremely helpful on your development machines is to set up the MySQL membership provider to install the membership schema in the database automatically if it does not exist. Saves you from manually running scripts. After installation of the .Net Connector go to your Machine.config and locate the system.web section, within it you should see entries for Membership, Profile and RoleManager. In addition to the default ASP.net providers that come with .Net you will see a new entries in each of those config sections for MySQL. Locate the MySQLMembershipProvider and add a new attribute autogenerateschema=”true”. It should look something like this:

    <membership>

      <providers>

        <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="LocalSqlServer" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" applicationName="/" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1" passwordAttemptWindow="10" passwordStrengthRegularExpression="" />

        <add name="MySQLMembershipProvider" autogenerateschema="true" type="MySql.Web.Security.MySQLMembershipProvider, MySql.Web, Version=6.1.2.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" connectionStringName="LocalMySqlServer" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" applicationName="/" requiresUniqueEmail="false" passwordFormat="Clear" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1" passwordAttemptWindow="10" passwordStrengthRegularExpression="" />

      </providers>

    </membership>

    <profile>

      <providers>

        <add name="AspNetSqlProfileProvider" connectionStringName="LocalSqlServer" applicationName="/" type="System.Web.Profile.SqlProfileProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

        <add name="MySQLProfileProvider" type="MySql.Web.Profile.MySQLProfileProvider, MySql.Web, Version=6.1.2.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" connectionStringName="LocalMySqlServer" applicationName="/" />

      </providers>

    </profile>

    <roleManager>

      <providers>

        <add name="AspNetSqlRoleProvider" connectionStringName="LocalSqlServer" applicationName="/" type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

        <add name="AspNetWindowsTokenRoleProvider" applicationName="/" type="System.Web.Security.WindowsTokenRoleProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

        <add name="MySQLRoleProvider" type="MySql.Web.Security.MySQLRoleProvider, MySql.Web, Version=6.1.2.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" connectionStringName="LocalMySqlServer" applicationName="/" />

      </providers>

Now you should be able to open the website administration tool from within Visual Studio and walk through setting up Users and Roles as well as Access Rules just like you would if you used the built in ASP.net membership providers.

Good Luck!

ASP.net: Programmatically Submit Form Post

On occasion it is necessary to post data via an HTTP POST to another page as though that post was submitted by a user but without the user’s interaction. In order to accomplish this use the following code:

        private void PostForm()

        {

            WebRequest request = WebRequest.Create(_targetUrl);

            request.Method = "POST";

            request.ContentType = "application/x-www-form-urlencoded";

            string postContent = string.Format("parameter1={0}&parameter2={1}", _parameter1, _parameter2);

            byte[] postContentBytes = Encoding.ASCII.GetBytes(postContent);

            request.ContentLength = postContentBytes.Length;

            Stream writer = request.GetRequestStream();

            writer.Write(postContentBytes, 0, postContentBytes.Length);

            writer.Close();

            HttpWebResponse testResponse = (HttpWebResponse)request.GetResponse();

            if (!testResponse.StatusDescription.Equals("OK", StringComparison.InvariantCultureIgnoreCase))

            {

                //TODO: Handle failure

            }

        }

ASP.net: CSS, Images and IIS Virtual Directory Application Deployment

Have you ever tried to deploy an application to a virtual directory in IIS? For example lets say you have your primary website running at http://www.graphicnetdesign.com and you decide it would be nice to have a sub-domain where you can host all of your utility and experimental applications so you create http://dev.graphicnetdesign.com. It is under this sub-domain where you have created virtual directories in IIS to host each of those applications. So you set up a virtual directory for a little application you are playing around with which checks the heart beat of all your websites and web applications and you name it ippoke and your new application’s URL looks like http://dev.graphicnetdesign.com/ippoke.

All excited you quickly FTP your new application to that virtual directory for a little smoke testing. But, sadly, the application which was developing so nicely on your development machine looks like crap. It appears as though your CSS is not loading and if it is it looks like all of the Images embedded in that CSS file are not loading. The Horror! You quickly double check all of your references to the CSS file, you check all of the image URLs within the CSS file and everything looks perfect and it all runs perfectly on your dev machine. What is going on?

After hours of frustration and Google searches you find an article which states that under IIS when using virtual directories to host ASP.net applications IIS does not use the virtual directory as the root of your newly deployed application, instead it uses the root of the main site or http://dev.graphicnetdesign.com. What does that all mean, it means that even though the path to your logo.png file in your ippoke application is ‘/images/logo.png’ and that works on your dev machine and is relative to the root of your application on that machine once deployed to the virtual directory the path required to get that image is has to now be relative to the root of the site under which you created the virtual directory in which your ippoke application is running. In other words the relative path of ‘/images/logo.png’ is no longer a correct path to that image because it is now relative to the root site and resolves to http://dev.graphicnetdesign.com/images/logo.png. In order to make your CSS and images work correctly in the virtual directory you have to change all of your references to resolve from the root so the path needs to be ‘/ippoke/images/logo.png’ instead of ‘/images/logo.png’.

What a pain! So now you have to come up with some way to support the differences between your local dev environment where the site is being developed as a root website in visual studio and what it will eventually be deployed as. In Visual Studio you can actually change the virtual path in the properties of the web application project and there are several other ways you can try to resolve the issue by using separate CSS files for each environment, or by editing the CSS file just before deployment but all of these issues just make more work and make maintenance of the site horrifying. They also do not make the application portable without a lot of editing, for example if I decided to move the application to be its own root site or if I want to change it to a different virtual directory or deploy the site to a completely different server.

The only way I have found to combat the virtual directory CSS and Image bug when it comes to ASP.net applications was to simply create my own custom ASP.net Server Control which is smart enough to know when the application has been deployed as a root site or as a virtual site and armed with that knowledge it creates a copy of the CSS file and updates all image references with the correct virtual directory path so the CSS and Images can be resolved by IIS. This control checks to see if a virtual CSS file exists already and if not it will create one and write it to disk on the first call to the application. If the file already exists it will check to see if the original CSS file has been recently updated and if so the control will create a new version of the virtual CSS in order to capture the changes. Once it has taken care of building the virtual CSS file it then writes out a new tag for the virtual CSS file to the page and that is that. No more missing images, no more missing CSS files and it just works when deployed as either a root site or within a virtual directory.

The code for this control is below, feel free to use it.

Version:0.9 StartHTML:0000000105 EndHTML:0000024318 StartFragment:0000000105 EndFragment:0000024318

using System;

using System.ComponentModel;

using System.IO;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

namespace GraphicNetDesign.ServerControls.Web.UI

{

    [DefaultProperty("Href")]

    [ToolboxItem(true)]

    [ToolboxData("")]

    public class CssVirtualLink : WebControl

    {

        #region Private Fields

        private string _virtualPath;

        private string _originalCssRelativePath;

        private string _originalCssDirectoryPath;

        private string _virtualCssRelativePath;

        private string _virtualCssDirectoryPath;

        private FileInfo _originalCssFile;

        private FileInfo _virtualCssFile;

        #endregion

        #region Public Properties

        [Bindable(true)]

        [Category("Appearance")]

        [DefaultValue("")]

        [Localizable(true)]

        public string Href

        {

            get

            {

                String s = (String)ViewState["href"];

                return (s ?? "[" + ID + "]");

            }

            set

            {

                ViewState["href"] = value;

            }

        }

        #endregion

        #region Protected Methods

        /// 

        /// Renders the contents.

        /// 

        /// The output.

        protected override void RenderContents(HtmlTextWriter output)

        {

            output.WriteBeginTag("link");

            output.WriteAttribute("rel", "stylesheet");

            //Update the CSS file image paths to match virtual directory

            UpdateFile();

            output.WriteAttribute("href", Href);

            output.WriteEndTag("link");

        }

        #endregion

        #region Private Methods

        protected void UpdateFile()

        {

            //Get application's virtual path

            GetApplicationVirtualPath();

            //Get Original CSS file and its actual path

            GetOriginalCssPaths();

            //Build VCSS file path

            GetVirtualCssPaths();

            //Check to see if the CSS file exists

            if (!_originalCssFile.Exists)

                return;

            //Check to see if the Virtual CSS file exists

            if (!_virtualCssFile.Exists || CompareModifiedDates())

                CreateVirtualCssFile();

            //Update Href to the Virtual CSS file

            Href = _virtualCssRelativePath;

        }

        private void GetApplicationVirtualPath()

         {

             _virtualPath = HttpRuntime.AppDomainAppVirtualPath;

             if (!_virtualPath.EndsWith("/"))

                 _virtualPath += "/";

             Page.Trace.Warn("VCSS", "Application Virtual Path: " + _virtualPath);

         }

        private void GetOriginalCssPaths()

        {

            _originalCssRelativePath = (_virtualPath + Href).Replace("//", "/");

            _originalCssDirectoryPath = Page.Server.MapPath(_originalCssRelativePath);

            _originalCssFile = new FileInfo(_originalCssDirectoryPath);

            Page.Trace.Warn("VCSS", "Original CSS Relative Path: " + _originalCssRelativePath);

            Page.Trace.Warn("VCSS", "Original CSS Directory Path: " + _originalCssDirectoryPath);

            Page.Trace.Warn("VCSS", "Original CSS File Exists: " + _originalCssFile.Exists);

        }

        private void GetVirtualCssPaths()

        {

            _virtualCssRelativePath = (_virtualPath + Href.Replace(".css", "_virtual.css")).Replace("//", "/");

            _virtualCssDirectoryPath = Page.Server.MapPath(_virtualCssRelativePath);

            _virtualCssFile = new FileInfo(_virtualCssDirectoryPath);

            Page.Trace.Warn("VCSS", "Virtual CSS Relative Path: " + _virtualCssRelativePath);

            Page.Trace.Warn("VCSS", "Virtual CSS Directory Path: " + _virtualCssDirectoryPath);

            Page.Trace.Warn("VCSS", "Virtual CSS File Exists: " + _virtualCssFile.Exists);

        }

        private bool CompareModifiedDates()

        {

            if (_originalCssFile.LastWriteTime > _virtualCssFile.LastWriteTime)

                return true;

            return false;

        }

        private void CreateVirtualCssFile()

        {

            try

            {

                if (_virtualCssFile.Exists)

                    _virtualCssFile.Delete();

                Page.Trace.Warn("VCSS", "Loading original CSS file for copy.");

                string contents;

                using (FileStream fs = new FileStream(_originalCssDirectoryPath, FileMode.Open, FileAccess.Read))

                {

                    using (StreamReader sr = new StreamReader(fs))

                    {

                        contents = sr.ReadToEnd();

                        sr.Close();

                    }

                    fs.Close();

                }

                Page.Trace.Warn("VCSS", "Original CSS file successfully loaded.");

                Page.Trace.Warn("VCSS", "Creating Virtual CSS file from Original CSS file.");

                using (StreamWriter sw = new StreamWriter(_virtualCssDirectoryPath, false))

                {

                    //Update all URL elements in the CSS file 

                    //to reflect the correct virtual path

                    sw.Write(contents.Replace("url(\"/", "url(\"" + _virtualPath));

                    sw.Close();

                }

                Page.Trace.Warn("VCSS", "Virtual CSS file creation complete.");

            }

            catch (Exception ex)

            {

                Page.Trace.Warn("VCSS", "Exception Thrown: " + ex.Message);

            }

        }

        #endregion

    }

}

ASP.net Membership Provider Setup

In order to set up your application to use Microsoft’s ASP.net Membership Provider you need to do the following.

1. Set up your database by installing the Membership schema. This is done by locating the aspnet_regsql.exe utility found in the C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727 directory. Run it using the -W flag (aspnet_regsql -W) and walk through the wizard that you are presented with.

2. Add a connection string to your web.config which points to the database you just installed the Memberhsip schema in.



<add name="LocalSqlServer"
connectionString="server=THESQLSERVER;uid=DATABASEUSERID;pwd=DATABASEPASSWORD;database=DATABASENAME"
providerName="System.Data.SqlClient" />

3. Then you need to add the membership provider to the web.config as follows




<add connectionStringName="LocalSqlServer" name="AspNetSqlMembershipProvider"
enablePasswordRetrieval="false" enablePasswordReset="true"
applicationName="/" requiresUniqueEmail="false" passwordFormat="Hashed"
maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6"
minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
passwordStrengthRegularExpression="" requiresQuestionAndAnswer="true"
type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

4. Thats it, your done!

Unable to evaluate expression because the code is optimized or a nativeframe is on top of the call stack

When calling Response.Redirect from within a Try-Catch block you may get the error message ‘Unable to evaluate expression because the code is optimized or a native frame is on top of the call stack’. The exact reason for this is unknown to me but to fix the issue make sure that anytime Response.Rediredt is called int a Try-Catch you set the second parameter (EndResponse) to false so that the response if not killed after the return.

try

{

HttpContext.Current.Response.Redirect(

_targetUrl, false);

}

catch (Exception ex)

{

//Handle Exception

}