Adds ContentType factory implementations U4-980
Adds ContentType repository implementation U4-956
This commit is contained in:
90
src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs
Normal file
90
src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
internal class ContentTypeFactory : IEntityFactory<IContentType, DocumentTypeDto>
|
||||
{
|
||||
private readonly Guid _nodeObjectType;
|
||||
|
||||
#region Implementation of IEntityFactory<IContentType,DocumentTypeDto>
|
||||
|
||||
public ContentTypeFactory(Guid nodeObjectType)
|
||||
{
|
||||
_nodeObjectType = nodeObjectType;
|
||||
}
|
||||
|
||||
public IContentType BuildEntity(DocumentTypeDto dto)
|
||||
{
|
||||
var contentType = new ContentType
|
||||
{
|
||||
Id = dto.ContentTypeDto.NodeDto.NodeId,
|
||||
Key =
|
||||
dto.ContentTypeDto.NodeDto.UniqueId.HasValue
|
||||
? dto.ContentTypeDto.NodeDto.UniqueId.Value
|
||||
: dto.ContentTypeDto.NodeDto.NodeId.ToGuid(),
|
||||
Alias = dto.ContentTypeDto.Alias,
|
||||
Name = dto.ContentTypeDto.NodeDto.Text,
|
||||
Icon = dto.ContentTypeDto.Icon,
|
||||
Thumbnail = dto.ContentTypeDto.Thumbnail,
|
||||
SortOrder = dto.ContentTypeDto.NodeDto.SortOrder,
|
||||
Description = dto.ContentTypeDto.Description,
|
||||
CreateDate = dto.ContentTypeDto.NodeDto.CreateDate,
|
||||
Path = dto.ContentTypeDto.NodeDto.Path,
|
||||
Level = dto.ContentTypeDto.NodeDto.Level,
|
||||
ParentId = dto.ContentTypeDto.NodeDto.ParentId,
|
||||
UserId =
|
||||
dto.ContentTypeDto.NodeDto.UserId.HasValue
|
||||
? dto.ContentTypeDto.NodeDto.UserId.Value
|
||||
: 0,
|
||||
Trashed = dto.ContentTypeDto.NodeDto.Trashed
|
||||
};
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public DocumentTypeDto BuildDto(IContentType entity)
|
||||
{
|
||||
var documentTypeDto = new DocumentTypeDto
|
||||
{ContentTypeDto = BuildContentTypeDto(entity), ContentTypeNodeId = entity.Id};
|
||||
//NOTE TemplateId and IsDefault currently not added
|
||||
return documentTypeDto;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private ContentTypeDto BuildContentTypeDto(IContentType entity)
|
||||
{
|
||||
var contentTypeDto = new ContentTypeDto
|
||||
{
|
||||
Alias = entity.Alias,
|
||||
Description = entity.Description,
|
||||
Icon = entity.Icon,
|
||||
Thumbnail = entity.Thumbnail,
|
||||
NodeId = entity.Id,
|
||||
NodeDto = BuildNodeDto(entity)
|
||||
};
|
||||
return contentTypeDto;
|
||||
}
|
||||
|
||||
private NodeDto BuildNodeDto(IContentType entity)
|
||||
{
|
||||
var nodeDto = new NodeDto
|
||||
{
|
||||
CreateDate = entity.CreateDate,
|
||||
NodeId = entity.Id,
|
||||
Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)),
|
||||
NodeObjectType = _nodeObjectType,
|
||||
ParentId = entity.ParentId,
|
||||
Path = entity.Path,
|
||||
SortOrder = entity.SortOrder,
|
||||
Text = entity.Name,
|
||||
Trashed = false,
|
||||
UniqueId = entity.Key,
|
||||
UserId = entity.UserId
|
||||
};
|
||||
return nodeDto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
internal class PropertyGroupFactory : IEntityFactory<IEnumerable<PropertyGroup>, IEnumerable<TabDto>>
|
||||
{
|
||||
private int _id;
|
||||
|
||||
public PropertyGroupFactory(int id)
|
||||
{
|
||||
_id = id;
|
||||
}
|
||||
|
||||
#region Implementation of IEntityFactory<IEnumerable<PropertyGroup>,IEnumerable<TabDto>>
|
||||
|
||||
public IEnumerable<PropertyGroup> BuildEntity(IEnumerable<TabDto> dto)
|
||||
{
|
||||
var propertyGroups = new PropertyGroupCollection();
|
||||
foreach (var tabDto in dto)
|
||||
{
|
||||
var group = new PropertyGroup();
|
||||
group.Id = tabDto.Id;
|
||||
group.Name = tabDto.Text;
|
||||
group.SortOrder = tabDto.SortOrder;
|
||||
group.PropertyTypes = new PropertyTypeCollection();
|
||||
|
||||
foreach (var typeDto in tabDto.PropertyTypeDtos)
|
||||
{
|
||||
group.PropertyTypes.Add(new PropertyType(typeDto.DataTypeDto.ControlId,
|
||||
typeDto.DataTypeDto.DbType.EnumParse<DataTypeDatabaseType>(true))
|
||||
{
|
||||
Alias = typeDto.Alias,
|
||||
DataTypeId = typeDto.DataTypeId,
|
||||
Description = typeDto.Description,
|
||||
Id = typeDto.Id,
|
||||
Name = typeDto.Name,
|
||||
HelpText = typeDto.HelpText,
|
||||
Mandatory = typeDto.Mandatory,
|
||||
SortOrder = typeDto.SortOrder
|
||||
});
|
||||
|
||||
}
|
||||
group.ResetDirtyProperties();
|
||||
propertyGroups.Add(group);
|
||||
}
|
||||
|
||||
return propertyGroups;
|
||||
}
|
||||
|
||||
public IEnumerable<TabDto> BuildDto(IEnumerable<PropertyGroup> entity)
|
||||
{
|
||||
return entity.Select(propertyGroup => BuildTabDto(propertyGroup)).ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal TabDto BuildTabDto(PropertyGroup propertyGroup)
|
||||
{
|
||||
var tabDto = new TabDto
|
||||
{
|
||||
ContentTypeNodeId = _id,
|
||||
SortOrder = propertyGroup.SortOrder,
|
||||
Text = propertyGroup.Name
|
||||
};
|
||||
|
||||
if (propertyGroup.HasIdentity)
|
||||
tabDto.Id = propertyGroup.Id;
|
||||
|
||||
tabDto.PropertyTypeDtos = propertyGroup.PropertyTypes.Select(propertyType => BuildPropertyTypeDto(propertyGroup.Id, propertyType)).ToList();
|
||||
|
||||
return tabDto;
|
||||
}
|
||||
|
||||
internal PropertyTypeDto BuildPropertyTypeDto(int tabId, PropertyType propertyType)
|
||||
{
|
||||
var propertyTypeDto = new PropertyTypeDto
|
||||
{
|
||||
Alias = propertyType.Alias,
|
||||
ContentTypeId = _id,
|
||||
DataTypeId = propertyType.DataTypeId,
|
||||
Description = propertyType.Description,
|
||||
HelpText = propertyType.HelpText,
|
||||
Mandatory = propertyType.Mandatory,
|
||||
Name = propertyType.Name,
|
||||
SortOrder = propertyType.SortOrder,
|
||||
TabId = tabId
|
||||
};
|
||||
|
||||
if (propertyType.HasIdentity)
|
||||
propertyTypeDto.Id = propertyType.Id;
|
||||
|
||||
return propertyTypeDto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.Caching;
|
||||
using Umbraco.Core.Persistence.Factories;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.Relators;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a repository for doing CRUD operations for <see cref="IContentType"/>
|
||||
/// </summary>
|
||||
internal class ContentTypeRepository : PetaPocoRepositoryBase<int, IContentType>, IContentTypeRepository
|
||||
{
|
||||
public ContentTypeRepository(IUnitOfWork work) : base(work)
|
||||
{
|
||||
}
|
||||
|
||||
public ContentTypeRepository(IUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache)
|
||||
{
|
||||
}
|
||||
|
||||
#region Overrides of RepositoryBase<int,IContentType>
|
||||
|
||||
protected override IContentType PerformGet(int id)
|
||||
{
|
||||
var contentTypeSql = GetBaseQuery(false);
|
||||
contentTypeSql.Append(GetBaseWhereClause(id));
|
||||
|
||||
var documentTypeDto = Database.Query<DocumentTypeDto, ContentTypeDto, NodeDto>(contentTypeSql).FirstOrDefault();
|
||||
|
||||
if (documentTypeDto == null)
|
||||
return null;
|
||||
|
||||
//TODO Get ContentType composition according to new table
|
||||
|
||||
var propertySql = new Sql();
|
||||
propertySql.Select("*");
|
||||
propertySql.From("cmsTab");
|
||||
propertySql.RightJoin("cmsPropertyType ON [cmsTab].[id] = [cmsPropertyType].[tabId]");
|
||||
propertySql.InnerJoin("cmsDataType ON [cmsPropertyType].[dataTypeId] = [cmsDataType].[nodeId]");
|
||||
propertySql.Where("[cmsPropertyType].[contentTypeId] = @Id", new { Id = id });
|
||||
|
||||
var tabDtos = Database.Fetch<TabDto, PropertyTypeDto, DataTypeDto, TabDto>(new TabPropertyTypeRelator().Map, propertySql);
|
||||
|
||||
var factory = new ContentTypeFactory(NodeObjectTypeId);
|
||||
var contentType = factory.BuildEntity(documentTypeDto);
|
||||
|
||||
var propertyFactory = new PropertyGroupFactory(id);
|
||||
var propertyGroups = propertyFactory.BuildEntity(tabDtos);
|
||||
contentType.PropertyGroups = new PropertyGroupCollection(propertyGroups);
|
||||
|
||||
((ContentType)contentType).ResetDirtyProperties();
|
||||
return contentType;
|
||||
}
|
||||
|
||||
protected override IEnumerable<IContentType> PerformGetAll(params int[] ids)
|
||||
{
|
||||
if (ids.Any())
|
||||
{
|
||||
foreach (var id in ids)
|
||||
{
|
||||
yield return Get(id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var nodeDtos = Database.Fetch<NodeDto>("WHERE nodeObjectType = @NodeObjectType", new { NodeObjectType = NodeObjectTypeId });
|
||||
foreach (var nodeDto in nodeDtos)
|
||||
{
|
||||
yield return Get(nodeDto.NodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<IContentType> PerformGetByQuery(IQuery<IContentType> query)
|
||||
{
|
||||
var sqlClause = GetBaseQuery(false);
|
||||
var translator = new SqlTranslator<IContentType>(sqlClause, query);
|
||||
var sql = translator.Translate();
|
||||
|
||||
var documentTypeDtos = Database.Fetch<DocumentTypeDto, ContentTypeDto, NodeDto>(sql);
|
||||
|
||||
foreach (var dto in documentTypeDtos)
|
||||
{
|
||||
yield return Get(dto.ContentTypeNodeId);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides of PetaPocoRepositoryBase<int,IContentType>
|
||||
|
||||
protected override Sql GetBaseQuery(bool isCount)
|
||||
{
|
||||
var sql = new Sql();
|
||||
//NOTE: If IsDefault=true we won't get ContentTypes like File, Folder etc. but only DocumentTypes.
|
||||
//Which is why "AND cmsDocumentType.IsDefault = @IsDefault" has been removed from sql below.
|
||||
//But might need to add it if we create a MediaTypeRepository
|
||||
sql.Select(isCount ? "COUNT(*)" : "*");
|
||||
sql.From("cmsDocumentType");
|
||||
sql.RightJoin("cmsContentType ON ([cmsContentType].[nodeId] = [cmsDocumentType].[contentTypeNodeId])");
|
||||
sql.InnerJoin("umbracoNode ON ([cmsContentType].[nodeId] = [umbracoNode].[id])");
|
||||
sql.Where("[umbracoNode].[nodeObjectType] = @NodeObjectType", new { NodeObjectType = NodeObjectTypeId });
|
||||
return sql;
|
||||
}
|
||||
|
||||
protected override Sql GetBaseWhereClause(object id)
|
||||
{
|
||||
var sql = new Sql();
|
||||
sql.Where("[umbracoNode].[id] = @Id", new { Id = id });
|
||||
return sql;
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> GetDeleteClauses()
|
||||
{
|
||||
var list = new List<string>
|
||||
{
|
||||
string.Format("DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id"),
|
||||
string.Format("DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id"),
|
||||
string.Format("DELETE FROM cmsTagRelationship WHERE nodeId = @Id"),
|
||||
string.Format("DELETE FROM cmsContentTypeAllowedContentType WHERE Id = @Id"),
|
||||
string.Format("DELETE FROM cmsPropertyType WHERE contentTypeId = @Id"),
|
||||
string.Format("DELETE FROM cmsTab WHERE contenttypeNodeId = @Id"),
|
||||
string.Format("DELETE FROM cmsDocumentType WHERE contentTypeNodeId = @Id"),
|
||||
string.Format("DELETE FROM cmsContentType WHERE NodeId = @Id"),
|
||||
string.Format("DELETE FROM umbracoNode WHERE id = @Id")
|
||||
};
|
||||
return list;
|
||||
}
|
||||
|
||||
protected override Guid NodeObjectTypeId
|
||||
{
|
||||
get { return new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unit of Work Implementation
|
||||
|
||||
protected override void PersistNewItem(IContentType entity)
|
||||
{
|
||||
((ContentType)entity).AddingEntity();
|
||||
|
||||
var factory = new ContentTypeFactory(NodeObjectTypeId);
|
||||
var dto = factory.BuildDto(entity);
|
||||
|
||||
//Logic for setting Path, Level and SortOrder
|
||||
var parent = Database.First<NodeDto>("WHERE id = @ParentId", new { ParentId = entity.ParentId });
|
||||
int level = parent.Level + 1;
|
||||
int sortOrder =
|
||||
Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType",
|
||||
new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId });
|
||||
|
||||
//Create the (base) node data - umbracoNode
|
||||
var nodeDto = dto.ContentTypeDto.NodeDto;
|
||||
nodeDto.Path = parent.Path;
|
||||
nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture));
|
||||
nodeDto.SortOrder = sortOrder;
|
||||
var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto);
|
||||
|
||||
//Update with new correct path
|
||||
nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId);
|
||||
Database.Update(nodeDto);
|
||||
|
||||
//Update entity with correct values
|
||||
entity.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set
|
||||
entity.Path = nodeDto.Path;
|
||||
entity.SortOrder = sortOrder;
|
||||
entity.Level = level;
|
||||
|
||||
//Insert new ContentType entry
|
||||
var contentTypeDto = dto.ContentTypeDto;
|
||||
Database.Insert(contentTypeDto);
|
||||
|
||||
//TODO Insert new DocumentType entries - NOTE only seems relevant as long as Templates resides in the DB
|
||||
//TODO Insert allowed Templates and DocumentTypes
|
||||
//TODO Insert ContentType composition in new table
|
||||
|
||||
var propertyFactory = new PropertyGroupFactory(nodeDto.NodeId);
|
||||
|
||||
//Insert Tabs
|
||||
foreach (var propertyGroup in entity.PropertyGroups)
|
||||
{
|
||||
var tabDto = propertyFactory.BuildTabDto(propertyGroup);
|
||||
var primaryKey = Convert.ToInt32(Database.Insert(tabDto));
|
||||
propertyGroup.Id = primaryKey;//Set Id on PropertyGroup
|
||||
}
|
||||
|
||||
//Insert PropertyTypes
|
||||
foreach (var propertyGroup in entity.PropertyGroups)
|
||||
{
|
||||
foreach (var propertyType in propertyGroup.PropertyTypes)
|
||||
{
|
||||
var propertyTypeDto = propertyFactory.BuildPropertyTypeDto(propertyGroup.Id, propertyType);
|
||||
var primaryKey = Convert.ToInt32(Database.Insert(propertyTypeDto));
|
||||
propertyType.Id = primaryKey;//Set Id on PropertyType
|
||||
}
|
||||
}
|
||||
|
||||
((ContentType)entity).ResetDirtyProperties();
|
||||
}
|
||||
|
||||
protected override void PersistUpdatedItem(IContentType entity)
|
||||
{
|
||||
//Updates Modified date
|
||||
((ContentType)entity).UpdatingEntity();
|
||||
|
||||
var propertyFactory = new PropertyGroupFactory(entity.Id);
|
||||
var factory = new ContentTypeFactory(NodeObjectTypeId);
|
||||
var dto = factory.BuildDto(entity);
|
||||
var nodeDto = dto.ContentTypeDto.NodeDto;
|
||||
var o = Database.Update(nodeDto);
|
||||
|
||||
//Look up ContentType entry to get PrimaryKey for updating the DTO
|
||||
var dtoPk = Database.First<ContentTypeDto>("WHERE nodeId = @Id", new { Id = entity.Id });
|
||||
var contentTypeDto = dto.ContentTypeDto;
|
||||
contentTypeDto.PrimaryKey = dtoPk.PrimaryKey;
|
||||
Database.Update(contentTypeDto);
|
||||
|
||||
//Look up DocumentType entries for updating - this could possibly be a "remove all, insert all"-approach
|
||||
|
||||
//Check Dirty properties for Tabs/Groups and PropertyTypes - insert and delete accordingly
|
||||
if (((ICanBeDirty)entity).IsPropertyDirty("PropertyGroups") || entity.PropertyGroups.Any(x => x.IsDirty()))
|
||||
{
|
||||
//Delete PropertyTypes by excepting entries from db with entries from collections
|
||||
var dbPropertyTypes = Database.Fetch<PropertyTypeDto>("WHERE contentTypeId = @Id", new { Id = entity.Id }).Select(x => x.Alias);
|
||||
var entityPropertyTypes = entity.PropertyTypes.Select(x => x.Alias);
|
||||
var aliases = dbPropertyTypes.Except(entityPropertyTypes);
|
||||
foreach (var alias in aliases)
|
||||
{
|
||||
Database.Delete<PropertyTypeDto>("WHERE contentTypeId = @Id AND Alias = @Alias", new { Id = entity.Id, Alias = alias });
|
||||
}
|
||||
//Delete Tabs/Groups by excepting entries from db with entries from collections
|
||||
var dbPropertyGroups = Database.Fetch<TabDto>("WHERE contenttypeNodeId = @Id", new { Id = entity.Id }).Select(x => x.Text);
|
||||
var entityPropertyGroups = entity.PropertyGroups.Select(x => x.Name);
|
||||
var tabs = dbPropertyGroups.Except(entityPropertyGroups);
|
||||
foreach (var tabName in tabs)
|
||||
{
|
||||
Database.Delete<TabDto>("WHERE contenttypeNodeId = @Id AND text = @Name", new { Id = entity.Id, Name = tabName });
|
||||
}
|
||||
|
||||
//Run through all groups and types to insert or update entries
|
||||
foreach (var propertyGroup in entity.PropertyGroups)
|
||||
{
|
||||
var tabDto = propertyFactory.BuildTabDto(propertyGroup);
|
||||
int groupPrimaryKey = propertyGroup.HasIdentity
|
||||
? Database.Update(tabDto)
|
||||
: Convert.ToInt32(Database.Insert(tabDto));
|
||||
if (!propertyGroup.HasIdentity)
|
||||
propertyGroup.Id = groupPrimaryKey;//Set Id on new PropertyGroup
|
||||
|
||||
//This should indicate that neither group nor property types has been touched, but this implies a deeper 'Dirty'-lookup
|
||||
//if(!propertyGroup.IsDirty()) continue;
|
||||
|
||||
foreach (var propertyType in propertyGroup.PropertyTypes)
|
||||
{
|
||||
var propertyTypeDto = propertyFactory.BuildPropertyTypeDto(propertyGroup.Id, propertyType);
|
||||
int typePrimaryKey = propertyType.HasIdentity
|
||||
? Database.Update(propertyTypeDto)
|
||||
: Convert.ToInt32(Database.Insert(propertyTypeDto));
|
||||
if (!propertyType.HasIdentity)
|
||||
propertyType.Id = typePrimaryKey;//Set Id on new PropertyType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
((ContentType)entity).ResetDirtyProperties();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -99,15 +99,18 @@
|
||||
<Compile Include="Persistence\Caching\RuntimeCacheProvider.cs" />
|
||||
<Compile Include="Persistence\DatabaseFactory.cs" />
|
||||
<Compile Include="Persistence\Factories\ContentFactory.cs" />
|
||||
<Compile Include="Persistence\Factories\ContentTypeFactory.cs" />
|
||||
<Compile Include="Persistence\Factories\DataTypeDefinitionFactory.cs" />
|
||||
<Compile Include="Persistence\Factories\IEntityFactory.cs" />
|
||||
<Compile Include="Persistence\Factories\PropertyFactory.cs" />
|
||||
<Compile Include="Persistence\Factories\PropertyGroupFactory.cs" />
|
||||
<Compile Include="Persistence\Mappers\ModelDtoMapper.cs" />
|
||||
<Compile Include="Persistence\Querying\ExpressionHelper.cs" />
|
||||
<Compile Include="Persistence\Querying\IQuery.cs" />
|
||||
<Compile Include="Persistence\Querying\Query.cs" />
|
||||
<Compile Include="Persistence\Querying\SqlTranslator.cs" />
|
||||
<Compile Include="Persistence\Repositories\ContentRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\ContentTypeRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\DataTypeDefinitionRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\IContentRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\IContentTypeRepository.cs" />
|
||||
|
||||
Reference in New Issue
Block a user