Skip to content

Instantly share code, notes, and snippets.

@JJCLane
Last active November 16, 2016 18:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JJCLane/8020073f67525cce658893efd4b1b2ae to your computer and use it in GitHub Desktop.
Save JJCLane/8020073f67525cce658893efd4b1b2ae to your computer and use it in GitHub Desktop.
Updated LeBlender data resolver which works for multiple properties without duplicate serialization (credit to hfloyd & mattmorrisonsolidstudios)
using System;
using System.Collections.Generic;
using System.Linq;
using Lecoati.LeBlender.Extension.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Courier.Core;
using Umbraco.Courier.Core.Logging;
using Umbraco.Courier.Core.ProviderModel;
using Umbraco.Courier.DataResolvers.PropertyDataResolvers;
using Umbraco.Courier.ItemProviders;
namespace Lecoati.LeBlender.Extension.Courier
{
public sealed class LeBlenderGridDataResolver : GridCellResolverProvider
{
private readonly IDataTypeService _dataTypeService;
private ItemProvider _propertyItemProvider;
public override string EditorAlias => "Umbraco.Grid";
public bool ResolverAuditing = false;
public enum AuditSeverity
{
All = 0,
Info = 1,
Warning = 2,
Error = 3
}
private enum Direction
{
Extracting,
Packaging
}
public LeBlenderGridDataResolver()
{
_dataTypeService = ApplicationContext.Current.Services.DataTypeService;
}
public override bool ShouldRun(string view, GridValueControlModel cell)
{
try
{
if (view.Contains("leblender")) return true;
}
catch (Exception ex)
{
CourierLogHelper.Error<LeBlenderGridDataResolver>("NON-CRITICAL: LeBlenderGridDataResolver.ShouldRun had an error", ex);
return false;
}
return false;
}
public override void PackagingCell(Item item, ContentProperty propertyData, GridValueControlModel cell)
{
ProcessCell(item, propertyData, cell, Direction.Packaging);
}
public override void ExtractingCell(Item item, ContentProperty propertyData, GridValueControlModel cell)
{
ProcessCell(item, propertyData, cell, Direction.Extracting);
}
private void ProcessCell(Item item, ContentProperty propertyData, GridValueControlModel cell, Direction direction)
{
// Error checking
if (cell.Value == null) return;
// Set the item provider based on execution context
_propertyItemProvider = ItemProviderCollection.Instance.GetProvider(ItemProviderIds.propertyDataItemProviderGuid, ExecutionContext);
// For building JObjects during loop, we use this for the new cell value once resolution is complete
var jArray = new JArray();
// Loop through each cell and create a strongly typed custom 'blender widget' to work with
foreach (var properties in cell.Value.Select(jItem =>
new LeBlenderGridWidget(cell.Editor.Alias, jItem)) // Initialise LeBlender widget from jItem
.Select(leblenderWidget => leblenderWidget.Properties.ToList())) // Get properties from widget
{
// Build product object
dynamic jObject = new JObject();
// Each widget has properties which are Umbraco Datatypes
foreach (var property in properties)
{
// If property is null or there is no data for the property, skip it
if (property?.Value == null || property.DataTypeGuid == null) continue;
// Get info about the Property's Datatypes
var umbracoDatatype = _dataTypeService.GetDataTypeDefinitionById(new Guid(property.DataTypeGuid));
// Create a fake doc type for courier dependency resolution
var fakeItem = DocTypeFaker.CreateFake(item, EditorAlias, cell.Editor.Alias, property, umbracoDatatype);
// Resolve and associate dependencies for 'blender widget'
ResolveAndAssociateBlenderWidgetDependencies(item, direction, fakeItem);
// We need to update the original jItem JSON with new resolved values
if (fakeItem.Data == null || !fakeItem.Data.Any()) continue;
jObject[property.Alias] = new JObject();
// Build the product properties
var prodValue = fakeItem.Data[0].Value;
if (prodValue.ToString().IsValidJson())
{
// Deserialize fake object data value
var serializedNewValue = fakeItem.Data[0].Value as string ?? JsonConvert.SerializeObject(fakeItem.Data[0].Value);
try
{
jObject[property.Alias].value = JObject.Parse(serializedNewValue);
}
catch (JsonReaderException)
{
jObject[property.Alias].value = JArray.Parse(serializedNewValue);
}
}
else
{
jObject[property.Alias].value = prodValue;
}
jObject[property.Alias].dataTypeGuid = property.DataTypeGuid;
jObject[property.Alias].editorAlias = property.Alias;
jObject[property.Alias].editorName = property.Name;
}
// Add the JObject with product object to JArray
jArray.Add(jObject);
}
// Use new jArray or if not modified the original value from cell
if (jArray.IsNullOrEmpty()) return;
cell.Value = jArray;
}
private void ResolveAndAssociateBlenderWidgetDependencies(Item item, Direction direction, Item fakeItem)
{
// run the 'fake' item through Courier's data resolvers
switch (direction)
{
case Direction.Packaging:
try
{
ResolutionManager.Instance.PackagingItem(fakeItem, _propertyItemProvider);
}
catch (Exception ex)
{
CourierLogHelper.Error<LeBlenderGridDataResolver>(string.Concat("Error packaging data value: ", fakeItem.Name), ex);
}
break;
case Direction.Extracting:
try
{
ResolutionManager.Instance.ExtractingItem(fakeItem, _propertyItemProvider);
}
catch (Exception ex)
{
CourierLogHelper.Error<LeBlenderGridDataResolver>(string.Concat("Error extracting data value: ", fakeItem.Name), ex);
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(direction), direction, null);
}
// Gather the dependencies and resources from the Data Resolvers
item.Dependencies.AddRange(fakeItem.Dependencies);
item.Resources.AddRange(fakeItem.Resources);
}
}
internal static class DocTypeFaker
{
public static ContentPropertyData CreateFake(Item item, string editorAlias, string widgetEditorAlias,
LeBlenderPropertyModel model, IDataTypeDefinition umbracoDatatype)
{
// We will pretend that each widget property is it's own Doctype property,
// so we can run the resolvers which have already been created for each Datatype
// (This is how Nested Content's Data Resolver works)
return new ContentPropertyData
{
ItemId = item.ItemId,
Name = $"{item.Name} [{editorAlias}: LeBlender Widget {widgetEditorAlias} (Property: {model.Alias})]",
Data = new List<ContentProperty>
{
new ContentProperty
{
Alias = model.Alias,
DataType = new Guid(model.DataTypeGuid),
PropertyEditorAlias = umbracoDatatype.PropertyEditorAlias,
Value = model.Value
}
}
};
}
}
internal class LeBlenderGridWidget
{
public string WidgetName { get; set; }
public IEnumerable<LeBlenderPropertyModel> Properties { get; set; }
public LeBlenderGridWidget(string widgetName, dynamic model)
{
WidgetName = widgetName;
var props = new List<LeBlenderPropertyModel>();
try
{
var data = JsonConvert.DeserializeObject(model.ToString());
foreach (var item in data)
{
var lbPropModel = new LeBlenderPropertyModel
{
Name = item.Value.editorName,
Alias = item.Value.editorAlias,
DataTypeGuid = item.Value.dataTypeGuid,
Value = item.Value.value
};
props.Add(lbPropModel);
}
}
catch (Exception ex)
{
CourierLogHelper.Error<LeBlenderGridWidget>($"{widgetName}: Error while converting a dynamic property model to a new LeBlenderGridWidget model.", ex);
LogHelper.Error<LeBlenderGridWidget>($"{widgetName}: Error while converting a dynamic property model to a new LeBlenderGridWidget model.", ex);
}
Properties = props;
}
}
internal static class JsonExtensions
{
public static bool IsNullOrEmpty(this JToken token)
{
return (token == null) ||
(token.Type == JTokenType.Array && !token.HasValues) ||
(token.Type == JTokenType.Object && !token.HasValues) ||
(token.Type == JTokenType.String && token.ToString() == string.Empty) ||
(token.Type == JTokenType.Null);
}
public static bool IsValidJson(this string strInput)
{
strInput = strInput.Trim();
if ((!strInput.StartsWith("{") || !strInput.EndsWith("}")) &&
(!strInput.StartsWith("[") || !strInput.EndsWith("]"))) return false;
try
{
var obj = JToken.Parse(strInput);
return true;
}
catch (JsonReaderException)
{
// Exception in parsing json
return false;
}
catch (Exception)
{
//some other exception
return false;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment