diff --git a/UET/Lib/Redpoint.ThirdParty.Docker.Registry.DotNet/Helpers/QueryStringExtensions.cs b/UET/Lib/Redpoint.ThirdParty.Docker.Registry.DotNet/Helpers/QueryStringExtensions.cs
index 8a5d4ed8..b0a9aa1f 100644
--- a/UET/Lib/Redpoint.ThirdParty.Docker.Registry.DotNet/Helpers/QueryStringExtensions.cs
+++ b/UET/Lib/Redpoint.ThirdParty.Docker.Registry.DotNet/Helpers/QueryStringExtensions.cs
@@ -17,7 +17,7 @@ internal static class QueryStringExtensions
///
///
internal static void AddFromObjectWithQueryParameters(this QueryString queryString, [NotNull] T instance)
- where T: class
+ where T : class
{
if (instance == null) throw new ArgumentNullException(nameof(instance));
diff --git a/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CloudFrameworkModelAnalyzerCodeFixProvider.cs b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CloudFrameworkModelAnalyzerCodeFixProvider.cs
new file mode 100644
index 00000000..517df098
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CloudFrameworkModelAnalyzerCodeFixProvider.cs
@@ -0,0 +1,75 @@
+namespace Redpoint.CloudFramework.Analyzer
+{
+ using Microsoft.CodeAnalysis;
+ using Microsoft.CodeAnalysis.CodeActions;
+ using Microsoft.CodeAnalysis.CodeFixes;
+ using Microsoft.CodeAnalysis.CSharp;
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
+ using Microsoft.CodeAnalysis.Rename;
+ using Microsoft.CodeAnalysis.Text;
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Immutable;
+ using System.Composition;
+ using System.Linq;
+ using System.Threading;
+ using System.Threading.Tasks;
+
+#if FALSE
+
+ [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CloudFrameworkModelAnalyzerCodeFixProvider)), Shared]
+ public class CloudFrameworkModelAnalyzerCodeFixProvider : CodeFixProvider
+ {
+ public sealed override ImmutableArray FixableDiagnosticIds
+ {
+ get { return ImmutableArray.Create(CloudFrameworkModelAnalyzer.DiagnosticId); }
+ }
+
+ public sealed override FixAllProvider GetFixAllProvider()
+ {
+ // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
+ return WellKnownFixAllProviders.BatchFixer;
+ }
+
+ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
+
+ // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest
+ var diagnostic = context.Diagnostics.First();
+ var diagnosticSpan = diagnostic.Location.SourceSpan;
+
+ // Find the type declaration identified by the diagnostic.
+ var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First();
+
+ // Register a code action that will invoke the fix.
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ title: CodeFixResources.CodeFixTitle,
+ createChangedSolution: c => MakeUppercaseAsync(context.Document, declaration, c),
+ equivalenceKey: nameof(CodeFixResources.CodeFixTitle)),
+ diagnostic);
+ }
+
+ private static async Task MakeUppercaseAsync(Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken)
+ {
+ // Compute new uppercase name.
+ var identifierToken = typeDecl.Identifier;
+ var newName = identifierToken.Text.ToUpperInvariant();
+
+ // Get the symbol representing the type to be renamed.
+ var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
+ var typeSymbol = semanticModel.GetDeclaredSymbol(typeDecl, cancellationToken);
+
+ // Produce a new solution that has all references to that type renamed, including the declaration.
+ var originalSolution = document.Project.Solution;
+ var optionSet = originalSolution.Workspace.Options;
+ var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(false);
+
+ // Return the new solution with the now-uppercase type name.
+ return newSolution;
+ }
+ }
+
+#endif
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CodeFixResources.Designer.cs b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CodeFixResources.Designer.cs
new file mode 100644
index 00000000..a4618b05
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CodeFixResources.Designer.cs
@@ -0,0 +1,72 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Redpoint.CloudFramework.Analyzer {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class CodeFixResources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal CodeFixResources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Redpoint.CloudFramework.Analyzer.CodeFixResources", typeof(CodeFixResources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Make uppercase.
+ ///
+ internal static string CodeFixTitle {
+ get {
+ return ResourceManager.GetString("CodeFixTitle", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CodeFixResources.resx b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CodeFixResources.resx
new file mode 100644
index 00000000..97abe689
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CodeFixResources.resx
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Make uppercase
+ The title of the code fix.
+
+
\ No newline at end of file
diff --git a/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/Redpoint.CloudFramework.Analyzer.CodeFixes.csproj b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/Redpoint.CloudFramework.Analyzer.CodeFixes.csproj
new file mode 100644
index 00000000..367b5538
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/Redpoint.CloudFramework.Analyzer.CodeFixes.csproj
@@ -0,0 +1,22 @@
+
+
+
+ netstandard2.0
+ false
+ Redpoint.CloudFramework.Analyzer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Package/Redpoint.CloudFramework.Analyzer.Package.csproj b/UET/Redpoint.CloudFramework.Analyzer.Package/Redpoint.CloudFramework.Analyzer.Package.csproj
new file mode 100644
index 00000000..cdb91650
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Package/Redpoint.CloudFramework.Analyzer.Package.csproj
@@ -0,0 +1,35 @@
+
+
+
+ netstandard2.0
+ false
+ true
+ true
+
+
+
+
+ Redpoint.CloudFramework.Analyzer
+ false
+ true
+ true
+ $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Package/tools/install.ps1 b/UET/Redpoint.CloudFramework.Analyzer.Package/tools/install.ps1
new file mode 100644
index 00000000..53136a23
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Package/tools/install.ps1
@@ -0,0 +1,275 @@
+param($installPath, $toolsPath, $package, $project)
+
+if($project.Object.SupportsPackageDependencyResolution)
+{
+ if($project.Object.SupportsPackageDependencyResolution())
+ {
+ # Do not install analyzers via install.ps1, instead let the project system handle it.
+ return
+ }
+}
+
+$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
+
+foreach($analyzersPath in $analyzersPaths)
+{
+ if (Test-Path $analyzersPath)
+ {
+ # Install the language agnostic analyzers.
+ foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
+ {
+ if($project.Object.AnalyzerReferences)
+ {
+ $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
+ }
+ }
+ }
+}
+
+# $project.Type gives the language name like (C# or VB.NET)
+$languageFolder = ""
+if($project.Type -eq "C#")
+{
+ $languageFolder = "cs"
+}
+if($project.Type -eq "VB.NET")
+{
+ $languageFolder = "vb"
+}
+if($languageFolder -eq "")
+{
+ return
+}
+
+foreach($analyzersPath in $analyzersPaths)
+{
+ # Install language specific analyzers.
+ $languageAnalyzersPath = join-path $analyzersPath $languageFolder
+ if (Test-Path $languageAnalyzersPath)
+ {
+ foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
+ {
+ if($project.Object.AnalyzerReferences)
+ {
+ $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
+ }
+ }
+ }
+}
+# SIG # Begin signature block
+# MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor
+# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
+# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA/i+qRUHsWzI0s
+# FVk99zLgt/HOEQ33uvkFsWtHTHZgf6CCDXYwggX0MIID3KADAgECAhMzAAADTrU8
+# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
+# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
+# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
+# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw
+# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
+# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
+# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU
+# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1
+# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm
+# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa
+# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq
+# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
+# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw
+# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
+# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
+# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
+# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
+# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
+# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
+# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk
+# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31
+# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2
+# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d
+# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM
+# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh
+# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX
+# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir
+# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8
+# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A
+# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H
+# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
+# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
+# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
+# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
+# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
+# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
+# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
+# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
+# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
+# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
+# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
+# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
+# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
+# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
+# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
+# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
+# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
+# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
+# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
+# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
+# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
+# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
+# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
+# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
+# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
+# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
+# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
+# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
+# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
+# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
+# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
+# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
+# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
+# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
+# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
+# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
+# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
+# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
+# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
+# /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
+# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
+# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
+# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB
+# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
+# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIEY4Ow3COroWH11sAEOoStJj
+# 1u4sq9rcx0dAx0gyZLHCMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
+# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
+# BQAEggEAFlSLlk17KQp2AwLbr5e4T5zyE44MGZOdNejIip+Mg8co8qho16qdNSvg
+# AhvoJYxPJJr70DSCU9BIUoWY7hXoi9S4P08YlAid7BUT5OciIgHlrb8I900LaE+S
+# 83rSvfVU1CZCjiSwcgng5DD2VPRo0Lu4G9p2Ky14dyOPwtPvrpsib5s9kewZLdiy
+# /KxEDLKX8P+cHat1xH7RaZLDNxweRS6GSomjE2vjOlQHNSW879XR8bSoAt/m4uR1
+# WyrAxTGZb4miEYX+I5HsrWvbZLw9NSCJ/crbbap3LIobfQtK5binjY7v4MQp/5Oq
+# y4S/4FAfwhWDXfaQfq6YTeOjHRVQbKGCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC
+# F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
+# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
+# AwQCAQUABCCV3irbWIoYz2Llfx9YQhUNtcP6GOrL7+YTUXQ4y5qzWAIGZNTJrsAW
+# GBMyMDIzMDgzMTAwMTI1OC45ODNaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
+# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
+# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
+# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTIwMC0w
+# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
+# ghHqMIIHIDCCBQigAwIBAgITMwAAAc9SNr5xS81IygABAAABzzANBgkqhkiG9w0B
+# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
+# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
+# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy
+# MTFaFw0yNDAyMDExOTEyMTFaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
+# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
+# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
+# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTIwMC0wNUUwLUQ5NDcxJTAjBgNV
+# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
+# AQUAA4ICDwAwggIKAoICAQC4Pct+15TYyrUje553lzBQodgmd5Bz7WuH8SdHpAoW
+# z+01TrHExBSuaMKnxvVMsyYtas5h6aopUGAS5WKVLZAvUtH62TKmAE0JK+i1hafi
+# CSXLZPcRexxeRkOqeZefLBzXp0nudMOXUUab333Ss8LkoK4l3LYxm1Ebsr3b2OTo
+# 2ebsAoNJ4kSxmVuPM7C+RDhGtVKR/EmHsQ9GcwGmluu54bqiVFd0oAFBbw4txTU1
+# mruIGWP/i+sgiNqvdV/wah/QcrKiGlpWiOr9a5aGrJaPSQD2xgEDdPbrSflYxsRM
+# dZCJI8vzvOv6BluPcPPGGVLEaU7OszdYjK5f4Z5Su/lPK1eST5PC4RFsVcOiS4L0
+# sI4IFZywIdDJHoKgdqWRp6Q5vEDk8kvZz6HWFnYLOlHuqMEYvQLr6OgooYU9z0A5
+# cMLHEIHYV1xiaBzx2ERiRY9MUPWohh+TpZWEUZlUm/q9anXVRN0ujejm6OsUVFDs
+# sIMszRNCqEotJGwtHHm5xrCKuJkFr8GfwNelFl+XDoHXrQYL9zY7Np+frsTXQpKR
+# NnmI1ashcn5EC+wxUt/EZIskWzewEft0/+/0g3+8YtMkUdaQE5+8e7C8UMiXOHkM
+# K25jNNQqLCedlJwFIf9ir9SpMc72NR+1j6Uebiz/ZPV74do3jdVvq7DiPFlTb92U
+# KwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFDaeKPtp0eTSVdG+gZc5BDkabTg4MB8G
+# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
+# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
+# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
+# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
+# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
+# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
+# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBQgm4pnA0xkd/9uKXJMzdMYyxUfUm/ZusU
+# Ba32MEZXQuMGp20pSuX2VW9/tpTMo5bkaJdBVoUyd2DbDsNb1kjr/36ntT0jvL3A
+# oWStAFhZBypmpPbx+BPK49ZlejlM4d5epX668tRRGfFip9Til9yKRfXBrXnM/q64
+# IinN7zXEQ3FFQhdJMzt8ibXClO7eFA+1HiwZPWysYWPb/ZOFobPEMvXie+GmEbTK
+# bhE5tze6RrA9aejjP+v1ouFoD5bMj5Qg+wfZXqe+hfYKpMd8QOnQyez+Nlj1ityn
+# OZWfwHVR7dVwV0yLSlPT+yHIO8g+3fWiAwpoO17bDcntSZ7YOBljXrIgad4W4gX+
+# 4tp1eBsc6XWIITPBNzxQDZZRxD4rXzOB6XRlEVJdYZQ8gbXOirg/dNvS2GxcR50Q
+# dOXDAumdEHaGNHb6y2InJadCPp2iT5QLC4MnzR+YZno1b8mWpCdOdRs9g21QbbrI
+# 06iLk9KD61nx7K5ReSucuS5Z9nbkIBaLUxDesFhr1wmd1ynf0HQ51Swryh7YI7TX
+# T0jr81mbvvI9xtoqjFvIhNBsICdCfTR91ylJTH8WtUlpDhEgSqWt3gzNLPTSvXAx
+# XTpIM583sZdd+/2YGADMeWmt8PuMce6GsIcLCOF2NiYZ10SXHZS5HRrLrChuzedD
+# RisWpIu5uTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
+# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
+# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
+# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
+# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
+# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
+# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
+# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
+# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
+# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
+# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
+# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
+# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
+# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
+# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
+# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
+# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
+# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
+# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
+# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
+# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
+# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
+# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
+# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
+# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
+# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
+# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
+# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
+# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
+# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
+# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
+# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
+# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
+# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
+# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
+# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
+# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
+# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
+# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN
+# MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
+# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
+# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
+# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjkyMDAtMDVFMC1EOTQ3MSUwIwYDVQQD
+# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQDq
+# 8xzVXwLguauAQj1rrJ4/TyEMm6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
+# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
+# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
+# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6JpNsjAiGA8yMDIzMDgzMDIzMjIy
+# NloYDzIwMjMwODMxMjMyMjI2WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDomk2y
+# AgEAMAcCAQACAi7oMAcCAQACAhLnMAoCBQDom58yAgEAMDYGCisGAQQBhFkKBAIx
+# KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI
+# hvcNAQELBQADggEBADE16CHIs3WeXYKQG9djja+StAB7gsNJxV9p+CETL3847UL3
+# +DIeoj6p5g0FkS8PJK2xc+UbcNx6XJO+WUXEbU9GL4mrOCQjYOM+i3r8FEU+l3Gh
+# 6ZG/9ygsIYRXEfDKK4lsbrrUkFQs9nDISHT3f8JZEJJXSsGmwcHWlNC0LC8bv0Jp
+# e2Bw2+SNc6SlGD8Vv45r4WFPHhfSioCz4HSsF1He3/2Wku7OH85FKvugBlsca7+F
+# bpGsDSL4LO9bv60DxuD+8xBZuyTB8s64ifCGlOXCNpK5VaHND48PhoJbuD0COwlM
+# Rn5NlT6T4hhtkPOqNscMlzYHmTOKc5NhWK8PyrIxggQNMIIECQIBATCBkzB8MQsw
+# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
+# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy
+# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAc9SNr5xS81IygABAAABzzAN
+# BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G
+# CSqGSIb3DQEJBDEiBCAjGiC3/PscCMBvPZjpZqbYcL2WRZ+Ecf74oiIPQKkjSzCB
+# +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EILPpsLqeNS4NuYXE2VJlMuvQeWVA
+# 80ZDFhpOPjSzhPa/MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
+# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
+# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
+# MTACEzMAAAHPUja+cUvNSMoAAQAAAc8wIgQgWdbLA3Co0zf3nE5086HU0tdn0vK7
+# yXyU9aAFpszvWFIwDQYJKoZIhvcNAQELBQAEggIANrHN06oaP0N1lUSxxoJteJgI
+# fQjN82xV5VSE0cwNCBy05fg1VydPERF59+MIwIlJHhikSo3YOj5tt3AohC78U46P
+# 2IdrLHKlA3rjiLUGwQvGUKJUSsEH4uueA1mmh9jwUBJhY3NjTcMQaQqp/oxZTaya
+# l7NqbubBIZvsDD126SUr8jtVWtZZzw8pnWCFb4Rijii4fY1UiQzfQLFwqQuid6tE
+# I0AaY3IoTlp7U9K2wfAPWcP1G7n3qv+990GEiQlGJlCfIJSSJQodzL2QZF5HCn/K
+# SfgkRPn3y/Aax8683mWCT8zricYzO3MZ9j0T7tAcqOiWb7PFCsk5Va44lq4Gv0u+
+# +60FYCAA/Qn6eMuNkqIpBeIK0+NYUcMSwPdY/riKXdgkVLwChEJC5WWznD/Iqsil
+# Jj9XYailhXzYx7Pa2MLays62LPwCnUxRQBTD9/rL3XQMj69iA4lisb51dKAtrqAU
+# 53aRXFn+twYYFTAUQ/oNK14nZEQE5H53xAfhphMokJOu+CnQQKeCMeYlex6Q4zfw
+# TQxP/xXZ+QW2cSZTwh1d5iE0XMhKCZxhxIOF/rRA+75L5GUz60reRZPeH/7USYZL
+# VPc0+kxIdSbNFJAhAp0u59wSMQdBofor3+HfDfmxmoSjfCSH4TvOkNIulX1PPJPX
+# UB7H4n7XHWJPkUU0cUw=
+# SIG # End signature block
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Package/tools/uninstall.ps1 b/UET/Redpoint.CloudFramework.Analyzer.Package/tools/uninstall.ps1
new file mode 100644
index 00000000..1f6b6d4f
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Package/tools/uninstall.ps1
@@ -0,0 +1,282 @@
+param($installPath, $toolsPath, $package, $project)
+
+if($project.Object.SupportsPackageDependencyResolution)
+{
+ if($project.Object.SupportsPackageDependencyResolution())
+ {
+ # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it.
+ return
+ }
+}
+
+$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
+
+foreach($analyzersPath in $analyzersPaths)
+{
+ # Uninstall the language agnostic analyzers.
+ if (Test-Path $analyzersPath)
+ {
+ foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
+ {
+ if($project.Object.AnalyzerReferences)
+ {
+ $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
+ }
+ }
+ }
+}
+
+# $project.Type gives the language name like (C# or VB.NET)
+$languageFolder = ""
+if($project.Type -eq "C#")
+{
+ $languageFolder = "cs"
+}
+if($project.Type -eq "VB.NET")
+{
+ $languageFolder = "vb"
+}
+if($languageFolder -eq "")
+{
+ return
+}
+
+foreach($analyzersPath in $analyzersPaths)
+{
+ # Uninstall language specific analyzers.
+ $languageAnalyzersPath = join-path $analyzersPath $languageFolder
+ if (Test-Path $languageAnalyzersPath)
+ {
+ foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
+ {
+ if($project.Object.AnalyzerReferences)
+ {
+ try
+ {
+ $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
+ }
+ catch
+ {
+
+ }
+ }
+ }
+ }
+}
+# SIG # Begin signature block
+# MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor
+# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
+# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDC68wb97fg0QGL
+# yXrxJhYfmibzcOh8caqC0uZprfczDaCCDXYwggX0MIID3KADAgECAhMzAAADTrU8
+# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
+# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
+# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
+# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw
+# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
+# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
+# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU
+# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1
+# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm
+# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa
+# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq
+# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
+# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw
+# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
+# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
+# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
+# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
+# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
+# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
+# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk
+# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31
+# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2
+# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d
+# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM
+# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh
+# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX
+# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir
+# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8
+# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A
+# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H
+# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
+# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
+# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
+# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
+# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
+# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
+# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
+# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
+# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
+# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
+# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
+# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
+# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
+# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
+# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
+# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
+# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
+# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
+# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
+# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
+# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
+# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
+# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
+# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
+# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
+# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
+# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
+# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
+# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
+# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
+# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
+# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
+# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
+# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
+# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
+# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
+# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
+# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
+# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
+# /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
+# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
+# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
+# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB
+# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
+# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIBdcqRcs5QL71hlQnl3M636V
+# 5iTZvb6co3MHeMuIr36qMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
+# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
+# BQAEggEARbhO/zqm6SWBf6DSJ3P1r82VjSyMGXaLtMfTOq/bqHOXPOqC25R1v5uO
+# zu4ri+UOS8dU6EfW6C9Xf/Z1Ue/oxrvxn5j8mPvsmcs5OyDO0hW0Wv6pYEy5Z5up
+# mfcqvfUfl3+ir29lgPuz0f1mLpz0XxqhjqElEi5RfZD1k1YVg65f0qHroP2txql5
+# TfC77DXJ8verzVm1wqXBHTAERQD94TJobahYTCmyaudMjLUVFakv2lTMv0YTnrQR
+# So006ZQg3i1jcVCJt/bRDGKh3xUo1IHgoh3NjMEkxT3iWt8rnX8Us6T6Zg8B2OxC
+# 0EnuIu/eYYUlYTLQxO9eks7w1kVcUqGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC
+# F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
+# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
+# AwQCAQUABCBQ1HvJFFsT0ICl6oEZlSNN8DlUeQobBHt/oTUGN6iKBgIGZNTKS0Bc
+# GBMyMDIzMDgzMTAwMTI1OC4zODVaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
+# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
+# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
+# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046N0YwMC0w
+# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
+# ghHtMIIHIDCCBQigAwIBAgITMwAAAdWpAs/Fp8npWgABAAAB1TANBgkqhkiG9w0B
+# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
+# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
+# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy
+# MzBaFw0yNDAyMDExOTEyMzBaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
+# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
+# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
+# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046N0YwMC0wNUUwLUQ5NDcxJTAjBgNV
+# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
+# AQUAA4ICDwAwggIKAoICAQDFfak57Oph9vuxtloABiLc6enT+yKH619b+OhGdkyh
+# gNzkX80KUGI/jEqOVMV4Sqt/UPFFidx2t7v2SETj2tAzuVKtDfq2HBpu80vZ0vyQ
+# DydVt4MDL4tJSKqgYofCxDIBrWzJJjgBolKdOJx1ut2TyOc+UOm7e92tVPHpjdg+
+# Omf31TLUf/oouyAOJ/Inn2ih3ASP0QYm+AFQjhYDNDu8uzMdwHF5QdwsscNa9PVS
+# GedLdDLo9jL6DoPF4NYo06lvvEQuSJ9ImwZfBGLy/8hpE7RD4ewvJKmM1+t6eQuE
+# sTXjrGM2WjkW18SgUZ8n+VpL2uk6AhDkCa355I531p0Jkqpoon7dHuLUdZSQO40q
+# mVIQ6qQCanvImTqmNgE/rPJ0rgr0hMPI/uR1T/iaL0mEq4bqak+3sa8I+FAYOI/P
+# C7V+zEek+sdyWtaX+ndbGlv/RJb5mQaGn8NunbkfvHD1Qt5D0rmtMOekYMq7QjYq
+# E3FEP/wAY4TDuJxstjsa2HXi2yUDEg4MJL6/JvsQXToOZ+IxR6KT5t5fB5FpZYBp
+# VLMma3pm5z6VXvkXrYs33NXJqVWLwiswa7NUFV87Es2sou9Idw3yAZmHIYWgOQ+D
+# IY1nY3aG5DODiwN1rJyEb+mbWDagrdVxcncr6UKKO49eoNTXEW+scUf6GwXG0KEy
+# mQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFK/QXKNO35bBMOz3R5giX7Ala2OaMB8G
+# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
+# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
+# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
+# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
+# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
+# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
+# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBmRddqvQuyjRpx0HGxvOqffFrbgFAg0j82
+# v0v7R+/8a70S2V4t7yKYKSsQGI6pvt1A8JGmuZyjmIXmw23AkI5bZkxvSgws8rrB
+# tJw9vakEckcWFQb7JG6b618x0s9Q3DL0dRq46QZRnm7U6234lecvjstAow30dP0T
+# nIacPWKpPc3QgB+WDnglN2fdT1ruQ6WIVBenmpjpG9ypRANKUx5NRcpdJAQW2FqE
+# HTS3Ntb+0tCqIkNHJ5aFsF6ehRovWZp0MYIz9bpJHix0VrjdLVMOpe7wv62t90E3
+# UrE2KmVwpQ5wsMD6YUscoCsSRQZrA5AbwTOCZJpeG2z3vDo/huvPK8TeTJ2Ltu/I
+# tXgxIlIOQp/tbHAiN8Xptw/JmIZg9edQ/FiDaIIwG5YHsfm2u7TwOFyd6OqLw18Z
+# 5j/IvDPzlkwWJxk6RHJF5dS4s3fnyLw3DHBe5Dav6KYB4n8x/cEmD/R44/8gS5Pf
+# uG1srjLdyyGtyh0KiRDSmjw+fa7i1VPoemidDWNZ7ksNadMad4ZoDvgkqOV4A6a+
+# N8HIc/P6g0irrezLWUgbKXSN8iH9RP+WJFx5fBHE4AFxrbAUQ2Zn5jDmHAI3wYcQ
+# DnnEYP51A75WFwPsvBrfrb1+6a1fuTEH1AYdOOMy8fX8xKo0E0Ys+7bxIvFPsUpS
+# zfFjBolmhzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
+# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
+# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
+# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
+# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
+# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
+# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
+# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
+# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
+# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
+# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
+# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
+# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
+# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
+# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
+# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
+# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
+# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
+# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
+# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
+# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
+# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
+# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
+# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
+# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
+# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
+# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
+# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
+# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
+# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
+# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
+# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
+# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
+# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
+# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
+# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
+# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
+# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
+# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ
+# MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
+# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
+# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
+# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjdGMDAtMDVFMC1EOTQ3MSUwIwYDVQQD
+# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBO
+# Ei+S/ZVFe6w1Id31m6Kge26lNKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
+# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
+# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
+# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6JpN3jAiGA8yMDIzMDgzMDIzMjMx
+# MFoYDzIwMjMwODMxMjMyMzEwWjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDomk3e
+# AgEAMAoCAQACAiLLAgH/MAcCAQACAhLTMAoCBQDom59eAgEAMDYGCisGAQQBhFkK
+# BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ
+# KoZIhvcNAQELBQADggEBAC/zAZ9IEqD3nHBUWwxFTfnDSRErnCJ6XGxH5jOVtR0t
+# 9pi7yCpApLpA2D12g1lcv4ugnwGpsbwmFjrcF4WlHemRa77qv409xNhNKrnh3H+U
+# X2hvy9Utp9LiJiqS7lOW5VN1Uv+LbnA+FWt//4J+YLv44D/dliUGjYX623X7KiEX
+# dbdXPR/Sn+W2YVQ19O8liKaFDnDnIAz+WLCfL6EaoGu4Te/Mr65Khy3YWTwQfXxr
+# gR/JMDzLzWossnGszYCN8S8d9X6mfzWuYv4JHLEiThW++WbMLeT2hhKPomcbvqU4
+# wPb/ylDrTrWuAr/fVndECXVjCIzYJiFwOWn/ZfN9FpQxggQNMIIECQIBATCBkzB8
+# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
+# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N
+# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAdWpAs/Fp8npWgABAAAB
+# 1TANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE
+# MC8GCSqGSIb3DQEJBDEiBCAHS+EjBkJ/YPugQQv1D7eXVQOI4DWPIYpUxpssheDN
+# lTCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EINm/I4YM166JMM7EKIcYvlcb
+# r2CHjKC0LUOmpZIbBsH/MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
+# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
+# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
+# IDIwMTACEzMAAAHVqQLPxafJ6VoAAQAAAdUwIgQgBVj4mNvMkXn0xEeEe3i6urr/
+# L5Eh2p4uPk3zOcR/jHIwDQYJKoZIhvcNAQELBQAEggIAiKbnM5VQAwi5gWDevpEJ
+# oHiZRrb0HApEUkca54Oye2+5tyRfWo3ruZvLZueHWsbEzYXclOscHVPjyR1t6s+p
+# W13utx4mDab1BaJCBq1q9IH1HDfCf/kSMb0H02UDNBumrt1s5/oaiiY75S3kigpt
+# XHCKV/AKbi+4vORYdBwJhWvy4kug+zZHWAp7+K4rVeAaMbWOitriFPQaSdZlOkNT
+# VKMbYUtX6VEnhyfr5O390TxPJCdkDznT6rAf5FlQEkim7wb2gb/Osi73KF0dtnRd
+# 0ePNT4GxulYTrAhHsZmyuinh/FIyqJDtW3C+2eVt/lx6GJfAtDA/gVCe9mAL4bYv
+# 7vjocHeLvWJ8bc0PYMCRZXMsU2zkJqVJ7pZWq+z5hGQAHemrQj9hUZBXCEFVZ8dd
+# jEZbIsg2nwZOFakhvdAvvGlTXPbRMOCHblToXAKA8ksRbLob8CDL6Cstoy5VL3al
+# fOAWLj3FYITlvGvAYCtJzHrHdqyphL805Co1syR6YDopR8tDrxgWzJKAby5fIolP
+# 7SfunXsCa0n3xx80aaxR0apljIXZWBSMGZdJmVzASQFxlexNsU1PmaLmmpn0fih/
+# 567e+kyzFG1wiw5btwttSW9hKgCX+yze1B4IK4yquTQSfPikrCpbBaFZ0OlWmOnL
+# r0eKAKY3WDyxYgNyeg6KeCc=
+# SIG # End signature block
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/ModelAnalyzerTests.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/ModelAnalyzerTests.cs
new file mode 100644
index 00000000..a44d6d9c
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/ModelAnalyzerTests.cs
@@ -0,0 +1,86 @@
+namespace Redpoint.CloudFramework.Analyzer.Tests
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading.Tasks;
+ using Xunit;
+
+ using VerifyDiag = Verifiers.CSharpAnalyzerVerifier<
+ CloudFrameworkModelAnalyzer>;
+
+ //using VerifyCodeFix = Verifiers.CSharpCodeFixVerifier<
+ // CloudFrameworkModelAnalyzer,
+ // CloudFrameworkModelAnalyzerCodeFixProvider>;
+
+ public class ModelAnalyzerTests
+ {
+ [Fact]
+ public async Task TestEmpty()
+ {
+ var test = string.Empty;
+
+ await VerifyDiag.VerifyAnalyzerAsync(test);
+ // await VerifyCodeFix.VerifyAnalyzerAsync(test);
+ }
+
+ [Fact]
+ public async Task TestInherit()
+ {
+ var test =
+ """
+ namespace Redpoint.CloudFramework.Models
+ {
+ public class Model where T : Model
+ {
+ }
+ }
+
+ sealed class Model1 : Redpoint.CloudFramework.Models.Model
+ {
+ }
+
+ sealed class {|#0:Model2|} : Redpoint.CloudFramework.Models.Model
+ {
+ }
+ """;
+
+ var expected = VerifyDiag
+ .Diagnostic(CloudFrameworkModelAnalyzer.InheritDiagnosticId)
+ .WithLocation(0)
+ .WithArguments("Model2");
+
+ await VerifyDiag.VerifyAnalyzerAsync(test, expected);
+ }
+
+ [Fact]
+ public async Task TestSealed()
+ {
+ var test =
+ """
+ namespace Redpoint.CloudFramework.Models
+ {
+ public class Model where T : Model
+ {
+ }
+ }
+
+ sealed class Model1 : Redpoint.CloudFramework.Models.Model
+ {
+ }
+
+ class {|#0:Model2|} : Redpoint.CloudFramework.Models.Model
+ {
+ }
+ """;
+
+ var expected = VerifyDiag
+ .Diagnostic(CloudFrameworkModelAnalyzer.SealedDiagnosticId)
+ .WithLocation(0)
+ .WithArguments("Model2");
+
+ await VerifyDiag.VerifyAnalyzerAsync(test, expected);
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Redpoint.CloudFramework.Analyzer.Tests.csproj b/UET/Redpoint.CloudFramework.Analyzer.Tests/Redpoint.CloudFramework.Analyzer.Tests.csproj
new file mode 100644
index 00000000..c8ad7bd4
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Redpoint.CloudFramework.Analyzer.Tests.csproj
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1+Test.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1+Test.cs
new file mode 100644
index 00000000..9df519ee
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1+Test.cs
@@ -0,0 +1,26 @@
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+
+namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers
+{
+ public static partial class CSharpAnalyzerVerifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ {
+ public class Test : CSharpAnalyzerTest
+ {
+ public Test()
+ {
+ SolutionTransforms.Add((solution, projectId) =>
+ {
+ var compilationOptions = solution.GetProject(projectId)!.CompilationOptions;
+ compilationOptions = compilationOptions!.WithSpecificDiagnosticOptions(
+ compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings));
+ solution = solution.WithProjectCompilationOptions(projectId, compilationOptions);
+
+ return solution;
+ });
+ }
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs
new file mode 100644
index 00000000..ea08c383
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs
@@ -0,0 +1,38 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+
+namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers
+{
+ public static partial class CSharpAnalyzerVerifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ {
+ ///
+ public static DiagnosticResult Diagnostic()
+ => CSharpAnalyzerVerifier.Diagnostic();
+
+ ///
+ public static DiagnosticResult Diagnostic(string diagnosticId)
+ => CSharpAnalyzerVerifier.Diagnostic(diagnosticId);
+
+ ///
+ public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
+ => CSharpAnalyzerVerifier.Diagnostic(descriptor);
+
+ ///
+ public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected)
+ {
+ var test = new Test
+ {
+ TestCode = source,
+ };
+
+ test.ExpectedDiagnostics.AddRange(expected);
+ await test.RunAsync(CancellationToken.None);
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeFixVerifier`2+Test.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeFixVerifier`2+Test.cs
new file mode 100644
index 00000000..2759cc5d
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeFixVerifier`2+Test.cs
@@ -0,0 +1,28 @@
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+
+namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers
+{
+ public static partial class CSharpCodeFixVerifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ where TCodeFix : CodeFixProvider, new()
+ {
+ public class Test : CSharpCodeFixTest
+ {
+ public Test()
+ {
+ SolutionTransforms.Add((solution, projectId) =>
+ {
+ var compilationOptions = solution.GetProject(projectId)!.CompilationOptions;
+ compilationOptions = compilationOptions!.WithSpecificDiagnosticOptions(
+ compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings));
+ solution = solution.WithProjectCompilationOptions(projectId, compilationOptions);
+
+ return solution;
+ });
+ }
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeFixVerifier`2.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeFixVerifier`2.cs
new file mode 100644
index 00000000..31533c4a
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeFixVerifier`2.cs
@@ -0,0 +1,61 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+
+namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers
+{
+ public static partial class CSharpCodeFixVerifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ where TCodeFix : CodeFixProvider, new()
+ {
+ ///
+ public static DiagnosticResult Diagnostic()
+ => CSharpCodeFixVerifier.Diagnostic();
+
+ ///
+ public static DiagnosticResult Diagnostic(string diagnosticId)
+ => CSharpCodeFixVerifier.Diagnostic(diagnosticId);
+
+ ///
+ public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
+ => CSharpCodeFixVerifier.Diagnostic(descriptor);
+
+ ///
+ public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected)
+ {
+ var test = new Test
+ {
+ TestCode = source,
+ };
+
+ test.ExpectedDiagnostics.AddRange(expected);
+ await test.RunAsync(CancellationToken.None);
+ }
+
+ ///
+ public static async Task VerifyCodeFixAsync(string source, string fixedSource)
+ => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource);
+
+ ///
+ public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource)
+ => await VerifyCodeFixAsync(source, new[] { expected }, fixedSource);
+
+ ///
+ public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource)
+ {
+ var test = new Test
+ {
+ TestCode = source,
+ FixedCode = fixedSource,
+ };
+
+ test.ExpectedDiagnostics.AddRange(expected);
+ await test.RunAsync(CancellationToken.None);
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeRefactoringVerifier`1+Test.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeRefactoringVerifier`1+Test.cs
new file mode 100644
index 00000000..075cdf46
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeRefactoringVerifier`1+Test.cs
@@ -0,0 +1,26 @@
+using Microsoft.CodeAnalysis.CodeRefactorings;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+
+namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers
+{
+ public static partial class CSharpCodeRefactoringVerifier
+ where TCodeRefactoring : CodeRefactoringProvider, new()
+ {
+ public class Test : CSharpCodeRefactoringTest
+ {
+ public Test()
+ {
+ SolutionTransforms.Add((solution, projectId) =>
+ {
+ var compilationOptions = solution.GetProject(projectId)!.CompilationOptions;
+ compilationOptions = compilationOptions!.WithSpecificDiagnosticOptions(
+ compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings));
+ solution = solution.WithProjectCompilationOptions(projectId, compilationOptions);
+
+ return solution;
+ });
+ }
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeRefactoringVerifier`1.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeRefactoringVerifier`1.cs
new file mode 100644
index 00000000..e80f9445
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeRefactoringVerifier`1.cs
@@ -0,0 +1,36 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeRefactorings;
+using Microsoft.CodeAnalysis.Testing;
+
+namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers
+{
+ public static partial class CSharpCodeRefactoringVerifier
+ where TCodeRefactoring : CodeRefactoringProvider, new()
+ {
+ ///
+ public static async Task VerifyRefactoringAsync(string source, string fixedSource)
+ {
+ await VerifyRefactoringAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource);
+ }
+
+ ///
+ public static async Task VerifyRefactoringAsync(string source, DiagnosticResult expected, string fixedSource)
+ {
+ await VerifyRefactoringAsync(source, new[] { expected }, fixedSource);
+ }
+
+ ///
+ public static async Task VerifyRefactoringAsync(string source, DiagnosticResult[] expected, string fixedSource)
+ {
+ var test = new Test
+ {
+ TestCode = source,
+ FixedCode = fixedSource,
+ };
+
+ test.ExpectedDiagnostics.AddRange(expected);
+ await test.RunAsync(CancellationToken.None);
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpVerifierHelper.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpVerifierHelper.cs
new file mode 100644
index 00000000..068a6688
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpVerifierHelper.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers
+{
+ internal static class CSharpVerifierHelper
+ {
+ ///
+ /// By default, the compiler reports diagnostics for nullable reference types at
+ /// , and the analyzer test framework defaults to only validating
+ /// diagnostics at . This map contains all compiler diagnostic IDs
+ /// related to nullability mapped to , which is then used to enable all
+ /// of these warnings for default validation during analyzer and code fix tests.
+ ///
+ internal static ImmutableDictionary NullableWarnings { get; } = GetNullableWarningsFromCompiler();
+
+ private static ImmutableDictionary GetNullableWarningsFromCompiler()
+ {
+ string[] args = { "/warnaserror:nullable" };
+ var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory);
+ var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions;
+
+ // Workaround for https://github.com/dotnet/roslyn/issues/41610
+ nullableWarnings = nullableWarnings
+ .SetItem("CS8632", ReportDiagnostic.Error)
+ .SetItem("CS8669", ReportDiagnostic.Error);
+
+ return nullableWarnings;
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicAnalyzerVerifier`1+Test.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicAnalyzerVerifier`1+Test.cs
new file mode 100644
index 00000000..24e0dbf2
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicAnalyzerVerifier`1+Test.cs
@@ -0,0 +1,17 @@
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+using Microsoft.CodeAnalysis.VisualBasic.Testing;
+
+namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers
+{
+ public static partial class VisualBasicAnalyzerVerifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ {
+ public class Test : VisualBasicAnalyzerTest
+ {
+ public Test()
+ {
+ }
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicAnalyzerVerifier`1.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicAnalyzerVerifier`1.cs
new file mode 100644
index 00000000..2dafbc7e
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicAnalyzerVerifier`1.cs
@@ -0,0 +1,38 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+using Microsoft.CodeAnalysis.VisualBasic.Testing;
+
+namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers
+{
+ public static partial class VisualBasicAnalyzerVerifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ {
+ ///
+ public static DiagnosticResult Diagnostic()
+ => VisualBasicAnalyzerVerifier.Diagnostic();
+
+ ///
+ public static DiagnosticResult Diagnostic(string diagnosticId)
+ => VisualBasicAnalyzerVerifier.Diagnostic(diagnosticId);
+
+ ///
+ public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
+ => VisualBasicAnalyzerVerifier.Diagnostic(descriptor);
+
+ ///
+ public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected)
+ {
+ var test = new Test
+ {
+ TestCode = source,
+ };
+
+ test.ExpectedDiagnostics.AddRange(expected);
+ await test.RunAsync(CancellationToken.None);
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeFixVerifier`2+Test.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeFixVerifier`2+Test.cs
new file mode 100644
index 00000000..4fe159ac
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeFixVerifier`2+Test.cs
@@ -0,0 +1,16 @@
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+using Microsoft.CodeAnalysis.VisualBasic.Testing;
+
+namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers
+{
+ public static partial class VisualBasicCodeFixVerifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ where TCodeFix : CodeFixProvider, new()
+ {
+ public class Test : VisualBasicCodeFixTest
+ {
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeFixVerifier`2.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeFixVerifier`2.cs
new file mode 100644
index 00000000..d5f0d397
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeFixVerifier`2.cs
@@ -0,0 +1,61 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+using Microsoft.CodeAnalysis.VisualBasic.Testing;
+
+namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers
+{
+ public static partial class VisualBasicCodeFixVerifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ where TCodeFix : CodeFixProvider, new()
+ {
+ ///
+ public static DiagnosticResult Diagnostic()
+ => VisualBasicCodeFixVerifier.Diagnostic();
+
+ ///
+ public static DiagnosticResult Diagnostic(string diagnosticId)
+ => VisualBasicCodeFixVerifier.Diagnostic(diagnosticId);
+
+ ///
+ public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
+ => VisualBasicCodeFixVerifier.Diagnostic(descriptor);
+
+ ///
+ public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected)
+ {
+ var test = new Test
+ {
+ TestCode = source,
+ };
+
+ test.ExpectedDiagnostics.AddRange(expected);
+ await test.RunAsync(CancellationToken.None);
+ }
+
+ ///
+ public static async Task VerifyCodeFixAsync(string source, string fixedSource)
+ => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource);
+
+ ///
+ public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource)
+ => await VerifyCodeFixAsync(source, new[] { expected }, fixedSource);
+
+ ///
+ public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource)
+ {
+ var test = new Test
+ {
+ TestCode = source,
+ FixedCode = fixedSource,
+ };
+
+ test.ExpectedDiagnostics.AddRange(expected);
+ await test.RunAsync(CancellationToken.None);
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeRefactoringVerifier`1+Test.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeRefactoringVerifier`1+Test.cs
new file mode 100644
index 00000000..cbeeab56
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeRefactoringVerifier`1+Test.cs
@@ -0,0 +1,14 @@
+using Microsoft.CodeAnalysis.CodeRefactorings;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+using Microsoft.CodeAnalysis.VisualBasic.Testing;
+
+namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers
+{
+ public static partial class VisualBasicCodeRefactoringVerifier
+ where TCodeRefactoring : CodeRefactoringProvider, new()
+ {
+ public class Test : VisualBasicCodeRefactoringTest
+ {
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeRefactoringVerifier`1.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeRefactoringVerifier`1.cs
new file mode 100644
index 00000000..45a02f1b
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeRefactoringVerifier`1.cs
@@ -0,0 +1,36 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeRefactorings;
+using Microsoft.CodeAnalysis.Testing;
+
+namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers
+{
+ public static partial class VisualBasicCodeRefactoringVerifier
+ where TCodeRefactoring : CodeRefactoringProvider, new()
+ {
+ ///
+ public static async Task VerifyRefactoringAsync(string source, string fixedSource)
+ {
+ await VerifyRefactoringAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource);
+ }
+
+ ///
+ public static async Task VerifyRefactoringAsync(string source, DiagnosticResult expected, string fixedSource)
+ {
+ await VerifyRefactoringAsync(source, new[] { expected }, fixedSource);
+ }
+
+ ///
+ public static async Task VerifyRefactoringAsync(string source, DiagnosticResult[] expected, string fixedSource)
+ {
+ var test = new Test
+ {
+ TestCode = source,
+ FixedCode = fixedSource,
+ };
+
+ test.ExpectedDiagnostics.AddRange(expected);
+ await test.RunAsync(CancellationToken.None);
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer/CloudFrameworkModelAnalyzer.cs b/UET/Redpoint.CloudFramework.Analyzer/CloudFrameworkModelAnalyzer.cs
new file mode 100644
index 00000000..4cee2b0d
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer/CloudFrameworkModelAnalyzer.cs
@@ -0,0 +1,90 @@
+namespace Redpoint.CloudFramework.Analyzer
+{
+ using Microsoft.CodeAnalysis;
+ using Microsoft.CodeAnalysis.CSharp;
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
+ using Microsoft.CodeAnalysis.Diagnostics;
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Immutable;
+ using System.Linq;
+ using System.Threading;
+
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class CloudFrameworkModelAnalyzer : DiagnosticAnalyzer
+ {
+ public const string InheritDiagnosticId = "CloudFrameworkModelAnalyzerInherit";
+ public const string SealedDiagnosticId = "CloudFrameworkModelAnalyzerSealed";
+
+ private static readonly DiagnosticDescriptor _inheritRule = new DiagnosticDescriptor(
+ InheritDiagnosticId,
+ "Inheritance of Model should have T match inheriting type",
+ "Type {0} must inherit from Model<{0}>",
+ "Cloud Framework",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: "Cloud Framework models must inherit from Model, where T matches the type that is inheriting.");
+
+ private static readonly DiagnosticDescriptor _sealedRule = new DiagnosticDescriptor(
+ SealedDiagnosticId,
+ "Types implementing Model should be sealed",
+ "Type {0} must be sealed",
+ "Cloud Framework",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: "Cloud Framework models must be sealed.");
+
+ public override ImmutableArray SupportedDiagnostics
+ {
+ get
+ {
+ return ImmutableArray.Create(_inheritRule, _sealedRule);
+ }
+ }
+
+ public override void Initialize(AnalysisContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterSyntaxNodeAction(AnalyzeClassDeclaration, SyntaxKind.ClassDeclaration);
+ }
+
+ private static void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context)
+ {
+ if (!(context.Node is ClassDeclarationSyntax classDeclaration))
+ {
+ return;
+ }
+
+ var symbol = context.SemanticModel.GetDeclaredSymbol(classDeclaration);
+
+ var baseType = symbol.BaseType;
+ if (baseType.IsGenericType &&
+ baseType.TypeArguments.Length >= 1)
+ {
+ var unboundGeneric = baseType.ConstructUnboundGenericType();
+ if (unboundGeneric.ContainingNamespace.ToString() == "Redpoint.CloudFramework.Models" &&
+ unboundGeneric.Name == "Model")
+ {
+ if (!SymbolEqualityComparer.Default.Equals(symbol, baseType.TypeArguments[0]))
+ {
+ var diagnostic = Diagnostic.Create(_inheritRule, symbol.Locations[0], symbol.Name);
+ context.ReportDiagnostic(diagnostic);
+ }
+
+ if (!symbol.IsSealed)
+ {
+ var diagnostic = Diagnostic.Create(_sealedRule, symbol.Locations[0], symbol.Name);
+ context.ReportDiagnostic(diagnostic);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.Analyzer/Redpoint.CloudFramework.Analyzer.csproj b/UET/Redpoint.CloudFramework.Analyzer/Redpoint.CloudFramework.Analyzer.csproj
new file mode 100644
index 00000000..26134e29
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.Analyzer/Redpoint.CloudFramework.Analyzer.csproj
@@ -0,0 +1,18 @@
+
+
+
+ netstandard2.0
+ false
+
+
+ *$(MSBuildProjectFile)*
+
+ $(NoWarn);CA1824
+
+
+
+
+
+
+
+
diff --git a/UET/Redpoint.CloudFramework.Tests.Shared/CloudFrameworkTestEnvironment.cs b/UET/Redpoint.CloudFramework.Tests.Shared/CloudFrameworkTestEnvironment.cs
index 33eec358..8304193b 100644
--- a/UET/Redpoint.CloudFramework.Tests.Shared/CloudFrameworkTestEnvironment.cs
+++ b/UET/Redpoint.CloudFramework.Tests.Shared/CloudFrameworkTestEnvironment.cs
@@ -344,22 +344,22 @@ public Task MutateEntityBeforeWrite(string @namespace, Entity entity)
return Task.CompletedTask;
}
- public Task PostCreate(string @namespace, T model, IModelTransaction? transaction) where T : Model, new()
+ public Task PostCreate(string @namespace, T model, IModelTransaction? transaction) where T : class, IModel, new()
{
return Task.CompletedTask;
}
- public Task PostDelete(string @namespace, T model, IModelTransaction? transaction) where T : Model, new()
+ public Task PostDelete(string @namespace, T model, IModelTransaction? transaction) where T : class, IModel, new()
{
return Task.CompletedTask;
}
- public Task PostUpdate(string @namespace, T model, IModelTransaction? transaction) where T : Model, new()
+ public Task PostUpdate(string @namespace, T model, IModelTransaction? transaction) where T : class, IModel, new()
{
return Task.CompletedTask;
}
- public Task PostUpsert(string @namespace, T model, IModelTransaction? transaction) where T : Model, new()
+ public Task PostUpsert(string @namespace, T model, IModelTransaction? transaction) where T : class, IModel, new()
{
return Task.CompletedTask;
}
diff --git a/UET/Redpoint.CloudFramework.Tests/BadModel.cs b/UET/Redpoint.CloudFramework.Tests/BadModel.cs
index c155f662..a7a5aa02 100644
--- a/UET/Redpoint.CloudFramework.Tests/BadModel.cs
+++ b/UET/Redpoint.CloudFramework.Tests/BadModel.cs
@@ -2,8 +2,8 @@
namespace Redpoint.CloudFramework.Tests
{
- [Kind("cf_badModel")]
- public class BadModel : AttributedModel
+ [Kind("cf_badModel")]
+ public sealed class BadModel : Model
{
[Type(FieldType.String), Indexed]
public object? badField { get; set; }
diff --git a/UET/Redpoint.CloudFramework.Tests/DefaultedBypassModel.cs b/UET/Redpoint.CloudFramework.Tests/DefaultedBypassModel.cs
index ff5455b3..a529546b 100644
--- a/UET/Redpoint.CloudFramework.Tests/DefaultedBypassModel.cs
+++ b/UET/Redpoint.CloudFramework.Tests/DefaultedBypassModel.cs
@@ -2,8 +2,8 @@
namespace Redpoint.CloudFramework.Tests
{
- [Kind("cf_defaultedModel")]
- public class DefaultedBypassModel : AttributedModel
+ [Kind("cf_defaultedModel")]
+ public sealed class DefaultedBypassModel : Model
{
[Type(FieldType.String), Indexed]
public string? myString { get; set; }
diff --git a/UET/Redpoint.CloudFramework.Tests/DefaultedInvalidModel.cs b/UET/Redpoint.CloudFramework.Tests/DefaultedInvalidModel.cs
index 7fe6ef0b..36dc1d3c 100644
--- a/UET/Redpoint.CloudFramework.Tests/DefaultedInvalidModel.cs
+++ b/UET/Redpoint.CloudFramework.Tests/DefaultedInvalidModel.cs
@@ -2,8 +2,8 @@
namespace Redpoint.CloudFramework.Tests
{
- [Kind("cf_defaultedModel")]
- public class DefaultedInvalidModel : AttributedModel
+ [Kind("cf_defaultedModel")]
+ public sealed class DefaultedInvalidModel : Model
{
[Type(FieldType.String), Indexed]
public string? myString { get; set; }
diff --git a/UET/Redpoint.CloudFramework.Tests/DefaultedModel.cs b/UET/Redpoint.CloudFramework.Tests/DefaultedModel.cs
index 587d7cbe..28a4da37 100644
--- a/UET/Redpoint.CloudFramework.Tests/DefaultedModel.cs
+++ b/UET/Redpoint.CloudFramework.Tests/DefaultedModel.cs
@@ -2,8 +2,8 @@
namespace Redpoint.CloudFramework.Tests
{
- [Kind("cf_defaultedModel")]
- public class DefaultedModel : AttributedModel
+ [Kind("cf_defaultedModel")]
+ public sealed class DefaultedModel : Model
{
// This model exists to ensure AttributedModel initializes
// properties to their default values.
diff --git a/UET/Redpoint.CloudFramework.Tests/EmbeddedEntityModel.cs b/UET/Redpoint.CloudFramework.Tests/EmbeddedEntityModel.cs
index 915cd671..de129363 100644
--- a/UET/Redpoint.CloudFramework.Tests/EmbeddedEntityModel.cs
+++ b/UET/Redpoint.CloudFramework.Tests/EmbeddedEntityModel.cs
@@ -5,8 +5,8 @@
using Redpoint.CloudFramework.Models;
using System;
- [Kind("cf_embeddedEntityModel")]
- public class EmbeddedEntityModel : AttributedModel
+ [Kind("cf_embeddedEntityModel")]
+ public sealed class EmbeddedEntityModel : Model
{
[Type(FieldType.String), Indexed]
public string? forTest { get; set; }
diff --git a/UET/Redpoint.CloudFramework.Tests/GeoDenseModel.cs b/UET/Redpoint.CloudFramework.Tests/GeoDenseModel.cs
index d0a6a88e..1532c28b 100644
--- a/UET/Redpoint.CloudFramework.Tests/GeoDenseModel.cs
+++ b/UET/Redpoint.CloudFramework.Tests/GeoDenseModel.cs
@@ -4,8 +4,8 @@
namespace Redpoint.CloudFramework.Tests
{
- [Kind("cf_geoDenseModel")]
- public class GeoDenseModel : AttributedModel
+ [Kind("cf_geoDenseModel")]
+ public sealed class GeoDenseModel : Model
{
[Type(FieldType.String), Indexed]
public string? forTest { get; set; }
diff --git a/UET/Redpoint.CloudFramework.Tests/GeoSparseModel.cs b/UET/Redpoint.CloudFramework.Tests/GeoSparseModel.cs
index ed954bb4..fd5d63e6 100644
--- a/UET/Redpoint.CloudFramework.Tests/GeoSparseModel.cs
+++ b/UET/Redpoint.CloudFramework.Tests/GeoSparseModel.cs
@@ -4,8 +4,8 @@
namespace Redpoint.CloudFramework.Tests
{
- [Kind("cf_geoSparseModel")]
- public class GeoSparseModel : AttributedModel
+ [Kind("cf_geoSparseModel")]
+ public sealed class GeoSparseModel : Model
{
[Type(FieldType.String), Indexed]
public string? forTest { get; set; }
diff --git a/UET/Redpoint.CloudFramework.Tests/RedisCacheRepositoryLayerTests.cs b/UET/Redpoint.CloudFramework.Tests/RedisCacheRepositoryLayerTests.cs
index 56708daf..d5cee85d 100644
--- a/UET/Redpoint.CloudFramework.Tests/RedisCacheRepositoryLayerTests.cs
+++ b/UET/Redpoint.CloudFramework.Tests/RedisCacheRepositoryLayerTests.cs
@@ -46,67 +46,67 @@ private static async Task HandleEventualConsistency(Func task)
await task().ConfigureAwait(true);
}
- [Kind("cf_TestLoadedEntityMatchesCreatedEntity")]
+ [Kind("cf_TestLoadedEntityMatchesCreatedEntity")]
private class TestLoadedEntityMatchesCreatedEntity_Model : RedisTestModel { }
- [Kind("cf_TestLoadedEntityIsInCache")]
+ [Kind("cf_TestLoadedEntityIsInCache")]
private class TestLoadedEntityIsInCache_Model : RedisTestModel { }
- [Kind("cf_TestMultipleEntityLoadWorks")]
+ [Kind("cf_TestMultipleEntityLoadWorks")]
private class TestMultipleEntityLoadWorks_Model : RedisTestModel { }
- [Kind("cf_TestMultipleEntityLoadWorksWithoutCacheClear")]
+ [Kind("cf_TestMultipleEntityLoadWorksWithoutCacheClear")]
private class TestMultipleEntityLoadWorksWithoutCacheClear_Model : RedisTestModel { }
- [Kind("cf_TestUpdatedEntityIsNotInCache")]
+ [Kind("cf_TestUpdatedEntityIsNotInCache")]
private class TestUpdatedEntityIsNotInCache_Model : RedisTestModel { }
- [Kind("cf_TestUpsertedEntityIsNotInCache")]
+ [Kind("cf_TestUpsertedEntityIsNotInCache")]
private class TestUpsertedEntityIsNotInCache_Model : RedisTestModel { }
- [Kind("cf_TestDeletedEntityIsNotInCache")]
+ [Kind("cf_TestDeletedEntityIsNotInCache")]
private class TestDeletedEntityIsNotInCache_Model : RedisTestModel { }
- [Kind("cf_TestCreateThenQuery")]
+ [Kind("cf_TestCreateThenQuery")]
private class TestCreateThenQuery_Model : RedisTestModel { }
- [Kind("cf_TestCreateThenQueryThenUpdateThenQuery")]
+ [Kind("cf_TestCreateThenQueryThenUpdateThenQuery")]
private class TestCreateThenQueryThenUpdateThenQuery_Model : RedisTestModel { }
- [Kind("cf_TestReaderCountIsSetWhileReading")]
+ [Kind("cf_TestReaderCountIsSetWhileReading")]
private class TestReaderCountIsSetWhileReading_Model : RedisTestModel { }
- [Kind("cf_TestTransactionalUpdateInvalidatesQuery")]
+ [Kind("cf_TestTransactionalUpdateInvalidatesQuery")]
private class TestTransactionalUpdateInvalidatesQuery_Model : RedisTestModel { }
- [Kind("cf_TestCreateInvalidatesQuery")]
+ [Kind("cf_TestCreateInvalidatesQuery")]
private class TestCreateInvalidatesQuery_Model : RedisTestModel { }
- [Kind("cf_TestUpdateWithNoOriginalDataDoesNotCrash")]
+ [Kind("cf_TestUpdateWithNoOriginalDataDoesNotCrash")]
private class TestUpdateWithNoOriginalDataDoesNotCrash_Model : RedisTestModel { }
- [Kind("cf_TestUpdateInvalidatesRelevantQuery")]
+ [Kind("cf_TestUpdateInvalidatesRelevantQuery")]
private class TestUpdateInvalidatesRelevantQuery_Model : RedisTestModel { }
- [Kind("cf_TestUpdateDoesNotInvalidateIrrelevantQuery")]
+ [Kind("cf_TestUpdateDoesNotInvalidateIrrelevantQuery")]
private class TestUpdateDoesNotInvalidateIrrelevantQuery_Model : RedisTestModel { }
- [Kind("cf_TestTransactionalUpdateDoesNotInvalidateCacheUntilCommit")]
+ [Kind("cf_TestTransactionalUpdateDoesNotInvalidateCacheUntilCommit")]
private class TestTransactionalUpdateDoesNotInvalidateCacheUntilCommit_Model : RedisTestModel { }
- [Kind("cf_TestTransactionalUpdateFromNull")]
+ [Kind("cf_TestTransactionalUpdateFromNull")]
private class TestTransactionalUpdateFromNull_Model : RedisTestModel { }
- [Kind("cf_TestNonTransactionalUpdateFromNull")]
+ [Kind("cf_TestNonTransactionalUpdateFromNull")]
private class TestNonTransactionalUpdateFromNull_Model : RedisTestModel { }
- [Kind("cf_TestQueryOrdering")]
+ [Kind("cf_TestQueryOrdering")]
private class TestQueryOrdering_Model : RedisTestModel { }
- [Kind("cf_TestQueryEverything")]
+ [Kind("cf_TestQueryEverything")]
private class TestQueryEverything_Model : RedisTestModel { }
- [Kind("cf_TestDeletedEntityIsNotInCachedQueryEverything")]
+ [Kind("cf_TestDeletedEntityIsNotInCachedQueryEverything")]
private class TestDeletedEntityIsNotInCachedQueryEverything_Model : RedisTestModel { }
[Fact]
diff --git a/UET/Redpoint.CloudFramework.Tests/RedisTestModel.cs b/UET/Redpoint.CloudFramework.Tests/RedisTestModel.cs
index ab169b1f..cbedc896 100644
--- a/UET/Redpoint.CloudFramework.Tests/RedisTestModel.cs
+++ b/UET/Redpoint.CloudFramework.Tests/RedisTestModel.cs
@@ -4,8 +4,9 @@
namespace Redpoint.CloudFramework.Tests
{
- [Kind("cf_redisTest")]
- public class RedisTestModel : AttributedModel
+ [Kind("cf_redisTest")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Cloud Framework", "CloudFrameworkModelAnalyzerSealed:Types implementing Model should be sealed", Justification = "Internal testing.")]
+ public class RedisTestModel : Model
{
[Type(FieldType.String), Indexed]
public string? forTest { get; set; }
diff --git a/UET/Redpoint.CloudFramework.Tests/Redpoint.CloudFramework.Tests.csproj b/UET/Redpoint.CloudFramework.Tests/Redpoint.CloudFramework.Tests.csproj
index c934857b..2913bad1 100644
--- a/UET/Redpoint.CloudFramework.Tests/Redpoint.CloudFramework.Tests.csproj
+++ b/UET/Redpoint.CloudFramework.Tests/Redpoint.CloudFramework.Tests.csproj
@@ -4,6 +4,10 @@
+
+ false
+ Analyzer
+
diff --git a/UET/Redpoint.CloudFramework.Tests/StringEnumRepositoryLayerTests.cs b/UET/Redpoint.CloudFramework.Tests/StringEnumRepositoryLayerTests.cs
index 5611cc0b..7f52d1b2 100644
--- a/UET/Redpoint.CloudFramework.Tests/StringEnumRepositoryLayerTests.cs
+++ b/UET/Redpoint.CloudFramework.Tests/StringEnumRepositoryLayerTests.cs
@@ -22,8 +22,8 @@ internal class TestStringEnum : StringEnum
public static readonly StringEnumValue C = Create("c");
}
- [Kind("testString")]
- internal class TestStringModel : AttributedModel
+ [Kind("testString")]
+ internal sealed class TestStringModel : Model
{
[Type(FieldType.String), Indexed, Default("a")]
public StringEnumValue enumValue { get; set; } = TestStringEnum.A;
diff --git a/UET/Redpoint.CloudFramework.Tests/TestModel.cs b/UET/Redpoint.CloudFramework.Tests/TestModel.cs
index 9265839c..216b0dcb 100644
--- a/UET/Redpoint.CloudFramework.Tests/TestModel.cs
+++ b/UET/Redpoint.CloudFramework.Tests/TestModel.cs
@@ -6,13 +6,8 @@ namespace Redpoint.CloudFramework.Tests
{
// This ensures the tests pass when using properties on base classes.
- [Kind("cf_testModel")]
- public class TestModel : TestBaseModel
- {
- }
-
- [Kind("cf_testBaseModel")]
- public class TestBaseModel : AttributedModel
+ [Kind("cf_testModel")]
+ public sealed class TestModel : Model
{
[Type(FieldType.String), Indexed]
public string? forTest { get; set; }
@@ -35,7 +30,9 @@ public class TestBaseModel : AttributedModel
public TestModel? untracked { get; set; }
[Type(FieldType.String), Indexed]
+#pragma warning disable CS0628 // New protected member declared in sealed type
protected string? protectedString1 { get; set; }
+#pragma warning restore CS0628 // New protected member declared in sealed type
[Type(FieldType.String), Indexed]
private string? privateString1 { get; set; }
diff --git a/UET/Redpoint.CloudFramework/Counter/DefaultShardedCounterModel.cs b/UET/Redpoint.CloudFramework/Counter/DefaultShardedCounterModel.cs
index 66dc7456..7298c86f 100644
--- a/UET/Redpoint.CloudFramework/Counter/DefaultShardedCounterModel.cs
+++ b/UET/Redpoint.CloudFramework/Counter/DefaultShardedCounterModel.cs
@@ -3,8 +3,8 @@
using Redpoint.CloudFramework.Models;
using System;
- [Kind("_rcfShardedCounter")]
- internal class DefaultShardedCounterModel : AttributedModel
+ [Kind("_rcfShardedCounter")]
+ internal sealed class DefaultShardedCounterModel : Model
{
///
/// The counter name shared amongst all shards of this counter.
diff --git a/UET/Redpoint.CloudFramework/Counter/IShardedCounterService.cs b/UET/Redpoint.CloudFramework/Counter/IShardedCounterService.cs
deleted file mode 100644
index 21e7548a..00000000
--- a/UET/Redpoint.CloudFramework/Counter/IShardedCounterService.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-namespace Redpoint.CloudFramework.Counter
-{
- using Redpoint.CloudFramework.Models;
- using Redpoint.CloudFramework.Repository.Transaction;
- using System;
- using System.Diagnostics.CodeAnalysis;
- using System.Threading.Tasks;
-
- [Obsolete("Use IShardedCounter instead.")]
- public interface IShardedCounterService
- {
- ///
- /// Returns the value of a sharded counter.
- ///
- /// The name of the sharded counter.
- /// The value of the sharded counter.
- Task Get(string name);
-
- ///
- /// Returns the value of a sharded counter stored in a custom model.
- ///
- /// The model that is used to store sharded counter data.
- /// The name of the sharded counter.
- /// The value of the sharded counter.
- Task GetCustom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(string name) where T : Model, IShardedCounterModel, new();
-
- ///
- /// Adjust the value of a sharded counter.
- ///
- /// The name of the sharded counter.
- /// The amount to modify the sharded counter by.
- /// The task to await on.
- Task Adjust(string name, long modifier);
-
- ///
- /// Adjust the value of a sharded counter stored in a custom model.
- ///
- /// The model that is used to store sharded counter data.
- /// The name of the sharded counter.
- /// The amount to modify the sharded counter by.
- /// The task to await on.
- Task AdjustCustom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(string name, long modifier) where T : Model, IShardedCounterModel, new();
-
- ///
- /// Adjust the value of a sharded counter inside an existing transaction. You *must* await this
- /// function and call the callback it returns after you commit the provided transaction.
- ///
- /// The name of the sharded counter.
- /// The amount to modify the sharded counter by.
- /// The existing transaction to update the counter in.
- /// The task to await on.
- Task> Adjust(string name, long modifier, IModelTransaction existingTransaction);
-
- ///
- /// Adjust the value of a sharded counter stored in a custom model, inside an existing transaction.
- /// You *must* await this function and call the callback it returns after you commit the provided transaction.
- ///
- /// The name of the sharded counter.
- /// The amount to modify the sharded counter by.
- /// The existing transaction to update the counter in.
- /// The task to await on.
- Task> AdjustCustom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(string name, long modifier, IModelTransaction existingTransaction) where T : Model, IShardedCounterModel, new();
- }
-}
diff --git a/UET/Redpoint.CloudFramework/Counter/LegacyShardedCounterModel.cs b/UET/Redpoint.CloudFramework/Counter/LegacyShardedCounterModel.cs
deleted file mode 100644
index 7163efa4..00000000
--- a/UET/Redpoint.CloudFramework/Counter/LegacyShardedCounterModel.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using Redpoint.CloudFramework.Models;
-
-namespace Redpoint.CloudFramework.Counter
-{
- using System.Collections.Generic;
- using System.Globalization;
-
- [Obsolete]
- internal class LegacyShardedCounterModel : Model, IShardedCounterModel
- {
- public long? count { get; set; }
-
- public override string GetKind()
- {
- return "RCF-SharedCounter";
- }
-
- public override long GetSchemaVersion()
- {
- return 1;
- }
-
- public override Dictionary GetTypes()
- {
- return new Dictionary {
- { "count", FieldType.Integer }
- };
- }
-
- public override HashSet GetIndexes()
- {
- return new HashSet
- {
- "count",
- };
- }
-
- public string? GetTypeFieldName()
- {
- return null;
- }
-
- public string GetCountFieldName()
- {
- return "count";
- }
-
- public string FormatShardName(string name, int index)
- {
- return string.Format(CultureInfo.InvariantCulture, "shard-{0}-{1}", name, index);
- }
- }
-}
\ No newline at end of file
diff --git a/UET/Redpoint.CloudFramework/Counter/LegacyShardedCounterService.cs b/UET/Redpoint.CloudFramework/Counter/LegacyShardedCounterService.cs
deleted file mode 100644
index c6523e76..00000000
--- a/UET/Redpoint.CloudFramework/Counter/LegacyShardedCounterService.cs
+++ /dev/null
@@ -1,224 +0,0 @@
-namespace Redpoint.CloudFramework.Counter
-{
- using Google.Cloud.Datastore.V1;
- using Redpoint.CloudFramework.Models;
- using Redpoint.CloudFramework.Repository;
- using Redpoint.CloudFramework.Repository.Transaction;
- using StackExchange.Redis;
- using System;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using System.Reflection;
- using System.Threading.Tasks;
-
-#pragma warning disable CS0612 // Type or member is obsolete
-#pragma warning disable CS0618 // Type or member is obsolete
- internal class LegacyShardedCounterService : IShardedCounterService
- {
- private readonly IRepository _repository;
- private readonly IDatabase _redisDatabase;
- private readonly Random _random;
-
- // This can only ever be increased; not decreased.
- public const int NumShards = 60;
-
- private const bool _enableRedis = true;
-
- public LegacyShardedCounterService(
- IRepository repository,
- IConnectionMultiplexer connectionMultiplexer)
- {
- ArgumentNullException.ThrowIfNull(connectionMultiplexer);
-
- _repository = repository;
- _redisDatabase = connectionMultiplexer.GetDatabase();
- _random = new Random();
- }
-
- private async IAsyncEnumerable GetAllKeys(string name) where T : Model, IShardedCounterModel, new()
- {
- var t = new T();
-
- var keyFactory = await _repository.GetKeyFactoryAsync().ConfigureAwait(false);
- for (var i = 0; i < NumShards; i++)
- {
- yield return keyFactory.CreateKey(t.FormatShardName(name, i));
- }
- }
-
- public async Task AdjustCustom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(string name, long modifier) where T : Model, IShardedCounterModel, new()
- {
- var t = new T();
-
- var counterProperty = typeof(T).GetProperty(t.GetCountFieldName(), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
- if (counterProperty == null)
- {
- throw new InvalidOperationException($"The count field name specified by GetCountFieldName '{t.GetCountFieldName()}' does not exist on the class.");
- }
-
- var typeName = t.GetTypeFieldName();
- var typeProperty = typeName == null ? null : typeof(T).GetProperty(typeName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
-
-#pragma warning disable CA5394 // Do not use insecure randomness
- var idx = _random.Next(0, NumShards);
-#pragma warning restore CA5394 // Do not use insecure randomness
- var keyFactory = await _repository.GetKeyFactoryAsync().ConfigureAwait(false);
- var key = keyFactory.CreateKey(t.FormatShardName(name, idx));
- var transaction = await _repository.BeginTransactionAsync().ConfigureAwait(false);
- var rollback = true;
- try
- {
- var create = false;
- var counter = await _repository.LoadAsync(key, transaction).ConfigureAwait(false);
- if (counter == null)
- {
- counter = new T
- {
- Key = key,
- };
- counterProperty.SetValue(counter, modifier);
- if (typeProperty != null)
- {
- typeProperty.SetValue(counter, "shard");
- }
- create = true;
- }
- else
- {
- counterProperty.SetValue(counter, ((long?)counterProperty.GetValue(counter) ?? 0) + modifier);
- }
- if (create)
- {
- await _repository.CreateAsync(counter, transaction).ConfigureAwait(false);
- }
- else
- {
- await _repository.UpdateAsync(counter, transaction).ConfigureAwait(false);
- }
- await _repository.CommitAsync(transaction).ConfigureAwait(false);
- if (_enableRedis)
- {
- await _redisDatabase.StringIncrementAsync("shard-" + name, modifier, CommandFlags.FireAndForget).ConfigureAwait(false);
- }
- rollback = false;
- }
- finally
- {
- if (rollback)
- {
- await transaction.Transaction.RollbackAsync().ConfigureAwait(false);
- }
- }
- }
-
-
- public async Task> AdjustCustom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(string name, long modifier, IModelTransaction existingTransaction) where T : Model, IShardedCounterModel, new()
- {
- var t = new T();
-
- var counterProperty = typeof(T).GetProperty(t.GetCountFieldName(), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
- if (counterProperty == null)
- {
- throw new InvalidOperationException($"The count field name specified by GetCountFieldName '{t.GetCountFieldName()}' does not exist on the class.");
- }
-
- var typeName = t.GetTypeFieldName();
- var typeProperty = typeName == null ? null : typeof(T).GetProperty(typeName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
-
-#pragma warning disable CA5394 // Do not use insecure randomness
- var idx = _random.Next(0, NumShards);
-#pragma warning restore CA5394 // Do not use insecure randomness
- var keyFactory = await _repository.GetKeyFactoryAsync().ConfigureAwait(false);
- var key = keyFactory.CreateKey(t.FormatShardName(name, idx));
-
- var create = false;
- var counter = await _repository.LoadAsync(key, existingTransaction).ConfigureAwait(false);
- if (counter == null)
- {
- counter = new T
- {
- Key = key,
- };
- counterProperty.SetValue(counter, modifier);
- if (typeProperty != null)
- {
- typeProperty.SetValue(counter, "shard");
- }
- create = true;
- }
- else
- {
- counterProperty.SetValue(counter, ((long?)counterProperty.GetValue(counter) ?? 0) + modifier);
- }
- if (create)
- {
- await _repository.CreateAsync(counter, existingTransaction).ConfigureAwait(false);
- }
- else
- {
- await _repository.UpdateAsync(counter, existingTransaction).ConfigureAwait(false);
- }
- return async () =>
- {
- if (_enableRedis)
- {
- await _redisDatabase.StringIncrementAsync("shard-" + name, modifier, CommandFlags.FireAndForget).ConfigureAwait(false);
- }
- };
- }
-
- public async Task GetCustom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(string name) where T : Model, IShardedCounterModel, new()
- {
- var t = new T();
-
- var counterProperty = typeof(T).GetProperty(t.GetCountFieldName(), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
- if (counterProperty == null)
- {
- throw new InvalidOperationException($"The count field name specified by GetCountFieldName '{t.GetCountFieldName()}' does not exist on the class.");
- }
-
- long total;
- if (_enableRedis)
- {
- var shardCache = await _redisDatabase.StringGetAsync("shard-" + name).ConfigureAwait(false);
- if (!(!shardCache.HasValue || !shardCache.IsInteger || !shardCache.TryParse(out total)))
- {
- return total;
- }
- }
-
- total =
- await _repository.LoadAsync(GetAllKeys(name))
- .Where(x => x.Value != null)
- .Select(x => (long?)counterProperty.GetValue(x.Value) ?? 0)
- .SumAsync().ConfigureAwait(false);
- if (_enableRedis)
- {
- await _redisDatabase.StringSetAsync(
- "shard-" + name,
- total,
- TimeSpan.FromSeconds(60),
- When.NotExists).ConfigureAwait(false);
- }
- return total;
- }
-
- public Task Get(string name)
- {
- return GetCustom(name);
- }
-
- public Task Adjust(string name, long modifier)
- {
- return AdjustCustom(name, modifier);
- }
-
- public Task> Adjust(string name, long modifier, IModelTransaction existingTransaction)
- {
- return AdjustCustom(name, modifier, existingTransaction);
- }
- }
-#pragma warning restore CS0612 // Type or member is obsolete
-#pragma warning restore CS0618 // Type or member is obsolete
-}
diff --git a/UET/Redpoint.CloudFramework/Event/EventApi.cs b/UET/Redpoint.CloudFramework/Event/EventApi.cs
index b1f46266..cf4e01fc 100644
--- a/UET/Redpoint.CloudFramework/Event/EventApi.cs
+++ b/UET/Redpoint.CloudFramework/Event/EventApi.cs
@@ -116,7 +116,7 @@ private SerializedEvent SerializeEvent(Event data)
return o;
}
- if (entity is Model m)
+ if (entity is IModel m)
{
// TODO
return null;
diff --git a/UET/Redpoint.CloudFramework/Models/DefaultLockModel.cs b/UET/Redpoint.CloudFramework/Models/DefaultLockModel.cs
index 4a30f9a4..6a5615cc 100644
--- a/UET/Redpoint.CloudFramework/Models/DefaultLockModel.cs
+++ b/UET/Redpoint.CloudFramework/Models/DefaultLockModel.cs
@@ -3,38 +3,14 @@
using NodaTime;
using System.Collections.Generic;
- public class DefaultLockModel : Model
+ [Kind("Lock")]
+ public class DefaultLockModel : Model
{
// The lock key is part of the name in the key.
+ [Type(FieldType.Timestamp), Indexed]
public Instant? dateExpiresUtc { get; set; }
- public string? acquisitionGuid { get; set; }
-
- public override string GetKind()
- {
- return "Lock";
- }
-
- public override long GetSchemaVersion()
- {
- return 1;
- }
- public override Dictionary GetTypes()
- {
- return new Dictionary
- {
- { "dateExpiresUtc", FieldType.Timestamp },
- { "acquisitionGuid", FieldType.String },
- };
- }
-
- public override HashSet GetIndexes()
- {
- return new HashSet
- {
- "dateExpiresUtc",
- "acquisitionGuid"
- };
- }
+ [Type(FieldType.String), Indexed]
+ public string? acquisitionGuid { get; set; }
}
}
diff --git a/UET/Redpoint.CloudFramework/Models/IModel.cs b/UET/Redpoint.CloudFramework/Models/IModel.cs
new file mode 100644
index 00000000..275e4393
--- /dev/null
+++ b/UET/Redpoint.CloudFramework/Models/IModel.cs
@@ -0,0 +1,28 @@
+namespace Redpoint.CloudFramework.Models
+{
+ using Google.Cloud.Datastore.V1;
+ using NodaTime;
+ using System;
+ using System.Collections.Generic;
+ using System.Reflection;
+
+ public interface IModel
+ {
+ Key Key { get; set; }
+ Instant? dateCreatedUtc { get; internal set; }
+ Instant? dateModifiedUtc { get; internal set; }
+ long? schemaVersion { get; set; }
+
+ internal Dictionary? _originalData { get; set; }
+
+ internal string GetKind();
+ internal Dictionary GetTypes();
+ internal HashSet GetIndexes();
+ internal Dictionary? GetDefaultValues();
+ internal PropertyInfo[] GetPropertyInfos();
+ internal PropertyInfo? GetPropertyInfo(string name);
+
+ long GetSchemaVersion();
+ string GetDatastoreNamespaceForLocalKeys();
+ }
+}
diff --git a/UET/Redpoint.CloudFramework/Models/IShardedCounterModel.cs b/UET/Redpoint.CloudFramework/Models/IShardedCounterModel.cs
deleted file mode 100644
index 46e03f30..00000000
--- a/UET/Redpoint.CloudFramework/Models/IShardedCounterModel.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-namespace Redpoint.CloudFramework.Models
-{
- [Obsolete("This interface is no longer used by IShardedCounter and IGlobalShardedCounter.")]
- public interface IShardedCounterModel
- {
- ///
- /// If specified, this Datastore field on the entity will have it's value set to "shard".
- ///
- /// The Datastore field name, or null for no field.
- string? GetTypeFieldName();
-
- ///
- /// The name of the field to actually store the count in. The bypasses
- /// the regular ORM datastore layer for performance, so it needs to explicitly know the Datastore field name here.
- ///
- /// The Datastore field name.
- string GetCountFieldName();
-
- ///
- /// Converts the sharded counter name and shard index into the name for the Datastore key.
- ///
- /// The sharded counter name.
- /// The index of the shard in the counter.
- /// The formatted name.
- string FormatShardName(string name, int index);
- }
-}
diff --git a/UET/Redpoint.CloudFramework/Models/KindAttribute.cs b/UET/Redpoint.CloudFramework/Models/KindAttribute.cs
index 3f4443e0..c5cd7d12 100644
--- a/UET/Redpoint.CloudFramework/Models/KindAttribute.cs
+++ b/UET/Redpoint.CloudFramework/Models/KindAttribute.cs
@@ -7,8 +7,7 @@
/// Sets the kind of the entity when this model is stored in Datastore.
///
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
- public sealed class KindAttribute<
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T> : Attribute, IKindAttribute
+ public sealed class KindAttribute : Attribute
{
public KindAttribute(string kind)
{
@@ -16,16 +15,5 @@ public KindAttribute(string kind)
}
public string Kind { get; }
-
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
- public Type Type => typeof(T);
- }
-
- internal interface IKindAttribute
- {
- string Kind { get; }
-
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
- Type Type { get; }
}
}
diff --git a/UET/Redpoint.CloudFramework/Models/Model.cs b/UET/Redpoint.CloudFramework/Models/Model.cs
index 3dc24336..1ce292b1 100644
--- a/UET/Redpoint.CloudFramework/Models/Model.cs
+++ b/UET/Redpoint.CloudFramework/Models/Model.cs
@@ -2,46 +2,100 @@
{
using Google.Cloud.Datastore.V1;
using NodaTime;
+ using Redpoint.CloudFramework.Repository.Converters.Value;
+ using Redpoint.CloudFramework.Repository.Converters.Value.Context;
+ using Redpoint.CloudFramework.Repository.Geographic;
using System;
using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Linq;
+ using System.Reflection;
- public abstract class Model
+ ///
+ /// A version of Model that you can inherit from, where the Datastore schema is defined
+ /// by attributes on the class and properties instead of implementing the abstract Model
+ /// methods.
+ ///
+ /// Implements caching so that when the application has to determine the schema from the
+ /// model class, it's slightly faster than the naive implementation of returning newly
+ /// constructed objects from the Model methods.
+ ///
+ public class Model<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T> : IModel, IGeoModel where T : Model
{
- // Declaring this field as nullable would make 99% of reading code
- // overly verbose handling scenarios that can never happen (it will never
- // be null for entities loaded from the database). The only time that
- // the key can be null is if you are creating an entity and haven't
- // called CreateAsync yet.
-#pragma warning disable CS8618
+ private readonly ModelInfo _modelInfo;
+
+ ///
+ /// The key for the entity in the database.
+ ///
+ ///
+ /// Declaring this field as nullable would make 99% of reading code overly verbose handling scenarios that can never happen (it will never be null for entities loaded from the database). The only time that the key can be null is if you are creating an entity and haven't called CreateAsync yet.
+ ///
public Key Key { get; set; }
-#pragma warning restore CS8618
- public Instant? dateCreatedUtc { get; internal set; }
- public Instant? dateModifiedUtc { get; internal set; }
+ ///
+ /// The date that the entity was created. Setting this field has no effect when updating entities.
+ ///
+ public Instant? dateCreatedUtc { get; set; }
+
+ ///
+ /// The date that the entity was last modified. Setting this field has no effect when updating entities.
+ ///
+ public Instant? dateModifiedUtc { get; set; }
+
+ ///
+ /// The schema version; this is used to detect when an entity requires a migration.
+ ///
public long? schemaVersion { get; set; }
///
- /// The original entity when it was loaded; this is used to clear caches
- /// when appropriate.
+ /// Tracks the original data when the entity was loaded so that caches can be correctly invalidated upon write.
///
- internal Dictionary? _originalData { get; set; }
+ Dictionary? IModel._originalData { get; set; }
+
+#pragma warning disable CS8618
+ public Model()
+#pragma warning restore CS8618
+ {
+ _modelInfo = ModelInfoRegistry.InitModel(this);
+ }
- public abstract string GetKind();
+ [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "This method will not be called by child classes.")]
+ string IModel.GetKind() => _modelInfo._kind;
- public abstract Dictionary GetTypes();
+ ///
+ /// This accessor is only for unit tests so they don't need to cast to IModel.
+ ///
+ internal string GetKind() => _modelInfo._kind;
- public abstract HashSet GetIndexes();
+ [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "This method will not be called by child classes.")]
+ Dictionary IModel.GetTypes() => _modelInfo._types;
- public abstract long GetSchemaVersion();
+ [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "This method will not be called by child classes.")]
+ HashSet IModel.GetIndexes() => _modelInfo._indexes;
- public virtual string GetDatastoreNamespaceForLocalKeys()
+ [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "This method will not be called by child classes.")]
+ Dictionary? IModel.GetDefaultValues() => _modelInfo._defaultValues;
+
+ [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "This method will not be called by child classes.")]
+ PropertyInfo[] IModel.GetPropertyInfos() => _modelInfo._propertyInfos;
+
+ [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "This method will not be called by child classes.")]
+ PropertyInfo? IModel.GetPropertyInfo(string name)
{
- throw new NotSupportedException("This model has a property of type 'local-key', but does not implement GetDatastoreNamespaceForLocalKeys");
+ if (_modelInfo._propertyInfoByName.TryGetValue(name, out var propertyInfo))
+ {
+ return propertyInfo;
+ }
+ return null;
}
- public virtual Dictionary? GetDefaultValues()
+ public virtual long GetSchemaVersion() => _modelInfo._schemaVersion;
+
+ public virtual string GetDatastoreNamespaceForLocalKeys()
{
- return null;
+ throw new NotSupportedException("This model has a property of type 'local-key', but does not implement GetDatastoreNamespaceForLocalKeys");
}
+
+ public virtual Dictionary GetHashKeyLengthsForGeopointFields() => _modelInfo._geoHashKeyLengths;
}
}
diff --git a/UET/Redpoint.CloudFramework/Models/ModelInfo.cs b/UET/Redpoint.CloudFramework/Models/ModelInfo.cs
new file mode 100644
index 00000000..6fa96091
--- /dev/null
+++ b/UET/Redpoint.CloudFramework/Models/ModelInfo.cs
@@ -0,0 +1,17 @@
+namespace Redpoint.CloudFramework.Models
+{
+ using System.Collections.Generic;
+ using System.Reflection;
+
+ internal sealed class ModelInfo
+ {
+ public required long _schemaVersion;
+ public required string _kind;
+ public required HashSet _indexes;
+ public required Dictionary _types;
+ public required Dictionary _defaultValues;
+ public required Dictionary _geoHashKeyLengths;
+ public required PropertyInfo[] _propertyInfos;
+ public required Dictionary _propertyInfoByName;
+ }
+}
diff --git a/UET/Redpoint.CloudFramework/Models/AttributedModel.cs b/UET/Redpoint.CloudFramework/Models/ModelInfoRegistry.cs
similarity index 52%
rename from UET/Redpoint.CloudFramework/Models/AttributedModel.cs
rename to UET/Redpoint.CloudFramework/Models/ModelInfoRegistry.cs
index 784700b2..437b9c73 100644
--- a/UET/Redpoint.CloudFramework/Models/AttributedModel.cs
+++ b/UET/Redpoint.CloudFramework/Models/ModelInfoRegistry.cs
@@ -1,76 +1,89 @@
namespace Redpoint.CloudFramework.Models
{
- using Redpoint.CloudFramework.Repository.Converters.Value;
using Redpoint.CloudFramework.Repository.Converters.Value.Context;
- using Redpoint.CloudFramework.Repository.Geographic;
+ using Redpoint.CloudFramework.Repository.Converters.Value;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
+ using System.Text;
+ using System.Threading.Tasks;
- ///
- /// A version of Model that you can inherit from, where the Datastore schema is defined
- /// by attributes on the class and properties instead of implementing the abstract Model
- /// methods.
- ///
- /// Implements caching so that when the application has to determine the schema from the
- /// model class, it's slightly faster than the naive implementation of returning newly
- /// constructed objects from the Model methods.
- ///
- public class AttributedModel : Model, IGeoModel
+ internal static class ModelInfoRegistry
{
- private struct ModelInfo
- {
- public long _schemaVersion;
- public string _kind;
- public HashSet _indexes;
- public Dictionary _types;
- public Dictionary _defaultValues;
- public Dictionary _geoHashKeyLengths;
- }
+ private static Dictionary _cachedInfo = [];
- private static readonly IValueConverter[] _stringEnumValueConverters = new IValueConverter[]
- {
+ private static readonly IValueConverter[] _stringEnumValueConverters =
+ [
new StringEnumValueConverter(),
new StringEnumArrayValueConverter(),
new StringEnumSetValueConverter(),
- };
+ ];
- private static Dictionary _cachedInfo = new Dictionary();
- private readonly Type _type;
- private readonly ModelInfo _modelInfo;
-
- [UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations.", Justification = "We're calling Activator.CreateInstance on the property type, where the property has a [Default] and thus must have a non-null value enforced by the C# compiler.")]
- public AttributedModel()
+ public static ModelInfo InitModel<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(Model model) where T : Model
{
- _type = GetType();
+ ArgumentNullException.ThrowIfNull(model, nameof(model));
+ if (typeof(T) != model.GetType())
+ {
+ throw new ArgumentException("The model value must have the exact same type as T.", nameof(model));
+ }
- if (!_cachedInfo.ContainsKey(_type))
+ var modelInfo = GetModelInfo();
+
+ var conversionContext = new ClrValueConvertFromContext();
+ foreach (var kv in modelInfo._defaultValues)
{
- lock (_cachedInfo)
+ var property = typeof(T).GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
+ var didHandle = false;
+ foreach (var valueConverter in _stringEnumValueConverters)
{
- var kindAttribute = _type.GetCustomAttributes(typeof(IKindAttribute), false).Cast().FirstOrDefault()
- ?? throw new InvalidOperationException($"Missing [Kind(\"...\")] attribute on {_type.FullName} class.");
- if (kindAttribute.Type != _type)
+ if (valueConverter.GetFieldType() == modelInfo._types[property.Name] &&
+ valueConverter.IsConverterForClrType(property.PropertyType))
{
- throw new InvalidOperationException($"Attribute [Kind(\"...\")] has T that differs from runtime type of class, which is {_type.FullName}.");
+ property.SetValue(
+ model,
+ valueConverter.ConvertFromClrDefaultValue(
+ conversionContext,
+ property.Name,
+ property.PropertyType,
+ kv.Value));
+ didHandle = true;
+ break;
}
+ }
+ if (!didHandle)
+ {
+ property.SetValue(model, kv.Value);
+ }
+ }
+
+ return modelInfo;
+ }
- var typeWithRuntimeInfo = kindAttribute.Type;
+ public static ModelInfo GetModelInfo<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>()
+ {
+ if (!_cachedInfo.ContainsKey(typeof(T)))
+ {
+ lock (_cachedInfo)
+ {
+ var kindAttribute = typeof(T).GetCustomAttributes(typeof(KindAttribute), false).Cast().FirstOrDefault()
+ ?? throw new InvalidOperationException($"Missing [Kind(\"...\")] attribute on {typeof(T).FullName} class.");
- long schemaVersion = typeWithRuntimeInfo.GetCustomAttributes(typeof(SchemaVersionAttribute), false).Cast().FirstOrDefault()?.SchemaVersion ?? 1;
+ long schemaVersion = typeof(T).GetCustomAttributes(typeof(SchemaVersionAttribute), false).Cast().FirstOrDefault()?.SchemaVersion ?? 1;
string kind = kindAttribute?.Kind!;
if (string.IsNullOrWhiteSpace(kind))
{
- throw new InvalidOperationException($"Attribute [Kind(\"...\")] on {_type.FullName} has an invalid value.");
+ throw new InvalidOperationException($"Attribute [Kind(\"...\")] on {typeof(T).FullName} has an invalid value.");
}
var indexes = new HashSet();
var types = new Dictionary();
var defaults = new Dictionary();
var geoHashKeyLengths = new Dictionary();
- foreach (var property in typeWithRuntimeInfo.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
+ foreach (var property in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
var isIndexed = property.GetCustomAttributes(typeof(IndexedAttribute), false).Length > 0;
var type = property.GetCustomAttributes(typeof(TypeAttribute), false).Cast().FirstOrDefault();
@@ -89,7 +102,7 @@ public AttributedModel()
{
if (geopoint == null)
{
- throw new InvalidOperationException($"Missing [Geopoint(...)] attribute on Geopoint field {typeWithRuntimeInfo.FullName}.{property.Name}. This attribute is required for Geopoint fields.");
+ throw new InvalidOperationException($"Missing [Geopoint(...)] attribute on Geopoint field {typeof(T).FullName}.{property.Name}. This attribute is required for Geopoint fields.");
}
else
{
@@ -102,9 +115,7 @@ public AttributedModel()
if (property.PropertyType.IsArray)
{
// We only support empty (non-null) arrays as defaults.
-#pragma warning disable IL3050 // The Array.CreateInstance will be called for an array type explicitly used by the codebase.
defaults.Add(property.Name, Array.CreateInstance(property.PropertyType.GetElementType()!, 0));
-#pragma warning restore IL3050
}
else
{
@@ -116,13 +127,13 @@ public AttributedModel()
if (property.PropertyType.IsValueType &&
property.PropertyType.Name != typeof(Nullable<>).Name)
{
- throw new InvalidOperationException($"Missing [Default(...)] attribute on {typeWithRuntimeInfo.FullName}.{property.Name}. Non-nullable value type properties must have the [Default] attribute. If you want to permit nulls, change this to a nullable value type instead (e.g. 'bool?' instead of 'bool').");
+ throw new InvalidOperationException($"Missing [Default(...)] attribute on {typeof(T).FullName}.{property.Name}. Non-nullable value type properties must have the [Default] attribute. If you want to permit nulls, change this to a nullable value type instead (e.g. 'bool?' instead of 'bool').");
}
}
}
}
- _cachedInfo[typeWithRuntimeInfo] = new ModelInfo()
+ _cachedInfo[typeof(T)] = new ModelInfo()
{
_schemaVersion = schemaVersion,
_kind = kind,
@@ -130,70 +141,15 @@ public AttributedModel()
_types = types,
_defaultValues = defaults,
_geoHashKeyLengths = geoHashKeyLengths,
+ _propertyInfos = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic),
+ _propertyInfoByName = typeof(T)
+ .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
+ .ToDictionary(k => k.Name, v => v),
};
}
}
- _modelInfo = _cachedInfo[_type];
-
- var conversionContext = new ClrValueConvertFromContext();
- foreach (var kv in _modelInfo._defaultValues)
- {
-#pragma warning disable IL2080 // To get to this point, _type must already have been checked with kindAttribute.Type != _type
- var property = _type.GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
- var didHandle = false;
- foreach (var valueConverter in _stringEnumValueConverters)
- {
- if (valueConverter.GetFieldType() == _modelInfo._types[property.Name] &&
- valueConverter.IsConverterForClrType(property.PropertyType))
- {
- property.SetValue(
- this,
- valueConverter.ConvertFromClrDefaultValue(
- conversionContext,
- property.Name,
- property.PropertyType,
- kv.Value));
- didHandle = true;
- break;
- }
- }
- if (!didHandle)
- {
- property.SetValue(this, kv.Value);
- }
-#pragma warning restore IL2080
- }
- }
-
- public sealed override HashSet GetIndexes()
- {
- return _modelInfo._indexes;
- }
-
- public sealed override string GetKind()
- {
- return _modelInfo._kind;
- }
-
- public sealed override long GetSchemaVersion()
- {
- return _modelInfo._schemaVersion;
- }
-
- public sealed override Dictionary GetTypes()
- {
- return _modelInfo._types;
- }
-
- public sealed override Dictionary GetDefaultValues()
- {
- return _modelInfo._defaultValues;
- }
-
- public Dictionary GetHashKeyLengthsForGeopointFields()
- {
- return _modelInfo._geoHashKeyLengths;
+ return _cachedInfo[typeof(T)];
}
}
}
diff --git a/UET/Redpoint.CloudFramework/Prefix/DefaultPrefix.cs b/UET/Redpoint.CloudFramework/Prefix/DefaultPrefix.cs
index a11c9efd..5b608074 100644
--- a/UET/Redpoint.CloudFramework/Prefix/DefaultPrefix.cs
+++ b/UET/Redpoint.CloudFramework/Prefix/DefaultPrefix.cs
@@ -59,7 +59,7 @@ public async Task ParseLimited(string identifier, string kind)
return _globalPrefix.ParseLimited(ns, identifier, kind);
}
- public async Task ParseLimited(string identifier) where T : Model, new()
+ public async Task ParseLimited(string identifier) where T : class, IModel, new()
{
var currentTenant = await _currentProjectService.GetTenant().ConfigureAwait(false);
if (currentTenant == null)
diff --git a/UET/Redpoint.CloudFramework/Prefix/GlobalPrefix.cs b/UET/Redpoint.CloudFramework/Prefix/GlobalPrefix.cs
index 49694481..4b4090b8 100644
--- a/UET/Redpoint.CloudFramework/Prefix/GlobalPrefix.cs
+++ b/UET/Redpoint.CloudFramework/Prefix/GlobalPrefix.cs
@@ -27,7 +27,7 @@ public GlobalPrefixRegistration()
_reversePrefixes = new Dictionary();
}
- public void RegisterPrefix<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string prefix) where T : Model, new()
+ public void RegisterPrefix<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string prefix) where T : class, IModel, new()
{
var kind = new T().GetKind();
@@ -114,7 +114,7 @@ public Key ParseLimited(string datastoreNamespace, string identifier, string kin
/// The datastore namespace of the resulting key.
/// The identifier to parse.
/// A key object.
- public Key ParseLimited(string datastoreNamespace, string identifier) where T : Model, new()
+ public Key ParseLimited(string datastoreNamespace, string identifier) where T : class, IModel, new()
{
return ParseLimited(datastoreNamespace, identifier, new T().GetKind());
}
diff --git a/UET/Redpoint.CloudFramework/Prefix/IGlobalPrefix.cs b/UET/Redpoint.CloudFramework/Prefix/IGlobalPrefix.cs
index 7cf7cd1f..349ad520 100644
--- a/UET/Redpoint.CloudFramework/Prefix/IGlobalPrefix.cs
+++ b/UET/Redpoint.CloudFramework/Prefix/IGlobalPrefix.cs
@@ -10,6 +10,6 @@ public interface IGlobalPrefix
Key Parse(string datastoreNamespace, string identifier);
Key ParseInternal(string datastoreNamespace, string identifier);
Key ParseLimited(string datastoreNamespace, string identifier, string kind);
- Key ParseLimited(string datastoreNamespace, string identifier) where T : Model, new();
+ Key ParseLimited(string datastoreNamespace, string identifier) where T : class, IModel, new();
}
}
diff --git a/UET/Redpoint.CloudFramework/Prefix/IPrefix.cs b/UET/Redpoint.CloudFramework/Prefix/IPrefix.cs
index aa60b50e..ffce443a 100644
--- a/UET/Redpoint.CloudFramework/Prefix/IPrefix.cs
+++ b/UET/Redpoint.CloudFramework/Prefix/IPrefix.cs
@@ -11,6 +11,6 @@ public interface IPrefix
Task Parse(string identifier);
Task ParseInternal(string identifier);
Task ParseLimited(string identifier, string kind);
- Task ParseLimited(string identifier) where T : Model, new();
+ Task ParseLimited(string identifier) where T : class, IModel, new();
}
}
diff --git a/UET/Redpoint.CloudFramework/Prefix/IPrefixRegistration.cs b/UET/Redpoint.CloudFramework/Prefix/IPrefixRegistration.cs
index 3bcc3002..89ba4181 100644
--- a/UET/Redpoint.CloudFramework/Prefix/IPrefixRegistration.cs
+++ b/UET/Redpoint.CloudFramework/Prefix/IPrefixRegistration.cs
@@ -10,6 +10,6 @@ public interface IPrefixRegistration
///
/// The model type.
/// The prefix to use for the model.
- void RegisterPrefix<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string prefix) where T : Model, new();
+ void RegisterPrefix<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string prefix) where T : class, IModel, new();
}
}
diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Expression/DefaultExpressionConverter.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Expression/DefaultExpressionConverter.cs
index 4d49cfa0..9b85972b 100644
--- a/UET/Redpoint.CloudFramework/Repository/Converters/Expression/DefaultExpressionConverter.cs
+++ b/UET/Redpoint.CloudFramework/Repository/Converters/Expression/DefaultExpressionConverter.cs
@@ -30,7 +30,7 @@ public DefaultExpressionConverter(
}
}
- private string GetFieldReferencedInExpression(Expression expression, ParameterExpression modelExpression, T referenceModel) where T : Model
+ private string GetFieldReferencedInExpression(Expression expression, ParameterExpression modelExpression, T referenceModel) where T : IModel
{
if (expression.NodeType == ExpressionType.MemberAccess)
{
@@ -55,23 +55,23 @@ private string GetFieldReferencedInExpression(Expression expression, Paramete
throw new InvalidOperationException($"Expression must be a member access operation, and the expression that the member access is being performed on must be the model parameter expression. It was a '{access.Expression.NodeType}' type expression instead.");
}
- if (access.Member.Name == nameof(Model.Key))
+ if (access.Member.Name == nameof(IModel.Key))
{
throw new InvalidOperationException($"The 'Key' property can only have the 'HasAncestor' extension method called on it; it can not be used in a comparison.");
}
else
{
- if (access.Member.Name == nameof(Model.dateCreatedUtc) ||
- access.Member.Name == nameof(Model.dateModifiedUtc) ||
+ if (access.Member.Name == nameof(IModel.dateCreatedUtc) ||
+ access.Member.Name == nameof(IModel.dateModifiedUtc) ||
(referenceModel.GetIndexes().Contains(access.Member.Name) &&
referenceModel.GetTypes().ContainsKey(access.Member.Name)))
{
return access.Member.Name;
}
- if (access.Member.Name == nameof(Model.schemaVersion))
+ if (access.Member.Name == nameof(IModel.schemaVersion))
{
- return nameof(Model.schemaVersion);
+ return nameof(IModel.schemaVersion);
}
}
@@ -126,7 +126,7 @@ private string GetFieldReferencedInExpression(Expression expression, Paramete
}
}
- public Filter? ConvertExpressionToFilter(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters, ref bool hasAncestorQuery) where T : Model
+ public Filter? ConvertExpressionToFilter(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters, ref bool hasAncestorQuery) where T : IModel
{
if (expression.NodeType == ExpressionType.Constant && ((ConstantExpression)expression).Type == typeof(bool) && (bool)((ConstantExpression)expression).Value! == true)
{
@@ -178,7 +178,7 @@ private string GetFieldReferencedInExpression(Expression expression, Paramete
if (callExpression.Method == typeof(RepositoryExtensions).GetMethod(nameof(RepositoryExtensions.HasAncestor), BindingFlags.Static | BindingFlags.Public) &&
callExpression.Arguments.Count == 2)
{
- if (propertyInfo.Name != nameof(Model.Key))
+ if (propertyInfo.Name != nameof(IModel.Key))
{
throw new InvalidOperationException($"You can only use 'HasAncestor' on the primary Key and not key properties. Attempted to use member access on property named '{propertyInfo.Name}'.");
}
@@ -279,7 +279,7 @@ private string GetFieldReferencedInExpression(Expression expression, Paramete
throw new InvalidOperationException($"Expression of type '{expression.NodeType}' is not supported in QueryAsync calls.");
}
- public IEnumerable? ConvertExpressionToOrder(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters) where T : Model
+ public IEnumerable? ConvertExpressionToOrder(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters) where T : IModel
{
if (expression.NodeType == ExpressionType.Or)
{
diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Expression/GeoQueryParameters.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Expression/GeoQueryParameters.cs
index 56c7b239..9ef99f5b 100644
--- a/UET/Redpoint.CloudFramework/Repository/Converters/Expression/GeoQueryParameters.cs
+++ b/UET/Redpoint.CloudFramework/Repository/Converters/Expression/GeoQueryParameters.cs
@@ -5,7 +5,7 @@
using Redpoint.CloudFramework.Models;
using System;
- internal class GeoQueryParameters where T : Model
+ internal class GeoQueryParameters where T : IModel
{
public required string GeoFieldName { get; set; }
public required LatLng MinPoint { get; set; }
diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Expression/IExpressionConverter.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Expression/IExpressionConverter.cs
index 24181e1f..1ea9457c 100644
--- a/UET/Redpoint.CloudFramework/Repository/Converters/Expression/IExpressionConverter.cs
+++ b/UET/Redpoint.CloudFramework/Repository/Converters/Expression/IExpressionConverter.cs
@@ -7,9 +7,9 @@
internal interface IExpressionConverter
{
- Filter? ConvertExpressionToFilter(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters, ref bool hasAncestorQuery) where T : Model;
+ Filter? ConvertExpressionToFilter(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters, ref bool hasAncestorQuery) where T : IModel;
- IEnumerable? ConvertExpressionToOrder(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters) where T : Model;
+ IEnumerable? ConvertExpressionToOrder(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters) where T : IModel;
Filter? SimplifyFilter(Filter? filter);
}
diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/NamedEnumConverter.cs b/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/NamedEnumConverter.cs
index a28bce9b..22653245 100644
--- a/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/NamedEnumConverter.cs
+++ b/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/NamedEnumConverter.cs
@@ -3,6 +3,7 @@
using Newtonsoft.Json;
using Redpoint.CloudFramework.Infrastructure;
using System;
+ using System.Diagnostics.CodeAnalysis;
using System.Reflection;
internal class NamedEnumConverter : JsonConverter
@@ -58,6 +59,7 @@ public override bool CanConvert(Type objectType)
throw new JsonSerializationException("Unexpected token when parsing enum.");
}
+ [SuppressMessage("Trimming", "IL2075:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations.", Justification = "This implementation only accesses enumeration members that will have already been accessed when passed into the 'value' argument.")]
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/VersionedJsonConverter.cs b/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/VersionedJsonConverter.cs
index f2cdc72a..64227300 100644
--- a/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/VersionedJsonConverter.cs
+++ b/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/VersionedJsonConverter.cs
@@ -4,11 +4,13 @@
using Newtonsoft.Json.Linq;
using Redpoint.CloudFramework.Models;
using System;
+ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
internal class VersionedJsonConverter : JsonConverter
{
+ [SuppressMessage("Trimming", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The parameter of method does not have matching annotations.", Justification = "This API does not support trimmed applications.")]
public override bool CanConvert(Type objectType)
{
// Check if the type has a SchemaVersion attribute.
@@ -33,6 +35,7 @@ public override bool CanConvert(Type objectType)
}
}
+ [SuppressMessage("Trimming", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The parameter of method does not have matching annotations.", Justification = "This API does not support trimmed applications.")]
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
var obj = JToken.ReadFrom(reader);
diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Model/EntityModelConverter.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Model/EntityModelConverter.cs
index 6eea847e..5d1c2ea3 100644
--- a/UET/Redpoint.CloudFramework/Repository/Converters/Model/EntityModelConverter.cs
+++ b/UET/Redpoint.CloudFramework/Repository/Converters/Model/EntityModelConverter.cs
@@ -28,7 +28,7 @@ public EntityModelConverter(
_valueConverterProvider = valueConverterProvider;
}
- public T From(string @namespace, Entity entity) where T : Model, new()
+ public T From(string @namespace, Entity entity) where T : class, IModel, new()
{
var @ref = new T();
@ref._originalData = new Dictionary();
@@ -44,8 +44,7 @@ public EntityModelConverter(
var types = @ref.GetTypes();
foreach (var kv in types)
{
- var typeInfo = @ref.GetType();
- var propInfo = typeInfo.GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ var propInfo = @ref.GetPropertyInfo(kv.Key);
if (propInfo == null)
{
_logger.LogWarning($"Model {typeof(T).FullName} declares property {kv.Key} but is missing C# declaration");
@@ -120,7 +119,7 @@ public EntityModelConverter(
return @ref;
}
- public Entity To(string @namespace, T? model, bool isCreateContext, Func? incompleteKeyFactory) where T : Model, new()
+ public Entity To(string @namespace, T? model, bool isCreateContext, Func? incompleteKeyFactory) where T : class, IModel, new()
{
var entity = new Entity();
@@ -138,11 +137,10 @@ public EntityModelConverter(
var indexes = model.GetIndexes();
foreach (var kv in types)
{
- var typeInfo = model.GetType();
- var propInfo = typeInfo.GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ var propInfo = model.GetPropertyInfo(kv.Key);
if (propInfo == null)
{
- throw new InvalidOperationException($"The property '{kv.Key}' could not be found on '{typeInfo.FullName}'. Ensure the datastore type declarations are correct.");
+ throw new InvalidOperationException($"The property '{kv.Key}' could not be found on '{model.GetType().FullName}'. Ensure the datastore type declarations are correct.");
}
var value = propInfo.GetValue(model);
diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Model/IModelConverter.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Model/IModelConverter.cs
index 94112c4c..ff3179bc 100644
--- a/UET/Redpoint.CloudFramework/Repository/Converters/Model/IModelConverter.cs
+++ b/UET/Redpoint.CloudFramework/Repository/Converters/Model/IModelConverter.cs
@@ -6,8 +6,8 @@
internal interface IModelConverter
{
- T? From(string @namespace, TOther data) where T : Model, new();
+ T? From(string @namespace, TOther data) where T : class, IModel, new();
- TOther To(string @namespace, T? model, bool isCreateContext, Func? incompleteKeyFactory) where T : Model, new();
+ TOther To(string @namespace, T? model, bool isCreateContext, Func? incompleteKeyFactory) where T : class, IModel, new();
}
}
diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Model/JsonModelConverter.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Model/JsonModelConverter.cs
index feebdf81..366b95d7 100644
--- a/UET/Redpoint.CloudFramework/Repository/Converters/Model/JsonModelConverter.cs
+++ b/UET/Redpoint.CloudFramework/Repository/Converters/Model/JsonModelConverter.cs
@@ -31,7 +31,7 @@ public JsonModelConverter(
_valueConverterProvider = valueConverterProvider;
}
- public T? From(string @namespace, string jsonCache) where T : Model, new()
+ public T? From(string @namespace, string jsonCache) where T : class, IModel, new()
{
var model = new T();
model._originalData = new Dictionary();
@@ -56,8 +56,7 @@ public JsonModelConverter(
var types = model.GetTypes();
foreach (var kv in types)
{
- var typeInfo = model.GetType();
- var propInfo = typeInfo.GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ var propInfo = model.GetPropertyInfo(kv.Key);
if (propInfo == null)
{
_logger.LogWarning($"Model {typeof(T).FullName} declares property {kv.Key} but is missing C# declaration");
@@ -132,7 +131,7 @@ public JsonModelConverter(
return model;
}
- public string To(string @namespace, T? model, bool isCreateContext, Func? incompleteKeyFactory) where T : Model, new()
+ public string To(string @namespace, T? model, bool isCreateContext, Func? incompleteKeyFactory) where T : class, IModel, new()
{
var hashset = new JObject();
@@ -152,8 +151,7 @@ public JsonModelConverter(
var types = model.GetTypes();
foreach (var kv in types)
{
- var typeInfo = model.GetType();
- var propInfo = typeInfo.GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ var propInfo = model.GetPropertyInfo(kv.Key);
if (propInfo == null)
{
_logger.LogWarning($"Model {typeof(T).FullName} declares property {kv.Key} but is missing C# declaration");
diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/ConvertFromDelayedLoad.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/ConvertFromDelayedLoad.cs
index f63a947f..14a46257 100644
--- a/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/ConvertFromDelayedLoad.cs
+++ b/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/ConvertFromDelayedLoad.cs
@@ -5,9 +5,9 @@
///
/// A delegate which is called by the conversion after all other fields are loaded, with the
- /// local namespace value provided by .
+ /// local namespace value provided by .
///
- /// The local namespace value provided by .
+ /// The local namespace value provided by .
/// The CLR value that would normally have been directly returned from or .
internal delegate object? ConvertFromDelayedLoad(string localNamespace);
}
diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/DatastoreValueConvertToContext.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/DatastoreValueConvertToContext.cs
index d58f48a3..a9bc1298 100644
--- a/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/DatastoreValueConvertToContext.cs
+++ b/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/DatastoreValueConvertToContext.cs
@@ -10,7 +10,7 @@ internal class DatastoreValueConvertToContext : ClrValueConvertFromContext
{
public required string ModelNamespace { get; init; }
- public required Model Model { get; init; }
+ public required IModel Model { get; init; }
public required Entity Entity { get; init; }
}
diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/JsonValueConvertToContext.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/JsonValueConvertToContext.cs
index d65ee3b0..f11985d6 100644
--- a/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/JsonValueConvertToContext.cs
+++ b/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/JsonValueConvertToContext.cs
@@ -9,6 +9,6 @@ internal class JsonValueConvertToContext : ClrValueConvertFromContext
{
public required string ModelNamespace { get; init; }
- public required Model Model { get; init; }
+ public required IModel Model { get; init; }
}
}
diff --git a/UET/Redpoint.CloudFramework/Repository/Datastore/DatastoreGlobalRepository.cs b/UET/Redpoint.CloudFramework/Repository/Datastore/DatastoreGlobalRepository.cs
index 67c53024..2880f082 100644
--- a/UET/Redpoint.CloudFramework/Repository/Datastore/DatastoreGlobalRepository.cs
+++ b/UET/Redpoint.CloudFramework/Repository/Datastore/DatastoreGlobalRepository.cs
@@ -52,7 +52,7 @@ public IBatchedAsyncEnumerable QueryAsync(
int? limit = null,
IModelTransaction? transaction = null,
RepositoryOperationMetrics? metrics = null,
- CancellationToken cancellationToken = default) where T : Model, new()
+ CancellationToken cancellationToken = default) where T : class, IModel, new()
{
return Layer.QueryAsync(@namespace, where, order, limit, transaction, metrics, cancellationToken);
}
@@ -65,7 +65,7 @@ public Task> QueryPaginatedAsync(
Expression>? order = null,
IModelTransaction? transaction = null,
RepositoryOperationMetrics? metrics = null,
- CancellationToken cancellationToken = default) where T : Model, new()
+ CancellationToken cancellationToken = default) where T : class, IModel, new()
{
return Layer.QueryPaginatedAsync(@namespace, cursor, limit, where, order, transaction, metrics, cancellationToken);
}
@@ -75,7 +75,7 @@ public Task> QueryPaginatedAsync(
Key key,
IModelTransaction? transaction = null,
RepositoryOperationMetrics? metrics = null,
- CancellationToken cancellationToken = default) where T : Model, new()
+ CancellationToken cancellationToken = default) where T : class, IModel, new()
{
return Layer.LoadAsync(@namespace, key, transaction, metrics, cancellationToken);
}
@@ -85,7 +85,7 @@ public Task> QueryPaginatedAsync(
IAsyncEnumerable keys,
IModelTransaction? transaction = null,
RepositoryOperationMetrics? metrics = null,
- CancellationToken cancellationToken = default) where T : Model, new()
+ CancellationToken cancellationToken = default) where T : class, IModel, new()
{
return Layer.LoadAsync(@namespace, keys, transaction, metrics, cancellationToken);
}
@@ -93,7 +93,7 @@ public Task> QueryPaginatedAsync(
public IAsyncEnumerable> LoadAcrossNamespacesAsync(
IAsyncEnumerable keys,
RepositoryOperationMetrics? metrics = null,
- CancellationToken cancellationToken = default) where T : Model, new()
+ CancellationToken cancellationToken = default) where T : class, IModel, new()
{
return Layer.LoadAcrossNamespacesAsync(keys, metrics, cancellationToken);
}
@@ -103,7 +103,7 @@ public async Task CreateAsync(
T model,
IModelTransaction? transaction = null,
RepositoryOperationMetrics? metrics = null,
- CancellationToken cancellationToken = default) where T : Model, new()
+ CancellationToken cancellationToken = default) where T : class, IModel, new()
{
return await Layer.CreateAsync(@namespace, new[] { model }.ToAsyncEnumerable(), transaction, metrics, cancellationToken).FirstAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
}
@@ -113,7 +113,7 @@ public IAsyncEnumerable CreateAsync(
IAsyncEnumerable models,
IModelTransaction? transaction = null,
RepositoryOperationMetrics? metrics = null,
- CancellationToken cancellationToken = default) where T : Model, new()
+ CancellationToken cancellationToken = default) where T : class, IModel, new()
{
return Layer.CreateAsync(@namespace, models, transaction, metrics, cancellationToken);
}
@@ -123,7 +123,7 @@ public async Task UpsertAsync(
T model,
IModelTransaction? transaction = null,
RepositoryOperationMetrics? metrics = null,
- CancellationToken cancellationToken = default) where T : Model, new()
+ CancellationToken cancellationToken = default) where T : class, IModel, new()
{
return await Layer.UpsertAsync(@namespace, new[] { model }.ToAsyncEnumerable(), transaction, metrics, cancellationToken).FirstAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
}
@@ -133,7 +133,7 @@ public IAsyncEnumerable UpsertAsync(
IAsyncEnumerable models,
IModelTransaction? transaction = null,
RepositoryOperationMetrics? metrics = null,
- CancellationToken cancellationToken = default) where T : Model, new()
+ CancellationToken cancellationToken = default) where T : class, IModel, new()
{
return Layer.UpsertAsync(@namespace, models, transaction, metrics, cancellationToken);
}
@@ -143,7 +143,7 @@ public async Task UpdateAsync(
T model,
IModelTransaction? transaction = null,
RepositoryOperationMetrics? metrics = null,
- CancellationToken cancellationToken = default) where T : Model, new()
+ CancellationToken cancellationToken = default) where T : class, IModel, new()
{
return await Layer.UpdateAsync(@namespace, new[] { model }.ToAsyncEnumerable(), transaction, metrics, cancellationToken).FirstAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
}
@@ -153,7 +153,7 @@ public IAsyncEnumerable UpdateAsync(
IAsyncEnumerable models,
IModelTransaction? transaction = null,
RepositoryOperationMetrics? metrics = null,
- CancellationToken cancellationToken = default) where T : Model, new()
+ CancellationToken cancellationToken = default) where T : class, IModel, new()
{
return Layer.UpdateAsync(@namespace, models, transaction, metrics, cancellationToken);
}
@@ -163,7 +163,7 @@ public Task DeleteAsync(
T model,
IModelTransaction? transaction = null,
RepositoryOperationMetrics? metrics = null,
- CancellationToken cancellationToken = default) where T : Model, new()
+ CancellationToken cancellationToken = default) where T : class, IModel, new()
{
return Layer.DeleteAsync(@namespace, new[] { model }.ToAsyncEnumerable(), transaction, metrics, cancellationToken);
}
@@ -173,7 +173,7 @@ public Task DeleteAsync(
IAsyncEnumerable models,
IModelTransaction? transaction = null,
RepositoryOperationMetrics? metrics = null,
- CancellationToken cancellationToken = default) where T : Model, new()
+ CancellationToken cancellationToken = default) where T : class, IModel, new()
{
return Layer.DeleteAsync(@namespace, models, transaction, metrics, cancellationToken);
}
@@ -182,7 +182,7 @@ public Task AllocateKeyAsync(
string @namespace,
IModelTransaction? transaction,
RepositoryOperationMetrics? metrics = null,
- CancellationToken cancellationToken = default) where T : Model, new()
+ CancellationToken cancellationToken = default) where T : class, IModel, new()
{
return Layer.AllocateKeyAsync(@namespace, transaction, metrics, cancellationToken);
}
@@ -190,7 +190,7 @@ public Task AllocateKeyAsync(
public Task GetKeyFactoryAsync