backports data type service updates and fixes unit tests with correct seeding value.

This commit is contained in:
Shannon
2013-12-16 17:17:23 +11:00
parent 6a9b8d5540
commit 2ce952bdbb
18 changed files with 502 additions and 177 deletions

View File

@@ -0,0 +1,29 @@
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a stored pre-value field value
/// </summary>
public class PreValue
{
public PreValue(int id, string value)
{
Value = value;
Id = id;
}
public PreValue(string value)
{
Value = value;
}
/// <summary>
/// The value stored for the pre-value field
/// </summary>
public string Value { get; private set; }
/// <summary>
/// The database id for the pre-value field value
/// </summary>
public int Id { get; private set; }
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents the pre-value data for a DataType
/// </summary>
/// <remarks>
/// Due to the legacy nature of the data that can be stored for pre-values, we have this class which encapsulates the 2 different
/// ways that pre-values are stored: A string array or a Dictionary.
///
/// Most legacy property editors won't support the dictionary format but new property editors should always use the dictionary format.
/// In order to get overrideable pre-values working we need a dictionary since we'll have to reference a pre-value by a key.
/// </remarks>
public class PreValueCollection
{
private IDictionary<string, PreValue> _preValuesAsDictionary;
private IEnumerable<PreValue> _preValuesAsArray;
public IEnumerable<PreValue> PreValuesAsArray
{
get
{
if (_preValuesAsArray == null)
{
throw new InvalidOperationException("The current pre-value collection is dictionary based, use the PreValuesAsDictionary property instead");
}
return _preValuesAsArray;
}
set { _preValuesAsArray = value; }
}
public IDictionary<string, PreValue> PreValuesAsDictionary
{
get
{
if (_preValuesAsDictionary == null)
{
throw new InvalidOperationException("The current pre-value collection is array based, use the PreValuesAsArray property instead");
}
return _preValuesAsDictionary;
}
set { _preValuesAsDictionary = value; }
}
/// <summary>
/// Check if it is a dictionary based collection
/// </summary>
public bool IsDictionaryBased
{
get { return _preValuesAsDictionary != null; }
}
public PreValueCollection(IEnumerable<PreValue> preVals)
{
_preValuesAsArray = preVals;
}
public PreValueCollection(IDictionary<string, PreValue> preVals)
{
_preValuesAsDictionary = preVals;
}
/// <summary>
/// Regardless of how the pre-values are stored this will return as a dictionary, it will convert an array based to a dictionary
/// </summary>
/// <returns></returns>
public IDictionary<string, PreValue> FormatAsDictionary()
{
if (IsDictionaryBased)
{
return PreValuesAsDictionary;
}
//it's an array so need to format it, the alias will just be an iteration
var result = new Dictionary<string, PreValue>();
var asArray = PreValuesAsArray.ToArray();
for (var i = 0; i < asArray.Length; i++)
{
result.Add(i.ToInvariantString(), asArray[i]);
}
return result;
}
}
}

View File

@@ -84,39 +84,58 @@ namespace Umbraco.Core.Persistence.UnitOfWork
});
}
/// <summary>
/// Commits all batched changes within the scope of a PetaPoco transaction <see cref="Transaction"/>
/// </summary>
/// <remarks>
/// Unlike a typical unit of work, this UOW will let you commit more than once since a new transaction is creaed per
/// Commit() call instead of having one Transaction per UOW.
/// </remarks>
public void Commit()
{
using (var transaction = Database.GetTransaction())
{
foreach (var operation in _operations.OrderBy(o => o.ProcessDate))
{
switch (operation.Type)
{
case TransactionType.Insert:
operation.Repository.PersistNewItem(operation.Entity);
break;
case TransactionType.Delete:
operation.Repository.PersistDeletedItem(operation.Entity);
break;
case TransactionType.Update:
operation.Repository.PersistUpdatedItem(operation.Entity);
break;
}
}
transaction.Complete();
}
/// <summary>
/// Commits all batched changes within the scope of a PetaPoco transaction <see cref="Transaction"/>
/// </summary>
/// <remarks>
/// Unlike a typical unit of work, this UOW will let you commit more than once since a new transaction is creaed per
/// Commit() call instead of having one Transaction per UOW.
/// </remarks>
public void Commit()
{
Commit(null);
}
// Clear everything
_operations.Clear();
_key = Guid.NewGuid();
}
/// <summary>
/// Commits all batched changes within the scope of a PetaPoco transaction <see cref="Transaction"/>
/// </summary>
/// <param name="transactionCompleting">
/// Allows you to set a callback which is executed before the transaction is committed, allow you to add additional SQL
/// operations to the overall commit process after the queue has been processed.
/// </param>
internal void Commit(Action<UmbracoDatabase> transactionCompleting)
{
using (var transaction = Database.GetTransaction())
{
foreach (var operation in _operations.OrderBy(o => o.ProcessDate))
{
switch (operation.Type)
{
case TransactionType.Insert:
operation.Repository.PersistNewItem(operation.Entity);
break;
case TransactionType.Delete:
operation.Repository.PersistDeletedItem(operation.Entity);
break;
case TransactionType.Update:
operation.Repository.PersistUpdatedItem(operation.Entity);
break;
}
}
//Execute the callback if there is one
if (transactionCompleting != null)
{
transactionCompleting(Database);
}
transaction.Complete();
}
// Clear everything
_operations.Clear();
_key = Guid.NewGuid();
}
public object Key
{

View File

@@ -9,6 +9,7 @@ using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.PropertyEditors;
using umbraco.interfaces;
namespace Umbraco.Core.Services
@@ -18,16 +19,16 @@ namespace Umbraco.Core.Services
/// </summary>
public class DataTypeService : IDataTypeService
{
private readonly RepositoryFactory _repositoryFactory;
private readonly RepositoryFactory _repositoryFactory;
private readonly IDatabaseUnitOfWorkProvider _uowProvider;
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
public DataTypeService()
: this(new RepositoryFactory())
{}
{ }
public DataTypeService(RepositoryFactory repositoryFactory)
: this(new PetaPocoUnitOfWorkProvider(), repositoryFactory)
: this(new PetaPocoUnitOfWorkProvider(), repositoryFactory)
{
}
@@ -36,9 +37,9 @@ namespace Umbraco.Core.Services
{
}
public DataTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory)
public DataTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory)
{
_repositoryFactory = repositoryFactory;
_repositoryFactory = repositoryFactory;
_uowProvider = provider;
}
@@ -74,7 +75,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Gets a <see cref="IDataTypeDefinition"/> by its control Id
/// </summary>
/// <param name="id">Id of the DataType control</param>
/// <param name="id">Id of the property editor</param>
/// <returns>Collection of <see cref="IDataTypeDefinition"/> objects with a matching contorl id</returns>
public IEnumerable<IDataTypeDefinition> GetDataTypeDefinitionByControlId(Guid id)
{
@@ -109,27 +110,25 @@ namespace Umbraco.Core.Services
{
using (var uow = _uowProvider.GetUnitOfWork())
{
var dtos = uow.Database.Fetch<DataTypePreValueDto>("WHERE datatypeNodeId = @Id", new {Id = id});
var dtos = uow.Database.Fetch<DataTypePreValueDto>("WHERE datatypeNodeId = @Id", new { Id = id });
var list = dtos.Select(x => x.Value).ToList();
return list;
}
}
/// <summary>
/// Gets all prevalues for an <see cref="IDataTypeDefinition"/>
/// Returns the PreValueCollection for the specified data type
/// </summary>
/// <remarks>
/// This method should be kept internal until a proper PreValue object model is introduced.
/// </remarks>
/// <param name="id">Id of the <see cref="IDataTypeDefinition"/> to retrieve prevalues from</param>
/// <returns>An enumerable list of Tuples containing Id, Alias, SortOrder, Value</returns>
internal IEnumerable<Tuple<int, string, int, string>> GetDetailedPreValuesByDataTypeId(int id)
/// <param name="id"></param>
/// <returns></returns>
public PreValueCollection GetPreValuesCollectionByDataTypeId(int id)
{
using (var uow = _uowProvider.GetUnitOfWork())
{
var dtos = uow.Database.Fetch<DataTypePreValueDto>("WHERE datatypeNodeId = @Id", new { Id = id });
var list = dtos.Select(x => new Tuple<int, string, int, string>(x.Id, x.Alias, x.SortOrder, x.Value)).ToList();
return list;
var list = dtos.Select(x => new Tuple<PreValue, string, int>(new PreValue(x.Id, x.Value), x.Alias, x.SortOrder)).ToList();
return PreValueConverter.ConvertToPreValuesCollection(list);
}
}
@@ -154,8 +153,8 @@ namespace Umbraco.Core.Services
/// <param name="userId">Id of the user issueing the save</param>
public void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition), this))
return;
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition), this))
return;
using (new WriteLock(Locker))
{
@@ -206,8 +205,11 @@ namespace Umbraco.Core.Services
/// </summary>
/// <param name="id">Id of the DataTypeDefinition to save PreValues for</param>
/// <param name="values">List of string values to save</param>
[Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")]
public void SavePreValues(int id, IEnumerable<string> values)
{
//TODO: Should we raise an event here since we are really saving values for the data type?
using (new WriteLock(Locker))
{
using (var uow = _uowProvider.GetUnitOfWork())
@@ -236,6 +238,111 @@ namespace Umbraco.Core.Services
}
}
/// <summary>
/// Saves/updates the pre-values
/// </summary>
/// <param name="id"></param>
/// <param name="values"></param>
/// <remarks>
/// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors
/// like 'dropdown list publishing keys'
/// </remarks>
public void SavePreValues(int id, IDictionary<string, PreValue> values)
{
//TODO: Should we raise an event here since we are really saving values for the data type?
using (new WriteLock(Locker))
{
using (var uow = _uowProvider.GetUnitOfWork())
{
using (var transaction = uow.Database.GetTransaction())
{
AddOrUpdatePreValues(id, values, uow);
transaction.Complete();
}
}
}
}
/// <summary>
/// This will save a data type and it's pre-values in one transaction
/// </summary>
/// <param name="dataTypeDefinition"></param>
/// <param name="values"></param>
/// <param name="userId"></param>
public void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary<string, PreValue> values, int userId = 0)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition), this))
return;
using (new WriteLock(Locker))
{
var uow = (PetaPocoUnitOfWork)_uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow))
{
dataTypeDefinition.CreatorId = userId;
repository.AddOrUpdate(dataTypeDefinition);
//complete the transaction, but run the delegate before the db transaction is finalized
uow.Commit(database => AddOrUpdatePreValues(dataTypeDefinition.Id, values, uow));
Saved.RaiseEvent(new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition, false), this);
}
}
Audit.Add(AuditTypes.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id);
}
private void AddOrUpdatePreValues(int id, IDictionary<string, PreValue> preValueCollection, IDatabaseUnitOfWork uow)
{
//first just get all pre-values for this data type so we can compare them to see if we need to insert or update or replace
var sql = new Sql().Select("*")
.From<DataTypePreValueDto>()
.Where<DataTypePreValueDto>(dto => dto.DataTypeNodeId == id)
.OrderBy<DataTypePreValueDto>(dto => dto.SortOrder);
var currentVals = uow.Database.Fetch<DataTypePreValueDto>(sql).ToArray();
//already existing, need to be updated
var valueIds = preValueCollection.Where(x => x.Value.Id > 0).Select(x => x.Value.Id).ToArray();
var existingByIds = currentVals.Where(x => valueIds.Contains(x.Id)).ToArray();
//These ones need to be removed from the db, they no longer exist in the new values
var deleteById = currentVals.Where(x => valueIds.Contains(x.Id) == false);
foreach (var d in deleteById)
{
uow.Database.Execute(
"DELETE FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId AND id=@Id",
new { DataTypeId = id, Id = d.Id });
}
var sortOrder = 1;
foreach (var pre in preValueCollection)
{
var existing = existingByIds.FirstOrDefault(valueDto => valueDto.Id == pre.Value.Id);
if (existing != null)
{
existing.Value = pre.Value.Value;
existing.SortOrder = sortOrder;
uow.Database.Update(existing);
}
else
{
var dto = new DataTypePreValueDto
{
DataTypeNodeId = id,
Value = pre.Value.Value,
SortOrder = sortOrder,
Alias = pre.Key
};
uow.Database.Insert(dto);
}
sortOrder++;
}
}
/// <summary>
/// Deletes an <see cref="IDataTypeDefinition"/>
/// </summary>
@@ -246,43 +353,43 @@ namespace Umbraco.Core.Services
/// <param name="dataTypeDefinition"><see cref="IDataTypeDefinition"/> to delete</param>
/// <param name="userId">Optional Id of the user issueing the deletion</param>
public void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0)
{
if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs<IDataTypeDefinition>(dataTypeDefinition), this))
return;
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentTypeRepository(uow))
{
//Find ContentTypes using this IDataTypeDefinition on a PropertyType
var query = Query<PropertyType>.Builder.Where(x => x.DataTypeDefinitionId == dataTypeDefinition.Id);
var contentTypes = repository.GetByQuery(query);
{
if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs<IDataTypeDefinition>(dataTypeDefinition), this))
return;
//Loop through the list of results and remove the PropertyTypes that references the DataTypeDefinition that is being deleted
foreach (var contentType in contentTypes)
{
if (contentType == null) continue;
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentTypeRepository(uow))
{
//Find ContentTypes using this IDataTypeDefinition on a PropertyType
var query = Query<PropertyType>.Builder.Where(x => x.DataTypeDefinitionId == dataTypeDefinition.Id);
var contentTypes = repository.GetByQuery(query);
foreach (var group in contentType.PropertyGroups)
{
var types = @group.PropertyTypes.Where(x => x.DataTypeDefinitionId == dataTypeDefinition.Id).ToList();
foreach (var propertyType in types)
{
@group.PropertyTypes.Remove(propertyType);
}
}
//Loop through the list of results and remove the PropertyTypes that references the DataTypeDefinition that is being deleted
foreach (var contentType in contentTypes)
{
if (contentType == null) continue;
repository.AddOrUpdate(contentType);
}
foreach (var group in contentType.PropertyGroups)
{
var types = @group.PropertyTypes.Where(x => x.DataTypeDefinitionId == dataTypeDefinition.Id).ToList();
foreach (var propertyType in types)
{
@group.PropertyTypes.Remove(propertyType);
}
}
var dataTypeRepository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow);
dataTypeRepository.Delete(dataTypeDefinition);
repository.AddOrUpdate(contentType);
}
uow.Commit();
var dataTypeRepository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow);
dataTypeRepository.Delete(dataTypeDefinition);
Deleted.RaiseEvent(new DeleteEventArgs<IDataTypeDefinition>(dataTypeDefinition, false), this);
}
uow.Commit();
Audit.Add(AuditTypes.Delete, string.Format("Delete DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id);
Deleted.RaiseEvent(new DeleteEventArgs<IDataTypeDefinition>(dataTypeDefinition, false), this);
}
Audit.Add(AuditTypes.Delete, string.Format("Delete DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id);
}
/// <summary>
@@ -290,6 +397,7 @@ namespace Umbraco.Core.Services
/// </summary>
/// <param name="id">Id of the DataType, which corresponds to the Guid Id of the control</param>
/// <returns><see cref="IDataType"/> object</returns>
[Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")]
public IDataType GetDataTypeById(Guid id)
{
return DataTypesResolver.Current.GetById(id);
@@ -299,6 +407,7 @@ namespace Umbraco.Core.Services
/// Gets a complete list of all registered <see cref="IDataType"/>'s
/// </summary>
/// <returns>An enumerable list of <see cref="IDataType"/> objects</returns>
[Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")]
public IEnumerable<IDataType> GetAllDataTypes()
{
return DataTypesResolver.Current.DataTypes;
@@ -308,22 +417,58 @@ namespace Umbraco.Core.Services
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IDataTypeService, DeleteEventArgs<IDataTypeDefinition>> Deleting;
public static event TypedEventHandler<IDataTypeService, DeleteEventArgs<IDataTypeDefinition>> Deleting;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IDataTypeService, DeleteEventArgs<IDataTypeDefinition>> Deleted;
public static event TypedEventHandler<IDataTypeService, DeleteEventArgs<IDataTypeDefinition>> Deleted;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IDataTypeService, SaveEventArgs<IDataTypeDefinition>> Saving;
public static event TypedEventHandler<IDataTypeService, SaveEventArgs<IDataTypeDefinition>> Saving;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IDataTypeService, SaveEventArgs<IDataTypeDefinition>> Saved;
public static event TypedEventHandler<IDataTypeService, SaveEventArgs<IDataTypeDefinition>> Saved;
#endregion
internal static class PreValueConverter
{
/// <summary>
/// Converts the tuple to a pre-value collection
/// </summary>
/// <param name="list"></param>
/// <returns></returns>
internal static PreValueCollection ConvertToPreValuesCollection(IEnumerable<Tuple<PreValue, string, int>> list)
{
//now we need to determine if they are dictionary based, otherwise they have to be array based
var dictionary = new Dictionary<string, PreValue>();
//need to check all of the keys, if there's only one and it is empty then it's an array
var keys = list.Select(x => x.Item2).Distinct().ToArray();
if (keys.Length == 1 && keys[0].IsNullOrWhiteSpace())
{
return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item1));
}
foreach (var item in list
.OrderBy(x => x.Item3) //we'll order them first so we maintain the order index in the dictionary
.GroupBy(x => x.Item2)) //group by alias
{
if (item.Count() > 1)
{
//if there's more than 1 item per key, then it cannot be a dictionary, just return the array
return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item1));
}
dictionary.Add(item.Key, item.First().Item1);
}
return new PreValueCollection(dictionary);
}
}
}
}

View File

@@ -83,13 +83,36 @@ namespace Umbraco.Core.Services
/// <returns>An enumerable list of string values</returns>
IEnumerable<string> GetPreValuesByDataTypeId(int id);
/// <summary>
/// Gets a pre-value collection by data type id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
PreValueCollection GetPreValuesCollectionByDataTypeId(int id);
/// <summary>
/// Saves a list of PreValues for a given DataTypeDefinition
/// </summary>
/// <param name="id">Id of the DataTypeDefinition to save PreValues for</param>
/// <param name="values">List of string values to save</param>
[Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")]
void SavePreValues(int id, IEnumerable<string> values);
/// <summary>
/// Saves a list of PreValues for a given DataTypeDefinition
/// </summary>
/// <param name="id">Id of the DataTypeDefinition to save PreValues for</param>
/// <param name="values">List of key/value pairs to save</param>
void SavePreValues(int id, IDictionary<string, PreValue> values);
/// <summary>
/// Saves the data type and it's prevalues
/// </summary>
/// <param name="dataTypeDefinition"></param>
/// <param name="values"></param>
/// <param name="userId"></param>
void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary<string, PreValue> values, int userId = 0);
/// <summary>
/// Gets a specific PreValue by its Id
/// </summary>

View File

@@ -707,20 +707,23 @@ namespace Umbraco.Core.Services
{
var prevalues = new XElement("PreValues");
var prevalueList = ((DataTypeService)_dataTypeService).GetDetailedPreValuesByDataTypeId(dataTypeDefinition.Id);
foreach (var tuple in prevalueList)
var prevalueList = _dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id)
.FormatAsDictionary();
var sort = 0;
foreach (var pv in prevalueList)
{
var prevalue = new XElement("PreValue");
prevalue.Add(new XAttribute("Id", tuple.Item1));
prevalue.Add(new XAttribute("Value", tuple.Item4));
prevalue.Add(new XAttribute("Alias", tuple.Item2));
prevalue.Add(new XAttribute("SortOrder", tuple.Item3));
prevalue.Add(new XAttribute("Id", pv.Value.Id));
prevalue.Add(new XAttribute("Value", pv.Value.Value));
prevalue.Add(new XAttribute("Alias", pv.Key));
prevalue.Add(new XAttribute("SortOrder", sort));
prevalues.Add(prevalue);
sort++;
}
var xml = new XElement("DataType", prevalues);
xml.Add(new XAttribute("Name", dataTypeDefinition.Name));
//The 'ID' when exporting is actually the property editor alias (in pre v7 it was the IDataType GUID id)
xml.Add(new XAttribute("Id", dataTypeDefinition.ControlId));
xml.Add(new XAttribute("Definition", dataTypeDefinition.Key));
xml.Add(new XAttribute("DatabaseType", dataTypeDefinition.DatabaseType.ToString()));

View File

@@ -439,6 +439,16 @@ namespace Umbraco.Core
return String.Format(CultureInfo.InvariantCulture, format, args);
}
/// <summary>
/// Converts an integer to an invariant formatted string
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string ToInvariantString(this int str)
{
return str.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Compares 2 strings with invariant culture and case ignored
/// </summary>

View File

@@ -189,6 +189,8 @@
<Compile Include="Models\ContentTypeCompositionBase.cs" />
<Compile Include="Models\ContentTypeExtensions.cs" />
<Compile Include="Models\ContentTypeSort.cs" />
<Compile Include="Models\PreValue.cs" />
<Compile Include="Models\PreValueCollection.cs" />
<Compile Include="Models\PublishedContent\IPublishedContentExtended.cs" />
<Compile Include="Models\PublishedContent\PublishedPropertyBase.cs" />
<Compile Include="Models\PublishedContent\PublishedContentModelFactoryImpl.cs" />