Brian David Berman: Technical Blog

Killing multiple birds with one Picasa

Posted in ASP.NET MVC, Business by briandberman on January 22, 2012

Last year, my wife Justine and I started a photography business. To be fair, she is the real talent of the company while I just handle the finances, legal, and web presence (social media, website, etc.) We quickly realized that we needed a way to attract clients and after doing extensive research we agreed that online advertising was the way to go and defined our primary marketing conversion path as such:

  1. Potential client finds us through a website that we advertise on
  2. Potential client decides, based on our ad, that we fit their style and seem like a good fit
  3. Potential client clicks a link in our ad and is brought to our website
  4. Our website immediately shows various image galleries of our work
  5. Potential client enjoys what they see and fills out the contact form on our website
  6. We are notified by email that someone is interested in our services
  7. Conversion complete

The area that I am going to touch on today is step 4 from above, the image gallery. Now, this seems like a no brainer to implement. There are literally hundreds of image gallery projects out there that I can download, extend and use on our site, right? Wrong.

By “wrong”, I mean “wrong, if you are me” and by “me”, I mean “married to my wife”. Let’s just say she is particular when it comes to her work and what we were seeing out there for existing photo galleries just wasn’t doing it for us. The only exception would have been a couple Flash-based photo galleries and that was obviously out of the question. So one thing led to another and I built a home-grown photo gallery in jQuery and everyone was happy.

Screenshot of Photo Gallery The gallery code I designed was powered by a collection of JSON objects. The JSON object looks as follows:

{
  LargeImage: "http://test.com/largeImage.jpg",
  ThumbImage: "http://test.com/thumbImage.jpg"
  AlternateText: "Some alt text",
  Order: 6,
  Page: 1,
}

But the happiness was short-lived. Short-lived because I didn’t consider the workflow for updating the galleries. How would she update the galleries? Would I have to be involved every time? Can the sizing of the thumbnails be automated at least?

It turned out that I would have to be involved every time she wanted to add photos to a gallery. Let’s say for example, she wanted to add 10 photos from a wedding photo shoot. She would provide me with the images and thumbnails (custom cropped thumbnails which means automation wasn’t possible.) I would then have to upload the new images to our development environment to view them with the other photos. If this was the end of this manual process, I probably would have been ok with it, but it wasn’t. Now I needed to work with her while she had me try different arrangements of thumbnails to see what looked right together. At the time, this involved me manually updating the JSON object, changing the “Order” property over and over until she was happy with the arrangement of the thumbnails. As you can imagine, this process was rather time consuming, especially at the frequency in which she wanted to update and keep the galleries fresh. So I had to think of a solution.

At one point, I played with the idea of creating a user interface that allowed her to upload images and thumbnails and have her move images around (drag and drop) until she was happy with the arrangement, then she could press “submit” or “save” and it would generate the JSON collection that the site would read. I was very close to implementing this but then it hit me. I had previously been unsuccessful when trying to incorporate a third party tool, but I had an idea that I knew would end this manual process and allow me to completely remove myself from it. Enter Google!

If you don’t know already, Google offers a pretty nice service for hosting images called Picasa Web Albums (PWA). It allows you to store, tag, share and host images for free. It even includes a very cool drag-and-drop HTML5 uploader in the browser. But the most important feature, and the key to bringing this altogether, was something called the Google Data Protocol. Specifically, the APIs within the Google.GData.Photos namespace. What this allows us to do is set up galleries within Picasa Web Albums and have the order, configuration, and even meta data entered there drive our website with very little code.

Thumbnails in Picasa Web Albums Website gallery
left: album set up in Picasa Web Albumsright: website automatically driven from Picasa Web Albums

So how does all this work? It’s really quite simple. In order to create and manage a gallery, you have to do the following:

  • Create 2 new albums in PWA, one for large images and another for thumbnails. Name the albums something such as FuzzyOnes and FuzzyOnesThumbs respectively.
  • Upload thumbnail images into FuzzyOnesThumbs first. This is where we will manage the order of images. We can drag, drop, move, arrange, etc. until we are happy with everything. The only thing we need to be concerned with is that the uploaded images are sized properly and have -Thumb in the filename, such as HappyDog-Thumb.jpg.
  • Upload large images into FuzzyOnes next. Once again, we need to make sure the images are sized properly and that they don’t have -Thumb in the filename, such as HappyDog.jpg. The naming is what ties the images together later on.
Once the albums are set up, the website does the rest of the work. Remember before how I mentioned that our gallery code simply takes a collection of JSON objects? The gallery code doesn’t care where the images come from or where they are hosted. If they are in the collection of JSON objects, they will be rendered.
So the only work that remains is consuming the data from PWA via the APIs within Google.GData.Photos, mapping them to a model that mirrors my JSON object and serializing it so my gallery code can do it’s thing.
My jQuery image gallery calls out to the following ASP.NET MVC route:
$(document).ready(function () {
     $.ajax({
          async: true,
          type: "POST",
          url: "../Galleries/Load/" + GalleryName,
          dataType: "json",
          complete: function (data) {
...

The ajax requests calls out to GalleriesController and executes the Load() method:

public class GalleriesController : Controller
{
     public JsonResult Load(string galleryName)
     {
          var galleryService = new GalleryService();
          return Json(galleryService.GetPhotos(galleryName), JsonRequestBehavior.AllowGet);
     }
}
The Load() method instantiates GalleryService and calls the GetPhotos() method, which returns a collection GalleryImage:
public class GalleryImage
{
     public string LargeImage { get; set; }
     public string ThumbImage { get; set; }
     public string AlternateText { get; set; }
     public int Order { get; set; }
     public int Page { get; set; }
}
public class GalleryService
{
     public List GetPhotos(string galleryName)
     {
          var picasaGalleryService = new PicasaGalleryService(galleryName);
          return picasaGalleryService.GalleryImages;
     }
}

GalleryService instantiates a more specific PicasaGalleryService, which exposes a GalleryImages property of type List

public class PicasaGalleryService
{
     private const int ImagesPerPage = 24;
     public List GalleryImages;

     public PicasaGalleryService(string galleryName)
     {
          GalleryImages = new List();

          var orderCounter = 1;
          var pageCounter = 1;

          foreach (var image in getImageSets(galleryName))
          {
               GalleryImages.Add(new GalleryImage
                    {
                         LargeImage = image.Value,
                         ThumbImage = image.Key,
                         Order = orderCounter,
                         Page = pageCounter
                    });

              pageCounter = ((orderCounter%ImagesPerPage) == 0) ? (pageCounter + 1) : pageCounter;
              orderCounter++;
          }
     }

     private static Dictionary getImageSets(string galleryName)
     {
          var picasaService = new PicasaService("Gallery");

          var thumbnailImageQuery = new PhotoQuery(PicasaQuery.CreatePicasaUri(ConfigurationSettings.AppSettings["GoogleUsername"], ConfigurationSettings.AppSettings[galleryName + "Thumbs"]));
          var thumbnailImageFeed = picasaService.Query(thumbnailImageQuery);

          var largePhotoQuery = new PhotoQuery(PicasaQuery.CreatePicasaUri(ConfigurationSettings.AppSettings["GoogleUsername"], ConfigurationSettings.AppSettings[galleryName]));
          var largePhotoFeed = picasaService.Query(largePhotoQuery);

          var imageSets = new Dictionary();

          foreach (var thumbnailImage in thumbnailImageFeed.Entries)
          {
               var proposedLargeImage = thumbnailImage.Title.Text.Replace("-Thumb", "");

               var largeImage = largePhotoFeed.Entries.Where(lpf => lpf.Content.AbsoluteUri.Contains(proposedLargeImage));

               if (largeImage.Count() == 1)
               {
                    imageSets.Add(thumbnailImage.Content.AbsoluteUri, largeImage.Single().Content.AbsoluteUri);
               }
          }

          return imageSets;
     }
}

Note: I have created app settings in the web.config to hold the Google username and the numeric representations of the PWA album name.

We are now in “set it and forget it” mode.

Now, the title of the blog post alludes to killing multiple birds with one stone (Picasa), so let’s recap what we have accomplished:

  • My wife can now manage her photo galleries via her Google account with Picasa Web Albums and our website will mirror it automatically.
  • I no longer have to be involved in the process of managing these galleries.
  • Less files to manage when creating builds, since all the gallery images exist on Google’s servers now.
  • I was able to remove all our photo gallery images from our hosting server, which saves space, bandwidth and money.
  • The content distribution of these images is now managed by Google, which means a faster and more reliable experience for our users.
  • The backup and maintenance of these images is now managed by Google, which again means more reliability.

That’s it. Thanks. If anyone out there has any comments, please let me know.

Super Simple Repository Pattern Example

Posted in ASP.NET, ASP.NET MVC by briandberman on May 7, 2009

After reading Chapter 1 of Professional ASP.NET MVC 1.0, I got a nice introduction to the “repository” pattern.  Using a repository helps us separate concerns, which is a big part of (but not limited to) ASP.NET MVC.  Let’s start off really simple by looking at a table (called Employees) I created and dragged into the LINQ to SQL Designer in Visual Studio 2008:

image

Once we save our .dbml file, Visual Studio 2008 creates an “Employee” class that exposes the available properties, allowing us to persist data back to our database.  A simple way to add a new record to this table is as follows:

 1: CompanyDataContext companyDataContext = new CompanyDataContext();
 2:
 3: Employee employee = new Employee();
 4:
 5: employee.LastName = "Berman";
 6: employee.FirstName = "Brian";
 7: employee.JobTitle = "Software Engineer";
 8: employee.Extension = "1234";
 9: employee.HireDate = DateTime.Parse("01/01/2006");
 10:
 11: companyDataContext.Employees.InsertOnSubmit(employee);
 12: companyDataContext.SubmitChanges();

The above produces the following result in the database:

image

While this may be a fine way to update a table, it is difficult to test and requires mention of the data context, as well as data storage implementation (LINQ to SQL) methods.  To fix this, we can add a “repository” layer:

 1: public class EmployeeRepository
 2: {
 3:     CompanyDataContext companyDataContext = new CompanyDataContext();
 4:
 5:     public void Add(Employee employee)
 6:     {
 7:         companyDataContext.Employees.InsertOnSubmit(employee);
 8:     }
 9:
 10:     public void Save()
 11:     {
 12:         companyDataContext.SubmitChanges();
 13:     }
 14: }

The repository class contains the data storage implementation activity, including the data context and methods.  We can then change our original code to the following:

 1: EmployeeRepository employeeRepository = new EmployeeRepository();
 2:
 3: Employee employee = new Employee();
 4:
 5: employee.LastName = "Yandle";
 6: employee.FirstName = "Justine";
 7: employee.JobTitle = "Teacher";
 8: employee.Extension = "5678";
 9: employee.HireDate = DateTime.Parse("01/01/2008");
 10:
 11: employeeRepository.Add(employee);
 12: employeeRepository.Save();

While this example doesn’t use less code, you will notice the “insert” and “save” functionality is handled through the database repository methods Add() and Save().  The result of running this code (along with our original code) produces the following in the database:

image

This allows our code to be more testable since we could now write unit tests against our objects without a database (mock objects).  Although less likely, we are also able to swap out our data storage implementation (e.g. going from LINQ to SQL to LINQ to Entities) at a later time in a much smoother fashion.  Some other example methods within the EmployeeRepository class are as follows:

 1: public class EmployeeRepository
 2:     {
 3:         CompanyDataContext companyDataContext = new CompanyDataContext();
 4:
 5:         public IQueryable<Employee> FindAllEmployees()
 6:         {
 7:             return companyDataContext.Employees;
 8:         }
 9:
 10:         public Employee GetEmployee(int id)
 11:         {
 12:             return companyDataContext.Employees.SingleOrDefault(e => e.EmployeeId == id);
 13:         }
 14:
 15:         public void Add(Employee employee)
 16:         {
 17:             companyDataContext.Employees.InsertOnSubmit(employee);
 18:         }
 19:
 20:         public void Save()
 21:         {
 22:             companyDataContext.SubmitChanges();
 23:         }
 24:     }

Hopefully this explains the repository pattern in the simplest way possible.  Please feel free to leave comments.  I am still learning and wouldn’t mind learning more through comments!  ;-)

Fun with ASP.NET MVC leads to nice oO Design discovery (Updated)

Posted in Uncategorized by briandberman on April 24, 2009

I was working through the first chapter of Professional ASP.NET MVC 1.0 and came across a really slick way to handle business rule violations.  Below is some sample code written by Scott Guthrie for the NerdDinner project.

   1: //
   2: // POST: /Dinners/Edit/2
   3: [AcceptVerbs(HttpVerbs.Post)]
   4: public ActionResult Edit(int id, FormCollection formValues)
   5: {
   6:     Dinner dinner = dinnerRepository.GetDinner(id);
   7:  
   8:     try
   9:     {
  10:         UpdateModel(dinner);
  11:  
  12:         dinnerRepository.Save();
  13:  
  14:         return RedirectToAction("Details", new { id = dinner.DinnerId });
  15:  
  16:     }
  17:  
  18:     catch
  19:     {
  20:         ModelState.AddRuleViolations(dinner.GetRuleViolations());
  21:  
  22:         return View(dinner);
  23:     }
  24: }

What’s going on here is that we are editing a Dinner object.  We pass in the ID of the Dinner we are editing as well as the form values from the form post.  At line 6, we instantiate a new dinner object based on the ID passed in.  We then “try” (lines 8-13) to update the model and save it back to the database.  If all goes well and the posted data is valid, the Dinner is saved and we are redirected (line 12).  If, on the other hand, something goes wrong (incorrect or incomplete data is passed in), it safe to say that a business rule has been violated.  Enter our “catch” (lines 14-19).  Since something went wrong, we update our model state with the violations from the GetRuleViolations method on the Dinner object.  We then return the dinner, showing the user the violations to fix.  I think this is, not only a great ASP.NET MVC example, but also a good OO design example.

(Update – Styles for code have been fixed. I am aware that I have some formatting issues.  I just opened this blog and haven’t looked at the CSS yet.  Thanks.)

My First Tech Blog Post

Posted in Thoughts by briandberman on April 23, 2009

After watching Scott Hanselman’s video on Social Networking for Developers, I’ve decided to take the plunge into technical blogging.  I’ll be posting thoughts, code samples, and anything I think is interesting and cool, when it comes to software development.  Thanks.

Follow

Get every new post delivered to your Inbox.