diff --git a/src/lib/.editorconfig b/src/lib/.editorconfig
new file mode 100644
index 000000000..a3c517978
--- /dev/null
+++ b/src/lib/.editorconfig
@@ -0,0 +1,85 @@
+
+[*.{cs,vb}]
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+indent_size = 4
+end_of_line = crlf
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
+
+[*.cs]
+csharp_indent_labels = one_less_than_current
+csharp_using_directive_placement = outside_namespace:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_prefer_braces = true:silent
+csharp_style_namespace_declarations = block_scoped:silent
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_throw_expression = true:suggestion
\ No newline at end of file
diff --git a/src/lib/PnP.Framework.Test/Framework/ProvisioningTemplates/DomainModelTests.cs b/src/lib/PnP.Framework.Test/Framework/ProvisioningTemplates/DomainModelTests.cs
index 774ff7e5f..66ec1ce57 100644
--- a/src/lib/PnP.Framework.Test/Framework/ProvisioningTemplates/DomainModelTests.cs
+++ b/src/lib/PnP.Framework.Test/Framework/ProvisioningTemplates/DomainModelTests.cs
@@ -444,7 +444,7 @@ public void CanHandleDomainObjectWithJsonFormatter()
{
using (Stream _formattedTemplate = new FileStream(this._provisioningTemplatePath7, FileMode.Open, FileAccess.Read, FileShare.Read))
{
- ITemplateFormatter formatter = new JsonPnPFormatter();
+ ITemplateFormatter formatter = new JsonPnPSchemaFormatter();
JsonTemplateProvider provider =
new JsonFileSystemTemplateProvider(
diff --git a/src/lib/PnP.Framework.sln b/src/lib/PnP.Framework.sln
index 5c1bdd30a..f53748bde 100644
--- a/src/lib/PnP.Framework.sln
+++ b/src/lib/PnP.Framework.sln
@@ -1,10 +1,11 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.30503.244
+# Visual Studio Version 17
+VisualStudioVersion = 17.4.33110.190
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C3C026AD-0B78-4779-9DC4-F875B42A48F4}"
ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
CHANGELOG.md = CHANGELOG.md
EndProjectSection
EndProject
diff --git a/src/lib/PnP.Framework/PnP.Framework.csproj b/src/lib/PnP.Framework/PnP.Framework.csproj
index 2e2903965..aaeda8231 100644
--- a/src/lib/PnP.Framework/PnP.Framework.csproj
+++ b/src/lib/PnP.Framework/PnP.Framework.csproj
@@ -304,4 +304,8 @@
+
+
+
+
diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Json/Converters/SiteCollectionsAndSitesConverter.cs b/src/lib/PnP.Framework/Provisioning/Providers/Json/Converters/SiteCollectionsAndSitesConverter.cs
new file mode 100644
index 000000000..7d64d506c
--- /dev/null
+++ b/src/lib/PnP.Framework/Provisioning/Providers/Json/Converters/SiteCollectionsAndSitesConverter.cs
@@ -0,0 +1,75 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using PnP.Framework.Provisioning.Model;
+using PnP.Framework.Provisioning.Providers.Xml;
+using System;
+
+namespace PnP.Framework.Provisioning.Providers.Json.Converters
+{
+ public class SiteCollectionsAndSitesConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ var siteTypeName = $"{PnPSerializationScope.Current?.BaseSchemaNamespace}.SiteCollection, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}";
+ var siteType = Type.GetType(siteTypeName, true);
+ return siteType.IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ // Define the specific source schema types
+ var communicationSiteTypeName = $"{PnPSerializationScope.Current?.BaseSchemaNamespace}.CommunicationSite, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}";
+ var communicationSiteType = Type.GetType(communicationSiteTypeName, true);
+ var teamSiteTypeName = $"{PnPSerializationScope.Current?.BaseSchemaNamespace}.TeamSite, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}";
+ var teamSiteType = Type.GetType(teamSiteTypeName, true);
+ var teamSiteNoGroupTypeName = $"{PnPSerializationScope.Current?.BaseSchemaNamespace}.TeamSiteNoGroup, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}";
+ var teamSiteNoGroupType = Type.GetType(teamSiteNoGroupTypeName, true);
+ var teamSubSiteNoGroupTypeName = $"{PnPSerializationScope.Current?.BaseSchemaNamespace}.TeamSubSiteNoGroup, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}";
+ var teamSubSiteNoGroupType = Type.GetType(teamSubSiteNoGroupTypeName, true);
+
+ JObject sourceJObject = JObject.Load(reader);
+ JToken discriminatorToken = sourceJObject.SelectToken("Type");
+ string discriminator = discriminatorToken?.Value() ?? "CommunicationSite";
+ discriminatorToken.Parent.Remove();
+
+ Object targetItem = null;
+ switch (discriminator)
+ {
+ case "CommunicationSite":
+ targetItem = sourceJObject.ToObject(communicationSiteType);
+ break;
+ case "TeamSite":
+ targetItem = sourceJObject.ToObject(teamSiteType);
+ break;
+ case "TeamSiteNoGroup":
+ targetItem = sourceJObject.ToObject(teamSiteNoGroupType);
+ break;
+ case "TeamNoGroupSubSite":
+ targetItem = sourceJObject.ToObject(teamSubSiteNoGroupType);
+ break;
+ }
+
+ return targetItem;
+ }
+
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ JToken t = JToken.FromObject(value);
+
+ if (t.Type != JTokenType.Object)
+ {
+ t.WriteTo(writer);
+ }
+ else
+ {
+ string typeProperty =value.GetType().Name;
+
+ JObject o = (JObject)t;
+ o.AddFirst(new JProperty("Type", typeProperty));
+
+ o.WriteTo(writer);
+ }
+ }
+ }
+}
diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Json/Converters/SiteColumnsConverter.cs b/src/lib/PnP.Framework/Provisioning/Providers/Json/Converters/SiteColumnsConverter.cs
new file mode 100644
index 000000000..ed914c0a0
--- /dev/null
+++ b/src/lib/PnP.Framework/Provisioning/Providers/Json/Converters/SiteColumnsConverter.cs
@@ -0,0 +1,44 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using PnP.Framework.Extensions;
+using PnP.Framework.Provisioning.Providers.Xml;
+using System;
+
+namespace PnP.Framework.Provisioning.Providers.Json.Converters
+{
+ public class SiteColumnsConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ var fieldsTypeName = $"{PnPSerializationScope.Current?.BaseSchemaNamespace}.ProvisioningTemplateSiteFields, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}";
+ var fieldsType = Type.GetType(fieldsTypeName, true);
+ return fieldsType.IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ JArray sourceJObject = JArray.Load(reader);
+ var e = sourceJObject.ToObject();
+
+ var fields = Activator.CreateInstance(objectType);
+ fields.SetPublicInstancePropertyValue("Any", e);
+
+ return fields;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ JToken t = JToken.FromObject(value);
+
+ if (t.Type != JTokenType.Object)
+ {
+ t.WriteTo(writer);
+ }
+ else
+ {
+ JArray a = (JArray)t.SelectToken("Any");
+ a.WriteTo(writer);
+ }
+ }
+ }
+}
diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPFormatter.cs b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPFormatter.cs
deleted file mode 100644
index e787dab38..000000000
--- a/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPFormatter.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using Newtonsoft.Json;
-using System;
-using System.IO;
-using System.Text;
-
-namespace PnP.Framework.Provisioning.Providers.Json
-{
- public class JsonPnPFormatter : ITemplateFormatterWithValidation
- {
- private TemplateProviderBase _provider;
-
- public void Initialize(TemplateProviderBase provider)
- {
- this._provider = provider;
- }
-
- public bool IsValid(Stream template)
- {
- return GetValidationResults(template).IsValid;
- }
-
- public ValidationResult GetValidationResults(System.IO.Stream template)
- {
- // We do not provide JSON validation capabilities
- return new ValidationResult { IsValid = true, Exceptions = null };
- }
-
- public System.IO.Stream ToFormattedTemplate(Model.ProvisioningTemplate template)
- {
- String jsonString = JsonConvert.SerializeObject(template, new BasePermissionsConverter());
- Byte[] jsonBytes = System.Text.Encoding.Unicode.GetBytes(jsonString);
- MemoryStream jsonStream = new MemoryStream(jsonBytes)
- {
- Position = 0
- };
-
- return (jsonStream);
- }
-
- public Model.ProvisioningTemplate ToProvisioningTemplate(System.IO.Stream template)
- {
- return (this.ToProvisioningTemplate(template, null));
- }
-
- public Model.ProvisioningTemplate ToProvisioningTemplate(System.IO.Stream template, string identifier)
- {
- StreamReader sr = new StreamReader(template, Encoding.Unicode);
- String jsonString = sr.ReadToEnd();
- Model.ProvisioningTemplate result = JsonConvert.DeserializeObject(jsonString, new BasePermissionsConverter());
- return (result);
- }
- }
-}
diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaBaseSerializer.cs b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaBaseSerializer.cs
new file mode 100644
index 000000000..59b218cac
--- /dev/null
+++ b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaBaseSerializer.cs
@@ -0,0 +1,706 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using PnP.Framework.Extensions;
+using PnP.Framework.Provisioning.Model;
+using PnP.Framework.Provisioning.Providers.Json.Converters;
+using PnP.Framework.Provisioning.Providers.Xml;
+using System;
+using System.Collections;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Xml.Serialization;
+
+namespace PnP.Framework.Provisioning.Providers.Json
+{
+ internal abstract class JsonPnPSchemaBaseSerializer : ITemplateFormatter, IProvisioningHierarchyFormatter
+ where TSchemaTemplate : new()
+ {
+ private TemplateProviderBase _provider;
+
+ protected TemplateProviderBase Provider => _provider;
+
+ public JsonPnPSchemaBaseSerializer() { }
+
+ public JsonPnPSchemaBaseSerializer(JsonSerializerSettings serializerSettings)
+ {
+ this.serializerSettings = serializerSettings;
+ }
+
+ // we need to use our converters, so use the property getter/setter to enforce that
+ private JsonSerializerSettings serializerSettings
+ {
+ get
+ {
+ if (_serializerSettings == null)
+ {
+ _serializerSettings = new JsonSerializerSettings();
+ _serializerSettings.Converters.Add(new SiteCollectionsAndSitesConverter());
+ _serializerSettings.Converters.Add(new SiteColumnsConverter());
+ }
+ return _serializerSettings;
+ }
+ set
+ {
+ _serializerSettings = value;
+ _serializerSettings.Converters.Add(new SiteCollectionsAndSitesConverter());
+ _serializerSettings.Converters.Add(new SiteColumnsConverter());
+ }
+ }
+ private JsonSerializerSettings _serializerSettings;
+
+ #region Serializer methods
+
+ ///
+ /// Deserializes a JSON-based object, created with JSONSerializer, into a Provisioning Template implementing the specified schema
+ ///
+ /// The JSON-based object
+ /// The resulting template
+ protected virtual void DeserializeTemplate(Object persistenceTemplate, ProvisioningTemplate template)
+ {
+ // Get all ProvisioningTemplate-level serializers to run in automated mode, ordered by DeserializationSequence
+ var serializers = GetSerializersForCurrentContext(SerializerScope.ProvisioningTemplate, a => a?.DeserializationSequence);
+
+ // Invoke all the ProvisioningTemplate-level serializers
+ InvokeSerializers(template, persistenceTemplate, serializers, SerializationAction.Deserialize);
+ }
+
+ ///
+ /// Serializes a ProvisioningTemplate into a JSON-based object generated with JSONSerializer
+ ///
+ /// The ProvisioningTemplate to serialize
+ /// The JSON-based object to serialize the template into
+ protected virtual void SerializeTemplate(ProvisioningTemplate template, Object persistenceTemplate)
+ {
+ // Get all ProvisioningTemplate-level serializers to run in automated mode, ordered by DeserializationSequence
+ var serializers = GetSerializersForCurrentContext(SerializerScope.ProvisioningTemplate, a => a?.SerializationSequence);
+
+ // Invoke all the ProvisioningTemplate-level serializers
+ InvokeSerializers(template, persistenceTemplate, serializers, SerializationAction.Serialize);
+ }
+
+ #endregion
+
+ #region ITemplateFormatter
+
+ public void Initialize(TemplateProviderBase provider)
+ {
+ this._provider = provider;
+ }
+
+ ///
+ /// NOT IMPLEMENTED: Returns true
+ ///
+ /// The source Stream (the JSON)
+ /// True (Not implemented for JSON)
+ public bool IsValid(Stream template)
+ {
+ return true;
+ }
+
+ public Stream ToFormattedTemplate(ProvisioningTemplate template)
+ {
+ if (template == null)
+ {
+ throw new ArgumentNullException(nameof(template));
+ }
+
+ using (var scope = new PnPSerializationScope(typeof(TSchemaTemplate)))
+ {
+ var result = new TSchemaTemplate();
+ Stream output = null;
+
+ // Process the template to generate the output stream
+ output = ProcessOutputStream(template, result);
+
+ return (output);
+ }
+ }
+
+ ///
+ /// Deserializes a Stream of bytes (the JSON) into a Provisioning Template
+ ///
+ /// The source Stream of bytes (the JSON)
+ /// The deserialized Provisioning Template
+ public ProvisioningTemplate ToProvisioningTemplate(Stream template)
+ {
+ return this.ToProvisioningTemplate(template, null);
+ }
+
+ ///
+ /// Deserializes a Stream of bytes (the JSON) into a Provisioning Template
+ ///
+ /// The source Stream of bytes (the JSON)
+ /// An optional identifier for the template to deserialize
+ /// The deserialized Provisioning Template
+ public ProvisioningTemplate ToProvisioningTemplate(Stream template, string identifier)
+ {
+ using (var scope = new PnPSerializationScope(typeof(TSchemaTemplate)))
+ {
+ // Prepare a variable to hold the resulting ProvisioningTemplate instance
+ var result = new ProvisioningTemplate();
+
+ // Prepare a variable to hold the single source formatted template
+ // We provide the result instance of ProvisioningTemplate in order
+ // to configure the tenant/hierarchy level items
+ // We get back the XML-based object to use with the other serializers
+ var source = ProcessInputStream(template, identifier, result);
+
+ // We process the chain of deserialization
+ // with the Provisioning-level serializers
+ DeserializeTemplate(source, result);
+
+ return (result);
+ }
+ }
+
+ #endregion
+
+ #region IProvisioningHierarchyFormattion
+
+ ///
+ /// Serializes a ProvisioningHierarchy into a Stream (the JSON)
+ ///
+ /// The ProvisioningHierarchy to serialize
+ /// The resulting Stream (the JSON)
+ public Stream ToFormattedHierarchy(ProvisioningHierarchy hierarchy)
+ {
+ if (hierarchy == null)
+ {
+ throw new ArgumentNullException(nameof(hierarchy));
+ }
+
+ using (var scope = new PnPSerializationScope(typeof(TSchemaTemplate)))
+ {
+ // We prepare a dummy template to leverage the existing deserialization infrastructure
+ var dummyTemplate = new ProvisioningTemplate();
+ dummyTemplate.Id = $"DUMMY-{Guid.NewGuid()}";
+ hierarchy.Templates.Add(dummyTemplate);
+
+ // Prepare the output wrapper
+ ProcessOutputHierarchy(dummyTemplate, out Type wrapperType, out object wrapper, out Array templates, out object templatesItem);
+
+ // Handle the Sequences, if any
+ // Get all ProvisioningHierarchy-level serializers to run in automated mode, ordered by SerializationSequence
+ var serializers = GetSerializersForCurrentContext(SerializerScope.ProvisioningHierarchy, a => a?.SerializationSequence);
+
+ // Invoke all the ProvisioningHierarchy-level serializers
+ InvokeSerializers(dummyTemplate, wrapper, serializers, SerializationAction.Serialize);
+
+ // Remove the dummy template
+ hierarchy.Templates.Remove(dummyTemplate);
+
+ // Add every single template to the output
+ var provisioningTemplates = Array.CreateInstance(typeof(TSchemaTemplate), hierarchy.Templates.Count);
+ for (int c = 0; c < hierarchy.Templates.Count; c++)
+ {
+ // Prepare variable to hold the output template
+ var outputTemplate = new TSchemaTemplate();
+
+ // Serialize the real templates
+ SerializeTemplate(hierarchy.Templates[c], outputTemplate);
+
+ // Add the serialized template to the output
+ provisioningTemplates.SetValue(outputTemplate, c);
+ }
+
+ templatesItem.SetPublicInstancePropertyValue("ProvisioningTemplate", provisioningTemplates);
+
+ templates.SetValue(templatesItem, 0);
+
+ if (provisioningTemplates.Length > 0)
+ {
+ wrapper.SetPublicInstancePropertyValue("Templates", templates);
+ }
+
+ /*
+ * JSON.Net will close a stream after it's written. We need to pass a stream back,
+ * so this hack will serialize to a different stream and copy...
+ */
+
+ MemoryStream output = new MemoryStream();
+
+ MemoryStream temp = new MemoryStream();
+ using (StreamWriter sw = new StreamWriter(temp))
+ using (JsonWriter jw = new JsonTextWriter(sw))
+ {
+ if (this.serializerSettings == null)
+ {
+ JsonSerializer.CreateDefault().Serialize(jw, wrapper);
+ }
+ else
+ {
+ JsonSerializer.Create(this.serializerSettings).Serialize(jw, wrapper);
+ }
+
+ sw.Flush();
+ temp.Position = 0;
+ temp.WriteTo(output);
+ }
+
+ // Re-base the Stream and return it
+ output.Position = 0;
+ return (output);
+ }
+ }
+
+ ///
+ /// Deserializes a source Stream (the JSON) into a ProvisioningHierarchy
+ ///
+ /// The source Stream (the JSON)
+ /// The resulting ProvisioningHierarchy object
+ public ProvisioningHierarchy ToProvisioningHierarchy(Stream hierarchy)
+ {
+ // Create a copy of the source stream
+ MemoryStream sourceStream = new MemoryStream();
+ hierarchy.Position = 0;
+ hierarchy.CopyTo(sourceStream);
+ sourceStream.Position = 0;
+
+ // Prepare the output variable
+ ProvisioningHierarchy resultHierarchy = new ProvisioningHierarchy();
+
+ // load the JSON we can inspect it
+ JObject json = default;
+
+ using (TextReader sr = new StreamReader(sourceStream))
+ using (JsonReader jr = new JsonTextReader(sr))
+ {
+ json = (JObject)JToken.ReadFrom(jr);
+ }
+
+ // Determine if we're working on a wrapped ProvisioningTemplate or not
+ if (!json.ContainsKey("Sequence") &&
+ !json.ContainsKey("SequenceItems"))
+ {
+ throw new ApplicationException("The provided provisioning file is not a Hierarchy!");
+ }
+
+ // Process all the provisioning templates included in the hierarchy
+ var templates = (JArray)json.SelectToken("Templates");
+
+ using (var scope = new PnPSerializationScope(typeof(TSchemaTemplate)))
+ {
+ foreach (var template in templates)
+ {
+ var provisioningTemplates = (JArray)template.SelectToken("ProvisioningTemplate");
+ foreach (var provisioningTemplateJson in provisioningTemplates)
+ {
+ //var provisioningTemplate = ToProvisioningTemplate(provisioningTemplateJson as JObject, provisioningTemplateJson.Value("ID"));
+
+ // Prepare a variable to hold the resulting ProvisioningTemplate instance
+ var result = new ProvisioningTemplate();
+ // Prepare a variable to hold the single source formatted template
+ var source = ProcessInputJObject(provisioningTemplateJson as JObject, provisioningTemplateJson.Value("ID"), result);
+ DeserializeTemplate(source, result);
+
+ // Add the generated template to the resulting hierarchy
+ resultHierarchy.Templates.Add(result);
+ }
+ }
+
+ // And now process the top level children elements
+ // using schema specific serializers
+
+ // We prepare a dummy template to leverage the existing serialization infrastructure
+ var dummyTemplate = new ProvisioningTemplate();
+ dummyTemplate.Id = $"DUMMY-{Guid.NewGuid()}";
+ resultHierarchy.Templates.Add(dummyTemplate);
+
+ // Deserialize the whole wrapper
+ Object wrapper = null;
+ var wrapperType = Type.GetType($"{PnPSerializationScope.Current?.BaseSchemaNamespace}.Provisioning, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}", true);
+ wrapper = Activator.CreateInstance(wrapperType);
+
+ using (var sr = json.CreateReader())
+ {
+ if (serializerSettings == null)
+ {
+ JsonSerializer.CreateDefault().Populate(sr, wrapper);
+ }
+ else
+ {
+ JsonSerializer.Create(serializerSettings).Populate(sr, wrapper);
+ }
+ }
+
+ #region Process Provisioning level serializers
+
+ // Get all serializers to run in automated mode, ordered by DeserializationSequence
+ var serializers = GetSerializersForCurrentContext(SerializerScope.Provisioning, a => a?.DeserializationSequence);
+
+ // Invoke all the serializers
+ InvokeSerializers(dummyTemplate, wrapper, serializers, SerializationAction.Deserialize);
+
+ #endregion
+
+ #region Process Tenant level serializers
+
+ // Get all serializers to run in automated mode, ordered by DeserializationSequence
+ serializers = GetSerializersForCurrentContext(SerializerScope.Tenant, a => a?.DeserializationSequence);
+
+ // Invoke all the serializers
+ InvokeSerializers(dummyTemplate, wrapper, serializers, SerializationAction.Deserialize);
+
+ #endregion
+
+ #region Process ProvisioningHierarchy level serializers
+
+ // Get all serializers to run in automated mode, ordered by DeserializationSequence
+ serializers = GetSerializersForCurrentContext(SerializerScope.ProvisioningHierarchy, a => a?.DeserializationSequence);
+
+ // Invoke all the serializers
+ InvokeSerializers(dummyTemplate, wrapper, serializers, SerializationAction.Deserialize);
+
+ #endregion
+
+ // Remove the dummy template from the hierarchy
+ resultHierarchy.Templates.Remove(dummyTemplate);
+ }
+
+ return (resultHierarchy);
+ }
+
+ #endregion
+
+ #region Private methods
+
+ ///
+ /// Converts a Stream of bytes (the JSON) into a schema-based object
+ ///
+ /// The source Stream of bytes (the JSON)
+ /// An optional identifier for the template to extract from the JSON
+ /// A reference ProvisioningTemplate object
+ /// The resulting schema-based object extracted from the Stream
+ protected Object ProcessInputStream(Stream template, string identifier, ProvisioningTemplate result)
+ {
+ if (template == null)
+ {
+ throw new ArgumentNullException(nameof(template));
+ }
+
+ // Crate a copy of the source stream
+ MemoryStream sourceStream = new MemoryStream();
+ template.CopyTo(sourceStream);
+ sourceStream.Position = 0;
+
+ JObject json = default;
+
+ using (TextReader sr = new StreamReader(sourceStream, true))
+ using (JsonReader jr = new JsonTextReader(sr))
+ {
+ json = (JObject)JToken.ReadFrom(jr);
+ }
+
+ return ProcessInputJObject(json, identifier, result);
+ }
+
+ private Object ProcessInputJObject(JObject json, string identifier, ProvisioningTemplate result)
+ {
+ // Prepare a variable to hold the single source formatted template
+ TSchemaTemplate source = default(TSchemaTemplate);
+
+ // Determine if we're working on a wrapped ProvisioningTemplate or not
+ if (json.ContainsKey("Templates"))
+ {
+ // Deserialize the whole wrapper
+ Object wrapper = null;
+ var wrapperType = Type.GetType($"{PnPSerializationScope.Current?.BaseSchemaNamespace}.Provisioning, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}", true);
+ wrapper = Activator.CreateInstance(wrapperType);
+
+ using (var sr = json.CreateReader())
+ {
+ if (serializerSettings == null)
+ {
+ JsonSerializer.CreateDefault().Populate(sr, wrapper);
+ }
+ else
+ {
+ JsonSerializer.Create(serializerSettings).Populate(sr, wrapper);
+ }
+ }
+
+ // Get all Provisioning-level serializers to run in automated mode, ordered by DeserializationSequence
+ var serializers = GetSerializersForCurrentContext(SerializerScope.Provisioning, a => a?.DeserializationSequence);
+
+ // Invoke all the Provisioning-level serializers
+ InvokeSerializers(result, wrapper, serializers, SerializationAction.Deserialize);
+
+ // Get the list of templates, if any, wrapped by the wrapper
+ var wrapperTemplates = wrapper.GetPublicInstancePropertyValue("Templates");
+
+ if (wrapperTemplates != null)
+ {
+ // Search for the requested Provisioning Template
+ foreach (var templates in (IEnumerable)wrapperTemplates)
+ {
+ // Let's see if we have an in-place template with the provided ID or if we don't have a provided ID at all
+ var provisioningTemplates = templates.GetPublicInstancePropertyValue("ProvisioningTemplate");
+
+ if (provisioningTemplates != null)
+ {
+ foreach (var t in (IEnumerable)provisioningTemplates)
+ {
+ var templateId = t.GetPublicInstancePropertyValue("ID") as String;
+
+ if ((templateId != null && templateId == identifier) || String.IsNullOrEmpty(identifier))
+ {
+ source = (TSchemaTemplate)t;
+ }
+ }
+
+ if (source == null)
+ {
+ var provisioningTemplateFiles = templates.GetPublicInstancePropertyValue("ProvisioningTemplateFile");
+
+ // If we don't have a template, but there are external file references
+ if (source == null && provisioningTemplateFiles != null)
+ {
+ foreach (var f in (IEnumerable)provisioningTemplateFiles)
+ {
+ var templateId = f.GetPublicInstancePropertyValue("ID") as String;
+
+ if ((templateId != null && templateId == identifier) || String.IsNullOrEmpty(identifier))
+ {
+ // Let's see if we have an external file for the template
+ var externalFile = f.GetPublicInstancePropertyValue("File") as String;
+
+ if (!String.IsNullOrEmpty(externalFile))
+ {
+ /*
+ * Not implementing file references in JSON
+ */
+ throw new NotImplementedException();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (source != null)
+ {
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ var IdAttribute = json.SelectToken("ID");
+
+ // If there is a provided ID, and if it doesn't equal the current ID
+ if (!String.IsNullOrEmpty(identifier) &&
+ json.ContainsKey("ID") &&
+ json.Value("ID") != identifier)
+ {
+ // TODO: Use resource file
+ throw new ApplicationException("The provided template identifier is not available!");
+ }
+ else
+ {
+ using (var sr = json.CreateReader())
+ {
+ if (serializerSettings == null)
+ {
+ source = JsonSerializer.CreateDefault().Deserialize(sr);
+ }
+ else
+ {
+ source = JsonSerializer.Create(serializerSettings).Deserialize(sr);
+ }
+ }
+ }
+ }
+
+ return (source);
+ }
+
+
+ ///
+ /// Serializes an in-memory ProvisioningTemplate into a Stream (the XML)
+ ///
+ /// The ProvisioningTemplate to serialize
+ /// The typed XML-based object defined using XmlSerializer
+ /// The resulting Stream (the XML)
+ protected Stream ProcessOutputStream(ProvisioningTemplate template, TSchemaTemplate result)
+ {
+ // Prepare the output wrapper
+ Type wrapperType;
+ object wrapper, templatesItem;
+ Array templates;
+
+ // Process the hierarchy part of the template
+ ProcessOutputHierarchy(template, out wrapperType, out wrapper, out templates, out templatesItem);
+
+ // Add the single template to the output
+ var provisioningTemplates = Array.CreateInstance(typeof(TSchemaTemplate), 1);
+ provisioningTemplates.SetValue(result, 0);
+
+ templatesItem.SetPublicInstancePropertyValue("ProvisioningTemplate", provisioningTemplates);
+
+ templates.SetValue(templatesItem, 0);
+
+ wrapper.SetPublicInstancePropertyValue("Templates", templates);
+
+ // Serialize the template mapping the ProvisioningTemplate object to the XML-based object
+ SerializeTemplate(template, result);
+
+ // Serialize the XML-based object into a Stream (the XML)
+ XmlSerializerNamespaces ns =
+ new XmlSerializerNamespaces();
+ ns.Add(((IXMLSchemaFormatter)this).NamespacePrefix,
+ ((IXMLSchemaFormatter)this).NamespaceUri);
+
+ MemoryStream output = new MemoryStream();
+ XmlSerializer xmlSerializer = new XmlSerializer(wrapperType);
+ if (ns != null)
+ {
+ xmlSerializer.Serialize(output, wrapper, ns);
+ }
+ else
+ {
+ xmlSerializer.Serialize(output, wrapper);
+ }
+
+ // Re-base the Stream and return it
+ output.Position = 0;
+ return (output);
+ }
+
+ ///
+ /// Prepares a ProvisioningTemplate to be wrapped into the Hierarchy container object
+ ///
+ /// The ProvisioningTemplate to wrap
+ /// The Type of the wrapper
+ /// The wrapper
+ /// The collection of template within the wrapper
+ /// The template to add
+ private void ProcessOutputHierarchy(ProvisioningTemplate template, out Type wrapperType, out object wrapper, out Array templates, out object templatesItem)
+ {
+ // Create the wrapper
+ wrapperType = Type.GetType($"{PnPSerializationScope.Current?.BaseSchemaNamespace}.Provisioning, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}", true);
+ wrapper = Activator.CreateInstance(wrapperType);
+
+ // Create the Preferences
+ var preferencesType = Type.GetType($"{PnPSerializationScope.Current?.BaseSchemaNamespace}.Preferences, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}", true);
+ Object preferences = Activator.CreateInstance(preferencesType);
+
+ wrapper.SetPublicInstancePropertyValue("Preferences", preferences);
+
+ // Get all Provisioning-level serializers to run in automated mode, ordered by SerializationSequence
+ var serializers = GetSerializersForCurrentContext(SerializerScope.Provisioning, a => a?.SerializationSequence);
+
+ // Invoke all the Provisioning-level serializers
+ InvokeSerializers(template, wrapper, serializers, SerializationAction.Serialize);
+
+ // Get all Tenant-levelserializers to run in automated mode, ordered by SerializationSequence
+ serializers = GetSerializersForCurrentContext(SerializerScope.Tenant, a => a?.SerializationSequence);
+
+ // Invoke all the Tenant-levelserializers
+ InvokeSerializers(template, wrapper, serializers, SerializationAction.Serialize);
+
+ // Configure the basic properties of the wrapper
+ if (template.ParentHierarchy != null)
+ {
+ wrapper.SetPublicInstancePropertyValue("Author", template.ParentHierarchy.Author);
+ wrapper.SetPublicInstancePropertyValue("DisplayName", template.ParentHierarchy.DisplayName);
+ wrapper.SetPublicInstancePropertyValue("Description", template.ParentHierarchy.Description);
+ wrapper.SetPublicInstancePropertyValue("ImagePreviewUrl", template.ParentHierarchy.ImagePreviewUrl);
+ wrapper.SetPublicInstancePropertyValue("Generator", template.ParentHierarchy.Generator);
+ wrapper.SetPublicInstancePropertyValue("Version", (Decimal)template.ParentHierarchy.Version);
+ }
+
+ // Configure the Generator
+ preferences.SetPublicInstancePropertyValue("Generator", this.GetType().Assembly.FullName);
+
+ // Configure the output Template
+ var templatesType = Type.GetType($"{PnPSerializationScope.Current?.BaseSchemaNamespace}.Templates, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}", true);
+ templates = Array.CreateInstance(templatesType, 1);
+ templatesItem = Activator.CreateInstance(templatesType);
+ templatesItem.SetPublicInstancePropertyValue("ID", $"CONTAINER-{template.Id}");
+ }
+
+ private IOrderedEnumerable> GetSerializersForCurrentContext(SerializerScope scope,
+ Func sortingSelector)
+ {
+ // Get all serializers to run in automated mode, ordered by sortingSelector
+ var currentAssembly = this.GetType().Assembly;
+
+ XMLPnPSchemaVersion currentSchemaVersion = GetCurrentSchemaVersion();
+
+ var serializers = currentAssembly.GetTypes()
+ // Get all the serializers
+ .Where(t => t.GetInterface(typeof(IPnPSchemaSerializer).FullName) != null
+ && t.BaseType.Name == typeof(Xml.PnPBaseSchemaSerializer<>).Name)
+ // Filter out those that are not targeting the current schema version or that are not in scope Template
+ .Where(t =>
+ {
+ var a = t.GetCustomAttributes(false).FirstOrDefault();
+ return (a.MinimalSupportedSchemaVersion <= currentSchemaVersion && a.Scope == scope);
+ })
+ // Order the remainings by supported schema version descendant, to get first the newest ones
+ .OrderByDescending(s =>
+ {
+ var a = s.GetCustomAttributes(false).FirstOrDefault();
+ return (a.MinimalSupportedSchemaVersion);
+ }
+ )
+ // Group those with the same target type (which is the first generic Type argument)
+ .GroupBy(t => t.BaseType.GenericTypeArguments.FirstOrDefault()?.FullName)
+ // Order the result by SerializationSequence
+ .OrderBy(g =>
+ {
+ var maxInGroup = g.OrderByDescending(s =>
+ {
+ var a = s.GetCustomAttributes(false).FirstOrDefault();
+ return (a.MinimalSupportedSchemaVersion);
+ }
+ ).FirstOrDefault();
+ return sortingSelector(maxInGroup.GetCustomAttributes(false).FirstOrDefault());
+ });
+ return serializers;
+ }
+
+ private static void InvokeSerializers(ProvisioningTemplate template, object persistenceTemplate,
+ IOrderedEnumerable> serializers, SerializationAction action)
+ {
+ foreach (var group in serializers)
+ {
+ // Get the first serializer only for each group (i.e. the most recent one for the current schema)
+ var serializerType = group.FirstOrDefault();
+ if (serializerType != null)
+ {
+ // Create an instance of the serializer
+ var serializer = Activator.CreateInstance(serializerType) as IPnPSchemaSerializer;
+ if (serializer != null)
+ {
+ // And run the Deserialize/Serialize method
+ if (action == SerializationAction.Serialize)
+ {
+ serializer.Serialize(template, persistenceTemplate);
+ }
+ else
+ {
+ serializer.Deserialize(persistenceTemplate, template);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Retrieves the current XML Schema version
+ ///
+ /// The current XML schema version
+ private static XMLPnPSchemaVersion GetCurrentSchemaVersion()
+ {
+ var currentSchemaTemplateNamespace = typeof(TSchemaTemplate).Namespace;
+ var currentSchemaVersionString = $"V{currentSchemaTemplateNamespace.Substring(currentSchemaTemplateNamespace.IndexOf(".Xml.") + 6)}";
+ var currentSchemaVersion = (XMLPnPSchemaVersion)Enum.Parse(typeof(XMLPnPSchemaVersion), currentSchemaVersionString);
+ return currentSchemaVersion;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaFormatter.cs b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaFormatter.cs
new file mode 100644
index 000000000..72a2d5773
--- /dev/null
+++ b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaFormatter.cs
@@ -0,0 +1,138 @@
+using Newtonsoft.Json;
+using PnP.Framework.Provisioning.Providers.Xml;
+using System;
+
+namespace PnP.Framework.Provisioning.Providers.Json
+{
+ ///
+ /// Helper class that abstracts from any specific version of JsonPnPSchemaFormatter
+ ///
+ public class JsonPnPSchemaFormatter : ITemplateFormatter
+ {
+ private TemplateProviderBase _provider;
+
+ public void Initialize(TemplateProviderBase provider)
+ {
+ this._provider = provider;
+ }
+
+ #region Static methods and properties
+
+ ///
+ /// Static method to retrieve an instance of the latest JsonPnPSchemaFormatter
+ ///
+ public static ITemplateFormatter LatestFormatter()
+ {
+ return LatestFormatter(null);
+ }
+
+ ///
+ /// Static method to retrieve an instance of the latest JsonPnPSchemaFormatter
+ ///
+ /// Custom to use
+ ///
+ public static ITemplateFormatter LatestFormatter(JsonSerializerSettings serializerSettings)
+ {
+ return new JsonPnPSchemaV202209Serializer(serializerSettings);
+ }
+
+
+ ///
+ /// Static method to retrieve a specific JsonPnPSchemaFormatter instance
+ ///
+ /// Provisioning schema version
+ ///
+ public static ITemplateFormatter GetSpecificFormatter(XMLPnPSchemaVersion version)
+ {
+ return GetSpecificFormatter(version, null);
+ }
+
+ public static ITemplateFormatter GetSpecificFormatter(XMLPnPSchemaVersion version, JsonSerializerSettings serializerSettings)
+ {
+ switch (version)
+ {
+ case XMLPnPSchemaVersion.V202209:
+ default:
+ return (new JsonPnPSchemaV202209Serializer(serializerSettings));
+ }
+ }
+ ///
+ /// Static method to retrieve a specific JsonPnPSchemaFormatter instance
+ ///
+ ///
+ ///
+ public static ITemplateFormatter GetSpecificFormatter(string namespaceUri)
+ {
+ return GetSpecificFormatter(namespaceUri, null);
+ }
+
+ public static ITemplateFormatter GetSpecificFormatter(string namespaceUri, JsonSerializerSettings serializerSettings)
+ {
+ switch (namespaceUri)
+ {
+ case XMLConstants.PROVISIONING_SCHEMA_NAMESPACE_2022_09:
+ default:
+ return new JsonPnPSchemaV202209Serializer(serializerSettings);
+ }
+ }
+
+ #endregion
+
+ public bool IsValid(System.IO.Stream template)
+ {
+ return true;
+ }
+
+ public System.IO.Stream ToFormattedTemplate(Model.ProvisioningTemplate template)
+ {
+ String jsonString = JsonConvert.SerializeObject(template, new BasePermissionsConverter());
+ Byte[] jsonBytes = System.Text.Encoding.Unicode.GetBytes(jsonString);
+ System.IO.MemoryStream jsonStream = new System.IO.MemoryStream(jsonBytes)
+ {
+ Position = 0
+ };
+
+ return (jsonStream);
+ }
+
+ public Model.ProvisioningTemplate ToProvisioningTemplate(System.IO.Stream template)
+ {
+ return (this.ToProvisioningTemplate(template, null));
+ }
+
+ public Model.ProvisioningTemplate ToProvisioningTemplate(System.IO.Stream template, string identifier)
+ {
+ using System.IO.StreamReader sr = new System.IO.StreamReader(template, System.Text.Encoding.Unicode);
+ String jsonString = sr.ReadToEnd();
+
+ Model.ProvisioningTemplate result = JsonConvert.DeserializeObject(jsonString, new BasePermissionsConverter());
+ return (result);
+ }
+
+ public System.IO.Stream ToFormattedHierarchy(Model.ProvisioningHierarchy hierarchy)
+ {
+ if (hierarchy == null)
+ {
+ throw new ArgumentNullException(nameof(hierarchy));
+ }
+
+ String jsonString = JsonConvert.SerializeObject(hierarchy, new BasePermissionsConverter());
+ Byte[] jsonBytes = System.Text.Encoding.Unicode.GetBytes(jsonString);
+ System.IO.MemoryStream jsonStream = new System.IO.MemoryStream(jsonBytes)
+ {
+ Position = 0
+ };
+
+ return (jsonStream);
+ }
+
+ public Model.ProvisioningHierarchy ToProvisioningHierarchy(System.IO.Stream hierarchy)
+ {
+ using System.IO.StreamReader sr = new System.IO.StreamReader(hierarchy, System.Text.Encoding.Unicode);
+ String jsonString = sr.ReadToEnd();
+ Model.ProvisioningHierarchy result = JsonConvert.DeserializeObject(jsonString, new BasePermissionsConverter());
+ return (result);
+ }
+
+ }
+}
diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaV202209Serializer.cs b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaV202209Serializer.cs
new file mode 100644
index 000000000..0edd534fd
--- /dev/null
+++ b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaV202209Serializer.cs
@@ -0,0 +1,24 @@
+using Newtonsoft.Json;
+
+namespace PnP.Framework.Provisioning.Providers.Json
+{
+ internal class JsonPnPSchemaV202209Serializer : JsonPnPSchemaBaseSerializer
+ {
+
+ public JsonPnPSchemaV202209Serializer()
+ : this(null) { }
+
+ public JsonPnPSchemaV202209Serializer(JsonSerializerSettings serializerSettings)
+ : base(serializerSettings) { }
+
+ protected override void DeserializeTemplate(object persistenceTemplate, Model.ProvisioningTemplate template)
+ {
+ base.DeserializeTemplate(persistenceTemplate, template);
+ }
+
+ protected override void SerializeTemplate(Model.ProvisioningTemplate template, object persistenceTemplate)
+ {
+ base.SerializeTemplate(template, persistenceTemplate);
+ }
+ }
+}
diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonTemplateProvider.cs b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonTemplateProvider.cs
index 22cc28aed..f24c02800 100644
--- a/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonTemplateProvider.cs
+++ b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonTemplateProvider.cs
@@ -25,11 +25,43 @@ protected JsonTemplateProvider(FileConnectorBase connector)
#region Base class overrides
+ public override ProvisioningHierarchy GetHierarchy(string uri)
+ {
+ return this.GetHierarchy(uri, null);
+ }
+
+ public override ProvisioningHierarchy GetHierarchy(string uri, IProvisioningHierarchyFormatter formatter)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
+ }
+
+ if (formatter is null)
+ {
+ var latestFormatter = JsonPnPSchemaFormatter.LatestFormatter();
+ latestFormatter.Initialize(this);
+ formatter = (IProvisioningHierarchyFormatter)latestFormatter;
+ }
+
+ ProvisioningHierarchy result = null;
+
+ var stream = this.Connector.GetFileStream(uri);
+
+ if (stream != null)
+ {
+ result = formatter.ToProvisioningHierarchy(stream);
+ }
+
+ return (result);
+
+ }
+
public override List GetTemplates()
{
- var formatter = new JsonPnPFormatter();
+ var formatter = new JsonPnPSchemaFormatter();
formatter.Initialize(this);
- return (this.GetTemplates(formatter));
+ return this.GetTemplates(formatter);
}
public override List GetTemplates(ITemplateFormatter formatter)
@@ -58,11 +90,6 @@ public override List GetTemplates(ITemplateFormatter forma
return (result);
}
- public override ProvisioningHierarchy GetHierarchy(string uri)
- {
- throw new NotImplementedException();
- }
-
public override ProvisioningTemplate GetTemplate(string uri)
{
return (this.GetTemplate(uri, (ITemplateProviderExtension[])null));
@@ -97,7 +124,7 @@ public override ProvisioningTemplate GetTemplate(string uri, string identifier,
if (formatter == null)
{
- formatter = new JsonPnPFormatter();
+ formatter = new JsonPnPSchemaFormatter();
formatter.Initialize(this);
}
@@ -158,7 +185,7 @@ public override ProvisioningTemplate GetTemplate(Stream stream, string identifie
if (formatter == null)
{
- formatter = new JsonPnPFormatter();
+ formatter = new JsonPnPSchemaFormatter();
formatter.Initialize(this);
}
@@ -179,7 +206,34 @@ public override ProvisioningTemplate GetTemplate(Stream stream, string identifie
public override void Save(ProvisioningHierarchy hierarchy)
{
- throw new NotImplementedException();
+ this.SaveAs(hierarchy, this.Uri);
+ }
+
+ public override void SaveAs(ProvisioningHierarchy hierarchy, string uri, ITemplateFormatter formatter = null)
+ {
+ if (hierarchy is null)
+ {
+ throw new ArgumentNullException(nameof(hierarchy));
+ }
+
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
+ }
+
+ if (formatter is null)
+ {
+ formatter = JsonPnPSchemaFormatter.LatestFormatter();
+ }
+
+ var stream = ((IProvisioningHierarchyFormatter)formatter).ToFormattedHierarchy(hierarchy);
+
+ this.Connector.SaveFileStream(uri, stream);
+
+ if (this.Connector is ICommitableFileConnector)
+ {
+ ((ICommitableFileConnector)this.Connector).Commit();
+ }
}
public override void Save(ProvisioningTemplate template)
@@ -202,11 +256,6 @@ public override void Save(ProvisioningTemplate template, ITemplateFormatter form
this.SaveAs(template, this.Uri, formatter, extensions);
}
- public override void SaveAs(ProvisioningHierarchy hierarchy, string uri, ITemplateFormatter formatter = null)
- {
- throw new NotImplementedException();
- }
-
public override void SaveAs(ProvisioningTemplate template, string uri)
{
this.SaveAs(template, uri, (ITemplateProviderExtension[])null);
@@ -236,7 +285,7 @@ public override void SaveAs(ProvisioningTemplate template, string uri, ITemplate
if (formatter == null)
{
- formatter = new JsonPnPFormatter();
+ formatter = new JsonPnPSchemaFormatter();
}
SaveToConnector(template, uri, formatter, extensions);
diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Markdown/MarkdownTemplateProvider.cs b/src/lib/PnP.Framework/Provisioning/Providers/Markdown/MarkdownTemplateProvider.cs
index da5fae971..0cfb0bb5c 100644
--- a/src/lib/PnP.Framework/Provisioning/Providers/Markdown/MarkdownTemplateProvider.cs
+++ b/src/lib/PnP.Framework/Provisioning/Providers/Markdown/MarkdownTemplateProvider.cs
@@ -25,6 +25,15 @@ protected MarkdownTemplateProvider(FileConnectorBase connector)
#region Base class overrides
+ public override ProvisioningHierarchy GetHierarchy(string uri)
+ {
+ throw new NotImplementedException();
+ }
+ public override ProvisioningHierarchy GetHierarchy(string uri, IProvisioningHierarchyFormatter formatter)
+ {
+ throw new NotImplementedException();
+ }
+
public override List GetTemplates()
{
var formatter = new MarkdownPnPFormatter();
@@ -58,11 +67,6 @@ public override List GetTemplates(ITemplateFormatter forma
return (result);
}
- public override ProvisioningHierarchy GetHierarchy(string uri)
- {
- throw new NotImplementedException();
- }
-
public override ProvisioningTemplate GetTemplate(string uri)
{
return (this.GetTemplate(uri, (ITemplateProviderExtension[])null));
diff --git a/src/lib/PnP.Framework/Provisioning/Providers/TemplateProviderBase.cs b/src/lib/PnP.Framework/Provisioning/Providers/TemplateProviderBase.cs
index 38554bb3d..56c2a4658 100644
--- a/src/lib/PnP.Framework/Provisioning/Providers/TemplateProviderBase.cs
+++ b/src/lib/PnP.Framework/Provisioning/Providers/TemplateProviderBase.cs
@@ -105,6 +105,21 @@ public String Uri
#endregion
#region Abstract Methods
+ ///
+ /// Gets ProvisioningHierarchy
+ ///
+ /// The source uri
+ /// Returns a ProvisioningHierarchy
+ public abstract ProvisioningHierarchy GetHierarchy(string uri);
+
+ ///
+ /// Gets ProvisioningHierarchy
+ ///
+ /// The source uri
+ /// Provisioning Hierarchy formatter
+ /// Returns a ProvisioningHierarchy
+ public abstract ProvisioningHierarchy GetHierarchy(string uri, IProvisioningHierarchyFormatter formatter);
+
///
/// Gets list of ProvisioningTemplates
///
@@ -118,13 +133,6 @@ public String Uri
/// Returns collection of ProvisioningTemplate
public abstract List GetTemplates(ITemplateFormatter formatter);
- ///
- /// Gets ProvisioningHierarchy
- ///
- /// The source uri
- /// Returns a ProvisioningHierarchy
- public abstract ProvisioningHierarchy GetHierarchy(string uri);
-
///
/// Gets ProvisioningTemplate
///
diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Xml/XMLTemplateProvider.cs b/src/lib/PnP.Framework/Provisioning/Providers/Xml/XMLTemplateProvider.cs
index 6e96a1688..b646a161c 100644
--- a/src/lib/PnP.Framework/Provisioning/Providers/Xml/XMLTemplateProvider.cs
+++ b/src/lib/PnP.Framework/Provisioning/Providers/Xml/XMLTemplateProvider.cs
@@ -29,6 +29,36 @@ protected XMLTemplateProvider(FileConnectorBase connector)
#region Base class overrides
+ public override ProvisioningHierarchy GetHierarchy(string uri)
+ {
+ return this.GetHierarchy(uri, null);
+ }
+
+ public override ProvisioningHierarchy GetHierarchy(string uri, IProvisioningHierarchyFormatter formatter)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
+ }
+
+ ProvisioningHierarchy result = null;
+
+ var stream = this.Connector.GetFileStream(uri);
+
+ if (stream != null)
+ {
+ if (formatter == null)
+ {
+ ITemplateFormatter specificFormatter = new XMLPnPSchemaFormatter().GetSpecificFormatterInternal(ref stream);
+ specificFormatter.Initialize(this);
+
+ formatter = (IProvisioningHierarchyFormatter)specificFormatter;
+ }
+ result = formatter.ToProvisioningHierarchy(stream);
+ }
+ return (result);
+ }
+
public override List GetTemplates()
{
var formatter = new XMLPnPSchemaFormatter();
@@ -71,29 +101,6 @@ public override List GetTemplates(ITemplateFormatter forma
return (result);
}
- public override ProvisioningHierarchy GetHierarchy(string uri)
- {
- if (uri == null)
- {
- throw new ArgumentNullException(nameof(uri));
- }
-
- ProvisioningHierarchy result = null;
-
- var stream = this.Connector.GetFileStream(uri);
-
- if (stream != null)
- {
- var formatter = new XMLPnPSchemaFormatter();
-
- ITemplateFormatter specificFormatter = formatter.GetSpecificFormatterInternal(ref stream);
- specificFormatter.Initialize(this);
- result = ((IProvisioningHierarchyFormatter)specificFormatter).ToProvisioningHierarchy(stream);
- }
-
- return (result);
- }
-
public override ProvisioningTemplate GetTemplate(string uri)
{
return (this.GetTemplate(uri, (ITemplateProviderExtension[])null));