Friday, 3 March 2017

Uploading images from SDL Web (Tridion) to SDL Media Manager

Consider the scenario where you need to migrate all the images from your SDL Web CMS to SDL Media Manager. For each image in SDL Web, the solution would involve:
  • Downloading the image
  • Uploading the image to SDL Media Manager
  • Creating a program and distribution in SDL Media Manager
  • Putting the distribution Live
  • Finding the "using components" in SDL Web and updating each one by replacing the relationship to the image in SDL Web with the relationship to the equivalent image in SDL Media Manager.
To complete all the steps above, you will require the following.
  • SDL Web Core Service client
  • SDL Web Stream Download client
  • SDL Media Manager client
  • ECL Service client
Here are the supporting app settings.

<appSettings>
<!-- Properties used by the Core Service -->
<add key="HostName" value="hostname-to-the-sdlweb-cme-server.com" />
<add key="ImpersonationUserDomain" value="the-domain"/>
<add key="ImpersonationUserName" value="the-username" />
<add key="ImpersonationUserPassword" value="********" />
<add key="ClientSettingsProvider.ServiceUri" value="" />
<!-- Properties used by the Media Manager integration -->
<add key="IssuerName" value="http://localhost:89/IWSTrust13" />
<add key="MediaManagerWebServiceAddress" value="https://customerdomain.sdlmedia.com/WebServices/MediaManager2011.svc" />
<!-- Supporting properties for running the script -->
<add key="TridionToMediaManagerFolderMappingsFile" value="D:\migration\folder-structure-mappings-complete.xlsx" />
<add key="MigrationSupportDirectory" value="D:\migration\Images\" />
<add key="MigratingAssetSchemaTcmId" value="tcm:5-33-8" />
<add key="MediaManagerAssetType" value="Image" />
<add key="MediaManagerOutlet" value="Image Outlet" />
<add key="MediaManagerTags" value="Image" />
</appSettings>

As shown above, the first set of properties is related to the SDL Web (Tridion) Core Service. The second set of properties is related to Media Manager, which includes the IssuerName specifying the URL to the Secure Token Service (STS). The third set of properties is required for specifying:
  • A spreadsheet mapping the SDL Web (Tridion) folder structures to the equivalent folder structures in Media Manager.
  • A directory used for storing the images downloaded from SDL Web for uploading to Media Manager.
  • The tcmid of the schema (asset type) to be migrated. In the listing above, the tcm:5-33-8 corresponds to the Image multimedia schema.
  • The name of the asset type (as defined in Media Manager) to be migrated.
  • The named of the outlet (as defined in Media Manager) to be used when creating the distributions in Media Manager.
  • The name of the tag (as defined in Media Manager) to be used when uploading and creating the assets in Media Manager.
The Media Manager Secure Token Service is essential for running the asset migration. It allows the communication to Media Manager to be established through the API. In the application settings above, the STS service is available from the http://localhost:89/IWSTrust13/ URL.

The Media Manager API does not support the creation of folders. Therefore, before migration can take place, the folder structures in SDL Web must be replicated in Media Manager to accommodate the assets. The TridionToMediaManagerFolderMappingsFile configuration property specifies a spreadsheet containing the full mappings between the existing folder structures in SDL Web and the equivalent folders created in SDL Media Manager.

Downloading assets from SDL Web (Tridion)


The code for downloading assets from SDL Web is shown below.

private static Dictionary<string, string> DownloadFromTridion(StreamDownloadClient streamDownloadClient, ComponentData multimediaComponent)
{
Dictionary<string, string> downloadInfo = null;
// Prepare file for download in local file system
string originalFileName = Path.GetFileName(multimediaComponent.BinaryContent.Filename);
string downloadFile = Options.MigrationSupportDirectory + originalFileName;
FileStream fs = File.Create(downloadFile);
try
{
// Read metadata fields from Tridion
XDocument metadata = XDocument.Parse(multimediaComponent.Metadata);
var nss = metadata.Root.GetDefaultNamespace();
string altText = metadata.Root.Element(nss + "AltText") != null ? metadata.Root.Element(nss + "AltText").Value : String.Empty;
// Download asset from Tridion
byte[] binaryContent = null;
if (multimediaComponent.BinaryContent.FileSize != -1)
{
Stream tempStream = streamDownloadClient.DownloadBinaryContent(multimediaComponent.Id);
var memoryStream = new MemoryStream();
tempStream.CopyTo(memoryStream);
binaryContent = memoryStream.ToArray();
tempStream.Close();
}
if (binaryContent != null)
fs.Write(binaryContent, 0, binaryContent.Length);
downloadInfo = new Dictionary<string, string>();
downloadInfo.Add("file", downloadFile);
downloadInfo.Add("originalFileName", originalFileName);
downloadInfo.Add("altText", altText);
downloadInfo.Add("title", multimediaComponent.Title);
}
catch(Exception exception)
{
log.Error(exception);
}
finally
{
fs.Close();
}
return downloadInfo;
}

Uploading to SDL Media Manager


The code for uploading to SDL Media Manager is shown below. This code also creates the program, the distribution and puts the distribution live.

private static Dictionary<string, object> UploadToMediaManager(IMediaManager2011 mediaManagerClient,
Dictionary<string, string> downloadInfo, long mediaManagerFolderId, long assetTypeId,
long outletId, long[] tags)
{
// Upload to Media Manager
UploadInfoData uploadInfoData = new UploadInfoData
{
Author = "Migration Upload Tool",
MakeAvailableForWebPublishing = true,
ProgramCreation = ProgramCreationOptions.OneProgramPerItem,
DistributionCreation = DistributionCreationOptions.OneDistribution
};
string uploadUrl = mediaManagerClient.GetUploadUrl(mediaManagerFolderId, assetTypeId, tags, downloadInfo["title"], uploadInfoData);
using (WebClient client = new WebClient())
{
try
{
client.UploadFile(uploadUrl, downloadInfo["file"]);
}
catch(Exception exception)
{
log.Error(exception);
return null;
}
WebHeaderCollection webResponse = client.ResponseHeaders;
long assetId = 0;
long.TryParse(webResponse.GetValues("AssetId").First(), out assetId);
long programId = 0;
long.TryParse(webResponse.GetValues("ProgramId").First(), out programId);
long distributionId = 0;
long.TryParse(webResponse.GetValues("DistributionId").First(), out distributionId);
Dictionary<string, long> responseIdsUpload = new Dictionary<string, long> { { "assetId", assetId }, { "programId", programId }, { "distributionId", distributionId } };
// Update the asset's properties
AssetData assetData = (AssetData)mediaManagerClient.GetItem(ItemTypes.Asset, responseIdsUpload["assetId"]);
assetData.Name = downloadInfo["title"];
assetData.Description = !String.IsNullOrEmpty(downloadInfo["altText"]) ? downloadInfo["altText"] : downloadInfo["title"];
mediaManagerClient.UpdateItem(assetData);
// Update the program's properties
ProgramData programData = (ProgramData)mediaManagerClient.GetItem(ItemTypes.Program, responseIdsUpload["programId"]);
programData.Name = downloadInfo["title"];
programData.Description = !String.IsNullOrEmpty(downloadInfo["altText"]) ? downloadInfo["altText"] : downloadInfo["title"];
mediaManagerClient.UpdateItem(programData);
return CreateDistribution(mediaManagerClient, responseIdsUpload, downloadInfo["title"], mediaManagerFolderId, outletId);
}
}
private static Dictionary<string, object> CreateDistribution(IMediaManager2011 mediaManagerClient,
Dictionary<string, long> responseIdsUpload, string assetDescription,
long mediaManagerFolderId, long outletId)
{
// Create the Distribution
long programId = responseIdsUpload["programId"];
var newDistribution = new DistributionData
{
Name = assetDescription,
Description = assetDescription,
FolderId = mediaManagerFolderId,
IsLive = true,
OutletId = outletId,
ProgramIds = new[] { programId }
};
DistributionData distributionData = null;
try
{
distributionData = (DistributionData)mediaManagerClient.AddItem(newDistribution);
}
catch(Exception exception)
{
log.Error("Error creating the distribution in Media Manager");
log.Error(exception);
return null;
}
try
{
mediaManagerClient.PutLiveAsync(ItemTypes.Distribution, new long[] { distributionData.Id });
}
catch(Exception exception)
{
log.Error("Failed to put live distribution: " + distributionData.GlobalId);
log.Error(exception);
}
Dictionary<string, object> uploadInfo = new Dictionary<string, object>();
uploadInfo.Add("distributionId", distributionData.Id);
uploadInfo.Add("description", distributionData.Description);
uploadInfo.Add("globalId", distributionData.GlobalId);
return uploadInfo;
}

Updating the relationships in SDL Web (Tridion)


The download and upload steps complete the process of migrating assets from SDL Web to SDL Media Manager. However, it is also important to update the components using the migrated asset by swapping the reference for the new asset in SDL Media Manager.

This step relies on a SessionAwareEclServiceClient only available through a netTcpBinding which is not exposed. Therefore, a solution invoking this service can only run on the CME server itself.

<netTcpBinding>
<binding name="EclNetTcpBinding" maxReceivedMessageSize="2147483647" receiveTimeout="00:10:00" sendTimeout="00:10:00">
<readerQuotas maxArrayLength="2147483647" maxStringContentLength="2147483647" />
</binding>
</netTcpBinding>
And through the following endpoint.

<client>
<endpoint name="EclBinaryEndpoint"
address="net.tcp://localhost:2660/ExternalContentLibrary/2012/netTcp"
binding="netTcpBinding"
bindingConfiguration="EclNetTcpBinding"
contract="Tridion.ExternalContentLibrary.Service.Client.ISessionAwareEclService"/>
</client>
You will need to copy the Tridion.ExternalContentLibrary.Service.Client.dll file from the SDL Web CME server and import into your solution in order to build the project.

The code below takes the core service and ECL clients, the migrated SDL Web multimedia component and a Dictionary object containing information gathered during the upload process. This information includes the distribution id, which is used to create or get the stub to the asset originating from Media Manager.

private static void UpdateWhereUsingComponents(ICoreService coreServiceClient, SessionAwareEclServiceClient eclClient, ComponentData componentData, Dictionary<string, object> uploadInfo)
{
Dictionary<string, string> stubInfo = null;
try
{
stubInfo = eclClient.CreateOrGetStubUris(new List<string> { "ecl:5-mm-" + uploadInfo["distributionId"] + "-dist-file" });
}
catch(Exception exception)
{
log.Error("Error generating stub for image " + componentData.Id);
log.Error(exception);
}
if (stubInfo == null || stubInfo.Count == 0)
return;
log.Info("Stub " + stubInfo.Values.First() + " generated for " + componentData.Id);
// Create a filter
UsingItemsFilterData usingItemsFilterData = new UsingItemsFilterData
{
BaseColumns = ListBaseColumns.Id, // to specify the detail in the XML
IncludedVersions = VersionCondition.OnlyLatestVersions,
ItemTypes = new[] { ItemType.Component } // to specify certain items
};
SynchronizeOptions syncOptions = new SynchronizeOptions();
syncOptions.SynchronizeFlags = SynchronizeFlags.FixNamespace | SynchronizeFlags.ApplyDefaultValuesForMissingMandatoryFields;
ReadOptions readOptions = new ReadOptions();
// Get the XML by calling .GetListXml on the client
XElement usingXML = coreServiceClient.GetListXml(componentData.Id, usingItemsFilterData);
XNamespace df = usingXML.Name.Namespace;
IEnumerable<XElement> itemElements = usingXML.Elements(df + "Item");
foreach (XElement itemElement in itemElements)
{
string usingItemTcmid = itemElement.FirstAttribute.Value;
string usingItemPubId = usingItemTcmid.Split('-')[0];
string multimediaLinkId = usingItemPubId + "-" + componentData.Id.Split('-')[1];
string stubTcmId = usingItemPubId + "-" + stubInfo.Values.First().Split('-')[1];
ComponentData usingItem = coreServiceClient.Read(usingItemTcmid, readOptions) as ComponentData;
usingItem = (ComponentData)coreServiceClient.SynchronizeWithSchema(usingItem, syncOptions).SynchronizedItem;
Boolean modified = false;
if (!String.IsNullOrEmpty(usingItem.Content) && usingItem.Content.Contains(multimediaLinkId))
{
usingItem.Content = usingItem.Content.Replace(multimediaLinkId, stubTcmId);
modified = true;
}
if (!String.IsNullOrEmpty(usingItem.Metadata) && usingItem.Metadata.Contains(multimediaLinkId))
{
usingItem.Metadata = usingItem.Metadata.Replace(multimediaLinkId, stubTcmId);
modified = true;
}
if (modified)
{
try
{
coreServiceClient.CheckOut(usingItem.Id, true, readOptions);
coreServiceClient.Save(usingItem, readOptions);
coreServiceClient.CheckIn(usingItem.Id, true, "Updated by the migration tool", readOptions);
log.Info("UPDATED RELATIONSHIP: " + usingItem.Id);
}
catch(Exception exception)
{
log.Error("Unable to update relationship in Tridion!");
log.Error(exception);
try
{
coreServiceClient.UndoCheckOut(usingItem.Id, true, readOptions);
}
catch(Exception undoCheckOutException)
{
log.Error("Unable to undo checkout!");
log.Error(undoCheckOutException);
}
continue;
}
}
}
}

1 comment:

  1. Very informative and It was an awesome post. I love reading your fantastic content. Thanks for sharing it with us. We are so greatful to your sharing. Download Razorsql

    ReplyDelete