Showing posts with label media library. Show all posts
Showing posts with label media library. Show all posts

Monday, January 4, 2021

Sitecore media cache headers on pdf extension

MediaCache headers on PDF extension

MediaResponse.MaxAge

Based upon a recommendation from Google we set the MediaResponse.MaxAge setting to 365.00:00:00. This sets the Cache-Control header for media from the Sitecore MediaLibrary to "public, max-age=31536000", which is very good for images within the site. The header tells browsers that cached content younger than max-age seconds can be used without consulting the server- it should be used for content that doesn't change. 

But.. we also have other media types within the MediaLibrary - and today I am focusing on one particular type here being PDF documents. We wanted other cache settings for the pdf documents as we did run into a situation where a pdf file did change and only the internet-gods know where the old document was cached - it was not that easy to get rid off. So, I would like to be able to alter the cache settings for certain media types.


SXA and the MediaRequestHandler 

Note that I am using SXA. The Sitecore eXperience Accelerator makes our developer lives so much easier on various parts of a Sitecore development process, and again this is one of those occasions. If you have SXA, you have a Sitecore.XA.Foundation.MediaRequestHandler.MediaRequestHandler which overrides the default Sitecore.Resources.Media.MediaRequestHandler and this handler introduces 2 new pipelines. We could try to override this code again but it seems like a better option to check the pipelines. The first one is the  <mediaRequestHandler> pipeline and has some processors from SXA within. The second one is much more interesting for me here and is called <mediaRequestHeaders>. This pipeline is called just after setting the default media headers and can be used to alter them. By default it is empty (it doesn't even exist) - let's add a processor to it.

 MediaRequestHeaders pipeline

Adding a processor to the pipeline is rather easy:

<mediaRequestHeaders>
    <processor type="Feature.Caching.Pipelines.MediaRequestHeaderProcessor, Feature.Caching" />
</mediaRequestHeaders>
Now I just need to write some code for that processor:
public class MediaRequestHeaderProcessor
{
    public void Process(MediaRequestHeadersArgs args)
    {
        Assert.ArgumentNotNull(args, nameof(args));
        var media = args.Media;
        var cache = args.Context.Response.Cache;

        if (media.MimeType.StartsWith("application", StringComparison.OrdinalIgnoreCase))
        {
            cache.SetMaxAge(TimeSpan.FromMinutes(15));
            cache.SetCacheability(HttpCacheability.ServerAndPrivate);
            cache.AppendCacheExtension("no-cache");
        }
    }
}
The code provided here is an example of what it could be. You can do anything you want in here actually...  Our processor gets the media item and the Cache context from the provided arguments - we don't have to fetch any extra data here which is nice. We check the MimeType of the media item and act accordingly. Note again that we can implement any logic for any media item here, based on any type of information we can get from the media item (type, extension, …).

Using the cache context we can set all the header information we want. As an example in the code I am setting the max-age, the cacheability and I'm appending a custom extension. This will result in a cache header like "private, no-cache, max-age=900". 

Such a header makes the browser cache the result for 900 seconds, makes sure no intermediate (proxy) servers can cache it, and tells the browser not to trust a local version without a server agreement. The no-cache addition doesn't mean it can't be cached, it means a browser must check ("revalidate") with the server before using the cached resource. For our pdf documents, this sounds reasonable.


Conclusion

SXA made my life easier once again 😃

The extra mediaRequestHeaders pipeline made it very easy to include any business logic I want to alter cache headers that are being send with media requests.


Thanks to Sitecore StackExchange - as this answer from Richard helped me into the right direction.


Monday, September 28, 2020

Custom SXA media query token

SXA query tokens

The resolveTokens pipeline is used to resolve tokens that can be used in a Sitecore SXA site in queries at several places: a query rendering variant, a template field source..  

In the multisite project that I'm working on -which resulted in a bunch of posts already- we wanted to have the source of an Image field to point towards a folder inside the media library that exists for each site.

One of the ootb tokens that can be used is $siteMedia. The problem with this token is that it will be resolved into the virtual media root within the site - which means that adding extra folders (e.g. $siteMedia/pdf) in the query does not work - the resulting path does not exist because the resolved part is a virtual media folder.

We can create a custom resolver for our own token however. This has been described by Maciej Gontarz in his blog post - there are some particular bits into our custom token however so I wanted to share anyway.

Custom SXA media root token

The idea is simple: we want to have the actual root of the media items for the site (inside the Media Library) and not the virtual one (inside the site). This way we can add a subfolder to it and still have a valid path. 

Note: this can really help your editors to get into the correct folder immediately, but it's also a bit dangerous as you will get an error when opening your item in the content editor when the resulting folder does not exist.  

To resolve our token we need a resolver - a processor to add into the pipeline:
using Sitecore.XA.Foundation.Multisite;
using Sitecore.XA.Foundation.Multisite.Extensions;
using Sitecore.XA.Foundation.TokenResolution.Pipelines.ResolveTokens;

public class ResolveTokens : ResolveTokensProcessor
{
  private const string Token = "$mediaSiteRoot";
  private readonly IMultisiteContext multisiteContext;

  public ResolveTokens(IMultisiteContext multisiteContext)
  {
      this.multisiteContext = multisiteContext;
  }

  public override void Process(ResolveTokensArgs args)
  {
      args.Query = ReplaceTokenWithItemPath(args.Query, Token, () => GetMediaRoot(args.ContextItem), args.EscapeSpaces);
  }

  private Item GetMediaRoot(Item contextItem)
  {
    var mediaRoot = multisiteContext.GetSiteMediaItem(contextItem);
    if (mediaRoot == null)
    {
      return null;
    }

    var root = mediaRoot.GetVirtualChildren();
    return root.Any() ? root.First() : mediaRoot;
  }
}
We defined our token as "$mediaSiteRoot" but you can call it (almost) anything you want - just try to avoid picking something that starts with an already existing token.

We use the ReplaceTokenWithItemPath function (comes with the ResolveTokensProcessor) as this will handle everything for us if we pass the correct item. To find our root item we start by fetching the media root item for the site using the MultisiteContext. If this is found, we can use the extension method GetVirtualChildren to get all virtual children that are defined on that item. 

SXA will define 2 children on the virtual media root - by default it's the first one we are looking for.

Now we need to add this resolver to the resolveTokens pipeline:
<sitecore>
  <pipelines>
    <resolveTokens>
      <processor type="Feature.Multisite.Pipelines.ResolveTokens, Feature.Multisite" resolve="true" 
             patch:before="processor[@type='Sitecore.XA.Foundation.TokenResolution.Pipelines.ResolveTokens.EscapeQueryTokens, Sitecore.XA.Foundation.TokenResolution']" />
    </resolveTokens>
  </pipelines>
</sitecore>
As you can see we patch this in before the EscapeQueryTokens. This means our token will be after the standard ones -which is ok- but it has to be before the escape one because otherwise the query won't work as it will be escaped (with #) already before we do our magic.

Using the custom token

We are now ready to use the token. In the source of our Image field we can now put things like 
query:$mediaSiteRoot/Content/Hero


Conclusion

We can now guide our editors to the correct folders inside the media library. But be aware - as mentioned before those folders do need to exist in all sites where you use the template. In our case we start from a master site that gets cloned to create new sites. This also means that the (media) folders that are created in that master site will get created in all new sites, which is great of course as we can make sure that certain folders exist this way. SXA can be really great sometimes 😃

Tuesday, April 1, 2014

Configure the Media Browser default view

Sitecore Media Browser

When you browse for an image from an Image field or Rich Text field in Sitecore, you get to see the Media Browser. In version 6 you would just see a tree view, from version 7 you get a search view as default. The tree view is also there, but you need to switch tabs..  Some people prefer the tree view though and want that as default.

Sitecore 7.0

 In Sitecore 7.0 you can alter the default tab in the Media Browser by editing the \sitecore\shell\Applications\Media\MediaBrowser\MediaBrowser.xml. You will see a Tabstrip with 2 tabs ("MediaLibraryTab" and "MediaTab"). The first tab is the default, so by switching them around you can set the MediaTab ("browse") as default.
The best way to handle your new file is to place it in the "\sitecore\shell\Override" folder.

The result will look like this:


Sitecore 7.1

In Sitecore 7.1 they changed the Media Browser. The search pane even appears to have a bug (the datasource if provided is not taken into account), but that's not our focus here.
The top of the Media Browser now looks like:



Switching the panes can be done with the buttons on the right (above the search field). Setting the default to the tree view however is again possible: in order to use the tree view as a default view you need to change the definition of the Media Browser in the "\App_Config\Include\Sitecore.Speak.config" file like this:

Default config:


<overrideXmlControls>
    <override xmlControl="Sitecore.Shell.Applications.Media.MediaBrowser" 
           with="/sitecore/client/Sitecore/Common/Dialogs/SelectMediaDialog" />
</overrideXmlControls>

Tree view config:


<overrideXmlControls>
    <override xmlControl="Sitecore.Shell.Applications.Media.MediaBrowser" 
            with="/sitecore/client/Sitecore/Common/Dialogs/SelectMediaViaTreeDialog" />
</overrideXmlControls>


The result will look like this:






This view takes your datasource into account (as it should) and displays it as the topfolder of the tree.

Sitecore 8

In Sitecore 8, the "overrideXmlControls" element can be found in "App_config\Include\Sitecore.Speak.Applications.config". The rest should stay the same.

Patching

A good remark was made that it is better to patch this value. It is actually always a good idea to use patches to alter Sitecore configs. The patch should look something like this:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/" >
  <sitecore>
    <overrideXmlControls>
      <override xmlControl="Sitecore.Shell.Applications.Media.MediaBrowser">
   <patch:attribute name="with">/sitecore/client/applications/Dialogs/SelectMediaViaTreeDialog</patch:attribute>
      </override>
    </overrideXmlControls>    
  </sitecore>
</configuration>