Implementing import methods in the Content- and ContentType services.

Adding tests to verify import is correct.
This commit is contained in:
Morten Christensen
2013-03-19 20:05:55 -01:00
parent ee9f91b5cf
commit 7def26a489
9 changed files with 2804 additions and 42 deletions

View File

@@ -60,27 +60,8 @@ namespace Umbraco.Core.Services
/// <returns><see cref="IContent"/></returns>
public IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0)
{
IContentType contentType = null;
IContent content = null;
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentTypeRepository(uow))
{
var query = Query<IContentType>.Builder.Where(x => x.Alias == contentTypeAlias);
var contentTypes = repository.GetByQuery(query);
if (!contentTypes.Any())
throw new Exception(string.Format("No ContentType matching the passed in Alias: '{0}' was found",
contentTypeAlias));
contentType = contentTypes.First();
if (contentType == null)
throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null",
contentTypeAlias));
}
content = new Content(name, parentId, contentType);
IContentType contentType = FindContentTypeByAlias(contentTypeAlias);
IContent content = new Content(name, parentId, contentType);
if (Creating.IsRaisedEventCancelled(new NewEventArgs<IContent>(content, contentTypeAlias, parentId), this))
return content;
@@ -106,27 +87,8 @@ namespace Umbraco.Core.Services
/// <returns><see cref="IContent"/></returns>
public IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0)
{
IContentType contentType = null;
IContent content = null;
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentTypeRepository(uow))
{
var query = Query<IContentType>.Builder.Where(x => x.Alias == contentTypeAlias);
var contentTypes = repository.GetByQuery(query);
if (!contentTypes.Any())
throw new Exception(string.Format("No ContentType matching the passed in Alias: '{0}' was found",
contentTypeAlias));
contentType = contentTypes.First();
if (contentType == null)
throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null",
contentTypeAlias));
}
content = new Content(name, parent, contentType);
IContentType contentType = FindContentTypeByAlias(contentTypeAlias);
IContent content = new Content(name, parent, contentType);
if (Creating.IsRaisedEventCancelled(new NewEventArgs<IContent>(content, contentTypeAlias, parent), this))
return content;
@@ -1002,6 +964,166 @@ namespace Umbraco.Core.Services
return content;
}
/// <summary>
/// Imports and saves package xml as <see cref="IContent"/>
/// </summary>
/// <param name="element">Xml to import</param>
/// <returns>An enumrable list of generated content</returns>
public IEnumerable<IContent> Import(XElement element)
{
var name = element.Name.LocalName;
if (name.Equals("DocumentSet"))
{
//This is a regular deep-structured import
var roots = from doc in element.Elements()
where (string) doc.Attribute("isDoc") == ""
select doc;
var contents = ParseRootXml(roots);
Save(contents);
return contents;
}
var attribute = element.Attribute("isDoc");
if (attribute != null)
{
//This is a single doc import
var elements = new List<XElement> { element };
var contents = ParseRootXml(elements);
Save(contents);
return contents;
}
throw new ArgumentException(
"The passed in XElement is not valid! It does not contain a root element called "+
"'DocumentSet' (for structured imports) nor is the first element a Document (for single document import).");
}
private IEnumerable<IContent> ParseRootXml(IEnumerable<XElement> roots)
{
var contentTypes = new Dictionary<string, IContentType>();
var contents = new List<IContent>();
foreach (var root in roots)
{
bool isLegacySchema = root.Name.LocalName.ToLowerInvariant().Equals("node");
string contentTypeAlias = isLegacySchema
? root.Attribute("nodeTypeAlias").Value
: root.Name.LocalName;
if (contentTypes.ContainsKey(contentTypeAlias) == false)
{
var contentType = FindContentTypeByAlias(contentTypeAlias);
contentTypes.Add(contentTypeAlias, contentType);
}
var content = CreateContentFromXml(root, contentTypes[contentTypeAlias], null, -1, isLegacySchema);
contents.Add(content);
var children = from child in root.Elements()
where (string)child.Attribute("isDoc") == ""
select child;
if(children.Any())
contents.AddRange(CreateContentFromXml(children, content, contentTypes, isLegacySchema));
}
return contents;
}
private IEnumerable<IContent> CreateContentFromXml(IEnumerable<XElement> children, IContent parent, Dictionary<string, IContentType> contentTypes, bool isLegacySchema)
{
var list = new List<IContent>();
foreach (var child in children)
{
string contentTypeAlias = isLegacySchema
? child.Attribute("nodeTypeAlias").Value
: child.Name.LocalName;
if (contentTypes.ContainsKey(contentTypeAlias) == false)
{
var contentType = FindContentTypeByAlias(contentTypeAlias);
contentTypes.Add(contentTypeAlias, contentType);
}
//Create and add the child to the list
var content = CreateContentFromXml(child, contentTypes[contentTypeAlias], parent, default(int), isLegacySchema);
list.Add(content);
//Recursive call
XElement child1 = child;
var grandChildren = from grand in child1.Elements()
where (string) grand.Attribute("isDoc") == ""
select grand;
if (grandChildren.Any())
list.AddRange(CreateContentFromXml(grandChildren, content, contentTypes, isLegacySchema));
}
return list;
}
private IContent CreateContentFromXml(XElement element, IContentType contentType, IContent parent, int parentId, bool isLegacySchema)
{
var id = element.Attribute("id").Value;
var level = element.Attribute("level").Value;
var sortOrder = element.Attribute("sortOrder").Value;
var nodeName = element.Attribute("nodeName").Value;
var path = element.Attribute("path").Value;
var template = element.Attribute("template").Value;
var properties = from property in element.Elements()
where property.Attribute("isDoc") == null
select property;
IContent content = parent == null
? new Content(nodeName, parentId, contentType)
{
Level = int.Parse(level),
SortOrder = int.Parse(sortOrder)
}
: new Content(nodeName, parent, contentType)
{
Level = int.Parse(level),
SortOrder = int.Parse(sortOrder)
};
foreach (var property in properties)
{
string propertyTypeAlias = isLegacySchema ? property.Attribute("alias").Value : property.Name.LocalName;
if(content.HasProperty(propertyTypeAlias))
content.SetValue(propertyTypeAlias, property.Value);
}
return content;
}
private IContentType FindContentTypeByAlias(string contentTypeAlias)
{
using (var repository = _repositoryFactory.CreateContentTypeRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query<IContentType>.Builder.Where(x => x.Alias == contentTypeAlias);
var types = repository.GetByQuery(query);
if (!types.Any())
throw new Exception(
string.Format("No ContentType matching the passed in Alias: '{0}' was found",
contentTypeAlias));
var contentType = types.First();
if (contentType == null)
throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null",
contentTypeAlias));
return contentType;
}
}
public XElement Export(IContent content, bool deep = false)
{
throw new NotImplementedException();
}
#region Internal Methods
/// <summary>
/// Internal method to Re-Publishes all Content for legacy purposes.

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Umbraco.Core.Auditing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Events;
@@ -460,6 +461,60 @@ namespace Umbraco.Core.Services
return dtd.ToString();
}
/// <summary>
/// Imports and saves package xml as <see cref="IContentType"/>
/// </summary>
/// <param name="element">Xml to import</param>
/// <returns>An enumrable list of generated ContentTypes</returns>
public List<IContentType> Import(XElement element)
{
var name = element.Name.LocalName;
if (name.Equals("DocumentTypes") == false)
{
throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called 'DocumentTypes'.");
}
var list = new List<IContentType>();
var documentTypes = from doc in element.Elements("DocumentType") select doc;
foreach (var documentType in documentTypes)
{
//TODO Check if the ContentType already exists by looking up the alias
list.Add(CreateContentTypeFromXml(documentType));
}
Save(list);
return list;
}
private IContentType CreateContentTypeFromXml(XElement documentType)
{
var infoElement = documentType.Element("Info");
var name = infoElement.Element("Name").Value;
var alias = infoElement.Element("Alias").Value;
var masterElement = infoElement.Element("Master");//Name of the master corresponds to the parent
var icon = infoElement.Element("Icon").Value;
var thumbnail = infoElement.Element("Thumbnail").Value;
var description = infoElement.Element("Description").Value;
var allowAtRoot = infoElement.Element("AllowAtRoot").Value;
var defaultTemplate = infoElement.Element("DefaultTemplate").Value;
var allowedTemplatesElement = infoElement.Elements("AllowedTemplates");
var structureElement = documentType.Element("Structure");
var genericPropertiesElement = documentType.Element("GenericProperties");
var tabElement = documentType.Element("Tab");
var contentType = new ContentType(-1)
{
Alias = alias,
Name = name,
Icon = icon,
Thumbnail = thumbnail,
AllowedAsRoot = allowAtRoot.ToLowerInvariant().Equals("true"),
Description = description
};
return contentType;
}
#region Event Handlers
/// <summary>

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Umbraco.Core.Models;
namespace Umbraco.Core.Services
@@ -287,5 +288,12 @@ namespace Umbraco.Core.Services
/// <param name="content"><see cref="IContent"/> to check if anscestors are published</param>
/// <returns>True if the Content can be published, otherwise False</returns>
bool IsPublishable(IContent content);
/// <summary>
/// Imports and saves package xml as <see cref="IContent"/>
/// </summary>
/// <param name="element">Xml to import</param>
/// <returns>An enumrable list of generated content</returns>
IEnumerable<IContent> Import(XElement element);
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Xml.Linq;
using Umbraco.Core.Models;
namespace Umbraco.Core.Services
@@ -149,5 +150,12 @@ namespace Umbraco.Core.Services
/// <param name="id">Id of the <see cref="IMediaType"/></param>
/// <returns>True if the media type has any children otherwise False</returns>
bool MediaTypeHasChildren(int id);
/// <summary>
/// Imports and saves package xml as <see cref="IContentType"/>
/// </summary>
/// <param name="element">Xml to import</param>
/// <returns>An enumrable list of generated ContentTypes</returns>
List<IContentType> Import(XElement element);
}
}

View File

@@ -0,0 +1,66 @@
using System.Linq;
using System.Xml.Linq;
using NUnit.Framework;
namespace Umbraco.Tests.Services.Importing
{
[TestFixture, RequiresSTA]
public class ContentImportTests : BaseServiceTest
{
[SetUp]
public override void Initialize()
{
base.Initialize();
}
[TearDown]
public override void TearDown()
{
base.TearDown();
}
[Test]
public void ContentTypeService_Can_Import_Package_Xml()
{
// Arrange
string strXml = ImportResources.package;
var xml = XElement.Parse(strXml);
var element = xml.Descendants("DocumentTypes").First();
var contentTypeService = ServiceContext.ContentTypeService;
// Act
var contentTypes = contentTypeService.Import(element);
var numberOfDocTypes = (from doc in element.Elements("DocumentType") select doc).Count();
// Assert
Assert.That(contentTypes, Is.Not.Null);
Assert.That(contentTypes.Any(), Is.True);
Assert.That(contentTypes.Count(), Is.EqualTo(numberOfDocTypes));
}
[Test]
public void ContentService_Can_Import_Package_Xml()
{
// Arrange
string strXml = ImportResources.package;
var xml = XElement.Parse(strXml);
var docTypesElement = xml.Descendants("DocumentTypes").First();
var element = xml.Descendants("DocumentSet").First();
var contentService = ServiceContext.ContentService;
var contentTypeService = ServiceContext.ContentTypeService;
// Act
var contentTypes = contentTypeService.Import(docTypesElement);
var contents = contentService.Import(element);
var numberOfDocs = (from doc in element.Descendants()
where (string) doc.Attribute("isDoc") == ""
select doc).Count();
// Assert
Assert.That(contents, Is.Not.Null);
Assert.That(contentTypes.Any(), Is.True);
Assert.That(contents.Any(), Is.True);
Assert.That(contents.Count(), Is.EqualTo(numberOfDocs));
}
}
}

View File

@@ -0,0 +1,90 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.18033
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Umbraco.Tests.Services.Importing {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class ImportResources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal ImportResources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Umbraco.Tests.Services.Importing.ImportResources", typeof(ImportResources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;
///&lt;umbPackage&gt;
/// &lt;files&gt;
/// &lt;file&gt;
/// &lt;guid&gt;Map.cshtml&lt;/guid&gt;
/// &lt;orgPath&gt;/macroScripts&lt;/orgPath&gt;
/// &lt;orgName&gt;Map.cshtml&lt;/orgName&gt;
/// &lt;/file&gt;
/// &lt;file&gt;
/// &lt;guid&gt;AccountController.cs&lt;/guid&gt;
/// &lt;orgPath&gt;/App_Code&lt;/orgPath&gt;
/// &lt;orgName&gt;AccountController.cs&lt;/orgName&gt;
/// &lt;/file&gt;
/// &lt;file&gt;
/// &lt;guid&gt;ContactController.cs&lt;/guid&gt;
/// &lt;orgPath&gt;/App_Code&lt;/orgPath&gt;
/// &lt;orgName&gt;ContactController.cs&lt;/orgName&gt;
/// &lt;/file&gt;
/// [rest of string was truncated]&quot;;.
/// </summary>
internal static string package {
get {
return ResourceManager.GetString("package", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="package" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>package.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

View File

@@ -262,6 +262,12 @@
<Compile Include="Services\ContentServicePerformanceTest.cs" />
<Compile Include="Services\ContentServiceTests.cs" />
<Compile Include="Services\ContentTypeServiceTests.cs" />
<Compile Include="Services\Importing\ContentImportTests.cs" />
<Compile Include="Services\Importing\ImportResources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>ImportResources.resx</DependentUpon>
</Compile>
<Compile Include="Services\ThreadSafetyServiceTest.cs" />
<Compile Include="Surface\PluginControllerAreaTests.cs" />
<Compile Include="Templates\MasterPageHelperTests.cs" />
@@ -376,6 +382,10 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>SqlResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Services\Importing\ImportResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ImportResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="TestHelpers\ExamineHelpers\ExamineResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ExamineResources.Designer.cs</LastGenOutput>
@@ -414,6 +424,7 @@
<Content Include="Migrations\SqlScripts\SqlCe-SchemaAndData-4110.sql" />
<Content Include="Migrations\SqlScripts\SqlCeTotal-480.sql" />
<Content Include="Migrations\SqlScripts\SqlServerTotal-480.sql" />
<Content Include="Services\Importing\package.xml" />
<Content Include="TestHelpers\ExamineHelpers\media.xml" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />