Johan Björnfot
Dec 16, 2013
  8650
(2 votes)

Blob property

To be able to support media as content (IContentMedia) we introduced a new property type Blob that makes it possible to store binary data related to a content instance. The most obvious usage is through IContentMedia.BinaryData which stores the binary data for a media item.

 

ImageDescriptor attribute

Another usage of blobs for media is the Thumbnail property for IContentMedia where a thumbnail format of the media is stored. For images the thumbnail is auto generated due to the usage of ImageDescriptor attribute. The base class ImageData looks like:

    public class ImageData : MediaData, IContentImage
    {
        /// <summary>
        /// Gets or sets the generated thumbnail for this media.
        /// </summary>
        [ImageDescriptor(Width = 48, Height = 48)]
        public override Framework.Blobs.Blob Thumbnail
        {
            get {return base.Thumbnail;}
            set {base.Thumbnail = value;}
        }
    }

Here we can see that the ImageDescriptor attribute specifies that it should generate a thumbnail in size 48*48. You can override the property and specify the size as you wish.

You can also have additional Blobs properties like LargeThumbnail, MediumThumbnail etc. with ImageDescriptor attribute on your media type. They will then be autogenerated like Thumbnail.

Lazy loading

The actual Blob is stored by the configured BlobProvider so in the content database only the URI to the blob is stored. The actual binary data for the blob will not be loaded before OpenRead method is called on the blob instance.

 

Blob routing

There is a partial route registered that makes it possible to route directly to a blob property on a content instance. The URL pattern is <content URL>/BlobPropertyName. So for example if there is an image with url http://mysite/globalassets/myimage.png then I can route to the thumbnail as http://mysite/globalassets/myimage.png/thumbnail. You can try this by adding “/thumbnail” to any image URL in a CMS 7.5 site.

Export-Import support

Blob properties are included/handled in export-import meaning the Blobs referenced from Blob properties will automatically be included in the export package. This means e.g. that it is possible to transfer content (including media/blobs) from one site with a specific blob provider (e.g. FileBlobProvider) to a site with another blob provider (e.g. blob provider for Amazon or Azure).

PDF version of page

The Blob property is not restricted to be used by only media. It is possible to add a Blob property to any IContent instance. In the example (I used Alloy templates) below I have added a property of type Blob to SitePageData as:

public virtual Blob PDF { get; set; }

I then added an eventhandler for IContentEvents.PublishedContent where I generate a PDF for the page and stores the PDF in the blob property. I can then view the PDF version of the page by appending “/pdf” to the URL for the page. The code for the example is below.

Note: In the example a package called Pechkin is used to generate the PDF but there are many different PDF generators to choose from.

    [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
    public class PDFCreatorModule : IInitializableModule
    {
        public void Initialize(InitializationEngine context)
        {
            var globalConfig = new GlobalConfig();
            globalConfig.SetMargins(new Margins(300, 100, 150, 100))
                .SetDocumentTitle("Test document")
                .SetPaperSize(PaperKind.A3Rotated);

            var contentEvents = context.Locate.Advanced.GetInstance<IContentEvents>();

            contentEvents.PublishingContent += (sender, args) =>
                {
                    var page = args.Content as SitePageData;
                    if (page != null && !page.ProcessedPDF)
                    {
                        args.Items["ProcessPDF"] = true;
                    }
                };
            contentEvents.PublishedContent += (sender, args) =>
            {
                var page = args.Content as SitePageData;
                if (page != null && args.Items["ProcessPDF"] != null)
                {
                    context.Locate.Advanced.GetInstance<PDFCreator>()
                        .CreatePDF(page, globalConfig, new WebClient());
                }
            };
        }

        public void Preload(string[] parameters)
        {}

        public void Uninitialize(InitializationEngine context)
        {}
    }

    public class PDFCreator
    {
        private IContentRepository _contentRepository;
        private BlobFactory _blobFactory;
        private UrlResolver _urlResolver;

        public PDFCreator(IContentRepository contentRepository, BlobFactory blobFactory, UrlResolver urlResolver)
        {
            _contentRepository = contentRepository;
            _blobFactory = blobFactory;
            _urlResolver = urlResolver;
        }

        public virtual void CreatePDF(SitePageData page, GlobalConfig gc, WebClient webClient)
        {
            page = page.CreateWritableClone() as SitePageData;
            page.PDF = _blobFactory.CreateBlob(Blob.GetContainerIdentifier(page.ContentGuid), ".pdf");

            var pageUrl = UrlResolver.Current.GetUrl(page.ContentLink, page.Language.Name);
            var absolutePageUrl = UriSupport.CreateAbsoluteUri(pageUrl);
            var htmlText = webClient.DownloadString(absolutePageUrl);
            var htmlWithAbsoluteUris = MakeUrisAbsoulte(htmlText, SiteDefinition.Current.SiteUrl);

            var pechin = new SynchronizedPechkin(gc);

            var oc = new ObjectConfig();
            oc.SetCreateExternalLinks(false)
              .SetFallbackEncoding(Encoding.Unicode)
              .SetLoadImages(true);

            using (var writeStream = page.PDF.OpenWrite())
            {
                var convertedData = pechin.Convert(oc, htmlWithAbsoluteUris);
                writeStream.Write(convertedData, 0, convertedData.Length);
            }

            page.ProcessedPDF = true;
            _contentRepository.Save(page, 
                SaveAction.Publish | SaveAction.ForceCurrentVersion | SaveAction.SkipValidation,
                                    AccessLevel.NoAccess);
        }

        const string pattern = @"(?<name>src|href)=""(?<value>/[^""]*)""";
        private string MakeUrisAbsoulte(string html, Uri baseUri)
        {
            var matchEvaluator = new MatchEvaluator(
                match =>
                    {
                        var uri = new Uri(match.Groups["value"].Value, UriKind.RelativeOrAbsolute);
                        Uri absoluteUri;
                        if (!uri.IsAbsoluteUri && Uri.TryCreate(baseUri, uri, out absoluteUri))
                        {
                            var name = match.Groups["name"].Value;
                            return string.Format("{0}=\"{1}\"", name, absoluteUri.AbsoluteUri);
                        }

                        return null;
                });
            return Regex.Replace(html, pattern, matchEvaluator);
        }
    }
Dec 16, 2013

Comments

Dec 18, 2013 01:54 AM

Really nice post Johan! Now I'm looking forward to playing with blob storage myself.

Petter Sørby
Petter Sørby Mar 6, 2014 02:49 PM

Thanks Johan. Very nice both feature and post.

Is it possible to get the URL in a strongly typed manner.

Ex something like:
@Html.PropertyFor(i => i.Thumbnail) to return

Please login to comment.
Latest blogs
PageCriteriaQueryService builder with Blazor and MudBlazor

This might be a stupid idea but my new years resolution was to do / test more stuff so here goes. This razor component allows users to build and...

Per Nergård (MVP) | Feb 10, 2025

Enhancing Optimizely CMS Multi-Site Architecture with Structured Isolation

The main challenge of building an Optimizely CMS website is to think about its multi site capabilities up front. Making adjustment after the fact c...

David Drouin-Prince | Feb 9, 2025 | Syndicated blog

How to: set access right to folders

Today I stumped upon this question Solution for Handling File Upload Permissions in Episerver CMS 12, and there is a simple solution for that Using...

Quan Mai | Feb 7, 2025 | Syndicated blog

Poking around in the new Visual Builder and the SaaS CMS

Early findings from using a SaaS CMS instance and the new Visual Builder grids.

Johan Kronberg | Feb 7, 2025 | Syndicated blog

Be careful with your (order) notes

This happened a quite ago but only now I have had time to write about it. Once upon a time, I was asked to look into a customer database (in a big...

Quan Mai | Feb 5, 2025 | Syndicated blog

Imagevault download picker

This open source extension enables you to download images as ImageData with ContentReference from the ImageVault picker. It serves as an alternativ...

Luc Gosso (MVP) | Feb 4, 2025 | Syndicated blog