Home » Data » Set New Media Library Item Fields Via the Sitecore Item Web API

Set New Media Library Item Fields Via the Sitecore Item Web API

Sitecore Technology MVP 2016
Sitecore MVP 2015
Sitecore MVP 2014

Enter your email address to follow this blog and receive notifications of new posts by email.

Tweets

On a recent project, I found the need to set field data on new media library items using the Sitecore Item Web API — a feature that is not supported “out of the box”.

After digging through Sitecore.ItemWebApi.dll, I discovered where one could add the ability to update fields on newly created media library items:

CreateMediaItems-out-of-box

Unfortunately, the CreateMediaItems method in the Sitecore.ItemWebApi.Pipelines.Request.ResolveAction class is declared private — introducing code to set fields on new media library items will require some copying and pasting of code.

Honestly, I loathe duplicating code. 😦

Unfortunately, we must do it in order to add the capability of setting fields on media library items via the Sitecore Item Web API (if you can think of a better way, please leave a comment).

I did just that on the following subclass of Sitecore.ItemWebApi.Pipelines.Request.ResolveAction:

using System;
using System.Collections.Generic;
using System.IO;
using System.Web;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.IO;
using Sitecore.ItemWebApi;
using Sitecore.ItemWebApi.Pipelines.Read;
using Sitecore.ItemWebApi.Pipelines.Request;
using Sitecore.Pipelines;
using Sitecore.Resources.Media;
using Sitecore.Text;
using Sitecore.Data.Fields;

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Request
{
    public class ResolveActionMediaItems : ResolveAction
    {
        protected override void ExecuteCreateRequest(RequestArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            if (IsMediaCreation(args.Context))
            {
                CreateMediaItems(args);
                return;
            }

            base.ExecuteCreateRequest(args);
        }

        private void CreateMediaItems(RequestArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Item parent = args.Context.Item;
            if (parent == null)
            {
                throw new Exception("The specified location not found.");
            }
            string fullPath = parent.Paths.FullPath;
            if (!fullPath.StartsWith("/sitecore/media library"))
            {
                throw new Exception(string.Format("The specified location of media items is not in the Media Library ({0}).", fullPath));
            }
            string name = args.Context.HttpContext.Request.Params["name"];
            if (string.IsNullOrEmpty(name))
            {
                throw new Exception("Item name not specified (HTTP parameter 'name').");
            }
            Database database = args.Context.Database;
            Assert.IsNotNull(database, "Database not resolved.");
            HttpFileCollection files = args.Context.HttpContext.Request.Files;
            Assert.IsTrue(files.Count > 0, "Files not found.");
            List<Item> list = new List<Item>();

            for (int i = 0; i < files.Count; i++)
            {
                HttpPostedFile file = files[i];
                if (file.ContentLength != 0)
                {
                    string fileName = file.FileName;
                    string uniqueName = ItemUtil.GetUniqueName(parent, name);
                    string destination = string.Format("{0}/{1}", fullPath, uniqueName);
                    MediaCreatorOptions options = new MediaCreatorOptions
                    {
                        AlternateText = fileName,
                        Database = database,
                        Destination = destination,
                        Versioned = false
                    };
                    Stream inputStream = file.InputStream;
                    string extension = FileUtil.GetExtension(fileName);
                    string filePath = string.Format("{0}.{1}", uniqueName, extension);
                    try
                    {
                        Item item = MediaManager.Creator.CreateFromStream(inputStream, filePath, options);
                        SetFields(item, args.Context.HttpContext.Request["fields"]); // MR: set field data on item if data is passed
                        list.Add(item);
                    }
                    catch
                    {
                        Logger.Warn("Cannot create the media item.");
                    }
                }
            }
            ReadArgs readArgs = new ReadArgs(list.ToArray());
            CorePipeline.Run("itemWebApiRead", readArgs);
            args.Result = readArgs.Result;
        }

        private static void SetFields(Item item, string fieldsQueryString)
        {
            if (!string.IsNullOrWhiteSpace(fieldsQueryString))
            {
                SetFields(item, new UrlString(fieldsQueryString));
            }
        }

        private static void SetFields(Item item, UrlString fields)
        {
            Assert.ArgumentNotNull(item, "item");
            Assert.ArgumentNotNull(fields, "fields");

            if (fields.Parameters.Count < 1)
            {
                return;
            }

            item.Editing.BeginEdit();

            foreach (string fieldName in fields.Parameters.Keys)
            {
                Field field = item.Fields[fieldName];
                if(field != null)
                {
                    field.Value = fields.Parameters[fieldName];
                }
            }

            item.Editing.EndEdit();
        }

        private bool IsMediaCreation(Sitecore.ItemWebApi.Context context)
        {
            Assert.ArgumentNotNull(context, "context");
            return context.HttpContext.Request.Files.Count > 0;
        }
    }
}

The above class reads fields supplied by client code via a query string passed in a query string parameter — the fields query string must be “url encoded” by the client code before being passed in the outer query string.

We then delegate to an instance of the Sitecore.Text.UrlString class when fields are supplied by client code — if you don’t check to see if the query string is null, empty or whitespace, the UrlString class will throw an exception if it’s not set — to parse the fields query string, and loop over the parameters within it — each parameter represents a field to be set on the item, and is set if it exists (see the SetFields methods above).

I replaced Sitecore.ItemWebApi.Pipelines.Request.ResolveAction in \App_Config\Include\Sitecore.ItemWebApi.config with our new pipeline processor above:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
	<sitecore>
		<pipelines>
		<!-- there is more stuff up here -->
		<!--Processes Item Web API requests. -->
		<itemWebApiRequest>
			<!-- there are more pipeline processors up here -->
			<processor type="Sitecore.Sandbox.ItemWebApi.Pipelines.Request.ResolveActionMediaItems, Sitecore.Sandbox" />
			<!-- there are more pipeline processors down here -->
		</itemWebApiRequest>
		<!-- there is more stuff down here -->
		</pipelines>
	</sitecore>
</configuration>

I then modified the media library item creation code in my copy of the console application written by Kern Herskind Nightingale — Director of Technical Services at Sitecore UK — to send field data to the Sitecore Item Web API:

create-media-console-2

I set some fields using test data:

create-media-console-1

After I ran the console application, I got a response — this is a good sign 🙂

pizza-created-result

As you can see, our test field data has been set on our pizza media library item:

pizza-in-media-library

If you have any thoughts or suggestions on this, please drop a comment.

Now I’m hungry — perhaps I’ll order a pizza! 🙂

Advertisements

12 Comments

  1. Aaron says:

    You may have already thought of this, but have you tried to use reflection to call the method? Could save some typing…

    Nice article though. Good read.

  2. ruby says:

    The pizza icon looks like the Sitecore logo 🙂

  3. Jan Sommer says:

    My SDN account have been deactivated so I can’t download the module and check for myself, but what would happen if you instead of overriding the pipeline processor placed another processor after Sitecore.ItemWebApi.Pipelines.Request.ResolveActionMediaItems that would set the fields instead? Looking at this code:

    ReadArgs readArgs = new ReadArgs(list.ToArray());
    CorePipeline.Run(“itemWebApiRead”, readArgs);
    args.Result = readArgs.Result;

    It seems that the processed Media Items are stored in args.Result which perhaps enables you to read them in a separate processor. This is just a wild guess though.

    • The ReadArgs parameters object instance does contain an instance of a Dynamic object that contains another instance of a Dynamic object — nested instances — where the “inner” instance contains properties about updated media items — ID, Language, and others — and definitely would allow for another pipeline processor to set fields on added media library items — one would have to retrieve these via ID from Sitecore.

      I was going to go down this road originally but was worried about the potential messiness of getting information out of the nested Dynamic instances. However, I don’t think the “copy and paste” solution is any better in my opinion.

      Mike

  4. Stewart says:

    Hi Mike,

    We’re building a migration tool and are massaging links into Sitecore format using IDs that are generated externally. This works fine for Content, as the api lets you set the ID on creation.

    However, it looks like setting the ID for media items is not available “out of the box”. (I’m not programming this, we’ll not the Sitecore interace anyway – just the massaging (in Smalltalk)).

    Just wondering if you have any suggestions as to how we can create Media Items with a specified ID?

    Thanks,

    Stewart

    • Hi Stewart,

      How are the Sitecore.Data.ID instances being passed during the creation of the content Items via the Sitecore API? None of the Add method overloads on Sitecore.Data.Items.Item take in a Sitecore.Data.ID (or System.Guid).

      Mike

      • Stewart says:

        Hi Mike,

        Thanks for your reply. We’re using the ItemManager. See below for a code snippet.

        Cheers,

        Stewart

        public void SitecoreNewFolder(string Name, Item parent, TemplateItem template, ID id)
        {
        try
        {
        Item newItem = ItemManager.CreateItem(Name, parent, template.ID, id, SecurityCheck.Disable);
        }
        catch (Exception ex)
        {
        divOutput.InnerHtml += “ Operation failed = ” + ex.Message + ” “;
        }
        }

        public void SitecoreNewItem(string Name, Item parent, TemplateItem template, ID id, string File, string Title)
        {
        try
        {
        Item newItem = ItemManager.CreateItem(Name, parent, template.ID, id, SecurityCheck.Disable);

        using (new SecurityDisabler())
        {
        newItem.Editing.BeginEdit();
        newItem.Fields[“Title”].Value = Title;
        newItem.Fields[“Overview”].Value = “Overview”;
        newItem.Fields[“Body”].Value = “Body for ” + File;
        newItem.Editing.EndEdit();
        }
        }
        catch (Exception ex)
        {
        divOutput.InnerHtml += “ Operation failed = ” + ex.Message + ” “;
        }
        }

      • Hi Stewart,

        I have a potential idea that might work though it isn’t pretty (I have not tested the code in the following GIST so am uncertain if this will work at all):

        The idea would be to inject an instance of the class above into the pipeline processor using the Sitecore Configuration Factory — please let me know if you would more direction on how to accomplish this — and use its CreateFromStream(Stream stream, string filePath, ID id, MediaCreatorOptions options) method.

        The DefaultItemName property on the class should be set in the Sitecore configuration file for the instance being injected into the pipeline processor.

        Mike

      • Stewart says:

        Hi Mike,

        Thank you very much for the code idea. I’ll get back to you once our developer has tried it out.

        Cheers,

        Stewart

  5. Alex says:

    Hi Sitecore Junkie!

    I got your code from my co-worker Stewart and I would like to thank you for sharing! It was really useful and gave me some direction to where to look for, one of my biggest frustrations with Sitecore is the very limited level of documentation out there, your blog is a good stop for developers!

    Let me give you some feedback about the original code, I’ve customized to a very small version from the original and now it looks like this:

    static void SitecoreNewMediaFile(string ItemName, Item ParentItem, Item ItemTemplate, ID ItemID, string SitecorePath, string FilePath, string ItemAlternativeText)
    {
    string Destination = SitecorePath.Remove(SitecorePath.LastIndexOf(‘.’), 4);

    try
    {
    using (new Sitecore.SecurityModel.SecurityDisabler())
    {
    MediaCreatorOptions mediacreatorOptions = new MediaCreatorOptions();

    mediacreatorOptions.AlternateText = ItemAlternativeText == “” ? ItemName : ItemAlternativeText;
    mediacreatorOptions.Database = MasterDB;
    mediacreatorOptions.Destination = Destination;
    mediacreatorOptions.Versioned = false;
    mediacreatorOptions.OutputFilePath = Destination;
    mediacreatorOptions.KeepExisting = true;

    TemplateItem TemplateItem = MasterDB.Templates[ItemTemplate.ID];
    Item Parent = MasterDB.GetItem(ParentItem.ID);

    Item mediaItem = ItemManager.CreateItem(ItemName, Parent, TemplateItem.ID, ItemID, SecurityCheck.Disable);

    Sitecore.Resources.Media.Media media = MediaManager.GetMedia(new MediaItem(mediaItem)); //Sitecore.Resources.Media.Media media = MediaManager.GetMedia(CreateItem(mediaItem.Paths.FullPath, FileUtil.GetFileName(filePath), clonedOptions));

    FileStream fsMediaItem = new FileStream(FilePath, FileMode.Open);

    media.SetStream(fsMediaItem, Path.GetExtension(FilePath).Remove(0, 1));

    media.MediaData.MediaItem.BeginEdit();
    media.MediaData.MediaItem.Alt = mediacreatorOptions.AlternateText;
    media.MediaData.MediaItem.EndEdit();

    // media.MediaData.MediaItem.InnerItem.Fields[]
    }
    }
    catch (Exception ex)
    {
    logFile.WriteLine(ex.Message.Replace(System.Environment.NewLine, ” “) + ” = MEDIA FILE = ItemName: {0} ParentID: {1} TemplateID: {2} ID: {3} SitecorePath: {4} FilePath: {5} Destination: {6} AltText: {7} “, ItemName, ParentItem.ID.ToString(), ItemTemplate.ID.ToString(), ItemID.ToString(), SitecorePath, FilePath, Destination, ItemAlternativeText);
    }
    }

    There was a problem with your original code, it was creating duplicated items and I’ve changed the function on “AttachStreamToMediaItem()”:

    Sitecore.Resources.Media.Media media = MediaManager.GetMedia(CreateItem(mediaItem.Paths.FullPath, Sitecore.IO.FileUtil.GetFileName(filePath), clonedOptions));

    The live above was replace to:

    Sitecore.Resources.Media.Media media = MediaManager.GetMedia(mediaItem);

    Now there is a side effect, With and Height are empty for images, I guess I will need to live with this – I’m really tired of this product to spend so much time trying this stuff.

    Hey thanks a lot!

Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: