Home > .net 3.5, c#, LINQ, Visual Studio 2008 > WebGallery 2.0 #2 – HttpHandler and LINQ Data Model

WebGallery 2.0 #2 – HttpHandler and LINQ Data Model

February 20, 2008

This is the second in a series of posts describing the changes to the WebGallery in version 2.0.  This post will cover the changes and coding techniques applied to the HttpHandler and the new LINQ-to-SQL data model.

#1 – Introduction

NOTE: Code downloads are located on the introduction post (easiest way to keep it current).

The Http Handler

The Http Handler ( WebStorageHandler.ashx ) is used to return files, images, and corresponding thumbnails based on query string parameters passed to it.

In version 1, the WebStorageHandler simply managed whether or not the object was a file (in the old data structure, ObjectType was not stored) and whether or not to send a thumbnail or full image.  The remaining streaming was left to the objects.  I found, with maintenance, that the coupling between the Photo and File objects and Page (for Response.BinaryWrite) was a real hassle to maintain.

In version 2, the WebStorageHandler truly handles everything—the objects are blind to whether or not they are going out a Response stream or being used on a Windows form.  The old idea of separate objects for Photos and Files has been replaced with a single LINQ entity—WebFile.  The handler uses the Linq.Binary object (which is an ‘image’ in the SQL database) directly.

int id =

Convert.ToInt32(context.Request.QueryString[“id”]);

bool thumbnail =

Convert.ToBoolean(context.Request.QueryString[“tb”]);

// Generate a corresponding web file object.

WebFile webFile = db.GetWebFileById(id);

if (thumbnail)

{

if (webFile.ObjectType == “Photo”)

       {

              ReturnImageToStream(context,

                     webFile.ContentTypeId,

                    “tb_” + webFile.FileName,

                    webFile.ObjectThumbnail.ToArray());

}

       else if (webFile.ObjectType == “File”)

       {

              ReturnImageToStream(context,

                     “image/x-png”,

                    “tb_MimeThumbnail.png”,

webFile.ContentType.MimeThumbnail.ToArray());

}

}

else

{

ReturnImageToStream(context,

             webFile.ContentTypeId,

             webFile.FileName,

             webFile.ObjectBody.ToArray());

}

The ReturnImageToStream method is a refactored out method that handles sending whatever binary object to the Response stream.

protected static void ReturnImageToStream(HttpContext context,

string contentType, string fileName, byte[] binary)

{

context.Response.ContentType = contentType;

       context.Response.AppendHeader(“content-disposition”,

“inline;filename=” + fileName + “;”);

context.Response.OutputStream.Write(binary, 0, binary.Length);

context.Response.End();

}

There is a glitch in the method as it stands.  As I mentioned in the first post, the goal of this project is to be fully XHTML 1.1 compliant and compatable with FireFox 2.0/3.0 (Preview), IE 6.0, IE 7.0, Safari (3.0.4), and Opera (9.25).  In almost all areas, the application is compatible and compliant… almost all areas. 😉

In our ReturnImageToStream, there is a glitch with Safari, as noted here in this WebKit bug report.  The content-disposition tag appears to be ignored in Safari, so the files show up as WebStorageHandler.jpg, etc.  It’s not a showstopper, but if you have a heavy Safari community using your site, its something to be aware of.

The WebStorage DataContext – LINQ to SQL

In past projects, version 1 included, I built nice n-tier applications.  I still do, to a degree, depending on the scope of the application; however, LINQ changes the complexity of building up the data layer.  Rather than TSQL, SPROCs, and managing custom classes, I drag my database tables onto the sheet, drag my SPROCs into the Methods window—and I have instant, matching custom classes to begin working.

For more information on LINQ, here are a few of my other posts.

  • Trimming the If/Then’s with LINQ
  • LINQ Methods – Using LINQ to Transform Your Data
  • LINQ – IQueryable, Mathematical Methods, and .Take (Top N)
  • The power of LINQ is easily seen in our WebStorageHandler—addressing binary fields directly (webFile.ObjectBody.ToArray()) and relationships directly through heirarchial notation (webFile.ContentType.MimeThumbnail.ToArray()).  The technology is extremely powerful, and, due to the DataContext being a partial class, very extensible.

    The WebGallery.designer.cs creates our DataContext’s partial class.  To add additional methods (since modifications to the designer.cs will be overridden when the LINQ GUI is updated), you can add a new partial class to your project named the same as the DataContext—in this case, WebGalleryDataContext.

    namespace WebGallery.Models

    {

        public partial class WebGalleryDataContext

        {

           

        }

    }

    Inside this partial class, we can add our data layer “additional” methods.

    Note: I do this to keep my code clean. There’s nothing preventing you from CRUD’ing right on your ASPX pages, but I prefer to keep my selects in one location (like n-tier) and use the DataContext AS my data layer. 

    For example, to have a nice, clean “Get” method:

    public List<T> Get<T>() where T : class

    {

    return GetTable<T>().ToList();

    }

    and can use it:

    List<Gallery> gallery = db.Get<Gallery>();

    List<WebFile> files = db.Get<WebFile>();

    Other, non-generic methods also exist within the partial class.  These methods have another tier of information associated with them—business logic.  Would it be ideal to separate out the data and business logic?  In a large application, probably, at least I have been.  In a small, single data source application like this—no, I honestly think the data context provides enough separation.

    In this example, a simple SELECT is being performed, but with an ordering clause.  This code could be repeated in dozens of places—but why?  DRY!

    public List<WebFile> GetWebFilesByGalleryName(string id)

    {

    return WebFiles.

    Where(i => i.Gallery.Name == id).

    OrderByDescending(i => i.DateCreated).ToList();

    }

    or, role business logic for determing who sees what galleries (which, Roles and Membership are really annoying to try to LINQ to, if you have ideas on how to directly attach to the membership and roles, please email me or comment):

    public List<Gallery> GetGalleriesByRole(IPrincipal user)

    {

    var galleryList = new List<Gallery>();

               

           // If an administrator, return all galleries;

    // they have access to them all.

           if (user.IsInRole(“Administrators”))

                  return Galleries.ToList();

               

    // else, go through each gallery and match

    // to the user’s role and Public.

           foreach (var gallery in Galleries)

           {

                  if (user.IsInRole(gallery.Role) ||

    gallery.Role == “Public”)

                 galleryList.Add(gallery);

           }

    return galleryList;

    }

    In the next post, we’ll dig into the presentation side of things—the galleries, administration consoles, and how those pages use the resources (the WebStorageHandler and DataContext) to generate clean, compliant HTML code.