Adding a Field to the Sitecore Media Upload Dialog

March 10, 2015 | Posted by Adam HerrNeckar

A customer recently requested a field added to the upload media dialog.  The stated intent was to facilitate the content administrator’s preferred workflow.  Specifically, as the admin user was interacting with a content item, they also added images to a content field.  This worked great, except when the user also needed to edit data on the newly uploaded media item.  For those occasions, the user would periodically forget - if not given an opportunity at the time of the upload.

Historically, Sitecore provided the (Sheer UI generated) select media dialog, and the media item would open after uploading to allow the user to make any additional changes.  This extra step was sufficient to remind the user to add additional content to the media item.  However, Sitecore 7.2 transitioned media selection to a new technology that bypassed this extra step.  Sitecore now leverages the Sitecore Process Enablement and Acceleration Kit (SPEAK) components for Sitecore media uploading.  SPEAK is an HTML5-compliant replacement UI framework for the Sheer UI.  The SPEAK Upload Media dialog adds “description” and “alternate text” at the time of upload to avoid the extra step of opening the media item for editing.

How did I do this?  I first needed to understand how the SPEAK dialogs worked.  I quickly tracked down the dialogs to the Core database location: /sitecore/client/Applications/Dialogs/UploadMediaDialog, but was not able to view details of the associated presentation.  I learned that Sitecore Rocks must be used to edit SPEAK applications, so I opened the SPEAK design within the Sitecore Rocks extension in Visual Studio.  Unfortunately, there was some trouble displaying the dialog, but clicking on the page elements, I found the resources used for the Uploader, and UploaderInfo renderings.

AddFieldtoSitecoreMediaUpload-Image1

 

I opened the UploaderInfo file (\Website\sitecore\shell\client\Business Component Library\Layouts\Renderings\Forms\UploaderInfo\UploaderInfo.cshtml), and found the HTML elements and [Knockout.js] data bindings for the “Alternate Text” label as the “alternate” field.  Bingo!  I added this file in my development solution and made the necessary changes to add new elements for a new field - immediately after “Alternate Text”.

Next, I found the Uploader script (\Website\sitecore\shell\client\Business Component Library\Layouts\Renderings\Forms\Uploader\Uploader.js), and observed the “alternate” field defined in several places.  I subsequently added this file to my development solution and added my new field binding variable in a similar way as the “alternate” field.

Done?  Not quite yet.   I noticed the upload dialog was posting to the url: /api/sitecore/Media/Upload.  At first I thought this was defined as a Sitecore Item Web API call.  Alas, no.  It was defined as an MVC route to a compiled Sitecore controller.  The posted Content-Type is multipart/form-data.  You can reference the MVC route prefix defined in the \Website\App_Config\Include\Sitecore.Speak.Mvc.config file. 

Unfortunately, I don’t have control over what happens inside the Sitecore DLL.  So, I opened .NET Reflector, and disassembled the Sitecore.Speak.Client.dll.  Within that library, I located the Sitecore.Controllers.MediaController, and the Upload method (MVC registered action).  Notice the name of the Controller is “MediaController” rather than just “Media”.  Now I was getting close…

AddFieldtoSitecoreMediaUpload-Image2

I decided the easiest approach was simply to extend Sitecore’s implementation by adding the disassembled source code to my solution as a “custom” media controller (C# class file) within the same Sitecore.Controllers namespace.  Please note I had to add additional project references for the following libraries:

  • Microsoft.CSharp
  • Sitecore.Speak.Client
  • System.Web.Mvc

And the code imported the following namespaces:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Web.Mvc;

using Sitecore.Controllers.Results;

using System.Web;

using System.IO;

using Sitecore.Data.Items;

using Sitecore.Data;

using Sitecore.Configuration;

using Sitecore.Resources.Media;

using Sitecore.Data.Managers;

using System.Drawing;

 

I added the simple logic to handle the new field within the controller, similar to how the description field was handled.  I also made sure to add comments to facilitate maintenance.  Here is what the final class implementation looked like (changes highlighted for easy reference):

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Web.Mvc;

using Sitecore.Controllers.Results;

using System.Web;

using System.IO;

using Sitecore.Data.Items;

using Sitecore.Data;

using Sitecore.Configuration;

using Sitecore.Resources.Media;

using Sitecore.Data.Managers;

using System.Drawing;

 

namespace Sitecore.Controllers

{

    /// <summary>

    /// CustomMediaController is simply a decompiled port of code from the Sitecore.Speak.Client.dll -> Sitecore.Controllers.MediaController

    /// We did this to allow the controller to handle binding the extended client value for "elevation code"

    /// </summary>

    public class CustomMediaController : Controller

    {

        // Methods

        private string ParseDestinationUrl(string destinationUrl)

        {

            if (string.IsNullOrEmpty(destinationUrl))

            {

                return "/sitecore/media library/Upload/";

            }

            if (!destinationUrl.EndsWith("/"))

            {

                destinationUrl = destinationUrl + "/";

            }

            return destinationUrl;

        }

 

        public JsonResult Upload(string database, string destinationUrl)

        {

            List<UploadedFileItem> list = new List<UploadedFileItem>();

            SitecoreViewModelResult result = new SitecoreViewModelResult();

            foreach (string str in base.Request.Files)

            {

                HttpPostedFileBase file = base.Request.Files[str];

                if (file != null)

                {

                    string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file.FileName);

                    if (!string.IsNullOrEmpty(base.Request.Form["name"]))

                    {

                        fileNameWithoutExtension = base.Request.Form["name"];

                    }

                    fileNameWithoutExtension = ItemUtil.ProposeValidItemName(fileNameWithoutExtension, "default");

                    string str3 = string.Empty;

                    if (!string.IsNullOrEmpty(base.Request.Form["alternate"]))

                    {

                        str3 = base.Request.Form["alternate"];

                    }

                    Database contentDatabase = Context.ContentDatabase;

                    if (!string.IsNullOrEmpty(database))

                    {

                        contentDatabase = Factory.GetDatabase(database);

                    }

                    if (contentDatabase == null)

                    {

                        contentDatabase = Context.ContentDatabase;

                    }

                    MediaCreatorOptions options = new MediaCreatorOptions

                    {

                        AlternateText = str3,

                        Database = contentDatabase,

                        FileBased = false,

                        IncludeExtensionInItemName = Settings.Media.IncludeExtensionsInItemNames,

                        KeepExisting = true,

                        Language = LanguageManager.DefaultLanguage,

                        Versioned = false,

                        Destination = this.ParseDestinationUrl(destinationUrl) + fileNameWithoutExtension

                    };

                    if (!ValidateFile(file, result))

                    {

                        return result;

                    }

                    Item innerItem = MediaManager.Creator.CreateFromStream(file.InputStream, "/upload/" + file.FileName, options);

                    if (!string.IsNullOrEmpty(base.Request.Form["description"]))

                    {

                        innerItem.Editing.BeginEdit();

                        innerItem["Description"] = base.Request.Form["description"];

                        innerItem.Editing.EndEdit();

                    }

                    //***************************************************************************************************

                    //**  Custom code - add the elevation code field

                    if (!string.IsNullOrEmpty(base.Request.Form["elevation"]))

                    {

                        innerItem.Editing.BeginEdit();

                        innerItem["Elevation Code"] = base.Request.Form["elevation"];

                        innerItem.Editing.EndEdit();

                    }

                    //***************************************************************************************************

                    MediaItem item = new MediaItem(innerItem);

                    MediaUrlOptions options2 = new MediaUrlOptions(130, 130, false)

                    {

                        Thumbnail = true,

                        BackgroundColor = Color.Transparent,

                        Database = item.Database

                    };

                    string mediaUrl = MediaManager.GetMediaUrl(item, options2);

                    list.Add(new UploadedFileItem(innerItem.Name, innerItem.ID.ToString(), innerItem.ID.ToShortID().ToString(), mediaUrl));

                }

            }

            ((dynamic)result.Result).uploadedFileItems = list;

            return result;

        }

 

        private static bool ValidateFile(HttpPostedFileBase file, SitecoreViewModelResult result)

        {

            List<ErrorItem> list = new List<ErrorItem>();

            int contentLength = file.ContentLength;

            bool flag = true;

            if (contentLength > Settings.Media.MaxSizeInDatabase)

            {

                list.Add(new ErrorItem("size", contentLength.ToString(), string.Format(ClientHost.Globalization.Translate("The file exceeds the maximum size ({0])."), Settings.Media.MaxSizeInDatabase)));

                flag = false;

            }

            if (!flag)

            {

                ((dynamic)result.Result).errorItems = list;

            }

            return flag;

        }

    }

}

 

Finally, I returned to the Uploader script (\Website\sitecore\shell\client\Business Component Library\Layouts\Renderings\Forms\Uploader\Uploader.js), and adjusted the url for the posting to the new Controller:

    setUploadUrl: function () {

      this.url = "/api/sitecore/CustomMedia/Upload" + "?database=" + this.databaseName + "&destinationUrl=" + this.model.get("destinationUrl");

      this.$el.find(".sc-uploader-fileupload").attr("data-url", this.url);

    },

 

Here is a picture of the SPEAK Upload Media Dialog (with the new field: “Elevation Code”.

 AddFieldtoSitecoreMediaUpload-Image3-1

Note: I did not limit the field to display for a particular template type, I added no field validations, and I did not attempt to extend the rendering template definition and display options.  I kept it simple to verify the concept and facilitate integration of platform updates.

 

Topics: sitecore cms, Sitecore 7, Sitecore Media Library

Want To know more?

Ask how EX Squared can develop your next enterprise web project. Contact us for a fast and free consultation.