Last active
November 16, 2016 18:01
-
-
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)
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.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