Thursday, December 16, 2010

Sending a Sitecore MediaStream through a HttpResponse

The following code can be used to stream Sitecore media back to the client without using the configured MediaRequestHandler.

 public class MediaDownload
 {
  public static void ProcessRequest(Page page, Media media)
  {
   Assert.ArgumentNotNull(page, "page");
   Assert.ArgumentNotNull(media, "media");
   if (!DoProcessRequest(page, media))
   {
    page.Response.StatusCode = 404;
    page.Response.ContentType = "text/html";
   }
  }

  private static bool DoProcessRequest(Page page, Media media)
  {
   Assert.ArgumentNotNull(page, "page");
   Assert.ArgumentNotNull(media, "media");
   
   if (media != null)
   {
    return DoProcessRequestToStream(page, media);
   }

   return false;
  }

  private static bool DoProcessRequestToStream(Page page, Sitecore.Resources.Media.Media media)
  {
   Assert.ArgumentNotNull(page, "context");
   Assert.ArgumentNotNull(media, "media");
   if (Modified(page, media) == Tristate.False)
   {
    SendMediaHeaders(media, page);
    page.Response.StatusCode = 304;
    return true;
   }
   MediaStream stream = media.GetStream();
   if (stream == null)
   {
    return false;
   }
   
   SendMediaHeaders(media, page);
   SendStreamHeaders(stream, page);
   DownloadHeaders.AddDownloadResponseHeader(page.Response, media.MediaData.MediaItem.Name, media.MimeType, media.Extension);
   using (stream)
   {
    WebUtil.TransmitStream(stream.Stream, page.Response, Settings.Media.StreamBufferSize);
   }
   return true;
  }

  private static Tristate Modified(Page page, Sitecore.Resources.Media.Media media)
  {
   DateTime time;
   string ifNoneMatch = page.Request.Headers["If-None-Match"];
   if (!string.IsNullOrEmpty(ifNoneMatch))
   {
    return Tristate.True;
   }
   string ifModifiedSince = page.Request.Headers["If-Modified-Since"];
   if (!string.IsNullOrEmpty(ifModifiedSince) && DateTime.TryParse(ifModifiedSince, out time))
   {
    return MainUtil.GetTristate(time != media.MediaData.Updated);
   }
   return Tristate.Undefined;
  }

  

  private static void SendMediaHeaders(Sitecore.Resources.Media.Media media, Page page)
  {
   DateTime updated = media.MediaData.Updated;
   if (updated > DateTime.Now)
   {
    updated = DateTime.Now;
   }
   HttpCachePolicy cache = page.Response.Cache;
   cache.SetLastModified(updated);
   cache.SetETag(media.MediaData.MediaId);
   cache.SetCacheability(Settings.MediaResponse.Cacheability);
   TimeSpan maxAge = Settings.MediaResponse.MaxAge;
   if (maxAge > TimeSpan.Zero)
   {
    if (maxAge > TimeSpan.FromDays(365.0))
    {
     maxAge = TimeSpan.FromDays(365.0);
    }
    cache.SetMaxAge(maxAge);
    cache.SetExpires(DateTime.Now + maxAge);
   }
   Tristate slidingExpiration = Settings.MediaResponse.SlidingExpiration;
   if (slidingExpiration != Tristate.Undefined)
   {
    cache.SetSlidingExpiration(slidingExpiration == Tristate.True);
   }
   string cacheExtensions = Settings.MediaResponse.CacheExtensions;
   if (cacheExtensions.Length > 0)
   {
    cache.AppendCacheExtension(cacheExtensions);
   }
  }

  private static void SendStreamHeaders(MediaStream stream, Page page)
  {
   stream.Headers.CopyTo(page.Response);
  }


 }

Wednesday, December 1, 2010

Using a DllImport referenced DLL indirectly from a TestTools.UnitTesting.TestClass

Project A uses DllImport to invoke code in a third party DLL. Project B uses Microsoft.VisualStudio.TestTools.UnitTesting to run some test cases on Project A.

The automated tests in project B were failing as the DLL required by project A wasn't being copied into the TestResults directory used by the tests.

Adding the DeploymentItem attribute to the test classes in Project B with the path to the DLL revolved the issue.