new document conventions builder

This commit is contained in:
2021-03-18 15:54:29 +01:00
parent 460fc3e640
commit df5bbe89ec
5 changed files with 168 additions and 115 deletions
+6 -2
View File
@@ -28,11 +28,14 @@ namespace zero.Core.Api
protected IPasswordHasher<BackofficeUser> PasswordHasher { get; private set; }
protected IZeroDocumentConventionsBuilder ConventionsBuilder { get; private set; }
public SetupApi(IZeroOptions options, IPasswordHasher<BackofficeUser> passwordHasher)
public SetupApi(IZeroOptions options, IPasswordHasher<BackofficeUser> passwordHasher, IZeroDocumentConventionsBuilder conventionsBuilder)
{
Options = options;
PasswordHasher = passwordHasher;
ConventionsBuilder = conventionsBuilder;
}
@@ -59,7 +62,8 @@ namespace zero.Core.Api
Database = model.Database.Name
};
raven.Setup(Options).Initialize();
ConventionsBuilder.Run(raven.Conventions);
raven.Initialize();
// create application
Application app = Prepare(new Application()
@@ -0,0 +1,156 @@
using Raven.Client.Documents.Conventions;
using Raven.Client.Json.Serialization;
using Raven.Client.Json.Serialization.NewtonsoftJson;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using zero.Core.Attributes;
using zero.Core.Entities;
using zero.Core.Extensions;
using zero.Core.Options;
using zero.Core.Utils;
namespace zero.Core.Database
{
public class ZeroDocumentConventionsBuilder : IZeroDocumentConventionsBuilder
{
protected HashSet<Type> PolymorphTypes { get; private set; } = new();
protected Type AcceptsZeroConventionsType { get; set; } = typeof(IZeroDbConventions);
protected char IdentityPartsSeparator { get; set; } = '.';
protected IZeroOptions Options { get; private set; }
protected static Dictionary<Type, string> CachedTypeCollectionNameMap = new();
public ZeroDocumentConventionsBuilder(IZeroOptions options)
{
Options = options;
}
/// <inheritdoc />
public void Run(DocumentConventions conventions)
{
conventions.MaxNumberOfRequestsPerSession = 100;
conventions.IdentityPartsSeparator = IdentityPartsSeparator;
conventions.TransformTypeCollectionNameToDocumentIdPrefix = TransformTypeCollectionNameToDocumentIdPrefix;
conventions.FindCollectionName = FindCollectionName;
conventions.RegisterAsyncIdConvention<IZeroEntity>((_, entity) => GetDocumentId(conventions, entity));
ConfigureJsonSerializer(conventions.Serialization);
}
/// <summary>
/// Get a document ID from an entity
/// </summary>
protected virtual Task<string> GetDocumentId<T>(DocumentConventions conventions, T entity)
{
string collection = conventions.GetCollectionName(entity);
StringBuilder documentId = new();
documentId.Append(conventions.TransformTypeCollectionNameToDocumentIdPrefix(collection));
documentId.Append(IdentityPartsSeparator);
documentId.Append(IdGenerator.Create());
return Task.FromResult(documentId.ToString());
}
/// <summary>
/// Finds the collection name for a certain type based on internal rules
/// </summary>
protected virtual string FindCollectionName(Type type)
{
string collection = null;
Func<string, string> cache = name =>
{
CachedTypeCollectionNameMap.Add(type, name);
return name;
};
if (CachedTypeCollectionNameMap.TryGetValue(type, out collection))
{
return collection;
}
// do not alter non-internal entities
if (!AcceptsZeroConventionsType.IsAssignableFrom(type))
{
return cache(DocumentConventions.DefaultGetCollectionName(type));
}
// use name from attribute if available
CollectionAttribute collectionAttribute = type.GetCustomAttribute<CollectionAttribute>(true);
if (collectionAttribute != null)
{
return cache(collectionAttribute.Name);
}
// use base interface if available
Type interfaceBaseType = type.GetInterfaces().FirstOrDefault(x => x.IsInterface && AcceptsZeroConventionsType.IsAssignableFrom(x) && x.Name != AcceptsZeroConventionsType.Name);
if (interfaceBaseType != null)
{
// use name from attribute if available
collectionAttribute = interfaceBaseType.GetCustomAttribute<CollectionAttribute>(true);
if (collectionAttribute != null)
{
return cache(collectionAttribute.Name);
}
}
// use base type for polymorphism
Type polymorphBaseType = PolymorphTypes.FirstOrDefault(x => type.IsSubclassOf(x));
if (polymorphBaseType != null)
{
return cache(DocumentConventions.DefaultGetCollectionName(polymorphBaseType));
}
return cache(DocumentConventions.DefaultGetCollectionName(type));
}
/// <summary>
/// Configures the JSON serializer + deserializer for document retrieval from RavenDB
/// </summary>
protected virtual void ConfigureJsonSerializer(ISerializationConventions conventions)
{
NewtonsoftJsonSerializationConventions typedConventions = conventions as NewtonsoftJsonSerializationConventions;
typedConventions.CustomizeJsonDeserializer = x => x.Converters.Add(new RefJsonConverter());
typedConventions.CustomizeJsonSerializer = x => x.Converters.Add(new RefJsonConverter());
}
/// <summary>
/// Translates the types collection name to the document id prefix
/// </summary>
protected virtual string TransformTypeCollectionNameToDocumentIdPrefix(string name)
{
if (!Options.Raven.CollectionPrefix.IsNullOrWhiteSpace())
{
name = Options.Raven.CollectionPrefix.EnsureEndsWith(IdentityPartsSeparator) + name.TrimStart(Options.Raven.CollectionPrefix);
}
return name.ToCamelCaseId();
}
}
public interface IZeroDocumentConventionsBuilder
{
/// <summary>
/// Applies internal rules to the RavenDB document conventions
/// </summary>
void Run(DocumentConventions conventions);
}
}
@@ -1,109 +0,0 @@
using Raven.Client.Documents;
using Raven.Client.Documents.Conventions;
using Raven.Client.Documents.Operations.CompareExchange;
using Raven.Client.Json.Serialization.NewtonsoftJson;
using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using zero.Core.Attributes;
using zero.Core.Entities;
using zero.Core.Options;
using zero.Core.Utils;
namespace zero.Core.Extensions
{
public static class RavenDocumentStoreExtensions
{
const char DOT = '.';
/// <summary>
/// Setup conventions for the document store
/// </summary>
public static IDocumentStore Setup(this IDocumentStore store, IZeroOptions options)
{
Type[] polymorphTypes = new Type[2] { typeof(SpaceContent), typeof(Page) };
Type dbConventionType = typeof(IZeroDbConventions);
store.Conventions.IdentityPartsSeparator = DOT;
store.Conventions.RegisterAsyncIdConvention<IZeroEntity>((_, entity) =>
{
string guid = IdGenerator.Create((entity is IBackofficeUser or IBackofficeUserRole) ? 8 : -1);
string collection = store.Conventions.GetCollectionName(entity);
string tag = store.Conventions.TransformTypeCollectionNameToDocumentIdPrefix(collection);
return Task.FromResult(tag + store.Conventions.IdentityPartsSeparator + guid);
});
store.Conventions.RegisterAsyncIdConvention<IPage>((_, entity) =>
{
return store.Conventions.AsyncDocumentIdGenerator(_, entity);
});
(store.Conventions.Serialization as NewtonsoftJsonSerializationConventions).CustomizeJsonDeserializer = x =>
{
x.Converters.Add(new RefJsonConverter());
};
(store.Conventions.Serialization as NewtonsoftJsonSerializationConventions).CustomizeJsonSerializer = x =>
{
x.Converters.Add(new RefJsonConverter());
};
store.Conventions.FindCollectionName = type =>
{
Type finalType = type;
// do not alter non-internal entities
if (!dbConventionType.IsAssignableFrom(type))
{
return DocumentConventions.DefaultGetCollectionName(type);
}
// use name from attribute if available
CollectionAttribute collection = type.GetCustomAttribute<CollectionAttribute>(true);
if (collection != null)
{
return collection.Name;
}
// use base interface if available
Type interfaceBaseType = type.GetInterfaces().FirstOrDefault(x => x.IsInterface && dbConventionType.IsAssignableFrom(x) && x.Name != dbConventionType.Name);
if (interfaceBaseType != null)
{
// use name from attribute if available
collection = interfaceBaseType.GetCustomAttribute<CollectionAttribute>(true);
if (collection != null)
{
return options.Raven.CollectionPrefix + collection.Name;
}
}
// use base type for polymorphism
Type polymorphBaseType = polymorphTypes.FirstOrDefault(x => type.IsSubclassOf(x));
if (polymorphBaseType != null)
{
finalType = polymorphBaseType;
}
return options.Raven.CollectionPrefix + DocumentConventions.DefaultGetCollectionName(finalType);
};
store.Conventions.TransformTypeCollectionNameToDocumentIdPrefix = name =>
{
if (!options.Raven.CollectionPrefix.IsNullOrWhiteSpace())
{
name = options.Raven.CollectionPrefix.EnsureEndsWith(store.Conventions.IdentityPartsSeparator) + name.TrimStart(options.Raven.CollectionPrefix);
}
return name.ToCamelCaseId();
};
return store;
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
<div class="media-item-preview" :class="{'media-pattern': value.image }">
<span class="media-item-check"><ui-icon symbol="fth-check" :size="14" /></span>
<img class="media-item-image" v-if="value.image" :src="value.image" />
<span class="media-item-icon" v-if="!value.image"><ui-icon :symbol="shared ? 'fth-globe' : (value.isFolder ? 'fth-folder' : 'fth-file')" :size="28" /></span>
<span class="media-item-icon" v-if="!value.image"><ui-icon :symbol="shared ? 'fth-globe' : (value.isFolder ? 'fth-folder' : 'fth-file')" :size="36" :stroke-width="1.5" /></span>
</div>
<p class="media-item-text" v-if="!shared">
<span :title="value.name">{{value.name}}</span>
+5 -3
View File
@@ -143,19 +143,21 @@ namespace zero.Web
/// </summary>
void ConfigureDatabase()
{
// add raven
Services.AddSingleton<IZeroDocumentConventionsBuilder, ZeroDocumentConventionsBuilder>();
Services.AddSingleton<IZeroStore, ZeroStore>(context =>
{
IZeroOptions options = context.GetService<IZeroOptions>();
IZeroDocumentConventionsBuilder conventionsBuilder = context.GetService<IZeroDocumentConventionsBuilder>();
IDocumentStore store = new ZeroStore(options)
{
Urls = new string[1] { options.Raven.Url }
};
store.Conventions.MaxNumberOfRequestsPerSession = 100;
conventionsBuilder.Run(store.Conventions);
IDocumentStore raven = store.Setup(options).Initialize();
IDocumentStore raven = store.Initialize();
// create all indexes
var assemblies = AssemblyDiscovery.Current.GetAssemblies().ToList();