Se afișează postările cu eticheta Roles. Afișați toate postările
Se afișează postările cu eticheta Roles. Afișați toate postările

luni, 10 mai 2010

Unit Testing With the ASP.NET Membership Provider – the Memory Membership Provider

Since it was first launched, along with the ASP.NET 2.0, the Provider Model proved its usefulness and reliability and has been widely used by various web applications, in different scenarios.

A good development practice is to have “test driven development”, or at least to write reliable unit tests for the “business logic” code and, since the web security (membership and roles) is an important part of the application’s “business logic” layer, the logical conclusion is that the Membership, Roles and Profile providers should be easily handled by unit tests and mocking frameworks (such as moq). But this is not possible, since most of the mocking frameworks require virtual methods or interfaces and the Membership Provider’s base (abstract) class doesn’t have any virtual methods. Creating wrappers for Membership provider is not the best way to go, since this adds more unwished code complexity.

Some business logic components may use the Membership Provider in this way:

public partial class
UserImport
{

public
MembershipProvider Members {get; set;}

public void
ImportUser(SomeUserData userData)
{
………
MembershipUser user = this.Members.CreateUser(username, password, email, question, answer, isApproved, providerUserKey, out status);
……..
}
}

Creating, running and maintaining a unit test for the ImportUser method implies using the ASPNETDB database, thus leading to poor testing performance.

Having this, I think the best option is to have a custom Memory Membership Provider, that may be used only for Unit Testing purposes, to easily manage users in memory without using the ASPNETDB database.

As already said, the primary objective of this Membership Provider is to easily handle the Users in memory, as properly handle the unit testing of other dependant “business logic” components.

public class
MemoryMembershipProvider : MembershipProvider
{
    public MemoryMembershipProvider()
    {
        this.Users = new List<MembershipUser>();
    }

The “Users” list is the memory data store.

Below are some possible implementations of common membership Provider methods:

public override
MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
    status = MembershipCreateStatus.Success;
    var theResult = default(MembershipUser);
    var searchForDuplicateUsername = from p in this.Users
                                     where p.UserName.ToLowerInvariant() == username.ToLowerInvariant()
                                     select p;
    if(searchForDuplicateUsername.Count() != 0)
    {
        status = MembershipCreateStatus.DuplicateUserName;
    }
    if(status == MembershipCreateStatus.Success)
    {
        theResult = new MembershipUser("MemoryProvider", username, Guid.NewGuid(), email, passwordQuestion, null, isApproved, false, DateTime.Now, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue);
    }
    return theResult;
}

public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
{
    totalRecords = this.Users.Count;
    var theResult = new MembershipUserCollection();
    var startRowIndex = pageSize * pageIndex;
    foreach (var user in this.Users.Skip(pageSize * pageIndex).Take(pageSize))
    {
        theResult.Add(user);
    }

    return theResult;
}

public override
MembershipUser GetUser(string username, bool userIsOnline)
{
    var query = from p in this.Users
                where p.UserName.ToLowerInvariant() == username.ToLowerInvariant()
                select p;

    returnquery.SingleOrDefault();
}

public overrideMembershipUser GetUser(object providerUserKey, bool userIsOnline)
{
    var query = from p in this.Users
                where p.ProviderUserKey == providerUserKey
                select p;

    returnquery.SingleOrDefault();
}

}

Of course, also the other methods of the Membership Provider may be custom implemented, as to properly support the unit testing and the various “business logic” scenarios.

Happy coding testing !

marți, 5 ianuarie 2010

Using LINQ to XML to create an ASP.NET XML Roles Provider

Since Authorization and Authentication are important aspects of websites, ASP.NET provides good support for these: ASP.NET 2.0 introduced the Membership and Roles provider model, and offers some really useful built-in providers: SQL Membership Provider, SQL Roles Provider, Windows Token Roles Provider, etc.

However, not every website is that complex to make use of these heavy components: take for instance the proof of concepts.
For the SQL providers we need to setup a database (option that may not be always available), to create pages for users and roles management, that also require some time to be developed. The Windows/Active Directory providers would only work for enterprise applications that usually require some intranet infrastructure.
Therefore, sometimes we need a more simple solution to store users and roles, like a XML file, but also make use of the great Membership and Roles provider model.

The benefits of a XML Roles Provider are quite simple: simple to implement it, to manage roles and user-roles, to deploy and configure. It is very simple to manage roles and user-roles: just manually edit the XML file. As already mentioned before, this is only suited for simple websites, where there is a limited set of users. The XML Roles Provider would work with Windows Authentication, but also with Forms Authentication and a custom XML Membership Provider.

To implement the XML Roles Provider, just create a new Roles Provider, by inheriting from the System.Web.Security.RoleProvider abstract class and override the abstract methods. For minimum authorization functionality, just add some actual implementation to these basic methods: public override string[] GetRolesForUser(string username) and public override bool IsUserInRole(string username, string roleName).

The XML Roles Provider uses .NET 3.5 LINQ To XML for querying data from an XML file located in the “/App_Data” folder. The XML file is not accessible via HTTP to users, since IIS restricts access to this ASP.NET folder. Since we would want the XML Roles Provider to get updated when the XML file is edited, one option is to use the FileSystemWatcher to monitor the changes occurred on the XML file.

XML file structure used by the XML Roles Provider

<?xml version="1.0" encoding="utf-8" ?>
<
roles>
  <
role roleName="Administrator">
    <
user>Admin</user>
    <
user>Mike</user>
  </role>
  <
role roleName="Manager">
    <user>Lucy</user>
  </role>
  <
role roleName="Customer">
    <user>Alex</user>
    <user>George</user>
    <user>Jan</user>
    <user>Mary</user>
  </role>
</
roles>

The XML Roles Provider Implementation

public class XmlRoleProvider : RoleProvider
{
    public override void AddUsersToRoles(string[] usernames, string[] roleNames)
    {
        throw new NotImplementedException();
    }

    public override string ApplicationName
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }

    public override voidCreateRole(string roleName)
    {
        throw new NotImplementedException();
    }

    public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
    {
        throw new NotImplementedException();
    }

    public override string[] FindUsersInRole(stringroleName, stringusernameToMatch)
    {
        throw new NotImplementedException();
    }

    public override string[] GetAllRoles()
    {
        throw new NotImplementedException();
    }

    public override string[] GetRolesForUser(string username)
    {
        var theResult = from p inDocument.Root.Elements("role").Elements("user")
                        where p.Value == username
                        select p.Parent.Attribute("roleName").Value;

        return theResult.ToArray();
    }

    public override string[] GetUsersInRole(string roleName)
    {
        throw new NotImplementedException();
    }

    public override bool IsUserInRole(string username, string roleName)
    {
        var userRoles = this.GetRolesForUser(username);
        var theResult = userRoles.Contains(roleName);
        return theResult;
    }

    public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
    {
        throw new NotImplementedException();
    }

    public override bool RoleExists(string roleName)
    {
        throw new NotImplementedException();
    }

    private FileSystemWatcher _watcher;
    private XDocument _document;
    private object _docLock = new object();

    public XDocument Document
    {
        get
       
{
            if (this._document == null)
            {
                string path = HttpContext.Current.Server.MapPath("~/App_Data/roles.xml");

                //Perform some locking:
               
lock (this._docLock)
                {
                    if (this._document == null)
                    {
                        this._document = XDocument.Load(path);
                    }
                }

                if (this._watcher == null)
                {
                    this._watcher = new FileSystemWatcher(HttpContext.Current.Server.MapPath("~/App_Data"), "*.xml");
                    this._watcher.NotifyFilter = NotifyFilters.LastWrite;
                    this._watcher.Changed += new FileSystemEventHandler(watcher_Changed);
                    this._watcher.EnableRaisingEvents = true;
                }
            }

            return this._document;
        }
    }

    void watcher_Changed(object sender, FileSystemEventArgs e)
    {
        lock (this._docLock)
        {
            this._document = null;
        }
    }
}