Moves most service interfaces, ensure IOHelper is registered

This commit is contained in:
Shannon
2019-11-12 16:31:08 +11:00
parent 40f1a1f6ea
commit 2b2db8b8ef
18 changed files with 184 additions and 168 deletions

View File

@@ -3,7 +3,7 @@ using System.Linq;
namespace Umbraco.Core.Services.Changes
{
internal class TreeChange<TItem>
public class TreeChange<TItem>
{
public TreeChange(TItem changedItem, TreeChangeTypes changeTypes)
{

View File

@@ -4,7 +4,7 @@ namespace Umbraco.Core.Services.Changes
{
public static class TreeChangeExtensions
{
internal static TreeChange<TItem>.EventArgs ToEventArgs<TItem>(this IEnumerable<TreeChange<TItem>> changes)
public static TreeChange<TItem>.EventArgs ToEventArgs<TItem>(this IEnumerable<TreeChange<TItem>> changes)
{
return new TreeChange<TItem>.EventArgs(changes);
}

View File

@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Services

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic;
using Microsoft.AspNet.Identity;
using Umbraco.Core.Models.Identity;
namespace Umbraco.Core.Services

View File

@@ -104,10 +104,10 @@ namespace Umbraco.Core.Services
ILanguage GetLanguageById(int id);
/// <summary>
/// Gets a <see cref="Language"/> by its iso code
/// Gets a <see cref="ILanguage"/> by its iso code
/// </summary>
/// <param name="isoCode">Iso Code of the language (ie. en-US)</param>
/// <returns><see cref="Language"/></returns>
/// <returns><see cref="ILanguage"/></returns>
ILanguage GetLanguageByIsoCode(string isoCode);
/// <summary>

View File

@@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.Text;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Services
{
/// <summary>
/// Defines part of the UserService/MemberService, which is specific to methods used by the membership provider.
/// The generic type is restricted to <see cref="IMembershipUser"/>. The implementation of this interface uses
/// either <see cref="IMember"/> for the MemberService or <see cref="IUser"/> for the UserService.
/// </summary>
/// <remarks>
/// Idea is to have this as an isolated interface so that it can be easily 'replaced' in the membership provider implementation.
/// </remarks>
public interface IMembershipMemberService<T> : IService
where T : class, IMembershipUser
{
/// <summary>
/// Gets the total number of Members or Users based on the count type
/// </summary>
/// <remarks>
/// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members
/// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science
/// but that is how MS have made theirs so we'll follow that principal.
/// </remarks>
/// <param name="countType"><see cref="MemberCountType"/> to count by</param>
/// <returns><see cref="System.int"/> with number of Members or Users for passed in type</returns>
int GetCount(MemberCountType countType);
/// <summary>
/// Checks if a Member with the username exists
/// </summary>
/// <param name="username">Username to check</param>
/// <returns><c>True</c> if the Member exists otherwise <c>False</c></returns>
bool Exists(string username);
/// <summary>
/// Creates and persists a new <see cref="IMembershipUser"/>
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="username">Username of the <see cref="IMembershipUser"/> to create</param>
/// <param name="email">Email of the <see cref="IMembershipUser"/> to create</param>
/// <param name="passwordValue">This value should be the encoded/encrypted/hashed value for the password that will be stored in the database</param>
/// <param name="memberTypeAlias">Alias of the Type</param>
/// <returns><see cref="IMembershipUser"/></returns>
T CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias);
/// <summary>
/// Creates and persists a new <see cref="IMembershipUser"/>
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="username">Username of the <see cref="IMembershipUser"/> to create</param>
/// <param name="email">Email of the <see cref="IMembershipUser"/> to create</param>
/// <param name="passwordValue">This value should be the encoded/encrypted/hashed value for the password that will be stored in the database</param>
/// <param name="memberTypeAlias">Alias of the Type</param>
/// <param name="isApproved">IsApproved of the <see cref="IMembershipUser"/> to create</param>
/// <returns><see cref="IMembershipUser"/></returns>
T CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved);
/// <summary>
/// Gets an <see cref="IMembershipUser"/> by its provider key
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="id">Id to use for retrieval</param>
/// <returns><see cref="IMembershipUser"/></returns>
T GetByProviderKey(object id);
/// <summary>
/// Get an <see cref="IMembershipUser"/> by email
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="email">Email to use for retrieval</param>
/// <returns><see cref="IMembershipUser"/></returns>
T GetByEmail(string email);
/// <summary>
/// Get an <see cref="IMembershipUser"/> by username
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="username">Username to use for retrieval</param>
/// <returns><see cref="IMembershipUser"/></returns>
T GetByUsername(string username);
/// <summary>
/// Deletes an <see cref="IMembershipUser"/>
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="membershipUser"><see cref="IMember"/> or <see cref="IUser"/> to Delete</param>
void Delete(T membershipUser);
/// <summary>
/// Saves an <see cref="IMembershipUser"/>
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="entity"><see cref="IMember"/> or <see cref="IUser"/> to Save</param>
/// <param name="raiseEvents">Optional parameter to raise events.
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
void Save(T entity, bool raiseEvents = true);
/// <summary>
/// Saves a list of <see cref="IMembershipUser"/> objects
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="entities"><see cref="IEnumerable{T}"/> to save</param>
/// <param name="raiseEvents">Optional parameter to raise events.
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
void Save(IEnumerable<T> entities, bool raiseEvents = true);
/// <summary>
/// Gets the default MemberType alias
/// </summary>
/// <remarks>By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll
/// return the first type that is not an admin, otherwise if there's only one we will return that one.</remarks>
/// <returns>Alias of the default MemberType</returns>
string GetDefaultMemberType();
/// <summary>
/// Finds a list of <see cref="IMembershipUser"/> objects by a partial email string
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="emailStringToMatch">Partial email string to match</param>
/// <param name="pageIndex">Current page index</param>
/// <param name="pageSize">Size of the page</param>
/// <param name="totalRecords">Total number of records found (out)</param>
/// <param name="matchType">The type of match to make as <see cref="StringPropertyMatchType"/>. Default is <see cref="StringPropertyMatchType.StartsWith"/></param>
/// <returns><see cref="IEnumerable{T}"/></returns>
IEnumerable<T> FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
/// <summary>
/// Finds a list of <see cref="IMembershipUser"/> objects by a partial username
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="login">Partial username to match</param>
/// <param name="pageIndex">Current page index</param>
/// <param name="pageSize">Size of the page</param>
/// <param name="totalRecords">Total number of records found (out)</param>
/// <param name="matchType">The type of match to make as <see cref="StringPropertyMatchType"/>. Default is <see cref="StringPropertyMatchType.StartsWith"/></param>
/// <returns><see cref="IEnumerable{T}"/></returns>
IEnumerable<T> FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
/// <summary>
/// Gets a list of paged <see cref="IMembershipUser"/> objects
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="pageIndex">Current page index</param>
/// <param name="pageSize">Size of the page</param>
/// <param name="totalRecords">Total number of records found (out)</param>
/// <returns><see cref="IEnumerable{T}"/></returns>
IEnumerable<T> GetAll(long pageIndex, int pageSize, out long totalRecords);
}
}

View File

@@ -1,5 +1,6 @@
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
@@ -19,7 +20,8 @@ namespace Umbraco.Core
AppCaches appCaches,
IUmbracoDatabaseFactory databaseFactory,
TypeLoader typeLoader,
IRuntimeState state)
IRuntimeState state,
IIOHelper ioHelper)
{
composition.RegisterUnique(logger);
composition.RegisterUnique(profiler);
@@ -30,6 +32,7 @@ namespace Umbraco.Core
composition.RegisterUnique(factory => factory.GetInstance<IUmbracoDatabaseFactory>().SqlContext);
composition.RegisterUnique(typeLoader);
composition.RegisterUnique(state);
composition.RegisterUnique(ioHelper);
}
}
}

View File

@@ -42,6 +42,11 @@ namespace Umbraco.Core.Runtime
/// </summary>
protected IProfilingLogger ProfilingLogger { get; private set; }
/// <summary>
/// Gets the <see cref="IIOHelper"/>
/// </summary>
protected IIOHelper IOHelper { get; private set; }
/// <inheritdoc />
public IRuntimeState State => _state;
@@ -56,6 +61,10 @@ namespace Umbraco.Core.Runtime
var profiler = Profiler = GetProfiler();
var profilingLogger = ProfilingLogger = new ProfilingLogger(logger, profiler);
IOHelper = GetIOHelper();
if (IOHelper == null)
throw new InvalidOperationException($"The object returned from {nameof(GetIOHelper)} cannot be null");
// the boot loader boots using a container scope, so anything that is PerScope will
// be disposed after the boot loader has booted, and anything else will remain.
// note that this REQUIRES that perWebRequestScope has NOT been enabled yet, else
@@ -130,7 +139,7 @@ namespace Umbraco.Core.Runtime
// create the composition
composition = new Composition(register, typeLoader, ProfilingLogger, _state, configs);
composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, mainDom, appCaches, databaseFactory, typeLoader, _state);
composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, mainDom, appCaches, databaseFactory, typeLoader, _state, IOHelper);
// run handlers
RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory);
@@ -313,6 +322,13 @@ namespace Umbraco.Core.Runtime
protected virtual IProfiler GetProfiler()
=> new LogProfiler(Logger);
/// <summary>
/// Gets a <see cref="IIOHelper"/>
/// </summary>
/// <returns></returns>
protected virtual IIOHelper GetIOHelper()
=> new Umbraco.Core.IO.IOHelper();
/// <summary>
/// Gets the application caches.
/// </summary>

View File

@@ -3,7 +3,7 @@ using Umbraco.Core.Models;
namespace Umbraco.Core.Services.Changes
{
internal static class ContentTypeChangeExtensions
public static class ContentTypeChangeExtensions
{
public static ContentTypeChange<TItem>.EventArgs ToEventArgs<TItem>(this IEnumerable<ContentTypeChange<TItem>> changes)
where TItem : class, IContentTypeComposition

View File

@@ -23,148 +23,5 @@ namespace Umbraco.Core.Services
IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType);
}
/// <summary>
/// Defines part of the UserService/MemberService, which is specific to methods used by the membership provider.
/// The generic type is restricted to <see cref="IMembershipUser"/>. The implementation of this interface uses
/// either <see cref="IMember"/> for the MemberService or <see cref="IUser"/> for the UserService.
/// </summary>
/// <remarks>
/// Idea is to have this as an isolated interface so that it can be easily 'replaced' in the membership provider implementation.
/// </remarks>
public interface IMembershipMemberService<T> : IService
where T : class, IMembershipUser
{
/// <summary>
/// Gets the total number of Members or Users based on the count type
/// </summary>
/// <remarks>
/// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members
/// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science
/// but that is how MS have made theirs so we'll follow that principal.
/// </remarks>
/// <param name="countType"><see cref="MemberCountType"/> to count by</param>
/// <returns><see cref="System.int"/> with number of Members or Users for passed in type</returns>
int GetCount(MemberCountType countType);
/// <summary>
/// Checks if a Member with the username exists
/// </summary>
/// <param name="username">Username to check</param>
/// <returns><c>True</c> if the Member exists otherwise <c>False</c></returns>
bool Exists(string username);
/// <summary>
/// Creates and persists a new <see cref="IMembershipUser"/>
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="username">Username of the <see cref="IMembershipUser"/> to create</param>
/// <param name="email">Email of the <see cref="IMembershipUser"/> to create</param>
/// <param name="passwordValue">This value should be the encoded/encrypted/hashed value for the password that will be stored in the database</param>
/// <param name="memberTypeAlias">Alias of the Type</param>
/// <returns><see cref="IMembershipUser"/></returns>
T CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias);
/// <summary>
/// Creates and persists a new <see cref="IMembershipUser"/>
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="username">Username of the <see cref="IMembershipUser"/> to create</param>
/// <param name="email">Email of the <see cref="IMembershipUser"/> to create</param>
/// <param name="passwordValue">This value should be the encoded/encrypted/hashed value for the password that will be stored in the database</param>
/// <param name="memberTypeAlias">Alias of the Type</param>
/// <param name="isApproved">IsApproved of the <see cref="IMembershipUser"/> to create</param>
/// <returns><see cref="IMembershipUser"/></returns>
T CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved);
/// <summary>
/// Gets an <see cref="IMembershipUser"/> by its provider key
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="id">Id to use for retrieval</param>
/// <returns><see cref="IMembershipUser"/></returns>
T GetByProviderKey(object id);
/// <summary>
/// Get an <see cref="IMembershipUser"/> by email
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="email">Email to use for retrieval</param>
/// <returns><see cref="IMembershipUser"/></returns>
T GetByEmail(string email);
/// <summary>
/// Get an <see cref="IMembershipUser"/> by username
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="username">Username to use for retrieval</param>
/// <returns><see cref="IMembershipUser"/></returns>
T GetByUsername(string username);
/// <summary>
/// Deletes an <see cref="IMembershipUser"/>
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="membershipUser"><see cref="IMember"/> or <see cref="IUser"/> to Delete</param>
void Delete(T membershipUser);
/// <summary>
/// Saves an <see cref="IMembershipUser"/>
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="entity"><see cref="IMember"/> or <see cref="IUser"/> to Save</param>
/// <param name="raiseEvents">Optional parameter to raise events.
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
void Save(T entity, bool raiseEvents = true);
/// <summary>
/// Saves a list of <see cref="IMembershipUser"/> objects
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="entities"><see cref="IEnumerable{T}"/> to save</param>
/// <param name="raiseEvents">Optional parameter to raise events.
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
void Save(IEnumerable<T> entities, bool raiseEvents = true);
/// <summary>
/// Gets the default MemberType alias
/// </summary>
/// <remarks>By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll
/// return the first type that is not an admin, otherwise if there's only one we will return that one.</remarks>
/// <returns>Alias of the default MemberType</returns>
string GetDefaultMemberType();
/// <summary>
/// Finds a list of <see cref="IMembershipUser"/> objects by a partial email string
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="emailStringToMatch">Partial email string to match</param>
/// <param name="pageIndex">Current page index</param>
/// <param name="pageSize">Size of the page</param>
/// <param name="totalRecords">Total number of records found (out)</param>
/// <param name="matchType">The type of match to make as <see cref="StringPropertyMatchType"/>. Default is <see cref="StringPropertyMatchType.StartsWith"/></param>
/// <returns><see cref="IEnumerable{T}"/></returns>
IEnumerable<T> FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
/// <summary>
/// Finds a list of <see cref="IMembershipUser"/> objects by a partial username
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="login">Partial username to match</param>
/// <param name="pageIndex">Current page index</param>
/// <param name="pageSize">Size of the page</param>
/// <param name="totalRecords">Total number of records found (out)</param>
/// <param name="matchType">The type of match to make as <see cref="StringPropertyMatchType"/>. Default is <see cref="StringPropertyMatchType.StartsWith"/></param>
/// <returns><see cref="IEnumerable{T}"/></returns>
IEnumerable<T> FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
/// <summary>
/// Gets a list of paged <see cref="IMembershipUser"/> objects
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="pageIndex">Current page index</param>
/// <param name="pageSize">Size of the page</param>
/// <param name="totalRecords">Total number of records found (out)</param>
/// <returns><see cref="IEnumerable{T}"/></returns>
IEnumerable<T> GetAll(long pageIndex, int pageSize, out long totalRecords);
}
}

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Core.Services
/// <param name="operatingUser"></param>
/// <param name="action"></param>
/// <param name="actionName"></param>
/// <param name="http"></param>
/// <param name="siteUri"></param>
/// <param name="createSubject"></param>
/// <param name="createBody"></param>
void SendNotifications(IUser operatingUser, IEnumerable<IContent> entities, string action, string actionName, Uri siteUri,

View File

@@ -1252,7 +1252,7 @@ namespace Umbraco.Core.Services.Implement
/// <summary>
/// Occurs after change.
/// </summary>
internal static event TypedEventHandler<IMediaService, TreeChange<IMedia>.EventArgs> TreeChanged;
public static event TypedEventHandler<IMediaService, TreeChange<IMedia>.EventArgs> TreeChanged;
#endregion

View File

@@ -294,13 +294,10 @@
<Compile Include="Serialization\JsonNetSerializer.cs" />
<Compile Include="Services\Changes\ContentTypeChange.cs" />
<Compile Include="Services\Changes\ContentTypeChangeExtensions.cs" />
<Compile Include="Services\Changes\TreeChange.cs" />
<Compile Include="Services\Changes\TreeChangeExtensions.cs" />
<Compile Include="Services\IDataTypeService.cs" />
<Compile Include="Services\IEntityService.cs" />
<Compile Include="Services\IFileService.cs" />
<Compile Include="Services\ILocalizationService.cs" />
<Compile Include="Services\IMacroService.cs" />
<Compile Include="Services\IMembershipMemberService.cs" />
<Compile Include="Services\INotificationService.cs" />
<Compile Include="Services\IPackagingService.cs" />
<Compile Include="Services\LocalizedTextServiceExtensions.cs" />
<Compile Include="Services\DateTypeServiceExtensions.cs" />
@@ -894,7 +891,6 @@
<Compile Include="Persistence\Querying\QueryExtensions.cs" />
<Compile Include="Persistence\Querying\SqlExpressionExtensions.cs" />
<Compile Include="Persistence\Querying\SqlTranslator.cs" />
<Compile Include="Persistence\Querying\StringPropertyMatchType.cs" />
<Compile Include="Persistence\Querying\TextColumnType.cs" />
<Compile Include="Persistence\Querying\ValuePropertyMatchType.cs" />
<Compile Include="Persistence\RecordPersistenceType.cs" />
@@ -1067,22 +1063,16 @@
<Compile Include="Services\Implement\EntityXmlSerializer.cs" />
<Compile Include="Services\Implement\ExternalLoginService.cs" />
<Compile Include="Services\Implement\FileService.cs" />
<Compile Include="Services\IAuditService.cs" />
<Compile Include="Services\IContentService.cs" />
<Compile Include="Services\IContentServiceBase.cs" />
<Compile Include="Services\IContentTypeService.cs" />
<Compile Include="Services\IContentTypeServiceBase.cs" />
<Compile Include="Services\IdkMap.cs" />
<Compile Include="Services\IExternalLoginService.cs" />
<Compile Include="Services\IMediaService.cs" />
<Compile Include="Services\IMediaTypeService.cs" />
<Compile Include="Services\IMemberService.cs" />
<Compile Include="Services\IMembershipMemberService.cs" />
<Compile Include="Services\IMembershipRoleService.cs" />
<Compile Include="Services\IMembershipUserService.cs" />
<Compile Include="Services\IMemberTypeService.cs" />
<Compile Include="Services\Implement\KeyValueService.cs" />
<Compile Include="Services\INotificationService.cs" />
<Compile Include="Services\IPublicAccessService.cs" />
<Compile Include="Services\IUserService.cs" />
<Compile Include="Services\Implement\LocalizationService.cs" />