codeSMART

The IEqualityComparer in action

Posted by: daggmano on: June 23, 2009

I recently had the situation where I had a class similar to the following:


public class Item
{
    public Guid ItemID { get; set; }
    public string ItemName { get; set; }
    public IList Categories { get; set; }
}

public class Category
{
    public Guid CategoryID { get; set; }
    public string CategoryName { get; set; }
}

Data for the application was pulled out of a Linq to SQL class, working off a SQL database.  (I don’t want to get into a discussion on the semantics of my design choice – perhaps Items should be members of Categories instead of the other way around.  Just enjoy the show for now…)

What I wanted to do was to filter a Linq query from the database (containing IQueryable<Item>) for items that contained Categories with a given CategoryID.  No problems!   Just run up a Linq to SQL query to get the Category instance matching that CategoryID as follows:


var myCategory = (from c in datacontext.dbCategories
                  where c.ID == myCategoryID
                  select new Category
                  {
                      CategoryID = c.ID,
                      CategoryName = c.Name
                  }).Single();

Note that the filter as shown below won’t work on an IQueryable<Item> (due to the Contains() clause), so we’ll evaluate the incoming IQueryable<Item> as it comes in, and (for convenience) convert it back to IQueryable on the way out.  We’ll implement the method as an extension method, because we can.


public IQueryable FilterByCategory(this IQueryable qry, Category cat)
{
    return (from i in qry.ToList()        // Evaluate the qry
            where i.Categories.Contains(cat)
            select i).AsQueryable();
}

Now, this is all fine, as far as it goes.   Except for one small thing.

It didn’t work.

My result set was empty.  Always.

So what’s going on?  Basically, here’s the thing.  Have a look at the following code fragments:


int x = 7;
int y = 7;
Debug.Assert(x == y);    // Assert is true

class MyObject
{
    public int Value;

    public MyObject(int val)
    {
        Value = val;
    }
}
MyObject a = new MyObject(7);
MyObject b = new MyObject(7);
Debug.Assert(a == b);    // Assert fails!

It may seem obvious, but the reason the second Assert fails is that, despite having the same internal value, a and b are not the same! And this is the problem with my filter query above.   Despite the Category instance I received from the SQL query having the same internal values as (perhaps) a Category in the Categories property of an Item, they are not the same object and so the comparison will fail.
What we need is some way of telling .NET that two instances of an object are the same, even if they are different objects, and this is where the IEqualityComparer comes in.  We can create the following class:


class CategoryComparer : IEqualityComparer
{
    #region IEqualityComparer Members

    public bool IsEqual(Category x, Category y)
    {
        return x.CategoryID == y.CategoryID;
    }

    public int GetHashCode(Category obj)
    {
        return obj.CategoryID.GetHashCode();
    }

    #endregion
}

We have created a class that says that two instances of Category are equal when their CategoryID values match, and have based their Hash Code on the CategoryID value rather than the instance itself.  All we need to do now is to plug it in to the query, as follows:


public IQueryable FilterByCategory(this IQueryable qry, Category cat)
{
    return (from i in qry.ToList()        // Evaluate the qry
            where i.Categories.Contains(cat, new CategoryComparer())
            select i).AsQueryable();
}

Voila!  .NET is a wonderful environment, but there are always a few gotchas lying around for the unsuspecting.  Work through probelms methodically, and you’ll find there’s always a way.

Tags: ,

Premature Optimization

Posted by: daggmano on: April 9, 2009

Aren’t the people at Microsoft nice?  Apparently they made Internet Explorer 8 just right for NineMSN.

optimised

optimised2

(Maybe a more truthful approach would be to say that the NineMSN site is optimized for IE8?  But why let the facts get in the way of a good story?)

Extending the Microsoft ASP.NET Membership Provider

Posted by: daggmano on: March 27, 2009

Microsoft did a wonderful thing when they gave us the ASP.NET Membership Provider.  With a single command line call to aspnet_regsql.exe, we can have a fully implemented membership system providing persistence of usernames, one-way encrypted passwords, question / answer combinations, account lockout, roles, personalization, and all sorts of other useful (and not-quite-so-useful) features.

However, there is also a bit missing.  Most websites will also want to store a bit more information about their users, such as first / last names, maybe their address, birthdate, favorite color, etc, etc.  For Microsoft to have supplied fields for all of this would have made things quite unwieldy, to say the least, but fortunately there is an easy way to extend the provider.

DISCLAIMER: Before I begin, let me say that this is not the only way to extend the provider, nor is it necessarily the best.  It is quite simple, however, and therein lies it’s appeal…

Database Tables

Once you have decided which fields you wish to add to the membership provider, create a table that includes these fields along with an ID field (you can call it what you want, but ID or UserID are obvious choices) of type uniqueidentifier.

The value in the ID field will be used to link to the ASP.NET Membership Provider tables (the UserId field in aspnet_Users).  You can create a relationship between these fields if you want, but I don’t worry about it as it saves messing with the auto-generated tables.  The table structure I am using for this example is as shown below:

databasetables

MyUser class

Create a class for your membership provider, called something like MyUser.  It’s structure should be something like the following:


public class MyUser
{
    public Guid ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public string FullName
    {
        get { return FirstName + " " + LastName; }
    }

    public MembershipUser User
    {
        get { return Membership.GetUser(ID); }
    }

    public static bool IsLoggedIn
    {
        get
        {
            MembershipUser u = null;
            try
            {
                u = Membership.GetUser();
            }
            catch
            {
            }

            return (u != null);
        }
    }

    public static bool IsAdmin
    {
        // Assumes a role called "Admin"
        get { return (IsLoggedIn && Roles.IsUserInRole("Admin")); }
    }

    public bool IsUserAdmin
    {
        get { return (Roles.IsUserInRole(User.UserName, "Admin")); }
    }

    private static UsersService _service;

    private static UsersService Service
    {
        get
        {
            if (_service == null)
                _service = new UsersService();

            return _service;
        }
    }

    public static MyUser GetUser(Guid userId)
    {
        return Service.GetUser(userId);
    }

    public static MyUser GetCurrentUser()
    {
        try
        {
            return Service.GetUser((Guid)Membership.GetUser().ProviderUserKey);
        }
        catch
        {
            return null;
        }
    }

    public static MyUser AddNewUser(string username, string password, string email, string firstName, string lastName, out MembershipCreateStatus createStatus)
    {
        MembershipUser u = Membership.CreateUser(username, password, email, null, null, false, out createStatus);
        if (u != null)
        {
            MyUser user = Service.AddUser((Guid)u.ProviderUserKey, firstName, lastName);
            return user;
        }
        else
            return null;
    }

    public static MyUser ConfirmUser(Guid id)
    {
        var user = Service.GetUser(id);

        if (user != null)
        {
            if (user.User.IsApproved)
                user = null;
            else
            {
                var mu = user.User;
                mu.IsApproved = true;
                Membership.UpdateUser(mu);
            }
        }
        return user;
    }
}

Okay, there’s quite a bit there, so let’s have a look at the various parts.

Lines 3-5 provide the extended field set we want, including the Guid field (which maps to the SQL uniqueidentifier) to link to the ASP.NET Membership Provider.  Lines 7-10 just provide a quick way to get a user’s full name, while lines 12-15 provides a simple way to get to the ASP.NET Membership User.

Following this are a few static and instance methods for getting the status of a user:  Lines 17-32 is a static call to see if anyone is currently logged in;  Lines 34-38 can be used to see if the currently logged in user is a member of the Admin role (you can use several of these methods for different roles), while lines 40-43 is an instance method to see if a particular user is a member of the Admin role.

Line 45 introduces a reference to a UsersService class.  We’ll be using this service class to provide access to our database repository, and I’ll provide full details below.  This is a static field, and lines 47-56 essentially provide a single property accessor to this field (sort of a Singleton, even though it’s all static).  Following this are several methods for CRUD (Create Read Update Delete – okay, there’s no Delete here, but you get the idea..).  The only surprise here is the ConfirmUser method.  I like users to confirm their email address by clicking a link in an email sent to them, so create their user as Unapproved.  When they click on a link in an email (which contains their UserID as a parameter), the ConfirmUser method is called with that ID, which updates their status.

Now, about that UsersService…

The UsersService class

The UsersService relies on a repository class, so let’s get that first.  I’m using Linq to SQL, so create a new LINQ to SQL class, and let’s call it LinqUsers.dbml.  Drag the User table (from the top of this post) onto the designer and check your namespaces if required.  I have named my data context ‘DB’ to make things easy.  Now, here’s the UsersRepository code…


public class UsersRepository
{
    public IQueryable<MyUser> GetUsers()
    {
        DB db = new DB();
        return from u in db.Users
               orderby u.LastName
               select new MyUser
               {
                   ID = u.ID,
                   FirstName = u.FirstName,
                   LastName = u.LastName
               };
    }

    public MyUser AddUser(Guid id, string firstName, string lastName)
    {
        MyUser result = null;

        using (DB db = new DB())
        {
            User user = new User();
            user.ID = id;
            user.FirstName = firstName;
            user.LastName = lastName;

            db.Users.InsertOnSubmit(user);
            db.SubmitChanges();

            result = GetUsers().ByID(id);
        }

        return result;
    }

    public bool RemoveUser(Guid id)
    {
        bool result = false;

        using (DB db = new DB())
        {
            var del = from u in db.Users
                      where u.ID == id
                      select u;

            db.Users.DeleteAllOnSubmit(del);
            db.SubmitChanges();

            result = true;
        }

        return result;
    }
}

The only thing this needs is the ByID() filter, as follows:


public static class UserFilters
{
    public static MyUser ByID(this IEnumerable<MyUser> qry, Guid id)
    {
        return (from u in qry
                where p.ID == id
                select p).SingleOrDefault();
    }
}

Now, the UsersService class can be defined as follows:


public class UsersService
{
    UsersRepository _repository = new UsersRepository();

    public IList<MyUser> GetUsers()
    {
        return _repository.GetUsers().ToList();
    }

    public MyUser GetUser(Guid id)
    {
        return _repository.GetUsers().ByID(id);
    }

    public MyUser AddUser(Guid id, string firstName, string lastName)
    {
        return _repository.AddUser(id, firstName, lastName);
    }

    public bool RemoveUser(Guid id)
    {
        MyUser user = _repository.GetUsers().ByID(id);
        if (user == null)
            return false;

        MembershipUser mu = user.User;
        _repository.RemoveUser(id);
        return Membership.DeleteUser(mu.UserName, true);
    }
}

This should all be fairly self explanatory.  The reference to the UsersRepository could be taken through a constructor, and the UsersRepository should really implement an IUsersRepository interface, so that IOC and Dependency Injection can be used to enable unit testing, but if you need it, you’ll know how to do it (yes, I do it on my own code, so don’t beat me…).

So that’s it, really.  While there is a fair whack of code in there, it’s all fairly simple stuff.  Linq to SQL makes the database work easy, and the database and MyUser class are easily extendable to provide for the management of more fields and roles.  Feel free to leave comments if this has helped, or to offer suggestions for how this could be improved.

SimpleSlideShow update…

Posted by: daggmano on: March 9, 2009

Ran into a problem with SimpleSlideShow recently where a flash drive contained files with a .jpg extension, but that were not JPEG files. While I don’t know exactly where they came from, the best solution was to add some code to SimpleSlideShow that would check the validity of alleged JPEG images before importing them. The result is at https://simpleslideshow.svn.codeplex.com/svn, version 1.1.

Yes, but why is his hair green??

Posted by: daggmano on: February 2, 2009

Overheard in a local shopping centre:

    FRIEND: Oh, so you’ve had your baby?

    MUM: Yes.

    FRIEND: And what did you have?

    MUM: A boy.

    FRIEND: Another boy?  Oh, you could start your own soccer field!

Err, yeah.

InsertScript for ASP.NET MVC

Posted by: daggmano on: January 13, 2009

I recently had a situation where a custom control I was using in a page required jQuery.  Since I like the idea of keeping such controls self-contained, I added the usual <script type="text/javascript" src="jquery-1.2.6.min.js"></script> tag.

No problem – until I then loaded a page that included this control AND also had its own requirement for jQuery.  It seems that jQuery doesn’t like begin included twice, so I had a choice.  I could:

  • Include jQuery once in the header (or wherever) of each page (i.e. in the Master Page), hence breaking the self-containment of my control, or;
  • Write a helper method to keep track of which script files had been included in each page and hence allow a script file to only be included once per request.

It should come as no surprise (as otherwise there would be no point in this post) that I went for the second option.

The HttpContext object has an Items collection, and since the HttpContext has a lifetime of one request, we can use this Items collection to keep track of our included files.  Just add the following static method to a static class (Don’t you just love Extension Methods!):


public static string InsertScript(this HtmlHelper helper, string jsFile)
{
    const string RESID = "__resources";

    List<string> _resources = (List<string>)helper.ViewContext.HttpContext.Items[RESID];
    if (_resources == null)
    {
        _resources = new List<string>();
        helper.ViewContext.HttpContext.Items[RESID] = _resources;
    }

    string url = ScriptUrl(jsFile);
    if (!_resources.Contains(url.ToLower()))
    {
        _resources.Add(url.ToLower());
        return "<script type=\"text/javascript\" src=\"" + url + "\"></script>";
    }
    else
        return "";
}

Notice the call to ScriptUrl(jsFile) – this is just a static helper method to map javascript file names to the Scripts folder in my web app. It’s up to you whether you use such a method.

It’s now just a matter of calling this from your .aspx pages, replacing the usual
<script type="text/javascript" src="myjavascriptfile.js"></script>
with
<%= Html.InsertScript("myjavascriptfile.js") %>

I is a 1337 developer!

Posted by: daggmano on: December 10, 2008

I have been using SubVersion for code management for the past few years.  I just made Commit # 1,337!

Sad, really…

Profiling ASP.NET MVC using the EQATEC Profiler

Posted by: daggmano on: November 28, 2008

I recently have had a need to do some code profiling of an ASP.NET MVC site I have been working on and so, being the cheapskate I am, found the EQATEC .NET Profiler.

Now, there are a couple of caveats to using this profiler for ASP.NET applications.  The first is that the profiler outputs its XML data file to one of the following locations: /Storage Card, /SD Card, /Temp or /.  The best way around this is to create a Temp directory in the root directory of the drive that hosts your ASP.NET application (eg. C:\Temp if your site is located in C:\Inetpub\...).  Make sure the IIS User associated with the relevant App Pool has Write access to this dir, and you’re sweet.

The second caveat is slightly more difficult to get around.  The problem is that the profiler will only output its XML file either when the Main method exits, or when the TakeProfileSnapshot() method is called in the Profiler.Runtime library.  We don’t have a Main method in ASP.NET, so we’ll use the TakeProfileSnapshot() call.

While the following is particularly focussed on ASP.NET MVC, it should be useful for ’standard’ ASP.NET as well.

First, we need to create a page that will call the TakeProfileSnapshot() method.  In my site, I have a ‘Setup’ Controller for performing tasks that only I know about (such as initially populating databases, etc), so I added a TriggerProfile action to this method and added a simple view to display the output, as follows.


public ActionResult TriggerProfile()
{
    try
    {
        ViewData["Result"] = "Success";
    }
    catch (Exception ex)
    {
        ViewData["Result"] = "Failed: " + ex.Message + "<br /><br />" + ex.ToString();
    }
    return View();
}

We’ll flesh it out in a bit.  The  TriggerProfile.aspx page simply has a <%= ViewData["Result"] %> tag to display the result.  Feel free to make this a bit prettier, but let’s face it – nobody but you should ever see this.

I didn’t want to include the EQATEC Profiler DLL in my project, as I don’t really like the idea of modifying a project for testing unless that change is transparent in production.

Reflection to the rescue!

Making sure you add a using System.Reflection; directive, add the following to that try {} block above, before the ViewData["Result"] = "Success"; line:


Assembly eqatec = Assembly.Load("EQATECProfilerRuntime, Version=1.2.60.0, Culture=neutral, PublicKeyToken=f1beac79aa82eef6");
Type runtime = eqatec.GetType("EQATEC.Profiler.Runtime");

MethodInfo takeSnapshot = runtime.GetMethod("TakeProfileSnapshot", new Type[] { });
takeSnapshot.Invoke(null, null);

So what’s happening here?

The EQATECProfilerRuntime.dll assembly is only present in the application once the bin folder has been profiled (the Profiler creates a bin (Profiled) folder – just rename this to bin and remember to restart IIS to activate the changes).  If the /Setup/TriggerProfile page is called in this case, it finds the dll, gets an instance of the TakeProfileSnapshot() method and invokes it, triggering a dump of the profiler output.  In the case that the code has not been profiled, EQATECProfilerRuntime.dll should not be present, and so the Assembly.Load(...) call will fail, and the output of the Exception will be displayed.

To use all of this, simply use the Profiler to profile the bin folder, rename the created folder back to bin, restart IIS and start browsing on those troublesome pages.  When you’re ready, make a call to the /Setup/TriggerProfile page (hopefully you’ll get a “Success” message), and check the C:\Temp folder for the resultant XML.

Happy profiling!

JSON and the Great Uncaching

Posted by: daggmano on: October 24, 2008

Internet Explorer has this annoying little habit of caching AJAX calls.  This recently came out to bite me on a site I was working on which used AJAX to fill some drop-down boxes on an edit page.  The first time through was fine (eg. editing an existing item).  If I then created a new item, lo and behold the drop-down boxes were already selected on the previous item’s value!

This problem only occurs in IE, but there’s an easy solution.  Write a quck javascript function as follows:


function uncache(url) {
    var d = new Date();
    var time = d.getTime();
    return url + ((url.indexOf('?') == -1) ? '?' : '&') + 'time=' + time;
}

Then, whenever you make a parameterless AJAX call (eg. to /GetValues/), run it through the above function instead (eg. uncache(’/GetValues/’) ).

Damn that QWERTY keyboard…

Posted by: daggmano on: September 29, 2008

While my touch typing skills are constantly improving, I’ve noticed over the past months that the hardest word to type seems to be my own name.  It seems that the ‘m’ and ‘n’ keys are just not where my fingers seem to want them, although the phenomenon only occurs when I type my name at the end of an email.  Maybe it’s time I set up an automatic signature on all my email accounts.

Cheers,
Darrem

(Afterthought: This Freudian typing could also explain why the word ‘type’ often comes out as ‘typoe’ – maybe it is!)

del.icio.us

Pages

 

July 2009
S M T W T F S
« Jun    
 1234
567891011
12131415161718
19202122232425
262728293031