Last active
January 13, 2017 10:46
-
-
Save michaelchart/e7785bfecc629f02fa19caf8f1736d25 to your computer and use it in GitHub Desktop.
Migrating your Umbraco 4 media library to Umbraco 7
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Web; | |
using Newtonsoft.Json; | |
using umbraco; | |
using Umbraco.Web.BaseRest; | |
using umbraco.cms.businesslogic.media; | |
namespace Umbraco4Site.Web.Services | |
{ | |
/// Umbraco 4 Base class to provide endpoints for working with media | |
[RestExtension("Media")] | |
public class BaseMedia | |
{ | |
/// <summary> | |
/// Method to generate a JSON export of the media within a certain folder. | |
/// Uses Member authentication with the "MediaExporter" member group | |
/// Authentication can be turned off by removing the AllowAll = false, AllowGroup = "MediaExporter" attributes below | |
/// </summary> | |
[RestExtensionMethod(ReturnXml = false, AllowAll = false, AllowGroup = "MediaExporter")] | |
public static string Export() | |
{ | |
var context = HttpContext.Current; | |
// must specify a folderId in the querystring, e.g. ?folderId=1234 | |
var folderId = context.Request.QueryString["folderId"]; | |
if (string.IsNullOrWhiteSpace(folderId)) | |
{ | |
context.Response.StatusCode = 400; | |
return "Must specify folder ID"; | |
} | |
// load the root media folder | |
var root = uQuery.GetMedia(folderId); | |
if (root == null) | |
{ | |
context.Response.StatusCode = 404; | |
return "Folder not found"; | |
} | |
// the base url to use for making an absolute URL from each media item's url path | |
var baseMediaUrl = context.Request.Url.GetLeftPart(UriPartial.Authority); | |
// get all media items within the given folder, convert to a simple DTO and sort by level so that folders can be created before their child items | |
var dto = root.GetDescendantOrSelfMedia().Select(m => ToDTO(m, baseMediaUrl)).OrderBy(m => m.Level); | |
context.Response.ContentType = "application/json"; | |
return JsonConvert.SerializeObject(dto); | |
} | |
/// <summary> | |
/// Method to generate convert a media item to a simple Data Transfer Object | |
/// </summary> | |
private static MediaDTO ToDTO(Media media, string baseMediaUrl) | |
{ | |
if (media == null) return null; | |
var filePath = media.GetProperty<string>("umbracoFile"); | |
return new MediaDTO | |
{ | |
Id = media.Id, | |
Name = media.Text, | |
Type = media.ContentType == null ? null : media.ContentType.Alias, | |
Level = media.Level, | |
Url = string.IsNullOrWhiteSpace(filePath) ? string.Empty : baseMediaUrl + filePath, | |
ParentId = media.ParentId, | |
Properties = media.GenericProperties.ToDictionary(p => p.PropertyType.Alias, p => p.Value.ToString()) | |
}; | |
} | |
} | |
public class MediaDTO | |
{ | |
public int Id { get; set; } | |
public string Name { get; set; } | |
public string Type { get; set; } | |
public string Url { get; set; } | |
public int ParentId { get; set; } | |
public int Level { get; set; } | |
public Dictionary<string, string> Properties { get; set; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Net; | |
using System.Net.Http; | |
using System.Web; | |
using System.Web.Http; | |
using Newtonsoft.Json; | |
using Umbraco.Core; | |
using Umbraco.Core.Models; | |
using Umbraco.Forms.Core; | |
using Umbraco.Web.WebApi; | |
using File = System.IO.File; | |
namespace Umbraco7Site.Web.Controllers.API | |
{ | |
// Umbraco 7 API controller providing some endpoints to work with media | |
// Uses Umbraco backoffice authentication which can be disabled by changing this class to inherit from UmbracoApiController instead of UmbracoAuthorizedApiController | |
public class MediaToolsController : UmbracoAuthorizedApiController | |
{ | |
private const string ImportListFile = "~/App_Data/MediaImport/imported.json"; | |
/// <summary> | |
/// Takes media items in a JSON structure exported from an Umbraco 4 site, and downloads and imports all the items into this Umbraco site: | |
/// [{"Id":-1,"Level":0,"Name":"SYSTEM DATA: umbraco master root","ParentId":-1,"Properties":{},"Type":null,"Url":""},{"Id":3729,"Level":1,"Name":"Homepage images","ParentId":-1,"Properties":{},"Type":"Folder","Url":""},{"Id":19268,"Level":6,"Name":"Homepage Banner","ParentId":3729,"Properties":{"altText":"","cropper":"","umbracoBytes":"375969","umbracoExtension":"png","umbracoFile":"/media/928758/homepage-banner.png","umbracoHeight":"360","umbracoWidth":"955"},"Type":"Image","Url":"http://www.example.com/media/928758/homepage-banner.png"}] | |
/// </summary> | |
/// <param name="media"></param> | |
/// <returns></returns> | |
public string Import([FromBody] IEnumerable<MediaDTO> media) | |
{ | |
Logger.Info(typeof(MediaToolsController), "Starting media import"); | |
// use the Umbraco MediaService API | |
var mediaService = Services.MediaService; | |
// store a list of already imported media items in a file, so that if the importer fails, we can continue where we left off | |
var importFilePath = HttpContext.Current.Server.MapPath(ImportListFile); | |
var imported = LoadImportList(importFilePath); | |
// order by level to ensure parent folders are created before their children are imported | |
media = media.OrderBy(m => m.Level); | |
var count = 0; | |
foreach (var item in media) | |
{ | |
Logger.Debug(typeof(MediaToolsController), $"Processing item {item.Id}"); | |
if (imported.ContainsKey(item.Id)) | |
{ | |
Logger.Debug(typeof(MediaToolsController), "Skipping item as it is already imported"); | |
continue; | |
} | |
IMedia mediaItem; | |
try | |
{ | |
using (var client = new WebClient()) | |
{ | |
// if we've already imported the parent item, find the ID of the newly created media item, otherwise use the root media folder (-1) as the parentId | |
var parentId = imported.ContainsKey(item.ParentId) ? imported[item.ParentId] : -1; | |
// create the new media item | |
mediaItem = mediaService.CreateMedia(item.Name, parentId, item.Type); | |
// if there's a file associated with this media item | |
if (!string.IsNullOrWhiteSpace(item.Url)) | |
{ | |
var fileName = Path.GetFileName(new Uri(item.Url).LocalPath); | |
// todo: could do this async? | |
var fileData = client.DownloadData(item.Url); | |
Logger.Debug(typeof(MediaToolsController), $"Downloaded file data ({item.Url})"); | |
var uploadFile = new MemoryStream(fileData); | |
// put the file contents to the media item | |
mediaItem.SetValue(Constants.Conventions.Media.File, fileName, uploadFile); | |
} | |
mediaService.Save(mediaItem); | |
Logger.Debug(typeof(MediaToolsController), $"Saved media item (new media ID {mediaItem.Id})"); | |
} | |
} | |
catch (Exception ex) | |
{ | |
Logger.Warn(typeof(MediaToolsController), $"Failed to download and import item {item.Id} ({ex.Message})"); | |
continue; | |
} | |
// record the ID of the new media item against the old media item ID | |
imported[item.Id] = mediaItem.Id; | |
// save our import list to a file in case we need to rerun the import from where we left off | |
SaveImportList(importFilePath, imported); | |
count++; | |
} | |
Logger.Info(typeof(MediaToolsController), $"Imported {count} media items"); | |
return $"Imported {count} media items"; | |
} | |
// load the list of already imported media items with their old->new ID mapping | |
private Dictionary<int, int> LoadImportList(string importFilePath) | |
{ | |
if (!File.Exists(importFilePath)) | |
{ | |
Logger.Info(typeof(MediaToolsController), "No previous import log"); | |
return new Dictionary<int, int>(); | |
} | |
else | |
{ | |
Logger.Info(typeof(MediaToolsController), "Loading previous import log"); | |
var json = File.ReadAllText(importFilePath); | |
Logger.Debug(typeof(MediaToolsController), $"Previous import log: {json}"); | |
return JsonConvert.DeserializeObject<Dictionary<int, int>>(json); | |
} | |
} | |
// save our import list to a file in case we need to rerun the import from where we left off | |
private void SaveImportList(string importFilePath, Dictionary<int, int> importList) | |
{ | |
var json = JsonConvert.SerializeObject(importList); | |
var directory = Path.GetDirectoryName(importFilePath); | |
if (directory != null) Directory.CreateDirectory(directory); | |
File.WriteAllText(importFilePath, json); | |
} | |
} | |
public class MediaDTO | |
{ | |
public int Id { get; set; } | |
public string Name { get; set; } | |
public string Type { get; set; } | |
public string Url { get; set; } | |
public int ParentId { get; set; } | |
public int Level { get; set; } | |
public Dictionary<string, string> Properties { get; set; } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment