diff --git a/src/Umbraco.Core/Models/ContentXmlEntity.cs b/src/Umbraco.Core/Models/ContentXmlEntity.cs
new file mode 100644
index 0000000000..ffa3bd2ff8
--- /dev/null
+++ b/src/Umbraco.Core/Models/ContentXmlEntity.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Xml.Linq;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Models
+{
+ ///
+ /// Used in content/media/member repositories in order to add this type of entity to the persisted collection to be saved
+ /// in a single transaction during saving an entity
+ ///
+ internal class ContentXmlEntity : IAggregateRoot
+ {
+ private readonly bool _entityExists;
+ private readonly Func _xml;
+
+ public ContentXmlEntity(bool entityExists, IContentBase content, Func xml)
+ {
+ if (content == null) throw new ArgumentNullException("content");
+ _entityExists = entityExists;
+ _xml = xml;
+ Content = content;
+ }
+
+ public XElement Xml
+ {
+ get { return _xml(); }
+ }
+ public IContentBase Content { get; private set; }
+
+ public int Id
+ {
+ get { return Content.Id; }
+ set { throw new NotSupportedException(); }
+ }
+
+ public Guid Key { get; set; }
+ public DateTime CreateDate { get; set; }
+ public DateTime UpdateDate { get; set; }
+
+ public bool HasIdentity
+ {
+ get { return _entityExists; }
+ }
+
+ public object DeepClone()
+ {
+ var clone = (ContentXmlEntity)MemberwiseClone();
+ //Automatically deep clone ref properties that are IDeepCloneable
+ DeepCloneHelper.DeepCloneRefProperties(this, clone);
+ return clone;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/PropertyExtensions.cs b/src/Umbraco.Core/Models/PropertyExtensions.cs
index ae01532c87..ce4aa31b72 100644
--- a/src/Umbraco.Core/Models/PropertyExtensions.cs
+++ b/src/Umbraco.Core/Models/PropertyExtensions.cs
@@ -22,42 +22,10 @@ namespace Umbraco.Core.Models
/// Xml of the property and its value
public static XElement ToXml(this Property property)
{
- return property.ToXml(ApplicationContext.Current.Services.DataTypeService);
+ var xmlSerializer = new EntityXmlSerializer();
+ return xmlSerializer.Serialize(ApplicationContext.Current.Services.DataTypeService, property);
}
- internal static XElement ToXml(this Property property, IDataTypeService dataTypeService)
- {
- var nodeName = UmbracoSettings.UseLegacyXmlSchema ? "data" : property.Alias.ToSafeAlias();
-
- var xd = new XmlDocument();
- var xmlNode = xd.CreateNode(XmlNodeType.Element, nodeName, "");
-
- //Add the property alias to the legacy schema
- if (UmbracoSettings.UseLegacyXmlSchema)
- {
- var alias = xd.CreateAttribute("alias");
- alias.Value = property.Alias.ToSafeAlias();
- xmlNode.Attributes.Append(alias);
- }
-
- //This seems to fail during testing
- //SD: With the new null checks below, this shouldn't fail anymore.
- var dt = property.PropertyType.DataType(property.Id, dataTypeService);
- if (dt != null && dt.Data != null)
- {
- //We've already got the value for the property so we're going to give it to the
- // data type's data property so it doesn't go re-look up the value from the db again.
- var defaultData = dt.Data as IDataValueSetter;
- if (defaultData != null)
- {
- defaultData.SetValue(property.Value, property.PropertyType.DataTypeDatabaseType.ToString());
- }
-
- xmlNode.AppendChild(dt.Data.ToXMl(xd));
- }
-
- var element = xmlNode.GetXElement();
- return element;
- }
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
index 98c17b74e4..19b730dd07 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
+using System.Data;
using System.Globalization;
using System.Linq;
+using System.Xml.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
@@ -23,6 +25,8 @@ namespace Umbraco.Core.Persistence.Repositories
private readonly IContentTypeRepository _contentTypeRepository;
private readonly ITemplateRepository _templateRepository;
private readonly CacheHelper _cacheHelper;
+ private readonly ContentPreviewRepository _contentPreviewRepository;
+ private readonly ContentXmlRepository _contentXmlRepository;
public ContentRepository(IDatabaseUnitOfWork work, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, CacheHelper cacheHelper)
: base(work)
@@ -30,6 +34,8 @@ namespace Umbraco.Core.Persistence.Repositories
_contentTypeRepository = contentTypeRepository;
_templateRepository = templateRepository;
_cacheHelper = cacheHelper;
+ _contentPreviewRepository = new ContentPreviewRepository(work, NullCacheProvider.Current);
+ _contentXmlRepository = new ContentXmlRepository(work, NullCacheProvider.Current);
EnsureUniqueNaming = true;
}
@@ -40,6 +46,8 @@ namespace Umbraco.Core.Persistence.Repositories
_contentTypeRepository = contentTypeRepository;
_templateRepository = templateRepository;
_cacheHelper = cacheHelper;
+ _contentPreviewRepository = new ContentPreviewRepository(work, NullCacheProvider.Current);
+ _contentXmlRepository = new ContentXmlRepository(work, NullCacheProvider.Current);
EnsureUniqueNaming = true;
}
@@ -554,6 +562,30 @@ namespace Umbraco.Core.Persistence.Repositories
return repo.GetPermissionsForEntity(entityId);
}
+ ///
+ /// Adds/updates content/published xml
+ ///
+ ///
+ public void AddOrUpdateContentXml(IContent content, Func xml)
+ {
+ var contentExists = Database.ExecuteScalar("SELECT COUNT(nodeId) FROM cmsContentXml WHERE nodeId = @Id", new { Id = content.Id }) != 0;
+
+ _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(contentExists, content, xml));
+ }
+
+ ///
+ /// Adds/updates preview xml
+ ///
+ ///
+ public void AddOrUpdatePreviewXml(IContent content, Func xml)
+ {
+ var previewExists =
+ Database.ExecuteScalar("SELECT COUNT(nodeId) FROM cmsPreviewXml WHERE nodeId = @Id AND versionId = @Version",
+ new { Id = content.Id, Version = content.Version }) != 0;
+
+ _contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity(previewExists, content, xml));
+ }
+
#endregion
///
@@ -614,5 +646,123 @@ namespace Umbraco.Core.Persistence.Repositories
return currentName;
}
+
+ #region Private classes
+
+ ///
+ /// Used content repository in order to add an entity to the persisted collection to be saved
+ /// in a single transaction during saving an entity
+ ///
+ private class ContentPreviewEntity : ContentXmlEntity
+ {
+ public ContentPreviewEntity(bool previewExists, IContentBase content, Func xml)
+ : base(previewExists, content, xml)
+ {
+ Version = content.Version;
+ }
+
+ public Guid Version { get; private set; }
+ }
+
+ ///
+ /// Private class to handle preview insert/update based on standard principles and units of work with transactions
+ ///
+ private class ContentPreviewRepository : PetaPocoRepositoryBase
+ {
+ public ContentPreviewRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache)
+ : base(work, cache)
+ {
+ }
+
+ #region Not implemented (don't need to for the purposes of this repo)
+ protected override ContentPreviewEntity PerformGet(int id)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override IEnumerable PerformGetAll(params int[] ids)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override IEnumerable PerformGetByQuery(IQuery query)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override Sql GetBaseQuery(bool isCount)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override string GetBaseWhereClause()
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override IEnumerable GetDeleteClauses()
+ {
+ return new List();
+ }
+
+ protected override Guid NodeObjectTypeId
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ protected override void PersistDeletedItem(ContentPreviewEntity entity)
+ {
+ throw new NotImplementedException();
+ }
+ #endregion
+
+ protected override void PersistNewItem(ContentPreviewEntity entity)
+ {
+ if (entity.Content.HasIdentity == false)
+ {
+ throw new InvalidOperationException("Cannot insert a preview for a content item that has no identity");
+ }
+
+ var previewPoco = new PreviewXmlDto
+ {
+ NodeId = entity.Id,
+ Timestamp = DateTime.Now,
+ VersionId = entity.Version,
+ Xml = entity.Xml.ToString(SaveOptions.None)
+ };
+
+ Database.Insert(previewPoco);
+ }
+
+ protected override void PersistUpdatedItem(ContentPreviewEntity entity)
+ {
+ if (entity.Content.HasIdentity == false)
+ {
+ throw new InvalidOperationException("Cannot update a preview for a content item that has no identity");
+ }
+
+ var previewPoco = new PreviewXmlDto
+ {
+ NodeId = entity.Id,
+ Timestamp = DateTime.Now,
+ VersionId = entity.Version,
+ Xml = entity.Xml.ToString(SaveOptions.None)
+ };
+
+ Database.Update(
+ "SET xml = @Xml, timestamp = @Timestamp WHERE nodeId = @Id AND versionId = @Version",
+ new
+ {
+ Xml = previewPoco.Xml,
+ Timestamp = previewPoco.Timestamp,
+ Id = previewPoco.NodeId,
+ Version = previewPoco.VersionId
+ });
+ }
+ }
+
+ #endregion
+
+
}
-}
+}
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs
new file mode 100644
index 0000000000..3590ad8815
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Xml.Linq;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.Rdbms;
+using Umbraco.Core.Persistence.Caching;
+using Umbraco.Core.Persistence.Querying;
+using Umbraco.Core.Persistence.UnitOfWork;
+
+namespace Umbraco.Core.Persistence.Repositories
+{
+ ///
+ /// Internal class to handle content/published xml insert/update based on standard principles and units of work with transactions
+ ///
+ internal class ContentXmlRepository : PetaPocoRepositoryBase
+ {
+ public ContentXmlRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache)
+ : base(work, cache)
+ {
+ }
+
+ #region Not implemented (don't need to for the purposes of this repo)
+ protected override ContentXmlEntity PerformGet(int id)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override IEnumerable PerformGetAll(params int[] ids)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override IEnumerable PerformGetByQuery(IQuery query)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override Sql GetBaseQuery(bool isCount)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override string GetBaseWhereClause()
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override IEnumerable GetDeleteClauses()
+ {
+ return new List();
+ }
+
+ protected override Guid NodeObjectTypeId
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ protected override void PersistDeletedItem(ContentXmlEntity entity)
+ {
+ throw new NotImplementedException();
+ }
+ #endregion
+
+ protected override void PersistNewItem(ContentXmlEntity entity)
+ {
+ if (entity.Content.HasIdentity == false)
+ {
+ throw new InvalidOperationException("Cannot insert an xml entry for a content item that has no identity");
+ }
+
+ var poco = new ContentXmlDto { NodeId = entity.Id, Xml = entity.Xml.ToString(SaveOptions.None) };
+ Database.Insert(poco);
+ }
+
+ protected override void PersistUpdatedItem(ContentXmlEntity entity)
+ {
+ if (entity.Content.HasIdentity == false)
+ {
+ throw new InvalidOperationException("Cannot update an xml entry for a content item that has no identity");
+ }
+
+ var poco = new ContentXmlDto { NodeId = entity.Id, Xml = entity.Xml.ToString(SaveOptions.None) };
+ Database.Update(poco);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
index 75bee41026..9484121ad7 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
@@ -45,7 +45,7 @@ namespace Umbraco.Core.Persistence.Repositories
_preValRepository = new DataTypePreValueRepository(work, NullCacheProvider.Current);
}
- private readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
+ private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
#region Overrides of RepositoryBase
@@ -166,42 +166,6 @@ namespace Umbraco.Core.Persistence.Repositories
#region Unit of Work Implementation
- public override void PersistUpdatedItem(IEntity entity)
- {
- if (entity is PreValue)
- {
- _preValRepository.PersistUpdatedItem(entity);
- }
- else
- {
- base.PersistUpdatedItem(entity);
- }
- }
-
- public override void PersistNewItem(IEntity entity)
- {
- if (entity is PreValue)
- {
- _preValRepository.PersistNewItem(entity);
- }
- else
- {
- base.PersistNewItem(entity);
- }
- }
-
- public override void PersistDeletedItem(IEntity entity)
- {
- if (entity is PreValue)
- {
- _preValRepository.PersistDeletedItem(entity);
- }
- else
- {
- base.PersistDeletedItem(entity);
- }
- }
-
protected override void PersistNewItem(IDataTypeDefinition entity)
{
((DataTypeDefinition)entity).AddingEntity();
@@ -326,7 +290,7 @@ AND umbracoNode.id <> @id",
public PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId)
{
- using (var l = new UpgradeableReadLock(Locker))
+ using (var l = new UpgradeableReadLock(_locker))
{
var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId));
if (cached != null && cached.Any())
@@ -343,7 +307,7 @@ AND umbracoNode.id <> @id",
public string GetPreValueAsString(int preValueId)
{
- using (var l = new UpgradeableReadLock(Locker))
+ using (var l = new UpgradeableReadLock(_locker))
{
//We need to see if we can find the cached PreValueCollection based on the cache key above
@@ -429,8 +393,9 @@ AND umbracoNode.id <> @id",
existing.SortOrder = sortOrder;
_preValRepository.AddOrUpdate(new PreValueEntity
{
- Alias = existing.Alias,
+ //setting an id will update it
Id = existing.Id,
+ Alias = existing.Alias,
SortOrder = existing.SortOrder,
Value = existing.Value,
DataType = dataType,
@@ -483,7 +448,7 @@ AND umbracoNode.id <> @id",
///
/// Private class to handle pre-value crud based on units of work with transactions
///
- public class PreValueEntity : Entity, IAggregateRoot
+ private class PreValueEntity : Entity, IAggregateRoot
{
public string Value { get; set; }
public string Alias { get; set; }
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
index 3a343febae..a47999f5c4 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Xml.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.Querying;
@@ -37,5 +38,19 @@ namespace Umbraco.Core.Persistence.Repositories
///
///
IEnumerable GetPermissionsForEntity(int entityId);
+
+ ///
+ /// Used to add/update published xml for the content item
+ ///
+ ///
+ ///
+ void AddOrUpdateContentXml(IContent content, Func xml);
+
+ ///
+ /// Used to add/update preview xml for the content item
+ ///
+ ///
+ ///
+ void AddOrUpdatePreviewXml(IContent content, Func xml);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index f6f38d6002..5336d70a42 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -29,6 +29,9 @@ namespace Umbraco.Core.Services
private readonly IDatabaseUnitOfWorkProvider _uowProvider;
private readonly IPublishingStrategy _publishingStrategy;
private readonly RepositoryFactory _repositoryFactory;
+ private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer();
+ private readonly IDataTypeService _dataTypeService;
+
//Support recursive locks because some of the methods that require locking call other methods that require locking.
//for example, the Move method needs to be locked but this calls the Save method which also needs to be locked.
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
@@ -57,6 +60,18 @@ namespace Umbraco.Core.Services
_uowProvider = provider;
_publishingStrategy = publishingStrategy;
_repositoryFactory = repositoryFactory;
+ _dataTypeService = new DataTypeService(provider, repositoryFactory);
+ }
+
+ public ContentService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, IPublishingStrategy publishingStrategy, IDataTypeService dataTypeService)
+ {
+ if (provider == null) throw new ArgumentNullException("provider");
+ if (repositoryFactory == null) throw new ArgumentNullException("repositoryFactory");
+ if (publishingStrategy == null) throw new ArgumentNullException("publishingStrategy");
+ _uowProvider = provider;
+ _publishingStrategy = publishingStrategy;
+ _repositoryFactory = repositoryFactory;
+ _dataTypeService = dataTypeService;
}
///
@@ -1302,13 +1317,14 @@ namespace Umbraco.Core.Services
var shouldBePublished = new List();
var shouldBeSaved = new List();
+ var asArray = items.ToArray();
using (new WriteLock(Locker))
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
int i = 0;
- foreach (var content in items)
+ foreach (var content in asArray)
{
//If the current sort order equals that of the content
//we don't need to update it, so just increment the sort order
@@ -1332,30 +1348,25 @@ namespace Umbraco.Core.Services
shouldBeSaved.Add(content);
repository.AddOrUpdate(content);
+
+ //Generate a new preview
+ var local = content;
+ repository.AddOrUpdatePreviewXml(content, () => _entitySerializer.Serialize(this, _dataTypeService, local));
+ }
+
+ foreach (var content in shouldBePublished)
+ {
+ //Create and Save ContentXml DTO
+ var local = content;
+ repository.AddOrUpdateContentXml(content, () => _entitySerializer.Serialize(this, _dataTypeService, local));
}
uow.Commit();
-
- foreach (var content in shouldBeSaved)
- {
- //Create and Save PreviewXml DTO
- var xml = content.ToXml();
- CreateAndSavePreviewXml(xml, content.Id, content.Version, uow.Database);
- }
-
- foreach (var content in shouldBePublished)
- {
- //Create and Save PreviewXml DTO
- var xml = content.ToXml();
- CreateAndSavePreviewXml(xml, content.Id, content.Version, uow.Database);
- //Create and Save ContentXml DTO
- CreateAndSaveContentXml(xml, content.Id, uow.Database);
- }
}
}
if (raiseEvents)
- Saved.RaiseEvent(new SaveEventArgs(items, false), this);
+ Saved.RaiseEvent(new SaveEventArgs(asArray, false), this);
if (shouldBePublished.Any())
_publishingStrategy.PublishingFinalized(shouldBePublished, false);
@@ -1468,7 +1479,8 @@ namespace Umbraco.Core.Services
using (var uow = _uowProvider.GetUnitOfWork())
{
- var xml = content.ToXml();
+ var xml = _entitySerializer.Serialize(this, _dataTypeService, content);
+
var poco = new ContentXmlDto { NodeId = content.Id, Xml = xml.ToString(SaveOptions.None) };
var exists =
uow.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = content.Id }) !=
@@ -1526,7 +1538,7 @@ namespace Umbraco.Core.Services
var xmlItems = new List();
foreach (var c in list)
{
- var xml = c.ToXml();
+ var xml = _entitySerializer.Serialize(this, _dataTypeService, c);
xmlItems.Add(new ContentXmlDto { NodeId = c.Id, Xml = xml.ToString(SaveOptions.None) });
}
@@ -1650,7 +1662,7 @@ namespace Umbraco.Core.Services
foreach (var c in updated)
{
- var xml = c.ToXml();
+ var xml = _entitySerializer.Serialize(this, _dataTypeService, c);
var poco = new ContentXmlDto { NodeId = c.Id, Xml = xml.ToString(SaveOptions.None) };
var exists = uow.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = c.Id }) !=
null;
@@ -1763,17 +1775,17 @@ namespace Umbraco.Core.Services
repository.AddOrUpdate(content);
- uow.Commit();
-
- var xml = content.ToXml();
- //Preview Xml
- CreateAndSavePreviewXml(xml, content.Id, content.Version, uow.Database);
-
+ //Generate a new preview
+ var local = content;
+ repository.AddOrUpdatePreviewXml(content, () => _entitySerializer.Serialize(this, _dataTypeService, local));
+
if (published)
{
//Content Xml
- CreateAndSaveContentXml(xml, content.Id, uow.Database);
+ repository.AddOrUpdateContentXml(content, () => _entitySerializer.Serialize(this, _dataTypeService, local));
}
+
+ uow.Commit();
}
if (raiseEvents)
@@ -1830,11 +1842,12 @@ namespace Umbraco.Core.Services
content.ChangePublishedState(PublishedState.Saved);
repository.AddOrUpdate(content);
- uow.Commit();
- //Preview Xml
- var xml = content.ToXml();
- CreateAndSavePreviewXml(xml, content.Id, content.Version, uow.Database);
+ //Generate a new preview
+ var local = content;
+ repository.AddOrUpdatePreviewXml(content, () => _entitySerializer.Serialize(this, _dataTypeService, local));
+
+ uow.Commit();
}
if (raiseEvents)
@@ -1906,39 +1919,7 @@ namespace Umbraco.Core.Services
}
return PublishStatusType.Success;
- }
-
- private void CreateAndSavePreviewXml(XElement xml, int id, Guid version, UmbracoDatabase db)
- {
- var previewPoco = new PreviewXmlDto
- {
- NodeId = id,
- Timestamp = DateTime.Now,
- VersionId = version,
- Xml = xml.ToString(SaveOptions.None)
- };
- var previewExists =
- db.ExecuteScalar("SELECT COUNT(nodeId) FROM cmsPreviewXml WHERE nodeId = @Id AND versionId = @Version",
- new { Id = id, Version = version }) != 0;
- int previewResult = previewExists
- ? db.Update(
- "SET xml = @Xml, timestamp = @Timestamp WHERE nodeId = @Id AND versionId = @Version",
- new
- {
- Xml = previewPoco.Xml,
- Timestamp = previewPoco.Timestamp,
- Id = previewPoco.NodeId,
- Version = previewPoco.VersionId
- })
- : Convert.ToInt32(db.Insert(previewPoco));
- }
-
- private void CreateAndSaveContentXml(XElement xml, int id, UmbracoDatabase db)
- {
- var contentPoco = new ContentXmlDto { NodeId = id, Xml = xml.ToString(SaveOptions.None) };
- var contentExists = db.ExecuteScalar("SELECT COUNT(nodeId) FROM cmsContentXml WHERE nodeId = @Id", new { Id = id }) != 0;
- int contentResult = contentExists ? db.Update(contentPoco) : Convert.ToInt32(db.Insert(contentPoco));
- }
+ }
private IContentType FindContentTypeByAlias(string contentTypeAlias)
{
diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs
new file mode 100644
index 0000000000..b16e74df4d
--- /dev/null
+++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs
@@ -0,0 +1,218 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Xml;
+using System.Xml.Linq;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Models;
+using Umbraco.Core.Strings;
+using umbraco.interfaces;
+
+namespace Umbraco.Core.Services
+{
+ ///
+ /// A helper class to serialize entities to XML
+ ///
+ internal class EntityXmlSerializer
+ {
+ ///
+ /// Exports an item to xml as an
+ ///
+ ///
+ ///
+ /// Content to export
+ /// Optional parameter indicating whether to include descendents
+ /// containing the xml representation of the Content object
+ public XElement Serialize(IContentService contentService, IDataTypeService dataTypeService, IContent content, bool deep = false)
+ {
+ //nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias);
+ var nodeName = UmbracoSettings.UseLegacyXmlSchema ? "node" : content.ContentType.Alias.ToSafeAliasWithForcingCheck();
+
+ var xml = Serialize(dataTypeService, content, nodeName);
+ xml.Add(new XAttribute("nodeType", content.ContentType.Id));
+ xml.Add(new XAttribute("creatorName", content.GetCreatorProfile().Name));
+ xml.Add(new XAttribute("writerName", content.GetWriterProfile().Name));
+ xml.Add(new XAttribute("writerID", content.WriterId));
+ xml.Add(new XAttribute("template", content.Template == null ? "0" : content.Template.Id.ToString(CultureInfo.InvariantCulture)));
+ xml.Add(new XAttribute("nodeTypeAlias", content.ContentType.Alias));
+
+ if (deep)
+ {
+ var descendants = contentService.GetDescendants(content).ToArray();
+ var currentChildren = descendants.Where(x => x.ParentId == content.Id);
+ AddChildXml(contentService, dataTypeService, descendants, currentChildren, xml);
+ }
+
+ return xml;
+ }
+
+ ///
+ /// Exports an item to xml as an
+ ///
+ ///
+ ///
+ /// Media to export
+ /// Optional parameter indicating whether to include descendents
+ /// containing the xml representation of the Media object
+ public XElement Serialize(IMediaService mediaService, IDataTypeService dataTypeService, IMedia media, bool deep = false)
+ {
+ //nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias);
+ var nodeName = UmbracoSettings.UseLegacyXmlSchema ? "node" : media.ContentType.Alias.ToSafeAliasWithForcingCheck();
+
+ var xml = Serialize(dataTypeService, media, nodeName);
+ xml.Add(new XAttribute("nodeType", media.ContentType.Id));
+ xml.Add(new XAttribute("writerName", media.GetCreatorProfile().Name));
+ xml.Add(new XAttribute("writerID", media.CreatorId));
+ xml.Add(new XAttribute("version", media.Version));
+ xml.Add(new XAttribute("template", 0));
+ xml.Add(new XAttribute("nodeTypeAlias", media.ContentType.Alias));
+
+ if (deep)
+ {
+ var descendants = mediaService.GetDescendants(media).ToArray();
+ var currentChildren = descendants.Where(x => x.ParentId == media.Id);
+ AddChildXml(mediaService, dataTypeService, descendants, currentChildren, xml);
+ }
+
+ return xml;
+ }
+
+ ///
+ /// Exports an item to xml as an
+ ///
+ ///
+ /// Member to export
+ /// containing the xml representation of the Member object
+ public XElement Serialize(IDataTypeService dataTypeService, IMember member)
+ {
+ //nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias);
+ var nodeName = UmbracoSettings.UseLegacyXmlSchema ? "node" : member.ContentType.Alias.ToSafeAliasWithForcingCheck();
+
+ var xml = Serialize(dataTypeService, member, nodeName);
+ xml.Add(new XAttribute("nodeType", member.ContentType.Id));
+ xml.Add(new XAttribute("nodeTypeAlias", member.ContentType.Alias));
+
+ xml.Add(new XAttribute("loginName", member.Username));
+ xml.Add(new XAttribute("email", member.Email));
+ xml.Add(new XAttribute("key", member.Key));
+
+ return xml;
+ }
+
+ public XElement Serialize(IDataTypeService dataTypeService, Property property)
+ {
+ var nodeName = UmbracoSettings.UseLegacyXmlSchema ? "data" : property.Alias.ToSafeAlias();
+
+ var xd = new XmlDocument();
+ var xmlNode = xd.CreateNode(XmlNodeType.Element, nodeName, "");
+
+ //Add the property alias to the legacy schema
+ if (UmbracoSettings.UseLegacyXmlSchema)
+ {
+ var alias = xd.CreateAttribute("alias");
+ alias.Value = property.Alias.ToSafeAlias();
+ xmlNode.Attributes.Append(alias);
+ }
+
+ //This seems to fail during testing
+ //SD: With the new null checks below, this shouldn't fail anymore.
+ var dt = property.PropertyType.DataType(property.Id, dataTypeService);
+ if (dt != null && dt.Data != null)
+ {
+ //We've already got the value for the property so we're going to give it to the
+ // data type's data property so it doesn't go re-look up the value from the db again.
+ var defaultData = dt.Data as IDataValueSetter;
+ if (defaultData != null)
+ {
+ defaultData.SetValue(property.Value, property.PropertyType.DataTypeDatabaseType.ToString());
+ }
+
+ xmlNode.AppendChild(dt.Data.ToXMl(xd));
+ }
+
+ var element = xmlNode.GetXElement();
+ return element;
+ }
+
+ ///
+ /// Used by Media Export to recursively add children
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void AddChildXml(IMediaService mediaService, IDataTypeService dataTypeService, IMedia[] originalDescendants, IEnumerable currentChildren, XElement currentXml)
+ {
+ foreach (var child in currentChildren)
+ {
+ //add the child's xml
+ var childXml = Serialize(mediaService, dataTypeService, child);
+ currentXml.Add(childXml);
+ //copy local (out of closure)
+ var c = child;
+ //get this item's children
+ var children = originalDescendants.Where(x => x.ParentId == c.Id);
+ //recurse and add it's children to the child xml element
+ AddChildXml(mediaService, dataTypeService, originalDescendants, children, childXml);
+ }
+ }
+
+ ///
+ /// Part of the export of IContent and IMedia and IMember which is shared
+ ///
+ ///
+ /// Base Content or Media to export
+ /// Name of the node
+ ///
+ private XElement Serialize(IDataTypeService dataTypeService, IContentBase contentBase, string nodeName)
+ {
+ //NOTE: that one will take care of umbracoUrlName
+ var url = contentBase.GetUrlSegment();
+
+ var xml = new XElement(nodeName,
+ new XAttribute("id", contentBase.Id),
+ new XAttribute("parentID", contentBase.Level > 1 ? contentBase.ParentId : -1),
+ new XAttribute("level", contentBase.Level),
+ new XAttribute("creatorID", contentBase.CreatorId),
+ new XAttribute("sortOrder", contentBase.SortOrder),
+ new XAttribute("createDate", contentBase.CreateDate.ToString("s")),
+ new XAttribute("updateDate", contentBase.UpdateDate.ToString("s")),
+ new XAttribute("nodeName", contentBase.Name),
+ new XAttribute("urlName", url),
+ new XAttribute("path", contentBase.Path),
+ new XAttribute("isDoc", ""));
+
+ foreach (var property in contentBase.Properties.Where(p => p != null && p.Value != null && p.Value.ToString().IsNullOrWhiteSpace() == false))
+ {
+ xml.Add(Serialize(dataTypeService, property));
+ }
+
+ return xml;
+ }
+
+ ///
+ /// Used by Content Export to recursively add children
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void AddChildXml(IContentService contentService, IDataTypeService dataTypeService, IContent[] originalDescendants, IEnumerable currentChildren, XElement currentXml)
+ {
+ foreach (var child in currentChildren)
+ {
+ //add the child's xml
+ var childXml = Serialize(contentService, dataTypeService, child);
+ currentXml.Add(childXml);
+ //copy local (out of closure)
+ var c = child;
+ //get this item's children
+ var children = originalDescendants.Where(x => x.ParentId == c.Id);
+ //recurse and add it's children to the child xml element
+ AddChildXml(contentService, dataTypeService, originalDescendants, children, childXml);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs
index 902021b2e3..5330507fd4 100644
--- a/src/Umbraco.Core/Services/PackagingService.cs
+++ b/src/Umbraco.Core/Services/PackagingService.cs
@@ -5,9 +5,7 @@ using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
-using System.Xml;
using System.Xml.Linq;
-using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
@@ -17,7 +15,6 @@ using Umbraco.Core.Packaging.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;
-using Umbraco.Core.Strings;
namespace Umbraco.Core.Services
{
@@ -69,79 +66,8 @@ namespace Umbraco.Core.Services
/// containing the xml representation of the Content object
public XElement Export(IContent content, bool deep = false)
{
- //nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias);
- var nodeName = UmbracoSettings.UseLegacyXmlSchema ? "node" : content.ContentType.Alias.ToSafeAliasWithForcingCheck();
-
- var xml = Export(content, nodeName);
- xml.Add(new XAttribute("nodeType", content.ContentType.Id));
- xml.Add(new XAttribute("creatorName", content.GetCreatorProfile().Name));
- xml.Add(new XAttribute("writerName", content.GetWriterProfile().Name));
- xml.Add(new XAttribute("writerID", content.WriterId));
- xml.Add(new XAttribute("template", content.Template == null ? "0" : content.Template.Id.ToString(CultureInfo.InvariantCulture)));
- xml.Add(new XAttribute("nodeTypeAlias", content.ContentType.Alias));
-
- if (deep)
- {
- var descendants = content.Descendants().ToArray();
- var currentChildren = descendants.Where(x => x.ParentId == content.Id);
- AddChildXml(descendants, currentChildren, xml);
- }
-
- return xml;
- }
-
- ///
- /// Part of the export of IContent and IMedia and IMember which is shared
- ///
- /// Base Content or Media to export
- /// Name of the node
- ///
- private XElement Export(IContentBase contentBase, string nodeName)
- {
- //NOTE: that one will take care of umbracoUrlName
- var url = contentBase.GetUrlSegment();
-
- var xml = new XElement(nodeName,
- new XAttribute("id", contentBase.Id),
- new XAttribute("parentID", contentBase.Level > 1 ? contentBase.ParentId : -1),
- new XAttribute("level", contentBase.Level),
- new XAttribute("creatorID", contentBase.CreatorId),
- new XAttribute("sortOrder", contentBase.SortOrder),
- new XAttribute("createDate", contentBase.CreateDate.ToString("s")),
- new XAttribute("updateDate", contentBase.UpdateDate.ToString("s")),
- new XAttribute("nodeName", contentBase.Name),
- new XAttribute("urlName", url),
- new XAttribute("path", contentBase.Path),
- new XAttribute("isDoc", ""));
-
- foreach (var property in contentBase.Properties.Where(p => p != null && p.Value != null && p.Value.ToString().IsNullOrWhiteSpace() == false))
- {
- xml.Add(property.ToXml());
- }
-
- return xml;
- }
-
- ///
- /// Used by Content Export to recursively add children
- ///
- ///
- ///
- ///
- private void AddChildXml(IContent[] originalDescendants, IEnumerable currentChildren, XElement currentXml)
- {
- foreach (var child in currentChildren)
- {
- //add the child's xml
- var childXml = Export(child);
- currentXml.Add(childXml);
- //copy local (out of closure)
- var c = child;
- //get this item's children
- var children = originalDescendants.Where(x => x.ParentId == c.Id);
- //recurse and add it's children to the child xml element
- AddChildXml(originalDescendants, children, childXml);
- }
+ var exporter = new EntityXmlSerializer();
+ return exporter.Serialize(_contentService, _dataTypeService, content, deep);
}
///
@@ -964,20 +890,10 @@ namespace Umbraco.Core.Services
///
/// Member to export
/// containing the xml representation of the Member object
- internal XElement Export(IMember member)
+ public XElement Export(IMember member)
{
- //nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias);
- var nodeName = UmbracoSettings.UseLegacyXmlSchema ? "node" : member.ContentType.Alias.ToSafeAliasWithForcingCheck();
-
- var xml = Export(member, nodeName);
- xml.Add(new XAttribute("nodeType", member.ContentType.Id));
- xml.Add(new XAttribute("nodeTypeAlias", member.ContentType.Alias));
-
- xml.Add(new XAttribute("loginName", member.Username));
- xml.Add(new XAttribute("email", member.Email));
- xml.Add(new XAttribute("key", member.Key));
-
- return xml;
+ var exporter = new EntityXmlSerializer();
+ return exporter.Serialize(_dataTypeService, member);
}
#endregion
@@ -990,49 +906,10 @@ namespace Umbraco.Core.Services
/// Media to export
/// Optional parameter indicating whether to include descendents
/// containing the xml representation of the Media object
- internal XElement Export(IMedia media, bool deep = false)
+ public XElement Export(IMedia media, bool deep = false)
{
- //nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias);
- var nodeName = UmbracoSettings.UseLegacyXmlSchema ? "node" : media.ContentType.Alias.ToSafeAliasWithForcingCheck();
-
- var xml = Export(media, nodeName);
- xml.Add(new XAttribute("nodeType", media.ContentType.Id));
- xml.Add(new XAttribute("writerName", media.GetCreatorProfile().Name));
- xml.Add(new XAttribute("writerID", media.CreatorId));
- xml.Add(new XAttribute("version", media.Version));
- xml.Add(new XAttribute("template", 0));
- xml.Add(new XAttribute("nodeTypeAlias", media.ContentType.Alias));
-
- if (deep)
- {
- var descendants = media.Descendants().ToArray();
- var currentChildren = descendants.Where(x => x.ParentId == media.Id);
- AddChildXml(descendants, currentChildren, xml);
- }
-
- return xml;
- }
-
- ///
- /// Used by Media Export to recursively add children
- ///
- ///
- ///
- ///
- private void AddChildXml(IMedia[] originalDescendants, IEnumerable currentChildren, XElement currentXml)
- {
- foreach (var child in currentChildren)
- {
- //add the child's xml
- var childXml = Export(child);
- currentXml.Add(childXml);
- //copy local (out of closure)
- var c = child;
- //get this item's children
- var children = originalDescendants.Where(x => x.ParentId == c.Id);
- //recurse and add it's children to the child xml element
- AddChildXml(originalDescendants, children, childXml);
- }
+ var exporter = new EntityXmlSerializer();
+ return exporter.Serialize(_mediaService, _dataTypeService, media, deep);
}
#endregion
diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs
index 453b7ec648..e185c4bda4 100644
--- a/src/Umbraco.Core/Services/ServiceContext.cs
+++ b/src/Umbraco.Core/Services/ServiceContext.cs
@@ -124,7 +124,7 @@ namespace Umbraco.Core.Services
_memberService = new Lazy(() => new MemberService(provider, repositoryFactory.Value, _memberGroupService.Value));
if (_contentService == null)
- _contentService = new Lazy(() => new ContentService(provider, repositoryFactory.Value, publishingStrategy));
+ _contentService = new Lazy(() => new ContentService(provider, repositoryFactory.Value, publishingStrategy, DataTypeService));
if (_mediaService == null)
_mediaService = new Lazy(() => new MediaService(provider, repositoryFactory.Value));
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 20a60f7b2a..19077d15f6 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -194,6 +194,7 @@
+
@@ -232,6 +233,7 @@
+
@@ -788,6 +790,7 @@
+
diff --git a/src/Umbraco.Tests/Models/DataValueSetterTests.cs b/src/Umbraco.Tests/Models/DataValueSetterTests.cs
index 1785bda3c7..2b8c793001 100644
--- a/src/Umbraco.Tests/Models/DataValueSetterTests.cs
+++ b/src/Umbraco.Tests/Models/DataValueSetterTests.cs
@@ -85,7 +85,8 @@ namespace Umbraco.Tests.Models
// Act
- var xml = property.ToXml(dataTypeSvcMock.Object);
+ var entitySerializer = new EntityXmlSerializer();
+ var xml = entitySerializer.Serialize(dataTypeSvcMock.Object, property);
// Assert
diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs
index 41c6086d94..fe78f719ec 100644
--- a/src/Umbraco.Tests/Services/ContentServiceTests.cs
+++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs
@@ -900,6 +900,49 @@ namespace Umbraco.Tests.Services
Assert.That(sut.Version, Is.EqualTo(version));
}
+ [Test]
+ public void Ensure_Content_Xml_Created()
+ {
+ var contentService = ServiceContext.ContentService;
+ var content = contentService.CreateContent("Home US", -1, "umbTextpage", 0);
+ content.SetValue("author", "Barack Obama");
+
+ contentService.Save(content);
+
+ var provider = new PetaPocoUnitOfWorkProvider();
+ var uow = provider.GetUnitOfWork();
+ using (RepositoryResolver.Current.ResolveByType(uow))
+ {
+ Assert.IsFalse(uow.Database.Exists(content.Id));
+ }
+
+ contentService.Publish(content);
+
+ uow = provider.GetUnitOfWork();
+ using (RepositoryResolver.Current.ResolveByType(uow))
+ {
+ Assert.IsTrue(uow.Database.Exists(content.Id));
+ }
+ }
+
+ [Test]
+ public void Ensure_Preview_Xml_Created()
+ {
+ var contentService = ServiceContext.ContentService;
+ var content = contentService.CreateContent("Home US", -1, "umbTextpage", 0);
+ content.SetValue("author", "Barack Obama");
+
+ contentService.Save(content);
+
+ var provider = new PetaPocoUnitOfWorkProvider();
+ var uow = provider.GetUnitOfWork();
+ using (RepositoryResolver.Current.ResolveByType(uow))
+ {
+ Assert.IsTrue(uow.Database.SingleOrDefault("WHERE nodeId=@nodeId AND versionId = @versionId", new{nodeId = content.Id, versionId = content.Version}) != null);
+ }
+
+ }
+
private IEnumerable CreateContentHierarchy()
{
var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage");