Upgrading to ASP.NET 4.0

Posted 08 June 2010, 22:00 | by | | Perma-link

Bit of a Link List this one, but it's got a few nuggets in there too.

Thanks to my excellent web hosts LiquidSix Hosting who upgraded our servers within about a week of ASP.NET 4.0 coming out, I've been able to focus on pulling this site over to the latest platform.

There were various reasons for wanting to do this, from cleaner web.config files, the new <%: %> syntax for HTML encoding output, and indeed answering some of my issues with Entity Framework, but obviously the key reason is it's new and shiney Grin

The key issue I had with it was that I forgot to update one small part of my web.config:

<system.web>
    <httpRuntime requestValidationMode="2.0"/>
</system.web>

As I'd forgotten to set this, I was unable to use any markup when editing my blogs. Thankfully the error message tells you exactly what to set, so the fix was easy.

Other tweaks that also went live this week:

  • TypeKit have recently opensourced their code, and added an events system, which means that in FireFox the Flicker of Unstyled Text should be somewhat less intrusive.
  • The launch of DoodleStatic to host my static files (JS, CSS, images) a small, cookieless domain to speed up download of the site infrastructure.
  • Changing my calls to jQuery and Typkit's core libraries to ajax.GoogleApis.com, so your browser should be able to find them in its cache where other sites have used them.
  • Thanks to Damien Gaurd for spending the time to point out how to use the System.ServiceModel.Syndication namespaces in the context of an ASP.NET MVC site, something I'd been meaning to do for some time.

And a few other minor tweaks to keep things tidy.

Filed under: ASP.NET, ASP.NET MVC, Site Updates

Implementing Flickr.Net

Posted 26 April 2010, 15:27 | by | | Perma-link

Flickr.NET is a ".NET library for accessing the Flickr API". It's hosted on CodePlex, and (thankfully for me) updated to version 3.0 Beta just before I downloaded it.

I host all my images here on the site, provide RSS feeds of both the latest photos and each album, however beyond using a service like twitterfeed I'd not found a nice way to integrate it into other social networks - Facebook can only accept one blog feed directly and while Google Buzz picked up on the blog feed through the "Connected Sites" options (I guess through the WebMaster tools association) it only seems to accept the first feed it finds, so I thought if I post the images to Flickr as well, then there would be more integration for them.

So, after spending a little time nosing around the Flickr API docs, I went off and got the latest version of Flickr.NET, dropped it in my projects /bin folder, and added a reference to it.

The next step was to request an API key from Flickr - you'll want a different key for each application you produce as the query limits are per key - and then set up the application - you need to give it a description, pick your application type (Desktop, Web or Mobile) and then define how you're handling authentication, for a web application this means supplying a callback url - while testing http://localhost/ addresses (with port numbers if required) worked fine.

Flickr.NET comes with it's own configuration section that you could use to set up Flickr.NET:

<flickrNet apiKey="APIKEY" secret="SHAREDSECRET" cacheDisabled="true">

Which then allows you to call the library as:

var flickr = new FlickrNet.Flickr();

However, I found that this didn't play nicely in a Medium Trust environment (the main disadvantage of shared hosting), as the constructor threw an exception complaining that the it couldn't write to the cache location (even though it's configured to be disabled) - I'll admit now that I didn't look too hard into diagnosing this issue - I'm only submitting images, rather than downloading them, so I didn't feel much need for caching.

So I pulled the API Key and Shared Secret out into the main appSettings settings, and factored out the constructor into a simple factory class:

public class FlickrControl
{
  internal static Flickr GetFlickr() {
    // Disable Cache before calling anything else
    Flickr.CacheDisabled = true;

    // Create Flickr instance with simple constructor.
    var flickr = new Flickr(WebConfigurationManager.AppSettings["FlickrApi"],
                            WebConfigurationManager.AppSettings["FlickrSecret"]);

    return flickr;
  }
}

I then created two new controllers - one to handle the initial login request, and one to handle the response back from Flickr:

// User wants to authenticate with Flickr
public ActionResult Flickr() {
  // Store calling page in session to return the user to later
  Session["Referrer"] = null != Request.UrlReferrer ?
                       Request.UrlReferrer.PathAndQuery : string.Empty;

  // Get an instance of Flickr
  var flickr = FlickrControl.GetFlickr();

  // Redirect the user to Flickr authentication service
  // asking for Delete priviledges (so we can remove images).
  return new RedirectResult(flickr.AuthCalcWebUrl(AuthLevel.Delete));
}

// Flickr is returning a logged in user
public ActionResult FlickrReturn(string frob) {
  var flickr = FlickrControl.GetFlickr();

  // Generate an Auth Token from the "frob" returned from Flickr
  var auth = flickr.AuthGetToken(frob);

  // Store the authentication token for later use
  // ExternalSites.FlickrAuth is a static string value for finding this object.
  Session[ExternalSites.FlickrAuth] = auth;

  // See if we can find the users previous request to return them to.
  var referrer = Session["Referrer"] as string;

  if (!string.IsNullOrEmpty(referrer)) {
    // We found their previous page, bounce them back
    return new RedirectResult(referrer);
  }

  return RedirectToAction("Index");
}

Then when a user uploads, edits or deletes an image from the site, we check to see if they are authenticated with Flickr, and perform the same action on Flickr:

// Inside the Upload action
// Check to see if the user is Authenticated, and that they want to upload the image
if (null != Session[ExternalSites.FlickrAuth] && editPhoto.UploadToExternal) {
  var auth = Session[ExternalSites.FlickrAuth] as Auth;

  if (auth != null) {
    var flickr = FlickrControl.GetFlickr();
    // Add the user's auth token to our Flickr instance
    flickr.AuthToken = auth.Token;

    // See below
    FlickrControl.UploadImage(photo, flickr, ImageData.FileName);

    // Upload image adds the Flickr id to the photo, so we need to save
    // that to the image as well.
    m_PhotoRepository.Save();
  }
}

This is the method for uploading an image:

internal static void UploadImage(DoodlePhoto photo, Flickr flickr, string fileName) {
  if (null == photo.PhotoDetail) {
    // If editing a photo, the image won't be in memory, so needs to be loaded.
    photo.PhotoDetailReference.Load();
  }

  if (null == photo.Album) {
    // If editing a photo, the Album info won't be in memory, so needs to be loaded.
    photo.AlbumReference.Load();
  }

  // UploadPicture returns the id of the image on Flickr
  photo.FlickrId = flickr.UploadPicture(
              // Raw stream of image bytes
              new MemoryStream(photo.PhotoDetail.BytesOriginal.ToArray()),
              // might as well pass in string.Empty here,
              // not really needed as we're passing in a stream
              fileName,
              // The title that's displayed in Flickr
              photo.Caption,
              // The description that appears under the image in Flickr
              photo.Description,
              // The tag list
              photo.CollapsedTags,
              // Set the permissions on the Album - if it's public,
              // whether Family or Friends can see it.
              photo.Album.IsPublic, true, true,
              // What type of image it is (they are effectively all photos).
              ContentType.Photo,
              // I'm not uploading anything inappropriate
              SafetyLevel.Safe,
              // What search level should be used
              photo.Album.IsPublic
                ? HiddenFromSearch.Visible
                : HiddenFromSearch.Hidden);

  // Once the image is uploaded, we need to add it to the a photo set as well.
  addPhotoToPhotoset(photo, flickr);
}

To update an existing image on Flickr I use the following method:

internal static void UpdateImage(DoodlePhoto photo, Flickr flickr) {
  // Update the metadata for an existing image
  flickr.PhotosSetMeta(photo.FlickrId, photo.Caption, photo.Description);
  // Replace all tags for an existing image
  flickr.PhotosSetTags(photo.FlickrId, photo.CollapsedTags);

  // Check to see if we've created this photoset already
  if (string.IsNullOrEmpty(photo.Album.FlickrPhotoSetId)) {
    // Photo set doesn't exist, therefore we can safetly add the image
    addPhotoToPhotoset(photo, flickr);
  } else {
    // Photoset already exists, so we need to check if the image is
    // not already in the photoset, and add it if needed
    if (!photoSetContainsPhoto(photo, flickr)) {
      addPhotoToPhotoset(photo, flickr);
    }
  }
}

To add a photo to a photoset:

private static void addPhotoToPhotoset(DoodlePhoto photo, Flickr flickr) {
  // Check to see if we've already created a photoset
  if (String.IsNullOrEmpty(photo.Album.FlickrPhotoSetId)) {
    // Create a new Photoset, with the Photo's FlickrId as the Primary Photo Id
    var photoset = flickr.PhotosetsCreate(photo.Album.Caption,
                                          photo.Album.Description,
                                          photo.FlickrId);

    // Store the Photoset Id with the Album.
    photo.Album.FlickrPhotoSetId = photoset.PhotosetId;
  } else {
    // Simply add the Photo to the Photoset.
    flickr.PhotosetsAddPhoto(photo.Album.FlickrPhotoSetId, photo.FlickrId);
  }
}

Finally, the code to check if a Photo already exists in a PhotoSet:

private static bool photoSetContainsPhoto(DoodlePhoto photo, Flickr flickr) {
  // Get the details of the photos in a Photoset, will become an issue
  // only when I add more than 500 images to a Photoset!
  var photos = flickr.PhotosetsGetPhotos(photo.Album.FlickrPhotoSetId);
  // Return true if the photo's Id appears in the list.
  return null != photos.SingleOrDefault(p => p.PhotoId == photo.FlickrId);
}

Deleting a photo from Flickr is a trivial exercise, simply requiring a call to PhotosDelete with the Flickr Id of the photo.

All in all, I found this to be very clean, and fairly intutive - especially if you take the time to read the API as you go.

Filed under: .Net, ASP.NET MVC, Site Updates

Upgrading to ASP.NET MVC 2.0

Posted 17 March 2010, 19:46 | by | | Perma-link

As you might have seen, ASP.NET MVC 2.0 was released this week at MIX along with a handy tool to help upgrade your solutions, so I decided to take the plunge and see how much effort I'd need to go through to upgrade. It turns out not much.

I also managed to move my admin section off to an area, which meant I could finally have a URL structure that allowed me to have duplicate controllers for blogs and albums, which is something I'd wanted since I built them (as opposed to an AdminBlogs controller and an AdminAlbums controller, and it meant that the Tabs controller, used for autocomplete on the photo admin screens, could be nicely grouped with them as well).

The only outstanding piece of work now is to move to using Data Annotations for my form validation, which is where the bulk of the pain I'd encountered lay.

Basically, I'd taken a similar approach as the validation pattern in Nerd Dinner (pp. 37f): I'd created a view model that implemented the following IValidatable interface:

public interface IValidatable
{
  bool IsValid { get; }
  IEnumerable<RuleViolation> GetRuleViolations();
  void OnValidate();
}

I initially created an abstract BaseValidater class, but of course you can't have multiple inheritance in C#, so this only worked for my non-entity classes… The RuleViolation is identical to the Nerd Dinner one.

So for each View Model I created an enumerable Errors with yields for each possible error, for example (line breaks added to long strings for formatting):

public IEnumerable<RuleViolation> GetRuleViolations() {
  if (string.IsNullOrEmpty(PostTitle) || 3 > PostTitle.Trim().Length) {
    yield return new RuleViolation("The title is required.", "PostTitle");
  }

  if (3 > PostPath.Trim().Length || !PostPath.IsUrlPart()) {
    yield return new RuleViolation("The path is required, and should only " +
                        "contain alpha-numerics and hyphens.", "PostPath");
  }

  if (50 > PostBody.Trim().Length) {
    yield return new RuleViolation("The post body is required, and should " +
                        "be longer than 50 characters", "PostBody");
  }
}

And coupled this with the following implementation (tweaked from Nerd Dinner - I prefer using .Any() to .Count() as that only checks to see if there are any values, rather than enumerating over the entire set - not an issue for in memory objects like this, but something to consider with databases Wink):

public bool IsValid {
  get { return (!GetRuleViolations().Any()); }
}

Then in the controller I went with the following code to handle errors:

if (blog.IsValid)
{
  [...]
  return [...]
}

foreach (RuleViolation violation in photo.GetRuleViolations()) {
  ModelState.AddModelError(violation.PropertyName, violation.ErrorMessage);
  ModelState.SetModelValue(violation.PropertyName,
             new ValueProviderResult(
                   ValueProvider[violation.PropertyName].AttemptedValue,
                   collection[violation.PropertyName],
                   System.Globalization.CultureInfo.CurrentCulture));
}

The ValueProvider in 1.0 was an IDictionary but this has changed in 2.0 to be an IValueProvider, which means that you can't just iterate through without knowing what the form items were called (which I wasn't, but others are) so my code changed to:

foreach (RuleViolation violation in photo.GetRuleViolations()) {
  ModelState.AddModelError(violation.PropertyName, violation.ErrorMessage);
  ModelState.SetModelValue(violation.PropertyName,
           new ValueProviderResult(
                 ValueProvider.GetValue(violation.PropertyName).AttemptedValue,
                 collection[violation.PropertyName],
                 System.Globalization.CultureInfo.CurrentCulture));
}

The only other issue I had was how this was all going to play on IIS 6, but that turned out to be very easy in the RegisterArea method:

public override void RegisterArea(AreaRegistrationContext context) {
  context.MapRoute(
        "Admin_default",
        "Admin.aspx/{controller}/{action}/{id}",
        new { action = "Index", id = UrlParameter.Optional }
  );
}

All in all, very easy and smooth.

Filed under: ASP.NET MVC, Site Updates