SVG support is a topic that has already been discussed multiple times in blog posts, stackoverflow answers etc.
The main and the most common solution is to register the SVG MIME Type under the /sitecore/mediaLibrary/mediaTypes node like the following line.
<mediaLibrary> <mediaTypes> <mediaType name="SVG" extensions="svg"> <mimeType>image/svg+xml</mimeType> <forceDownload>false</forceDownload> <sharedTemplate>system/media/unversioned/image</sharedTemplate> <versionedTemplate>system/media/versioned/image</versionedTemplate> <mediaValidator type="Sitecore.Resources.Media.ImageValidator"/> <thumbnails> <generator type="Sitecore.Resources.Media.ImageThumbnailGenerator, Sitecore.Kernel"> <extension>png</extension> </generator> <width>150</width> <height>150</height> <backgroundColor>#FFFFFF</backgroundColor> </thumbnails> </mediaType> </mediaTypes> </mediaLibrary>
And that is it ! Now your SVG images can be safely uploaded to the Media Library !
However now you will encounter some problems when the content editors start uploading and using SVG Images, so I decided to put a blog post about them.
Note: All source code shown here is using Sitecore 8.1 Initial Release. Also because the code for the corresponding pages is pretty big I will just post the relevant fragments here. The full code of the solution can be found on BitBucket.
Now we’ve got the SVG images into the media library. The next step is to use them around the site right? Which leads me to:
Problem 1: My Rich Text Editor is not accepting SVGs !
Imagine one of our content editors decided to place the SVG image in a Rich Text Field somewhere in the site, sounds simple right ? Well, it does sound simple, but as it seems – it is not, because after trying to add the image via the Insert Sitecore Media button the following message appears:
As it seems Sitecore still doesn’t like SVGs and it doesn’t treat them as images that can be placed in the Rich Text fields. So after spending some time with the decompiled Sitecore.Client assembly, the exception can be traced to the following line located in the InsertImageForm class in the OnOK function:
else if (!(MediaManager.GetMedia(MediaUri.Parse((Item) mediaItem)) is ImageMedia)) { SheerResponse.Alert("The selected item is not an image. Select an image to continue."); }
So Sitecore is not considering our SVG as ImageMedia.
Note: Here is the time to say that I haven`t found an approach to register the SVG as ImageMedia. If someone has an idea I will be very grateful if he can post it in the comments section.
Well the fix is pretty straightforward – nothing fancy here. We need to hijack the dialog and add some custom logic to recognize our SVG as a real image.
The first thing we need to do is to create our custom class that will be an exact replica of the decompiled code of the InsertImageForm class, except the modified else/if statement which should be modified to as follows:
else if (!(MediaManager.GetMedia(MediaUri.Parse(mediaItem)) is ImageMedia || mediaItem.MimeType == "image/svg+xml"])) { SheerResponse.Alert("The selected item is not an image. Select an image to continue."); }
The next step is to make the dialog use our own class. The XML Layout file for this dialog is sitecore\shell\Controls\Rich Text Editor\InsertImage\InsertImage.xml. There we need to replace the following line:
<CodeBeside Type="Sitecore.Shell.Controls.RichTextEditor.InsertImage.InsertImageForm,Sitecore.Client">
with
<CodeBeside Type="[YOUR_CLASS_NAME],[YOUR_ASSEMBLY]"/>
Note: I usually prefer to copy and paste the XML Layout into /sitecore/shell/override folder instead of modifying the existing one.
And now we can safely add our SVG Image !
But there is still a problem, which leads me to:
Problem 2: My Rich Text Editor is not handling the SVG correctly !
So now we need to make the image appear ! The problem shows itself when we check the html of the Rich Text Editor.
The SVG image is handled as a normal Sitecore Media File, meaning that it goes through the image handler and is displayed as a normal image in <img> tag. The problem is that the SVG is actually a XML File which needs to be rendered as it is for consistency. If your FrontEnd developers don’t want to manipulate the actual SVG when it gets rendered – you can stop reading here :). If you are one of the few unlucky ones – no worries, there is hope ! The salvation is again in the OnOK method of the InsertImageForm class, just a line bellow our modifications. If the image is considered a valid image file the following code gets executed:
MediaUrlOptions shellOptions = MediaUrlOptions.GetShellOptions(); shellOptions.Language = this.ContentLanguage; string text = !string.IsNullOrEmpty(HttpContext.Current.Request.Form["AlternateText"]) ? HttpContext.Current.Request.Form["AlternateText"] : mediaItem.Alt; Tag image = new Tag("img"); this.SetDimensions(mediaItem, shellOptions, image); image.Add("Src", MediaManager.GetMediaUrl(mediaItem, shellOptions)); image.Add("Alt", StringUtil.EscapeQuote(text)); image.Add("_languageInserted", "true"); if (this.Mode == "webedit") { SheerResponse.SetDialogValue(StringUtil.EscapeJavascriptString(image.ToString())); base.OnOK(sender, args); } else SheerResponse.Eval("scClose(" + StringUtil.EscapeJavascriptString(image.ToString()) + ")");
So no matter the image type – it always gets appended to an <img> tag with the corresponding URL. In order to make our SVG fully functional instead of rendering the image itself we need to render the XML contents of the actual media file. It can be done easily by reading the stream of the actual media image. Also we need to make sure that we set the width and height in which we want to display our SVG. The modified code looks like this:
if (mediaItem.MimeType == "image/svg+xml") { string result; using (StreamReader reader = new StreamReader(mediaItem.GetMediaStream(), Encoding.UTF8)) { result = reader.ReadToEnd(); } XDocument svg = XDocument.Parse(result); NameValueCollection form = HttpContext.Current.Request.Form; if (svg.Document?.Root != null) { int width; if (int.TryParse(form["Width"], out width)) { svg.Document.Root.SetAttributeValue("width", width); } int height; if (int.TryParse(form["Height"], out height)) { svg.Document.Root.SetAttributeValue("height", height); } result = svg.ToString(); } if (Mode == "webedit") { SheerResponse.SetDialogValue(StringUtil.EscapeJavascriptString(result)); base.OnOK(sender, args); } else { SheerResponse.Eval("scClose(" + StringUtil.EscapeJavascriptString(result) + ")"); } } else { Tag image = new Tag("img"); SetDimensions(mediaItem, shellOptions, image); image.Add("Src", MediaManager.GetMediaUrl(mediaItem, shellOptions)); image.Add("Alt", StringUtil.EscapeQuote(text)); image.Add("_languageInserted", "true"); if (Mode == "webedit") { SheerResponse.SetDialogValue(StringUtil.EscapeJavascriptString(image.ToString())); base.OnOK(sender, args); } else { SheerResponse.Eval("scClose(" + StringUtil.EscapeJavascriptString(image.ToString()) + ")"); } }
By doing this modification – now the SVG is rendered as the xml it should be rendered as ! 🙂
So up to now we can properly render SVG Images in our Rich Text Editor ! But
Problem 3: But what about rendering it on a Sublayout/Rendering ?
Well, the next struggle is to render the SVG correctly in our components. And here is one of the things I love most about Sitecore – all field renders (if used correctly :)) goes through the RenderField pipeline ! Meaning – we just need to modify the GetImageFieldValue processor with our own and we should be set !
The GetImageFieldValue processor renders the image thanks to ImageRenderer helper class. So we need to create our own ImageRenderer which is going to support SVGs as well.
We are going to create a new class named ImageRendererEx which will contain all the code from the original ImageRenderer, but have modifications for rendering our SVG images. The only change here should be in the Render() method. The modified Render function looks like this:
public virtual RenderFieldResult Render() { var obj = Item; if (obj == null) { return RenderFieldResult.Empty; } var keyValuePairs = Parameters; if (keyValuePairs == null) { return RenderFieldResult.Empty; } ParseNode(keyValuePairs); var innerField = obj.Fields[FieldName]; if (innerField != null) { imageField = new ImageField(innerField, FieldValue); ParseField(imageField); AdjustImageSize(imageField, scale, maxWidth, maxHeight, ref width, ref height); if (imageField.MediaItem != null) { MediaItem imageMediaItem = new MediaItem(imageField.MediaItem); if (imageMediaItem.MimeType == "image/svg+xml") { string result; using (StreamReader reader = new StreamReader(imageMediaItem.GetMediaStream(), Encoding.UTF8)) { result = reader.ReadToEnd(); } XDocument svg = XDocument.Parse(result); if (svg.Document?.Root != null) { if (width > 0) { svg.Document.Root.SetAttributeValue("width", width); } if (height > 0) { svg.Document.Root.SetAttributeValue("height", height); } result = svg.ToString(); } return new RenderFieldResult(result); } } } var site = Context.Site; if ((string.IsNullOrEmpty(source) || IsBroken(imageField)) && site != null && site.DisplayMode == DisplayMode.Edit) { source = GetDefaultImage(); className += " scEmptyImage"; className = className.TrimStart(' '); } if (string.IsNullOrEmpty(source)) { return RenderFieldResult.Empty; } var imageSource = GetSource(); var stringBuilder = new StringBuilder("<img "); AddAttribute(stringBuilder, "src", imageSource); AddAttribute(stringBuilder, "border", border); AddAttribute(stringBuilder, "hspace", hspace); AddAttribute(stringBuilder, "vspace", vspace); AddAttribute(stringBuilder, "class", className); AddAttribute(stringBuilder, "alt", HttpUtility.HtmlAttributeEncode(alt), xhtml); if (width > 0) { AddAttribute(stringBuilder, "width", width.ToString()); } if (height > 0) { AddAttribute(stringBuilder, "height", height.ToString()); } CopyAttributes(stringBuilder, keyValuePairs); stringBuilder.Append(" />"); return new RenderFieldResult(stringBuilder.ToString()); }
And we are done ! Now no matter if we are rendering the image in a MVC Rendering or WebForms Sublayout, if we are using the Sitecore Helper or the Web Control (Worth to mention that it will also work if you are using ORM as long as it is using the RenderField pipeline to fill the values – like Glass Mapper is :)) we will get a nicely formatted SVG image!
Full code can be found on BitBucket.
Happy SVGing ! 🙂
Leave a Reply