LavaBlast Software Blog

Help your franchise business get to the next level.
AddThis Feed Button

Lambda IEqualityComparer<T>

clock May 5, 2010 16:16 by author EtienneT

Just show me the code.

You’ve probably used an IEqualityComparer<T> before.  If you have, you probably know that using one is a pain because you need to implement the interface in your own class and override your Equals and GetHashCode methods.  In this post, we’ll focus on the most common scenario for checking equality: primary key comparisons.  For example, the CustomerID property of a Customer class.  This article offers a simple solution inspired by a StackOverflow post.  We’ll use lambda expressions instead of subclassing IEqualityComparer<T> for each class, thus eliminating lots of code and making everything far more readable.

A simple example should clarify the situation.  Here’s the traditional way you’d use an IEqualityComparer<T> to compare two Customer objects.

First extend from IEqualityComparer<T>:

public class CustomerComparer : IEqualityComparer<Customer>
{
    public bool Equals(Customer x, Customer y)
    {
        return x.CustomerID == y.CustomerID;
    }
 
    public int GetHashCode(Customer obj)
    {
        return obj.CustomerID.GetHashCode();
    }
}

Then you would use this new CustomerComparer class like so:

// First parameter == CustomerID
var x = new Customer(1);
var y = new Customer(2);
 
var list = new List<Customer>()
{
    new Customer(1),
    new Customer(2)
};
 
list.Contains(new Customer(1), new CustomerComparer());

As you can see, quite a bit of code just to find an object in a collection. (In this example, if we had not used our CustomerComparer, we would not have been able to find this new instance of the same customer in our list.)  Most of the time, the comparisons we want to make are pretty simple and could be expressed using a single line of code. Allow me to introduce my class (strongly inspired from this StackOverflow post) before showing a code example. This class allows us to pass a simple lambda expression to construct an IEqualityComparer<T> on the fly:

public class KeyEqualityComparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, T, bool> comparer;
    private readonly Func<T, object> keyExtractor;
 
    // Allows us to simply specify the key to compare with: y => y.CustomerID
    public KeyEqualityComparer(Func<T, object> keyExtractor) : this(keyExtractor, null) { }
    // Allows us to tell if two objects are equal: (x, y) => y.CustomerID == x.CustomerID
    public KeyEqualityComparer(Func<T, T, bool> comparer) : this(null, comparer) { }
 
    public KeyEqualityComparer(Func<T, object> keyExtractor, Func<T, T, bool> comparer)
    {
        this.keyExtractor = keyExtractor;
        this.comparer = comparer;
    }
 
    public bool Equals(T x, T y)
    {
        if (comparer != null)
            return comparer(x, y);
        else
        {
            var valX = keyExtractor(x);
            if (valX is IEnumerable<object>) // The special case where we pass a list of keys
                return ((IEnumerable<object>)valX).SequenceEqual((IEnumerable<object>)keyExtractor(y));
 
            return valX.Equals(keyExtractor(y));
        }
    }
 
    public int GetHashCode(T obj)
    {
        if (keyExtractor == null)
            return obj.ToString().ToLower().GetHashCode();
        else
        {
            var val = keyExtractor(obj);
            if (val is IEnumerable<object>) // The special case where we pass a list of keys
                return (int)((IEnumerable<object>)val).Aggregate((x, y) => x.GetHashCode() ^ y.GetHashCode());
 
            return val.GetHashCode();
        }
    }
}

Here is how you would use this code:

var x = new Customer(1);
var y = new Customer(2);
 
var list = new List<Customer>()
{
    new Customer(1),
    new Customer(2)
};
 
list.Contains(new Customer(1), z => z.CustomerID);

To be able to use this in Contains<T>, we need to add a new extension method (which was not included in the original StackOverflow post):

public static bool Contains<T>(this IEnumerable<T> list, T item, Func<T, object> keyExtractor)
{
    return list.Contains(item, new KeyEqualityComparer<T>(keyExtractor));
}

Once you’ve done this work for Contains, it is very easy to create extensions methods for Distinct, Except, Union, etc. Furthermore, the same principles also apply to IComparer<T>.

For a complete source code with all the classes and extension methods, you can go here: http://gist.github.com/391397

You can modify this code by forking the gist; don’t be shy to add useful methods!



BlogEngine.NET 1.6 Breaking Old Links

clock February 12, 2010 10:17 by author EtienneT

UPDATE: BlogEngine 1.6.0.1 will fix this problem

Download BlogEngine 1.6 Binary Patch (drop in your bin folder)

We recently installed an open source error logging project called Elmah on our BlogEngine.NET because we noticed that comments were broken on our blog and some people informed us of other errors.

After installing Elmah, we found out that we had a lot of exceptions like this:

The file '/post/2008/01/Attach-to-Process-with-one-shortcut.aspx' does not exist.

After some investigation, we found out that a lot of the links leading to our blog were broken!  This is bad news because we care about our Google rankings!

I investigated the BlogEngine.NET code a little bit because we considered this a major problem and found out that BlogEngine.NET changed the link pattern from:

http://blog.lavablast.com/post/2008/02/I2c-for-one2c-welcome-our-new-revision-control-overlords!.aspx  (the old pattern)

to

http://blog.lavablast.com/post/2008/02/11/I2c-for-one2c-welcome-our-new-revision-control-overlords!.aspx  (the new pattern)

The day is now required in the link. Otherwise, external links are redirected to an error page.

After investigating the code, I found out that the method RewritePost in the class BlogEngine.Core.Web.HttpModules.UrlRewrite was changed in svn revision 29143 from:

Post post = Post.Posts.Find(delegate(Post p)
{
    if (date != DateTime.MinValue && (p.DateCreated.Year != date.Year || p.DateCreated.Month != date.Month))
    {
        if (p.DateCreated.Day != 1 && p.DateCreated.Day != date.Day)
            return false;
    }
 
    return slug.Equals(Utils.RemoveIllegalCharacters(p.Slug), StringComparison.OrdinalIgnoreCase);
});
 

to

Post post = Post.Posts.Find(delegate(Post p)
{
    if (date != DateTime.MinValue &&
        (p.DateCreated.Year != date.Year || p.DateCreated.Month != date.Month || p.DateCreated.Day != date.Day))
    {
        return false;
    }
    return slug.Equals(Utils.RemoveIllegalCharacters(p.Slug), StringComparison.OrdinalIgnoreCase);
});

 

Days are now mandatory in the URL rewrite to lead to a valid post.  Our old links that were using the yyyy/mm pattern are now returning and error page, and without installing Elmah or double-checking links, I would probably not have noticed. .

The RelativeLink property of the class Post was changed at svn revision 8906 to change the pattern from yyyy/mm to yyyy/mm/dd.  This in itself was not a problem if the old links continued to work.  But with this new change to the UrlRewrite class, the legacy format no longer works.

I changed the code manually to fix our problem, but this might affect other BlogEngine.NET users as well.  If you have a pretty old blog where people link to your old blog posts, then you probably are affected.

After some investigation, this bug was introduced while fixing Bug 9212.

I made a temporary patch for those of you who run BlogEngine.NET 1.6 and are experiencing the same issue.

I changed the code in UrlRewrite to search posts a second time but ignoring days in the date if it didn’t find the post in the first search.  Here is my code:

var predicate = new Func<bool, Predicate<Post>>(includeDays =>
{
    return new Predicate<Post>(
        p =>
        {
            if (date != DateTime.MinValue &&
                (p.DateCreated.Year != date.Year || p.DateCreated.Month != date.Month || (p.DateCreated.Day != date.Day && includeDays)))
                    {
                        return false;
                    }
                    return slug.Equals(Utils.RemoveIllegalCharacters(p.Slug), StringComparison.OrdinalIgnoreCase);
                }
        );
});
 
// Search all post and include days in the search
// A valid link would look like this
// http://blog.com/post/2010/02/10/slug.aspx
Post post = Post.Posts.Find(predicate(true));
 
// We can't find this post, try to find it but ignore days in the path
// http://blog.com/post/2010/02/slug.aspx
// Some older blogengine.net site used this link pattern
// We want to preserve links to those sites.
if (post == null)
    post = Post.Posts.Find(predicate(false));

 

BlogEngine.NET developers are aware of this bug, but in the meantime, you can just drop this new version of BlogEngine.Core.dll in your bin folder to temporary fix the problem if you are using version 1.6 of BlogEngine.NET.

I highly recommend installing Elmah on your BlogEngine.NET to discover errors like these!

Download BlogEngine.NET 1.6 Binary Patch (drop in your bin folder)

kick it on DotNetKicks.com

Shout it



ASP.NET Regenerate .designer.cs

clock February 8, 2010 11:58 by author EtienneT

In this simple post, I want to share a quick tip with Visual Studio.  You probably encountered this problem before: you are designing an ASPX or ASCX form in Visual Studio and then for some reason an element you just added in the ASPX/ASCX is not available in the code behind.

If you are lucky you know exactly what you changed in the page to cause this problem and you fix it.  However, if you’re opening a file that hasn’t been worked on in a long time (maybe someone else did the most recent modification), it becomes difficult to know what exactly cause the .designer.cs automatic generation fail. There are a few common scenarios for this such as invalid HTML, missing or duplicate declarations, or unresolved references. Finding the core issue by hand is difficult because the designer does not let you know what the specific issue is.

Small Tip

To regenerate the .designer.cs file for a ASPX/ASCX, first delete the .designer.cs file, then right click on the ASPX/ASCX and click “Convert to Web Application”.  This will probably give you an error message that will help you find the root of the problem.  I noticed that there’s a difference in the error message you might get here versus when the file is opened in Design mode in Visual Studio.

For example, we had a .ASCX file where the .designer.cs file was not being generated when we modified it.  We could not figure out what the error was until we discovered this tip because Visual Studio did not give us any feedback.  Once I deleted the .designer.cs and ran “Convert to Web Application” on the file, I was prompted with an error message that informed me that we had a <%@ Register /> declaration in our ASCX that was also declared in our web.config file (and this was creating a conflict even if they both pointed to the same location).  Visual Studio could benefit from better error reporting for this particular scenario.

kick it on DotNetKicks.com

Shout it



YUI Compressor for Visual Studio

clock May 11, 2009 14:44 by author EtienneT

Although you don't want this for all things in life, you do want to ensure that your JavaScript and CSS files are as small as possible.  As a web programmer, a script minifier is a useful application that should be a part of your toolbelt. This article presents a simple way to hook up a popular minifer inside Visual Studio.

First you need to download YUI Compressor from Yahoo and unzip its contents anywhere.

External Tool

Now you want to make a new external tool in Visual Studio. (Tools -> External Tools...)

image

Then add a new tool and name it YUI Compressor.  You want to put the following values in the inputs.  Adjust the jar location depending of where you unzipped YUI Compressor.  It also assumes that java.exe is in your path.

image

  1. Title: Yui Compressor
  2. Command: java.exe
  3. Arguments: -jar "E:\yuicompressor-2.4.2\build\yuicompressor-2.4.2.jar" $(ItemPath) --charset "UTF8" --type js -o $(ItemFileName).min$(ItemExt)
  4. Initial Directory: $(ItemDir)
  5. Check “Use Output Window”

You can already test this.  Any problems will be presented in the Output window.  First you need to select your *.js file in the Solution Explorer.  Then in the Tool menu, select Yui Compressor.  Refresh the directory that contains your *.js file and you should have a file named *.min.js with the same prefix as your *.js.

You could do the same thing to minimize CSS just by making a new external tool with the –type argument value changed to css instead of js.

Toolbar

You can easily make a toolbar to quickly do this when you need it.  Go to Tools->Customize.  Then New…

image

Then go to the Command Tab and select Tools.

image

Drag and Drop the External Command 1 (or the # corresponding to your external tool if you have more than one) to the created toolbar.  Hit close and now you can put your new toolbar wherever you want.

I hope you find this useful! Setting up tools this way inside Visual Studio is a good way to speed up the development process because you don't need to memorize numerous command line parameters: everything is done with one click!

kick it on DotNetKicks.com


Co-Working Joliette

clock January 8, 2009 10:16 by author EtienneT

image Last night I had the pleasure to assist to the official opening of the new co-working space opened here in Joliette.  The mayor of Joliette M. René Laurin was there to officially launch the project along with eighty people who were there to visit, drink wine, and exchange business cards.  The new co-working space is located right on top of Librairie René Martin in downtown Joliette.

We want to encourage this kind of project because we think it encourage entrepreneurship here in Joliette.  It enables small companies to have an affordable office space and enables entrepreneurs to meet new people with different work specialities.

Readers who have followed our blog for some time now know that we have done the software for The Code Factory, a co-working space for software startups in Ottawa.  Co-Working Joliette has a different approach than The Code Factory for its pricing model and doesn't focus exclusively on software startups.  Joliette (~100,000 inhabitants) is not Ottawa or Montreal, so it doesn’t have the affluence of new people that those two cities may have, but Joliette still has many entrepreneurs who need office space and want to meet new people.  For this reason, entrepreneurs can work at Co-Working Joliette by purchasing a daily pass or rent office space on a monthly or yearly basis.  You can have your own closed office or share a work table with other people.  You can access your office 24/7, have a free parking space, and, of course, unlimited Internet access.

According to Benoit Grenier of Communication 8020, this is the second co-working space in the province of Quebec.  They already have partnerships with two other co-working spaces: one in New York, the New Worker, and one in Vancouver, WorkSpace.  They are in talks with a co-working space in London right now but nothing has been signed as of yet.

To me, the opening of a co-working location in a smaller community confirms what we've been claiming on our blog for the past two years. The Internet has made it possible to have clients all over the world, regardless of your company size and location. We've seen this first hand in the software world, but it also applies to firms specializing in marketing, translation, etc.

Good luck to all involved and let’s hope this project becomes a success in Joliette.



Declare your ObjectDataSource using lambda expressions

clock December 2, 2008 17:42 by author EtienneT

 

Example Page with Northwind | Download Source

Because SubSonic generates the business objects and the controllers in our data access layer, I often bind my controls to an ObjectDataSource. I love the ObjectDataSource but one of the most annoying things with it is that you must declare your select/update/delete method names as strings. The ObjectDataSource will call these methods via reflection, as the compiler doesn't have an explicit reference to the methods being invoked.  I found a solution to be able to set all the required properties of an ObjectDataSource simply by making a fake call on a lambda expression. Keep reading if you want to know how this works.  Simply set your SelectMethod like this:

ods.SetSelectMethod<ProductController>(ctrl => ctrl.FetchAll());

What are the issues with declaring names using strings?

Here is a typical ObjectDataSource declaration in your ASPX/ASCX:

<asp:ObjectDataSource ID="sqlDataSource" runat="server"
SelectMethod="FetchByProductAuthority" TypeName="Generated.StoreAuthority.ItemGroupController"
EnablePaging="True" SortParameterName="sort">
   <SelectParameters>
        <asp:Parameter Name="pa" Type="String" />
        <asp:Parameter Name="search" Type="String" />
        <asp:Parameter Name="user" Type="String" />
    </SelectParameters>
</asp:ObjectDataSource>

Why is it bad to have those properties set declaratively as strings, especially SelectMethod and TypeName?  If you ever rename your class, the select method or even add/remove its parameters, you will never know at compile time that you have broken your ODS (ObjectDataSource). Code refactoring tools don't affect these declarations either, so you are stuck using Find and Replace to change the strings in your declarations. In the worst case, you'll discover at run-time that you've broken your application.

Expressions + Lambda to the rescue

I just recently learned about Expression<T> from this article and it gave me an idea.  I managed to make something work with an extension method on the ObjectDataSource class plus a bit of extra glue logic that I will cover today.  My solution is not perfect since it’s the first time I play with Expressions, but I think it’s a good start.  I'd be ecstatic if you wanted to help me improve the syntax!

Here is how you would set your SelectMethod, TypeName and select parameters on an ODS, using my newly created extension method SetSelectMethod.

ods.SetSelectMethod<ProductController>(ctrl => ctrl.FetchAll());

 

imageWhat is happening in this piece of code?  We are calling an extension method on ObjectDataSource.  This extension method takes an Expression<Action<T>> as a parameter, where T is our controller in the model-view-controller pattern.  This basically means that we can pass an expression tree to the method that will be analyzed to figure out the information we're looking for. If you’re not familiar with what I’m saying, I recommend you take a look at this great article: Taking the magic out of Expression.  The expression tree enable us to retrieve all kinds of information from the lambda expression, like the TypeName (in this case “LavaBlast.Data.ProductController”) of the object used to make the method call and the Method name itself (SelectMethod = “FetchAll”).  We can even parse each parameter used and add them to the parameter collection of the ODS.  We can give Parameters default values derived from our expression tree of the lambda expression.  More on that later.

The figure on the left represents the expression tree for the lambda “ctrl => ctrl.FetchAll()”.

The goal of the lambda expression you pass to SetSelectMethod<T> is to make a fake call to the method you want the ODS to use when they make a Select operation.  By fake call I mean the ODS won’t invoke this lambda expression because it is there only to get analyzed for information, like the object name, method name, parameters, etc. However, since we are making a valid method call with all required parameters, you will be able to see at compile time that refactored code has affected this ODS. If we ever change this method in the future and it doesn’t compile anymore, then we’ll know it here.

It’s time for a real life example of something useful.  The last piece of code didn’t even include sorting and paging. Here is an example setting our ODS to utilize sorting and paging. Note that because paging is enabled, a SelectCountMethod is required by our ODS:

ods.SetSelectMethod<ProductController>(
    ctrl => ctrl.FetchAll(String.Empty, 0, 0),
    ctrl => ProductController.GetCount());


The method called has the following signature:

public virtual ProductCollection FetchAll(string sort, int startRowIndex, int maximumRows)


This time we have two lambda expressions, one with three empty arguments (these default values are ignored but the data types help us identify a particular method) and one defining our GetCount method.  The second one references a static method, but is very similar to what we've already covered. The properties TypeName, SelectMethod and SelectCount on the ODS will be specified by our SetSelectMethod.

Analyzing Parameters

What if we want to add parameters coming from the Session or from the value of a control on the page?  Or simply a Parameter having a default value?  The expression tree can be analyzed to discover the parameters to be added to our SelectParameter collection.

First of all, I’ll begin with a simple Parameter which has a constant default value.  Here is an example of a method filtering results depending on the logged in user's username:

ods.SetSelectMethod<ProductController>(ctrl => ctrl.FetchAll(HttpContext.Current.User.Identity))


The method declaration for this FetchAll:

public virtual C FetchAll(string user)


Imagining the currently logged in user is called "Etienne", we want to insert a Parameter with a default value of “Etienne”.  We do that by executing the value of the first parameter (HttpContext.Current.User.Identity), which will return “Etienne” and then when we create the parameter for which we set the default value.  We would create a parameter like this:

var parameter = new Parameter("user", DbType.String, "Etienne");


Now this was for a parameter with constant values, but what if the values comes from the Session (SessionParameter) or a Control value (ControlParameter)?

For example, in a page, I have a TextBox named txtSearch.  It's value can be used to filter the results in a GridView according to search terms.  We can't pass txtSearch.Text as above, as this would invoke it immediately instead of informing the ODS to query the control every time it fetches the data (it would become a Parameter instead of SessionParameter). However, we can pass a special type of parameter which will be recognized by our expression parser:

ods.SetSelectMethod<ProductController>(
    ctrl => ctrl.FetchAll(    ODSHelper.Control<string>(() => txtSearch.Text),
                            String.Empty, 0, 0),
    ctrl => ctrl.GetCount(String.Empty));


The related method declaration for FetchAll is as follow:

public virtual ProductCollection FetchAll(string search, string sort, int startRowIndex, int maximumRows)


Here the main difference  is the call to “ODSHelper.Control<string>(() => txtSearch.Text)”  as the first parameter to FetchAll.  At this point, we have to remember that this is a fake call to our method FetchAll.  The method call ODSHelper.Control<string> takes a lambda representing which control and which property on this control should be used while constructing the ControlParameter that will be added to the SelectParameter collection. From the information contained in ODSHelper.Control<string>(() => txtSearch.Text), we can construct a ControlParameter as follows and insert it into the ODS SelectParameter collection:

var parameter = new ControlParameter("search", DbType.String, "txtSearch", "Text");


The first generic parameter T we pass in the method ODSHelper.Control<T> is simply the return type.  We want our lambda to compile even if it’s a fake call, so we simply make ODSHelper.Control<T> return an object of type T, which is the data type of the parameter on our SelectMethod (in this case a string).

Analyzing a Lambda Expression

From this point forward, the article raises the geekiness bar even higher, as we drill-down into how all of this is implemented in the background. If you are interested how we analyze the expression tree, continue to read.

Let’s look at what we are doing inside the extension method SetSelectMethod<T>(Expression<Action<T>> exp).  I strongly suggest you download the project at the end of the article to see all the code file at once, but I’ll paste and explain the most important parts here.

public static void SetSelectMethod<T>(this ObjectDataSource ds, Expression<Action<T>> exp, Expression<Action<T>> expCount)


This is the declaration of our extension method.  We are extending ObjectDataSource and we need an Expression for the select method and one for the GetCount method. Since we are using an Action<T> this means the lambda expression doesn’t need to return anything.  We shall now dissect this method in greater detail.

// Make sure we have a lambda expression in the right format
var lambda = (LambdaExpression)exp;
if (lambda.Body.NodeType != ExpressionType.Call)
    throw new InvalidOperationException("Expression must be a call expression.");
 
var call = (MethodCallExpression)lambda.Body;
 
// Our lambda needs to be a call to a method
if (call.Method == null)
    throw new InvalidOperationException("Expression must be a method reference.");

 

 
Those first lines simply make sure that our lambda expression is indeed a method call on an object and that we have a valid method too.  Just from the information we have so far from the expression tree, we can already set the TypeName and SelectMethod of the ODS:

ds.TypeName = call.Object.Type.FullName;
ds.SelectMethod = call.Method.Name;

 

 
The interesting part lies in the fact that we can analyze the parameters of the method call and extract parameters the ODS needs to make the call to SelectMethod.  The loop to analyze the parameters is quite big.  I’ll put it all here and explain what is happening directly in code comments.  The most important thing to understand before reading the code is that if we encounter a method call in one of the parameters, we have to check to see if it’s one of the special method call like ODSHelper.Control or ODSHelper.Session.  We do that by checking if the method have a special attribute applied to them.  If the attribute is not there, we simply execute the method so that it returns a value and we use this value as the default value of the parameter.  Here is the code for the loop:

int i = 0;
// Then from the method call reflection information, we can get the parameters
foreach (var item in call.Method.GetParameters())
{
    // Don't add any parameters that are standard fields like sort and paging fields
    if (ds.SortParameterName != item.Name
        && ds.StartRowIndexParameterName != item.Name
        && ds.MaximumRowsParameterName != item.Name)
    {
        // Get the part of the expression tree that represents this parameter
        var mexp = call.Arguments[i];
 
        DbType type = GetDbType(item);
 
        object val = String.Empty;
        
        Parameter param = null;
        Type attribute = null;
        string propertyName = String.Empty;
 
        if (mexp is ConstantExpression)
        {
            // If it's a constant expression (for example if the parameter is 0 or a string "test")
            ConstantExpression c = mexp as ConstantExpression;
            val = c.Value; // just use this value
        }
        else if (mexp is MethodCallExpression)
        {
            // The following if is a special case if we want our parameter to be a session parameter
        // We could add more special cases for ControlParameter etc.
            var m = mexp as MethodCallExpression;
            // Check to see if the method call has the Session attribute applied to it
            if (m.Method.GetCustomAttributes(false).Count<object>(y => y.GetType() == typeof(SessionAttribute)) > 0)
            {
                attribute = typeof(SessionAttribute);
                // Just get the session field name to construct the SessionParameter
                val = Execute<string>(m.Arguments[0]);
            }
            else if (m.Method.GetCustomAttributes(false).Count<object>(y => y.GetType() == typeof(ControlAttribute)) > 0)
            {
                attribute = typeof(ControlAttribute);
 
                // A method call to pass a ControlParameter looks like this:
                // ODSHelper.Control<string, string>(() => txtName.Text)
                // Argument[0] contains the lambda expression
                // We just have to get the object name and the property name
                // and we have the values required for our ControlParameter
 
                // Argument[0] contains the lambda () => txtName.Text
                // This is an UnaryExpression and we have to get the right
                // side of this expression which is txtName.Text and analyze it
                var unary = ((UnaryExpression)m.Arguments[0]).Operand;
 
                val = LambdaHelper.GetObjectID(unary);
                propertyName = LambdaHelper.GetProperty(unary);
            }
            else
            {
                // Here the expression is more complex, maybe we are calling something like a method or accessing a property
                // to get our value back.  To get the value of the parameter passed, we actually need to construct
                // a new lambda, compile it and execute it to get the value returned and passed to our method call.
                val = Execute<object>(mexp);
            }
        }
        else
            val = Execute<object>(mexp);
 
        if (val != null)
        {
            if (attribute == typeof(SessionAttribute))
                param = new SessionParameter(item.Name, type, val.ToString()); // val contains the session field name
            else if (attribute == typeof(ControlAttribute))
                param = new ControlParameter(item.Name, type, val.ToString(), propertyName); // val contains the control name
            else
                param = new Parameter(item.Name, type, val.ToString()); // val contains the value passed to the method
 
            // Here we make sure that the parameter is not already added in the collection.
            // If it's there but it doesn't have the same value, we have to remove and add it
            bool contain = ds.SelectParameters.Contains(item.Name);
            if (!contain)
                ds.SelectParameters.Add(param);
            else if (contain && !ds.SelectParameters.HasValue(item.Name, val.ToString()))
            {
                ds.SelectParameters.Remove(item.Name);
                ds.SelectParameters.Add(param);
            }
        }
    }
    i++;
}


I think the comments explain the code pretty well.  I suggest that you download the complete source code to see the code for other method calls.

SessionParameter and ControlParameter

I want to get back to the syntax I chose to specify a parameter shall be a SessionParameter, a ControlParameter or simply a plain old Parameter.  We are using two static methods from ODSHelper class:

[Session]
public static T Session<T>(string sessionFieldName)
{
    return default(T);
}
 
[Control]
public static T Control<T>(Expression<Func<object>> action)
{
    return default(T);
}

 
Basically those methods do nothing.  They only return a default value of type T.  Why are we doing that?  We need to satisfy the parameter type of the method call where they will be used.

Why is it important to return T and not simply return a string?  For example, take the following method to be used as an ODS SelectMethod:

public TestCollection FetchAll(DateTime date, string search, string user, string sort, int startRowIndex, int maximumRows)

 
You want to pass this method with SetSelectMethod<T>.  The main difference is the first parameter.  It’ll take its value from the Session. Session[“FieldName”] will return a DateTime.  Here is how we could do it:

ObjectDataSource src = MainDataSource as ObjectDataSource;
 
src.SetSelectMethod<ItemGroupController>(
ctrl => ctrl.FetchAll(
ODSHelper.Session<DateTime>("FieldName"),
ODSHelper.Session<string>(SharedSessionVariables.GetSearchField(Page)),
HttpContext.Current.User.Identity.Name, 
String.Empty, 0, 0))

 
Pay attention to the first parameter.  What is important is that our method compiles and that our expression analyzer has enough information to fill the SessionParameter properties.  When we analyze the expression tree, we’ll be able to look at the parameter passed to the method call Session and see the Session field name.

The second really important characteristic is the custom attributes that both methods have ([Session] and [Control]).  Those unique attributes are used when we walk the expression tree to know what kind of parameter we need to create.  Without them, we could not distinguish between normal method calls and those that are here to give us information in the expression tree.

SessionParameter

ODSHelper.Session<string>(SharedSessionVariables.CurrentProductAuthority)

 

or

ODSHelper.Session<string>("CurrentProductAuthority")

 
If you don’t have a Session wrapper that defines the constant session field names, you can use the second option, which is more direct. When we analyze the expression tree, we simply evaluate the expression inside and create a SessionParameter with the result of the evaluation as the session field name.

ControlParameter

ODSHelper.Control<string>(() => txtName.Text)

 
This is how we pass in a control parameter.  Once again, we have a direct reference on the control and its property. If we remove the control later, our app won’t compile. txtName is this context is simply a TextBox that we have in our page.

ControlParameter needs two main things, the name of the control and the name of the property of the control.  We can get those two things by analyzing the expression tree of the passed lambda expression and add our ControlParameter in the parameter collection accordingly.

Parameter

For parameter inside our lambda expression that we pass, for example:

HttpContext.Current.User.Identity.Name

 
The expression analyzer simply executes this code and sets the returned value as the default value of the parameter.

Other Parameter Types

Right now my code is only supports those three kinds of parameters, but it would be pretty easy to add the other types of parameters (Query, Cookie, etc) with a little bit more work. Furthermore, the same logic could apply to the Update/Insert/Delete methods used by the ODS.

Conclusion

We need your opinion!  I think this project could be pretty useful.  The syntax is not very elegant, but I can't see a better way of doing this at this point.  Please download the project, play with the code, and give us your feedback! Ideally, this is something that would be built into the framework at a later date, but in the meantime we can still play around with it! This might not be the only way to solve the issues we are tackling, and if you have any other suggestions, let us know! (Example: Extending the ODS for each controller?)

Example Page with Northwind | Download Source

kick it on DotNetKicks.com


SQL Server - Restore a database backup via the command line

clock October 14, 2008 12:45 by author EtienneT

image Anyone who's ever developed a web application in .NET has had to play with a database management system, most probably SQL Server or its free cousin, SQL Server Express.  One of the tasks I personally hate doing with our SQL Server Express 2005 databases is restoring them from a backup, using SQL Management Studio.  We sometimes restore the point of sale database used by our customers to track down various issues or to build reports using their data as our test set. The process is not that long when you restore a backup from your own machine (restoring the MDF and LDF files to their original directory). If you restore databases from foreign systems, the process is simple only if both systems stored their databases in the same directory, which is rarely the case.

For example, I use Windows Vista x64 and our dedicated server uses a 32-bit version of Windows 2003.  Our data is stored in the default SQL Server directory, which is in the Program Files folder.  However, when using a 64-bit operating system, the program files directory is different (C:\Program Files (x86)).  Since the location of the MDF and LDF files are encoded directly in the bak file generated by SQL Server, restoring them via the command line is especially challenging when you don't control the original locations of the MDF and LDF files, nor their Logical Names.

Our goal is to be able to restore a database by executing a simple command such as this:

restore.bat LavaBlast

This command would look for LavaBlast.bak in the current directory and would restore the LavaBlast database to a default location on your computer where you want to store your MDF and LDF files.

Here is the code for restore.bat:

sqlcmd -S .\SQLEXPRESS -i attachDB.sql -v database="%1" -v root="%CD%"

We are simply calling sqlcmd (added to our path) to connect to our local instance of SQL Server Express and we are executing an SQL file (attachDB.sql) which includes two variables: database and root (the current path).

Here is the code for attachDB.sql:

USE MASTER
GO
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[$(database)]') AND type in (N'U'))
  ALTER DATABASE $(database) SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
 
create table #backupInformation (LogicalName varchar(100),
PhysicalName varchar(100),
Type varchar(1),
FileGroupName varchar(50) ,
Size bigint ,
MaxSize bigint,
FileId int,
CreateLSN int,
DropLSN int,
UniqueId uniqueidentifier,
ReadOnlyLSN int,
ReadWriteLSN int,
BackupSizeInBytes int,
SourceBlockSize int,
FileGroupId int,
LogGroupGUID uniqueidentifier,
DifferentialBaseLSN bigint,
DifferentialBaseGUID uniqueidentifier,
IsReadOnly bit, IsPresent bit )
 
insert into #backupInformation exec('restore filelistonly from disk = ''$(root)\$(database).bak''')
 
DECLARE @logicalNameD varchar(255);
DECLARE @logicalNameL varchar(255);
 
select top 1 @logicalNameD = LogicalName from #backupInformation where Type = 'D';
select top 1 @logicalNameL = LogicalName from #backupInformation where Type = 'L';
 
DROP TABLE #backupInformation 
 
RESTORE DATABASE $(database)
FROM DISK = '$(root)\$(database).bak'
WITH REPLACE,
MOVE @logicalNameD TO 'C:\Program Files (x86)\Microsoft SQL Server\MSSQL.1\MSSQL\Data\$(database).mdf',
MOVE @logicalNameL TO 'C:\Program Files (x86)\Microsoft SQL Server\MSSQL.1\MSSQL\Data\$(database).ldf'
GO

Simply put, we are extracting the logical names (and other metadata) from the .bak file into a temporary table. We then use those values to restore the MDF and LDF to the correct location, instead of the ones specified in the .bak file.

If you want to use this script, simply ensure you change the location of your SQL Server data files (the last lines in the SQL file) and you should be good to go. Please note that in its current form, the script only supports files with one MDF and one LDF file in the database backup. Furthermore, it assumes your .bak file has the same name as the database you want to import. We could also enhance the script by automatically adding permissions to the ASP.NET user after restoring the database. Feel free to post any enhancements you make in this post's comments and I hope you'll find this script useful! Enjoy.

kick it on DotNetKicks.com


BlogEngine.net Post Security

clock August 13, 2008 15:23 by author EtienneT

BlogEngine.net 1.3/1.4 supports user roles.  But we can't seem to be able to make it mandatory for users to sign in to see blog posts.  That's not something you usually want on a public blog, but for a corporate blog, maybe you want to make sure your news only gets to the people you want.  This seemed like a perfect candidate for a BlogEngine extension.

User Filtering

In our scenario, we don’t want any unregistered users to be able to see blog posts.  This can be easily checked by calling Membership.GetUser() and ensuring the returned value is not null.  We could filter out specific users as well, but we didn’t implement this feature in our extension.

Post Filtering

It could be interesting to restrict who can see the posts in a specific blog category.  For example, a blog category “Top Secret” which can only be read by your company's upper management…  Not very likely in a blog, but you get the point.  Our extension does this filtering by associating a blog Category with a membership Role in the extension’s settings.

image

By associating a membership role with a blog category name, the extension ensures the user has this role before displaying a post associated with this blog category name.  If you add two roles for the same category, posts with this category will only be served if the user has both roles.

Adding a setting with an empty category name will ensure that all posts require a particular role.

Code

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using BlogEngine.Core;
using BlogEngine.Core.Web.Controls;
using System.Collections.Generic;
 
/// <summary>
/// Summary description for PostSecurity
/// </summary>
[Extension("Checks to see if a user can see this blog post.",
            "1.0", "<a href=\"http://www.lavablast.com\">LavaBlast.com</a>")]
public class PostSecurity
{
    static protected ExtensionSettings settings = null;
 
    public PostSecurity()
    {
        Post.Serving += new EventHandler<ServingEventArgs>(Post_Serving);
 
        ExtensionSettings s = new ExtensionSettings("PostSecurity");
 
        s.AddParameter("Role", "Role", 50, true);
        s.AddParameter("Category", "Category", 50);
 
        // describe specific rules for entering parameters
        s.Help = "Checks to see if the user has any of those roles before displaying the post. ";
        s.Help += "You can associate a role with a specific category. ";
        s.Help += "All posts having this category will require that the user have the role. ";
        s.Help += "A parameter with only a role without a category will enable to filter all posts to this role. ";
 
        s.AddValues(new string[] { "Registered", "" });
 
        ExtensionManager.ImportSettings(s);
        settings = ExtensionManager.GetSettings("PostSecurity");
    }
 
    protected void Post_Serving(object sender, ServingEventArgs e)
    {
        Post post = (Post)sender;
        bool continu = false;
 
        MembershipUser user = Membership.GetUser();
 
        continu = user != null;
 
        if (user != null)
        {
            List<string> categories = new List<string>();
            foreach (Category cat in post.Categories)
                categories.Add(cat.Title);
 
            string[] r = Roles.GetRolesForUser();
 
            List<string> roles = new List<string>(r);
 
            DataTable table = settings.GetDataTable();
            foreach (DataRow row in table.Rows)
            {
                if (string.IsNullOrEmpty((string)row["Category"]))
                    continu &= roles.Contains((string)row["Role"]);
                else
                {
                    if (categories.Contains((string)row["Category"]))
                        continu &= roles.Contains((string)row["Role"]);
                }
            }
        }
 
        e.Cancel = !continu;
    }
}

 

Simply saving this code in a .cs and putting it in your App_Code/Extensions for BlogEngine.net shall enable the plugin.

kick it on DotNetKicks.com


jQuery Content Slider Tutorial

clock July 22, 2008 15:22 by author EtienneT

Simple Demo (Firefox, IE7, IE6, Opera, Safari) | Demo with content | Source Code

LavaBlast launched a new front page recently and we have incorporated a new jQuery slider. You can see it in action below (in the Flash animation) or you can go see it directly on our website home page.

Scroll down to find out how this little puppy was implemented!

 

 

HTML & CSS

HTML

This is the basic ASP.NET in the ASPX page:

<div class="FrontMenu">
    <div class="Bar">
        <asp:Repeater ID="Repeater" runat="server" onitemdatabound="Repeater_ItemDataBound">
            <ItemTemplate>
                <span class="item">
                    <a href="#" class="jFlowControl">
                        <asp:Label ID="lbl" runat="server" />
                    </a>
                    <div class="spike" style="z-index:999999">
                    </div>
                    <div class="right">
                    </div>
                </span>
            </ItemTemplate>
        </asp:Repeater>
    </div>
    <div class="Panel">
        <div><cms:Paragraph ID="paragraph1" runat="server" ContentName="frontMenu1" /></div>
        <div><cms:Paragraph ID="paragraph2" runat="server" ContentName="frontMenu2" /></div>
        <div><cms:Paragraph ID="paragraph3" runat="server" ContentName="frontMenu3" /></div>
        <div><cms:Paragraph ID="paragraph4" runat="server" ContentName="frontMenu4" /></div>
        <div><cms:Paragraph ID="paragraph6" runat="server" ContentName="frontMenu5" /></div>
    </div>
</div>

This is pretty simple HTML.  Everything is enclosed in the main div with the css class FrontMenu.  We have the animated bar at the top and the content panel underneath it.  The menu bar is generated by a simple repeater control bound to data in our ASP.NET page. Each menu item is a span containing a link that we’ll use to change the selected menu item.  The Panel div contains multiple dynamic paragraphs from our content management system (SubSonic CMS).  You could easily change this code to bind to data from another source.

Here is the code behind for this control.  We kept it pretty simple:

public partial class FrontPageMenu : UserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            List<string> list = new List<string>();
            list.Add("How can we help?");
            list.Add("Our Products");
            list.Add("Hot Features");
            list.Add("Testimonials");
            list.Add("Read");
 
            Repeater.DataSource = list;
            Repeater.DataBind();
        }
    }
 
    protected void Repeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
    {
        if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
        {
            Label lbl = e.Item.FindControl("lbl") as Label;
            lbl.Text = (string)e.Item.DataItem;
        }
    }
}

 

image

The following picture shows how the html menu item elements are rendered.  The CSS section of this article will dive deeper in the inner workings of the menu.

image

JQuery

jFlow

We used jFlow to scroll our panels when we click on a menu item.  The code was fairly straightforward to use.  We included this script in our user control.

$(".FrontMenu").jFlow({
    slides: ".FrontMenu .Panel",
    width: "974px",
    height: "344px",
    duration: 300
});

And then we included a simple reference to our script manager.

<asp:ScriptManagerProxy ID="proxy" runat="server">
    <Scripts>
        <asp:ScriptReference Path="~/js/jquery.flow.1.0.min.js" />
    </Scripts>
</asp:ScriptManagerProxy>

 

We could have used Next and Previous buttons but decided not to.

 

IE6 Problems

This menu did require some basic jQuery.  Except for the scrolling animation, we could have built almost all of this in pure CSS thanks to the hover pseudo-class. However, Internet Explorer 6 came back from the grave to haunt us…  As we still have some visitors who are using IE6, we could not afford to let our home page break down in IE6.  Microsoft gods: please please, hear our prayers and find a way to erase IE6 from the face of earth.  But hey, we've got work to do and can't wait five years for the browser to die like IE 5 did.

We discovered that using a:hover in IE6 to change our background image will make the browser crash.  We used IETester to test the menu in IE6: it made IETester crash.  We then tried Virtual PC running Win98 and IE6: Internet Explorer crashed again when we hovered over one of the links with a:hover CSS styles.

The solution to this problem was simply to apply a style to the hovered link (.hover). Then we could easily style the children of this element to our liking without breaking IE6.

$(".FrontMenu .Bar a").mouseover(function() { $(this).addClass("hover"); });
$(".FrontMenu .Bar a").mouseout(function() { $(this).removeClass("hover"); });

To change the style of the selected item we added the css class .sel to the span.item (the parent of the clicked link).  First of all, when a link is clicked, remove the currently selected item.  Second, set the parent of the link as the current selected item.  It’s important to return false as otherwise the browser will follow the link and scroll to the top of the page.

$("div.FrontMenu div.Bar a").click(function()
{
    $("div.FrontMenu div.Bar").children("span.sel").removeClass("sel");
    $(this).parent().addClass("sel");
    return false;
});

 

CSS

Let’s take a deeper look at the menu’s CSS.  Let’s start with the FirstMenu class which is not too complicated.

.FrontMenu
{
    padding: 5px 5px 0px 5px;
    margin-left: 2px;
    margin-top: -4px;
}
 
.FrontMenu .Bar
{
    background: #F6ECA4 url(images/MainPage/bar.jpg) no-repeat top left;
    width: 980px;
    height: 48px;
    position: relative;
}
 
.FrontMenu .Bar a
{
    color: #FFFFFF;
    font-size: large;
    font-family: Tahoma;
    position: relative;
    top: 7px;
    display: block;
    text-decoration: none;
    padding-right: 6px;
    margin-right: -6px;
    cursor: pointer;
}

Now let’s take a closer look at the Bar menu.  Here are the images we used to style our menu items.

CSS Images    
.FrontMenu .Bar span.sel

selLeft.gif

selLeft

selRight.gif

selRight

spike.gif

spike

.FrontMenu .Bar a.hover span

hoverLeft.gif

hoverLeft

hoverRight.gif

 

 

hoverRight

 

Like you saw in the jQuery part, we change the class in JavaScript to bypass some IE6 issues, so you should not be surprised by the CSS.

The code for the main span for each menu item:

.FrontMenu .Bar span.item
{
    line-height: 30px;
    margin: 0px 0px;
    float: left;
    position: relative;
    display: inline;
    cursor: pointer;
    width: 188px;
    text-align: center;
    margin-left: 6px;
}

Here is the code when you hover over the link inside the menu item:

/* We have to handle hover with jQuery because :hover makes IE6 crash when we change the background image. */
.FrontMenu .Bar a.hover
{
    background: transparent url(images/MainPage/hoverRight.gif) no-repeat top right;
    height: 30px;
}
 
/* We have to handle hover with jQuery because :hover makes IE6 crash when we change the background image. */
.FrontMenu .Bar a.hover span
{
    background: transparent url(images/MainPage/hoverLeft.gif) no-repeat top left;
    height: 30px;
    display: block;
}

 

Like you can see, the link contains the hoverLeft  background image and the span inside the link contains the hoverRight.  This enables the link to have any length and the control will resize easily.  If you ever get a link that is wider than the left image, simply make the image wider...

Then we only needed the CSS to change the menu item to make it look selected.

 

 

 

 

.FrontMenu .Bar span.sel a:hover { background: none; padding-left: 0px; margin-left: 0px; }
.FrontMenu .Bar span.sel a:hover span { background: none; padding-left: 0px; margin-left: 0px; }
 
.FrontMenu .Bar span.sel
{
    background: transparent url(images/MainPage/selLeft.gif) no-repeat top left;
    height: 48px;
}
 
.FrontMenu .Bar span.item .spike, .FrontMenu .Bar span.sel .spike
{
    background: transparent url(images/MainPage/spike.gif) no-repeat top left;
    display:none;
    position: absolute;
    top: 44px;
    left: 50%;
    margin-left: -11px;
    width: 22px;
    height: 17px;
    z-index: 9999;
}
 
.FrontMenu .Bar span.sel .spike
{
    display: block;
}
 
.FrontMenu .Bar span.sel .right
{
    background: transparent url(images/MainPage/selRight.gif) no-repeat top right;
    position: absolute;
    height: 48px;
    width: 4px;
    right: 0px;
    top: 0px;
}
 
.FrontMenu .Bar span.sel a { color: #d43300; }

 

The parent span for the menu item with the .sel class contains the selLeft image and the div.right inside this span contains the selRight image.  We have to make sure too that the hover style does not get applied when the item is selected.

The spike spike is applied in absolute position in the center.  To do that in absolute position, you have to set the following:

left: 50%;
margin-left: -11px;  // <—This is the width/2
width: 22px;

Even with a higher z-index, we were not able to make the spike go on top of the content panel in IE6.  Therefore, we had to put a margin on top of content panel just to make sure the spike was not overlapping the content panel below:

.FrontMenu div.Panel
{
    height: 328px;
    width: 974px;
    margin-top: 15px;
}

 

 

Simple Demo (Firefox, IE7, IE6, Opera, Safari) | Demo with content | Source Code

kick it on DotNetKicks.com



Image Post Processing Caching

clock July 15, 2008 13:41 by author EtienneT

Complete Source Code | Small Demo

This article present a small class library that abstracts opening, modifying (applying effects, resizing, etc.), and caching images in ASP.NET.  Everything needs to be abstracted to ensure the code is easily testable (opening, modifying, and caching of the images).  You may want to resize your images or convert them to black and white and cache the result, and want to test these operations.

You want to be able to read image data from different sources:

  • An image on the local disk on the web server
  • A remote image on the Internet that you want to download and cache
  • An image in your database

You want to be able to apply any number of post processing algorithms to the resulting image:

  • Resize the image (generate thumbnails)
  • Apply an image filter such as convert to black and white
  • Do anything on the image that requires computing and where caching the result proves beneficial from a performance standpoint. 

CacheManager

First of all, let's look at a nice caching class I found on a DotNetKicks kicked story.  Zack Owens gave a nice piece of code in his blog to help you manage your ASP.NET cached objects.  The goal of this class is simply to let you have a strongly typed way to access your cached objects.  Here is the code for the class with some slight modifications:

public class CacheManager<T> where T : class
{
    private Cache cache;
    private static CacheItemRemovedCallback callback;
    private static object _lock = new object();
 
    private TimeSpan cacheTime = new TimeSpan(1, 0, 0, 0); // Default 1 day
 
    public CacheManager(Cache cache)
    {
        this.cache = cache;
        if(callback == null)
            callback = new CacheItemRemovedCallback(RemovedFromCache);
    }
 
    public T Get(string key)
    {
        try
        {
            lock (_lock)
            {
                if (cache[key] == null)
                    return default(T);
 
                T b = CastToT(cache[key]);
 
                return b;
            }
        }
        catch (ArgumentException ex) // The object was disposed by something! return null;
        {
            return null;
        }
    }
 
    public void Add(string key, T obj, TimeSpan cacheTime)
    {
        lock (_lock)
        {
            if (obj != null)
                cache.Add(key, CastFromT(obj), null, DateTime.Now.Add(cacheTime), Cache.NoSlidingExpiration, CacheItemPriority.Default, callback);
        }
    }
 
    protected void RemovedFromCache(string key, object o, CacheItemRemovedReason reason)
    {
        T obj = o as T;
        if (obj != null)
        {
            lock (_lock)
            {
                DisposeObject(obj);
            }
        }
    }
 
    protected virtual void DisposeObject(T obj) { }
 
    protected virtual T CastToT(object obj) { return obj as T; }
 
    protected virtual object CastFromT(T obj) { return obj as T; }
 
    public TimeSpan CacheTime
    {
        get { return cacheTime; }
        set { cacheTime = value; }
    }
}

As you can see, this is a pretty simple class.  We defined some virtual methods to be implemented in our child class, for example DisposeObject if you want to cache disposable objects (continue to read if you want to know why this is a really bad idea).

The constructor requires a Cache object; we can simply pass along the Page's Cache (Page.Cache) to make it happy.  We now want to derive from CacheManager to help us in our main task which is to cache modified images.

ImageCacheManager

To create our image-specific cache manager, we defined a new class called ImageCacheManager which is a subclass of CacheManager  and will cache byte arrays (our images).  We implemented this feature in the past, but did a big mistake that led to a mysterious bug.  We were caching Bitmap objects in the ASP.NET cache but this was a big, big mistake.  Bitmap objects are GDI+ managed objects and they need to get disposed.  Even if we had methods to dispose the Bitmap when they were removed from the cache, some Bitmap objects were disposed while still in the cache (because of a memory leak elsewhere in the application). This caused errors downstream when we tried to use those objects later.  The lesson: we'll only cache only byte[] of the images.

The default image format is PNG in our case, but you can specify your own in the constructor.  In our case we are using PNG because we are in a controlled environment where we know everyone is using IE7, so we can use transparent PNG.  You probably want to use a different format for general public web sites since IE6 doesn't support transparent PNG.

This class will enable to download remote images and cache them locally, as well.  We needed this feature since we have a lot of remote point of sales which synchronize their product list from a central database.  We didn't want to send product images during synchronization because it would have been too much data.  Instead we decided to store our images on a central server and since our stores always have Internet access, they download and cache the images via this image cache manager.  In our product, when a franchisor changes a product image in the main database, the cached version of the picture in the point of sale will expire within the next day and the new picture would be downloaded when used.

ImageCacheManager is an abstract class.  It implements image caching and handles the fact that you want to apply post processing to an image with options (interface IImagePostProcess) and it abstracts the way you load the image (interface IImageReader).  Here is the code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Caching;
using System.Drawing;
using System.IO;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Web;
using System.Net;
using LavaBlast.ImageCaching.PostProcess;
using LavaBlast.ImageCaching.Reader;
 
namespace LavaBlast.ImageCaching
{
    /// <summary>
    /// This class specialize in caching modified images.  Images are cached as
    /// byte[].  You can apply different modifications in serial to the image
    /// before caching it.
    /// 
    /// Construct a cache key depending on the options of your image post processing.
    /// This enables to cache copies of an image with different post processing applied to it.
    /// 
    /// Control how the image will be read.  On local disk, via Internet etc.
    /// </summary>
    public abstract class ImageCacheManager : CacheManager<byte[]>
    {
        public ImageCacheManager(Cache cache) : this(cache, ImageFormat.Png) { }
 
        protected Dictionary<string, IImagePostProcess> postProcess = new Dictionary<string, IImagePostProcess>();
 
        protected ImageFormat format = ImageFormat.Png; // Default image format PNG
 
        public ImageCacheManager(Cache cache, ImageFormat format) : base(cache)
        {
            this.format = format;
 
            InitImagePostProcess();
        }
 
        /// <summary>
        /// Determine which image reader will be used to read this image.
        /// </summary>
        /// <param name="uriPath"></param>
        /// <returns></returns>
        protected abstract IImageReader GetReader(Uri uriPath);
 
        /// <summary>
        /// Fill the variable postProcess with post processing to apply
        /// to an image each time.
        /// </summary>
        protected abstract void InitImagePostProcess();
 
        /// <summary>
        /// This method shall return a unique key depending on the path of the
        /// image plus the options of it's post processing process.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        protected abstract string ConstructKey(Uri path, Dictionary<string, object> options);
 
        /// <summary>
        /// Get an image from the following path.  Use the provided options to use in post processing.
        /// If refresh is true, don't use the cached version.
        /// </summary>
        /// <param name="uriPath"></param>
        /// <param name="options"></param>
        /// <param name="refresh"></param>
        /// <returns></returns>
        protected byte[] GetImage(Uri uriPath, Dictionary<string, object> options, bool refresh)
        {
            string key = ConstructKey(uriPath, options);
            byte[] cached = Get(key);
 
            if (cached != null && !refresh)
                return cached;
            else
            {
                try
                {
                    byte[] image = ReadBitmap(uriPath); // Get the original data from the image
 
                    byte[] modified = PostProcess(image, options); // Do any post processing on the image (resize it or apply some effects)
 
                    Add(key, modified, CacheTime); // Add this modified version to the cache
 
                    return modified;
                }
                catch
                {
                    return null;
                }
            }
        }
 
        /// <summary>
        /// Run all post processing process on the image and return the resulting image.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        protected byte[] PostProcess(byte[] input, Dictionary<string, object> options)
        {
            byte[] result = input;
 
            foreach (string key in postProcess.Keys)
                result = postProcess[key].Process(result, options[key]);
 
            return result;
        }
 
        /// <summary>
        /// From a path, return a byte[] of the image.
        /// </summary>
        /// <param name="uriPath"></param>
        /// <returns></returns>
        protected byte[] ReadBitmap(Uri uriPath)
        {
            using (Stream stream = GetReader(uriPath).GetData(uriPath))
            {
 
                byte[] data = new byte[0];
 
                Bitmap pict = null;
 
                try
                {
                    pict = new Bitmap(stream);
                    data = ImageHelper.GetBytes(pict, format);
                }
                catch
                {
                    return null;
                }
                finally
                {
                    if (pict != null)
                        pict.Dispose();
                }
 
                stream.Close();
 
                return data;
            }
        }
    }
}

Because this is an abstract base class, we need a concrete implementation of ImageCacheManager.  We created ThumbnailCacheManager.  ThumbnailCacheManager checks the URI if it’s a local or remote file and uses the right image reader.  It has only one post processing task (resizing the image), but it could have more.  It construct the unique key for the cache from the processing task’s options.

Image Resizing

Here is a quick example of a typical image processing task: resizing it.  The class implement the simple method Process(byte[] input, object op) where op is in fact the options of the post processing process.  I could not use generics in my IImagePostProcess interface because of the way I store them later…  Here is a quick code example of how to resize an image.

namespace LavaBlast.ImageCaching.PostProcess
{
    /// <summary>
    /// Post processing that take an image and resize it
    /// </summary>
    public class ImageResizePostProcess : IImagePostProcess
    {
        public byte[] Process(byte[] input, object op)
        {
            byte[] oThumbNail;
 
            ImageResizeOptions options = (ImageResizeOptions)op;
 
            Bitmap pict = null, thumb = null;
 
            try
            {
                using (MemoryStream s = new MemoryStream(input))
                {
                    pict = new Bitmap(s); // Initial picture
 
                    s.Close();
                }
 
                thumb = new Bitmap(options.Size.Width, options.Size.Height); // Future thumb picture
 
                using (Graphics oGraphic = Graphics.FromImage(thumb))
                {
                    oGraphic.CompositingQuality = CompositingQuality.HighQuality;
                    oGraphic.SmoothingMode = SmoothingMode.HighQuality;
                    oGraphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    oGraphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
                    Rectangle oRectangle = new Rectangle(0, 0, options.Size.Width, options.Size.Height);
 
                    oGraphic.DrawImage(pict, oRectangle);
 
                    oThumbNail = ImageHelper.GetBytes(thumb, options.ImageFormat);
 
                    oGraphic.Dispose();
 
                    return oThumbNail;
                }
            }
            catch
            {
                return null;
            }
            finally
            {
                if (thumb != null)
                    thumb.Dispose();
                if (pict != null)
                    pict.Dispose();
            }
        }
    }
 
    public class ImageResizeOptions
    {
        public Size Size = Size.Empty;
        public ImageFormat ImageFormat = ImageFormat.Png;
    }
}

 

Reading the picture

Reading the picture is the easy part and I have included two implementations of IImageReader: one for a local images and one for a remote images.  You could easily implement one which loads images from you database.

LocalImageReader

/// <summary>
    /// An image reader to read images on local disk.
    /// </summary>
    public class LocalImageReader : IImageReader
    {
        public Stream GetData(Uri path)
        {
            FileStream stream = new FileStream(path.LocalPath, FileMode.Open);
 
            return stream;
        }
    }

RemoteImageReader

/// <summary>
    /// Image reader to read remote image on the web.
    /// </summary>
    public class RemoteImageReader : IImageReader
    {
        public Stream GetData(Uri url)
        {
            string path = url.ToString();
            try
            {
                if (path.StartsWith("~/"))
                    path = "file://" + HttpRuntime.AppDomainAppPath + path.Substring(2, path.Length - 2);
 
                WebRequest request = (WebRequest)WebRequest.Create(new Uri(path));
 
                WebResponse response = request.GetResponse() as WebResponse;
 
                return response.GetResponseStream();
            }
            catch { return new MemoryStream(); } // Don't make the program crash just because we have a picture which failed downloading
        }
    }

HttpHandler

Finally, you want to serve those images with an HttpHandler. The code for the HttpHandler is pretty simple as you only need to parse the parameters from the QueryString and pass them to the ThumbnailCacheManager presented above.  The handler receives a parameter “p” for the path of the image (local or remote) and a parameter “refresh” which can be used to ignore the cached version of the image.  Additionally, we can pass parameters such as “width” and “height” for our image resizing.  Warning: you must adapt this code to your environment otherwise you are exposing a security hole because of the path parameter.

When debugging your image caching HttpHandler, don't forget to clear your temporary Internet files from IE or FireFox because your images will also be cached in your web browser otherwise your code will not be executed!

using System;
using System.Collections;
using System.Data;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using LavaBlast.ImageCaching;
 
namespace WebApplication
{
    /// <summary>
    /// Really simple HttpHandler to output an image.
    /// </summary>
    public class ImageCaching : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            string path = context.Request.Params["p"] ?? "";
            bool refresh = context.Request.Params["refresh"] == "true";
 
            int width, height;
            if (!int.TryParse(context.Request.QueryString["width"], out width))
                width = 300;
            if (!int.TryParse(context.Request.QueryString["height"], out height))
                height = 60;
 
            byte[] image = null;
            ThumbnailCacheManager manager = new ThumbnailCacheManager(context.Cache);
            image = manager.GetThumbnail(path, width, height);
 
            if (image != null)
            {
                context.Response.ContentType = "image/png";
                try
                {
                    context.Response.OutputStream.Write(image, 0, image.Length);
                }
                catch { }
 
                context.Response.End();
            }
        }
 
        public bool IsReusable
        {
            get
            {
                return true;
            }
        }
    }
}

 

 

Source Code | Small Demo

kick it on DotNetKicks.com



Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010

Sign in