From 9d87c47f1c90c209f9e9e7afdd959c085b61137e Mon Sep 17 00:00:00 2001 From: JFronny <33260128+jfronny@users.noreply.github.com> Date: Thu, 19 Nov 2020 19:26:36 +0100 Subject: [PATCH] AspNet extensions --- .gitlab-ci.yml | 5 + .idea/.idea.CC-Functions/.idea/modules.xml | 2 +- AspNet/AspNet.csproj | 32 ++++ AspNet/DictionaryGuidConverter.cs | 138 +++++++++++++++++ AspNet/SaveLoadDict.cs | 164 +++++++++++++++++++++ AspNet/SerialDict.cs | 73 +++++++++ CC-Functions.sln | 6 + Core/Core.csproj | 1 - 8 files changed, 419 insertions(+), 2 deletions(-) create mode 100644 AspNet/AspNet.csproj create mode 100644 AspNet/DictionaryGuidConverter.cs create mode 100644 AspNet/SaveLoadDict.cs create mode 100644 AspNet/SerialDict.cs diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 12c3e69..712a6d5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -55,6 +55,11 @@ uptool: $tmp = $(Get-Item $(Resolve-Path *.nupkg).Path).Name echo $tmp dotnet nuget push $tmp -s https://api.nuget.org/v3/index.json -k $NUGET + cd ..\AspNet + dotnet pack --version-suffix "$suffix" -c Release -o . + $tmp = $(Get-Item $(Resolve-Path *.nupkg).Path).Name + echo $tmp + dotnet nuget push $tmp -s https://api.nuget.org/v3/index.json -k $NUGET cd ..\W32 dotnet pack --version-suffix "$suffix" -c Release -o . $tmp = $(Get-Item $(Resolve-Path *.nupkg).Path).Name diff --git a/.idea/.idea.CC-Functions/.idea/modules.xml b/.idea/.idea.CC-Functions/.idea/modules.xml index 6029ade..3eaf66f 100644 --- a/.idea/.idea.CC-Functions/.idea/modules.xml +++ b/.idea/.idea.CC-Functions/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/AspNet/AspNet.csproj b/AspNet/AspNet.csproj new file mode 100644 index 0000000..f4a5453 --- /dev/null +++ b/AspNet/AspNet.csproj @@ -0,0 +1,32 @@ + + + + Library + CC_Functions.AspNet + CC_Functions.AspNet + false + CC-Functions.AspNet + CC-Functions.AspNet + JFronny + Random pieces of code for Asp.Net, including my SerialDict integrated Database + Copyright 2020 + https://gitlab.com/JFronny/CC-Functions + https://gitlab.com/JFronny/CC-Functions.git + git + 1.1.* + 1.0.0.0 + 0.0 + 1.1.$(VersionSuffix) + net5.0 + + + bin\Debug\Core.xml + + + bin\Release\Core.xml + + + + + + diff --git a/AspNet/DictionaryGuidConverter.cs b/AspNet/DictionaryGuidConverter.cs new file mode 100644 index 0000000..3dd98c3 --- /dev/null +++ b/AspNet/DictionaryGuidConverter.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace CC_Functions.AspNet +{ + /// + /// Provides serializing dictionary with Guid keys + /// + public class DictionaryGuidConverter : JsonConverterFactory + { + /// + public override bool CanConvert(Type typeToConvert) + { + if (!typeToConvert.IsGenericType) + { + return false; + } + + if (typeToConvert.GetGenericTypeDefinition() != typeof(Dictionary<,>)) + { + return false; + } + + return typeToConvert.GetGenericArguments()[0] == typeof(Guid); + } + + /// + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + Type valueType = typeToConvert.GetGenericArguments()[1]; + + JsonConverter converter = (JsonConverter)Activator.CreateInstance( + typeof(DictionaryGuidConverterInner<>).MakeGenericType(valueType), + BindingFlags.Instance | BindingFlags.Public, + null, + new object[] { options }, + null); + + return converter; + } + + private class DictionaryGuidConverterInner : JsonConverter> + { + private readonly JsonConverter _valueConverter; + private Type _valueType; + + public DictionaryGuidConverterInner(JsonSerializerOptions options) + { + // For performance, use the existing converter if available. + _valueConverter = (JsonConverter)options + .GetConverter(typeof(TValue)); + + // Cache the key and value types. + _valueType = typeof(TValue); + } + + public override Dictionary Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + Dictionary dictionary = new(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + return dictionary; + } + + // Get the key. + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + string propertyName = reader.GetString(); + + // For performance, parse with ignoreCase:false first. + if (!Guid.TryParse(propertyName, out Guid key)) + { + throw new JsonException( + $"Unable to convert \"{propertyName}\" to Guid."); + } + + // Get the value. + TValue v; + if (_valueConverter != null) + { + reader.Read(); + v = _valueConverter.Read(ref reader, _valueType, options); + } + else + { + v = JsonSerializer.Deserialize(ref reader, options); + } + + // Add to dictionary. + dictionary.Add(key, v); + } + + throw new JsonException(); + } + + public override void Write( + Utf8JsonWriter writer, + Dictionary dictionary, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + + foreach (KeyValuePair kvp in dictionary) + { + writer.WritePropertyName(kvp.Key.ToString()); + + if (_valueConverter != null) + { + _valueConverter.Write(writer, kvp.Value, options); + } + else + { + JsonSerializer.Serialize(writer, kvp.Value, options); + } + } + + writer.WriteEndObject(); + } + } + } +} \ No newline at end of file diff --git a/AspNet/SaveLoadDict.cs b/AspNet/SaveLoadDict.cs new file mode 100644 index 0000000..64c7a89 --- /dev/null +++ b/AspNet/SaveLoadDict.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace CC_Functions.AspNet +{ + /// + /// Provides synchronizing for dictionaries with saving/loading backends + /// + /// The key type + /// The param type + public abstract class SaveLoadDict : IDictionary + { + /// + public IEnumerator> GetEnumerator() + { + lock (this) return Load().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + lock (this) return GetEnumerator(); + } + + /// + public void Add(KeyValuePair item) + { + lock (this) Add(item.Key, item.Value); + } + + /// + public void Clear() + { + lock (this) Save(new Dictionary()); + } + + /// + public bool Contains(KeyValuePair item) + { + lock (this) return Load().Contains(item); + } + + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + lock (this) Load().CopyTo(array, arrayIndex); + } + + /// + public bool Remove(KeyValuePair item) + { + lock (this) + { + IDictionary dictionary = Load(); + try + { + return dictionary.Remove(item); + } + finally + { + Save(dictionary); + } + } + } + + /// + public int Count + { + get { lock (this) return Load().Count; } + } + + /// + public bool IsReadOnly => false; + + /// + public void Add(T key, U value) + { + lock (this) + { + IDictionary dictionary = Load(); + dictionary.Add(key, value); + Save(dictionary); + } + } + + /// + public bool ContainsKey(T key) + { + lock (this) return Load().ContainsKey(key); + } + + /// + public bool Remove(T key) + { + lock (this) + { + IDictionary dictionary = Load(); + try + { + return dictionary.Remove(key); + } + finally + { + Save(dictionary); + } + } + } + + /// + public bool TryGetValue(T key, out U value) + { + lock (this) return Load().TryGetValue(key, out value); + } + + /// + public U this[T key] + { + get + { + lock (this) return Load()[key]; + } + set + { + lock (this) + { + IDictionary dictionary = Load(); + dictionary[key] = value; + Save(dictionary); + } + } + } + + /// + public ICollection Keys + { + get { lock (this) return Load().Keys; } + } + + /// + public ICollection Values + { + get { lock (this) return Load().Values; } + } + + /// + /// Replace the current content based on the current state + /// + /// Function to mutate the content + public void Mutate(Func, IDictionary> f) + { + lock (this) Save(f(Load())); + } + /// + /// Save the dictionary content + /// + /// Dictionary content to save + protected abstract void Save(IDictionary v); + /// + /// Load the content to a dictionary + /// + /// The loaded content + protected abstract IDictionary Load(); + } +} diff --git a/AspNet/SerialDict.cs b/AspNet/SerialDict.cs new file mode 100644 index 0000000..54fb443 --- /dev/null +++ b/AspNet/SerialDict.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ProtoBuf; + +namespace CC_Functions.AspNet +{ + /// + /// An implementation of SaveLoadDict that uses Protobuf-net for serialization and Guid keys + /// + /// The data type + public abstract class SerialDict : SaveLoadDict + { + //Interface + /// + /// Gets the directory containing databases + /// + protected abstract string DatabasesDir { get; } + /// + /// Gets the file name for this database. Must not contain the path + /// + protected abstract string DatabaseFileName { get; } + /// + /// Called when the database is loaded. Use for preparing initial entries + /// + public event LoadedD Loaded; + /// + /// Called when the database is loaded. Use for preparing initial entries + /// + /// The current dictionary content + public delegate void LoadedD(IDictionary v); + /// + /// Loads the dictionary and replaces Guids with strings. Use for sending + /// + /// The simplified dictionary + public IDictionary GetSendable() => Load().ToDictionary(s => s.Key.ToString(), s => s.Value); + + //Internal + private string GetRel() => Path.Combine(DatabasesDir, DatabaseFileName); + + /// + protected override IDictionary Load() + { + if (!Directory.Exists(DatabasesDir)) + Directory.CreateDirectory(DatabasesDir); + IDictionary v; + if (File.Exists(GetRel())) + { + using (FileStream file = File.OpenRead(GetRel())) + v = Serializer.Deserialize>(file); + Loaded?.Invoke(v); + return v; + } + else + { + v = new Dictionary(); + Loaded?.Invoke(v); + Save(v); + return v; + } + } + + /// + protected override void Save(IDictionary dict) + { + if (!Directory.Exists(DatabasesDir)) + Directory.CreateDirectory(DatabasesDir); + using FileStream file = File.Create(GetRel()); + Serializer.Serialize(file, dict); + } + } +} \ No newline at end of file diff --git a/CC-Functions.sln b/CC-Functions.sln index 8c6947a..58e66fd 100644 --- a/CC-Functions.sln +++ b/CC-Functions.sln @@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLITest", "CLITest\CLITest. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{780EC190-E223-46DE-B6FB-1CF56ABDF34E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet", "AspNet\AspNet.csproj", "{1A9F3CD1-559B-4429-B8A6-490AF7188A2E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,6 +47,10 @@ Global {780EC190-E223-46DE-B6FB-1CF56ABDF34E}.Debug|Any CPU.Build.0 = Debug|Any CPU {780EC190-E223-46DE-B6FB-1CF56ABDF34E}.Release|Any CPU.ActiveCfg = Release|Any CPU {780EC190-E223-46DE-B6FB-1CF56ABDF34E}.Release|Any CPU.Build.0 = Release|Any CPU + {1A9F3CD1-559B-4429-B8A6-490AF7188A2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A9F3CD1-559B-4429-B8A6-490AF7188A2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A9F3CD1-559B-4429-B8A6-490AF7188A2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A9F3CD1-559B-4429-B8A6-490AF7188A2E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Core/Core.csproj b/Core/Core.csproj index 75cf4d4..70ca43c 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -4,7 +4,6 @@ Library CC_Functions.Core CC_Functions.Core - 8 false CC-Functions.Core CC-Functions.Core