diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..1ba2afc6
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+*.css linguist-detectable=false
+*.js linguist-detectable=false
+*.html linguist-detectable=false
+*.cshtml linguist-detectable=false
diff --git a/.github/workflows/publish-net3.yml b/.github/workflows/publish-net3.yml
new file mode 100644
index 00000000..cc125f52
--- /dev/null
+++ b/.github/workflows/publish-net3.yml
@@ -0,0 +1,59 @@
+name: .NET Core
+
+on: [push]
+
+env:
+ NUGET_KEY: ${{ secrets.NUGET_API_KEY }}
+ PACKAGE_VERSION_PREFIX: 3
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup .NET Core 3
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: |
+ 3.1.x
+ 5.0.x
+ - name: Restore Dependencies
+ run: dotnet restore
+ # - name: Build with dotnet
+ # run: dotnet build ./Framework.sln --configuration Release
+ - name: Create NuGet Packages
+ run: |
+ echo "Packing..."
+ dotnet pack --configuration Release ./Common/src/Adriva.Common.Core.csproj
+ dotnet pack --configuration Release ./Analytics/src/Adriva.Extensions.Analytics.AppInsights/Adriva.Extensions.Analytics.AppInsights.csproj
+ dotnet pack --configuration Release ./Caching/src/Adriva.Extensions.Caching.Abstractions/Adriva.Extensions.Caching.Abstractions.csproj
+ dotnet pack --configuration Release ./Caching/src/Adriva.Extensions.Caching.Memory/Adriva.Extensions.Caching.Memory.csproj
+ dotnet pack --configuration Release ./Caching/src/Adriva.Extensions.Caching.SqlServer/Adriva.Extensions.Caching.SqlServer.csproj
+ dotnet pack --configuration Release ./DevTools/Adriva.DevTools.Cli/Adriva.DevTools.Cli.csproj
+ dotnet pack --configuration Release ./Reporting/src/Adriva.Extensions.Reporting.Abstractions/Adriva.Extensions.Reporting.Abstractions.csproj
+ dotnet pack --configuration Release ./Reporting/src/Adriva.Extensions.Reporting.Excel/Adriva.Extensions.Reporting.Excel.csproj
+ dotnet pack --configuration Release ./Reporting/src/Adriva.Extensions.Reporting.Http/Adriva.Extensions.Reporting.Http.csproj
+ dotnet pack --configuration Release ./Reporting/src/Adriva.Extensions.Reporting.SqlServer/Adriva.Extensions.Reporting.SqlServer.csproj
+ dotnet pack --configuration Release ./Storage/src/Adriva.Storage.Abstractions/Adriva.Storage.Abstractions.csproj
+ dotnet pack --configuration Release ./Storage/src/Adriva.Storage.SqlServer/Adriva.Storage.SqlServer.csproj
+ dotnet pack --configuration Release ./Storage/src/Adriva.Storage.Azure/Adriva.Storage.Azure.csproj
+ dotnet pack --configuration Release ./Workflow/src/Adriva.Extensions.WorkflowEngine/Adriva.Extensions.WorkflowEngine.csproj
+ - name: Push Packages
+ run: |
+ echo "Pushing packages to Nuget"
+ dotnet nuget push "./Common/src/bin/Release/Adriva.Common.Core.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Analytics/src/Adriva.Extensions.Analytics.AppInsights/bin/Release/Adriva.Extensions.Analytics.AppInsights.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Caching/src/Adriva.Extensions.Caching.Abstractions/bin/Release/Adriva.Extensions.Caching.Abstractions.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Caching/src/Adriva.Extensions.Caching.Memory/bin/Release/Adriva.Extensions.Caching.Memory.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Caching/src/Adriva.Extensions.Caching.SqlServer/bin/Release/Adriva.Extensions.Caching.SqlServer.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./DevTools/Adriva.DevTools.Cli/bin/Release/Adriva.DevTools.Cli.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Reporting/src/Adriva.Extensions.Reporting.Abstractions/bin/Release/Adriva.Extensions.Reporting.Abstractions.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Reporting/src/Adriva.Extensions.Reporting.Excel/bin/Release/Adriva.Extensions.Reporting.Excel.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Reporting/src/Adriva.Extensions.Reporting.Http/bin/Release/Adriva.Extensions.Reporting.Http.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Reporting/src/Adriva.Extensions.Reporting.SqlServer/bin/Release/Adriva.Extensions.Reporting.SqlServer.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Storage/src/Adriva.Storage.Abstractions/bin/Release/Adriva.Storage.Abstractions.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Storage/src/Adriva.Storage.SqlServer/bin/Release/Adriva.Storage.SqlServer.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Storage/src/Adriva.Storage.Azure/bin/Release/Adriva.Storage.Azure.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Workflow/src/Adriva.Extensions.WorkflowEngine/bin/Release/Adriva.Extensions.WorkflowEngine.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
diff --git a/.github/workflows/publish-net7.yml b/.github/workflows/publish-net7.yml
new file mode 100644
index 00000000..54c9a504
--- /dev/null
+++ b/.github/workflows/publish-net7.yml
@@ -0,0 +1,58 @@
+name: .NET Core
+
+on: [push]
+
+env:
+ NUGET_KEY: ${{ secrets.NUGET_API_KEY }}
+ PACKAGE_VERSION_PREFIX: 7
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup .NET Core 7
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: |
+ 7.0.x
+ - name: Restore Dependencies
+ run: dotnet restore
+ # - name: Build with dotnet
+ # run: dotnet build ./Framework.sln --configuration Release
+ - name: Create NuGet Packages
+ run: |
+ echo "Packing..."
+ dotnet pack --configuration Release ./Common/src/Adriva.Common.Core.csproj
+ dotnet pack --configuration Release ./Analytics/src/Adriva.Extensions.Analytics.AppInsights/Adriva.Extensions.Analytics.AppInsights.csproj
+ dotnet pack --configuration Release ./Caching/src/Adriva.Extensions.Caching.Abstractions/Adriva.Extensions.Caching.Abstractions.csproj
+ dotnet pack --configuration Release ./Caching/src/Adriva.Extensions.Caching.Memory/Adriva.Extensions.Caching.Memory.csproj
+ dotnet pack --configuration Release ./Caching/src/Adriva.Extensions.Caching.SqlServer/Adriva.Extensions.Caching.SqlServer.csproj
+ dotnet pack --configuration Release ./DevTools/Adriva.DevTools.Cli/Adriva.DevTools.Cli.csproj
+ dotnet pack --configuration Release ./Reporting/src/Adriva.Extensions.Reporting.Abstractions/Adriva.Extensions.Reporting.Abstractions.csproj
+ dotnet pack --configuration Release ./Reporting/src/Adriva.Extensions.Reporting.Excel/Adriva.Extensions.Reporting.Excel.csproj
+ dotnet pack --configuration Release ./Reporting/src/Adriva.Extensions.Reporting.Http/Adriva.Extensions.Reporting.Http.csproj
+ dotnet pack --configuration Release ./Reporting/src/Adriva.Extensions.Reporting.SqlServer/Adriva.Extensions.Reporting.SqlServer.csproj
+ dotnet pack --configuration Release ./Storage/src/Adriva.Storage.Abstractions/Adriva.Storage.Abstractions.csproj
+ dotnet pack --configuration Release ./Storage/src/Adriva.Storage.SqlServer/Adriva.Storage.SqlServer.csproj
+ dotnet pack --configuration Release ./Storage/src/Adriva.Storage.Azure/Adriva.Storage.Azure.csproj
+ dotnet pack --configuration Release ./Workflow/src/Adriva.Extensions.WorkflowEngine/Adriva.Extensions.WorkflowEngine.csproj
+ - name: Push Packages
+ run: |
+ echo "Pushing packages to Nuget"
+ dotnet nuget push "./Common/src/bin/Release/Adriva.Common.Core.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Analytics/src/Adriva.Extensions.Analytics.AppInsights/bin/Release/Adriva.Extensions.Analytics.AppInsights.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Caching/src/Adriva.Extensions.Caching.Abstractions/bin/Release/Adriva.Extensions.Caching.Abstractions.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Caching/src/Adriva.Extensions.Caching.Memory/bin/Release/Adriva.Extensions.Caching.Memory.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Caching/src/Adriva.Extensions.Caching.SqlServer/bin/Release/Adriva.Extensions.Caching.SqlServer.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./DevTools/Adriva.DevTools.Cli/bin/Release/Adriva.DevTools.Cli.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Reporting/src/Adriva.Extensions.Reporting.Abstractions/bin/Release/Adriva.Extensions.Reporting.Abstractions.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Reporting/src/Adriva.Extensions.Reporting.Excel/bin/Release/Adriva.Extensions.Reporting.Excel.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Reporting/src/Adriva.Extensions.Reporting.Http/bin/Release/Adriva.Extensions.Reporting.Http.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Reporting/src/Adriva.Extensions.Reporting.SqlServer/bin/Release/Adriva.Extensions.Reporting.SqlServer.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Storage/src/Adriva.Storage.Abstractions/bin/Release/Adriva.Storage.Abstractions.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Storage/src/Adriva.Storage.SqlServer/bin/Release/Adriva.Storage.SqlServer.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Storage/src/Adriva.Storage.Azure/bin/Release/Adriva.Storage.Azure.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "./Workflow/src/Adriva.Extensions.WorkflowEngine/bin/Release/Adriva.Extensions.WorkflowEngine.$PACKAGE_VERSION_PREFIX.*.nupkg" --api-key $NUGET_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..96f63e9d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,339 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+.DS_Store
+## dynamically generated assets
+demo/wwwroot/assets/
+
+##sqlite analytics database
+analytics.db*
+
+## project specific files
+/Optimization/src/Adriva.Extensions.Optimization.Web/loader.js
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 00000000..6eb59fb8
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,119 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Launch Common Tests",
+ "type": "coreclr",
+ "request": "launch",
+ "cwd": "${workspaceFolder}/Common/test",
+ "program": "dotnet",
+ "args": [
+ "test",
+ "${workspaceFolder}/Common/test/test.csproj"
+ ],
+ "env": {
+ "VSTEST_HOST_DEBUG": "1"
+ }
+ },
+ {
+ "name": "Launch Optimization Tests",
+ "type": "coreclr",
+ "request": "launch",
+ "cwd": "${workspaceFolder}/Optimization/test",
+ "program": "dotnet",
+ "args": [
+ "test",
+ "${workspaceFolder}/Optimization/test/test.csproj"
+ ],
+ "env": {
+ "VSTEST_HOST_DEBUG": "1"
+ }
+ },
+ {
+ "name": "Launch Demo Web",
+ "preLaunchTask": "build demo",
+ "type": "coreclr",
+ "request": "launch",
+ "cwd": "${workspaceFolder}/demo",
+ "program": "${workspaceFolder}/demo/bin/Debug/netcoreapp3.1/demo.dll",
+ "osx": {
+ "name": "Launch Demo Web",
+ "program": "dotnet",
+ "type": "coreclr",
+ "request": "launch",
+ "args": [
+ "${workspaceFolder}/demo/bin/Debug/net7.0/demo.dll"
+ ]
+ },
+ "launchBrowser": {
+ "enabled": true,
+ "args": "${auto-detect-url}"
+ },
+ "env": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ {
+ "name": "Launch Dev Tools",
+ "preLaunchTask": "build dev-tools",
+ "type": "coreclr",
+ "request": "launch",
+ "cwd": "${workspaceFolder}/DevTools/Adriva.DevTools.Cli",
+ "program": "${workspaceFolder}/DevTools/Adriva.DevTools.Cli/bin/Debug/netcoreapp3.1/Adriva.DevTools.Cli.dll",
+ "args": [
+ "update-report",
+ "-p",
+ "/Users/mnemonic/Projects/po/portal/PetrolOfisi.Portal.Web/ReportStore",
+ "-n",
+ "UserProfile/UserProfileList",
+ // "-m",
+ // "/Users/mnemonic/Projects/netcorelibs/Framework/DevTools/Adriva.DevTools.Cli/mappings.txt",
+ "-o",
+ "/Users/mnemonic/Projects/report-output"
+ ],
+ "osx": {
+ "program": "dotnet3",
+ "args": [
+ "${workspaceFolder}/DevTools/Adriva.DevTools.Cli/bin/Debug/netcoreapp3.1/Adriva.DevTools.Cli.dll",
+ "update-report",
+ "-p",
+ "/Users/mnemonic/Projects/po/portal/PetrolOfisi.Portal.Web/ReportStore",
+ "-n",
+ "DealerInfo/DealerGroup",
+ "-o",
+ "/Users/mnemonic/Projects/report-output",
+ "-m",
+ "/Users/mnemonic/Projects/netcorelibs/Framework/DevTools/Adriva.DevTools.Cli/mappings.txt",
+ "-ls",
+ "ReportSchema.json"
+ ],
+ "targetArchitecture": "x86_64"
+ }
+ },
+ {
+ "name": "Launch Worker Host",
+ "preLaunchTask": "build worker",
+ "type": "coreclr",
+ "request": "launch",
+ "cwd": "${workspaceFolder}/Worker/src/Adriva.Worker.Host",
+ "program": "${workspaceFolder}/Worker/src/Adriva.Worker.Host/bin/Debug/netcoreapp3.1/Adriva.Worker.Host.dll",
+ "osx": {
+ "name": "Launch Worker Host",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet3",
+ "args": [
+ "${workspaceFolder}/Worker/src/Adriva.Worker.Host/bin/Debug/netcoreapp3.1/Adriva.Worker.Host.dll"
+ ],
+ "targetArchitecture": "x86_64"
+ }
+ },
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach",
+ "processId": "${command:pickProcess}",
+ }
+ ],
+ "compounds": []
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 00000000..1f98d799
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,108 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=733558
+ // for the documentation about the tasks.json format
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build all",
+ "command": "dotnet",
+ "type": "shell",
+ "args": [
+ "build",
+ "${workspaceFolder}/Framework.sln",
+ // Ask dotnet build to generate full paths for file names.
+ "/property:GenerateFullPaths=true",
+ // Do not generate summary otherwise it leads to duplicate errors in Problems panel
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "group": "build",
+ "presentation": {
+ "reveal": "silent"
+ },
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "build demo",
+ "command": "dotnet",
+ "type": "shell",
+ "args": [
+ "build",
+ "${workspaceFolder}/demo/demo.sln",
+ // Ask dotnet build to generate full paths for file names.
+ "/property:GenerateFullPaths=true",
+ // Do not generate summary otherwise it leads to duplicate errors in Problems panel
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "group": "build",
+ "presentation": {
+ "reveal": "always"
+ },
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "build dev-tools",
+ "command": "dotnet",
+ "type": "shell",
+ "args": [
+ "build",
+ "${workspaceFolder}/DevTools/DevTools.sln",
+ // Ask dotnet build to generate full paths for file names.
+ "/property:GenerateFullPaths=true",
+ // Do not generate summary otherwise it leads to duplicate errors in Problems panel
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "group": "build",
+ "presentation": {
+ "reveal": "always"
+ },
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "build worker",
+ "command": "dotnet",
+ "type": "shell",
+ "args": [
+ "build",
+ "${workspaceFolder}/Worker/Worker.sln",
+ // Ask dotnet build to generate full paths for file names.
+ "/property:GenerateFullPaths=true",
+ // Do not generate summary otherwise it leads to duplicate errors in Problems panel
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "group": "build",
+ "presentation": {
+ "reveal": "always"
+ },
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "build storage",
+ "command": "dotnet",
+ "type": "shell",
+ "args": [
+ "build",
+ "${workspaceFolder}/Storage/Storage.sln",
+ // Ask dotnet build to generate full paths for file names.
+ "/property:GenerateFullPaths=true",
+ // Do not generate summary otherwise it leads to duplicate errors in Problems panel
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "group": "build",
+ "presentation": {
+ "reveal": "always"
+ },
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "build docs",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "build",
+ "${workspaceFolder}/documentation/documentation.csproj"
+ ],
+ "group": "build",
+ "problemMatcher": "$msCompile"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Analytics/Analytics.sln b/Analytics/Analytics.sln
new file mode 100644
index 00000000..2428f954
--- /dev/null
+++ b/Analytics/Analytics.sln
@@ -0,0 +1,96 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BF45F630-9E4C-45FD-8C9B-18277AD20DF3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adriva.Extensions.Analytics.AppInsights", "src\Adriva.Extensions.Analytics.AppInsights\Adriva.Extensions.Analytics.AppInsights.csproj", "{248E5BC3-64FD-4B0B-A51E-666618CD1EE5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adriva.Extensions.Analytics.Server", "src\Adriva.Extensions.Analytics.Server\Adriva.Extensions.Analytics.Server.csproj", "{EB347292-68B0-4069-932E-FC9CDA9538D2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adriva.Extensions.Analytics.Server.AppInsights", "src\Adriva.Extensions.Analytics.Server.AppInsights\Adriva.Extensions.Analytics.Server.AppInsights.csproj", "{49EE6ABC-EA0D-4B68-B968-06995EC47CC7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adriva.Extensions.Analytics.Repository.EntityFramework", "src\Adriva.Extensions.Analytics.Repository.EntityFramework\Adriva.Extensions.Analytics.Repository.EntityFramework.csproj", "{9CCE0D85-7EE2-408B-853E-5D022A009506}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite", "src\Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite\Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite.csproj", "{DE19027C-218C-49C2-A882-7D66C3E0C381}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {248E5BC3-64FD-4B0B-A51E-666618CD1EE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {248E5BC3-64FD-4B0B-A51E-666618CD1EE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {248E5BC3-64FD-4B0B-A51E-666618CD1EE5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {248E5BC3-64FD-4B0B-A51E-666618CD1EE5}.Debug|x64.Build.0 = Debug|Any CPU
+ {248E5BC3-64FD-4B0B-A51E-666618CD1EE5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {248E5BC3-64FD-4B0B-A51E-666618CD1EE5}.Debug|x86.Build.0 = Debug|Any CPU
+ {248E5BC3-64FD-4B0B-A51E-666618CD1EE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {248E5BC3-64FD-4B0B-A51E-666618CD1EE5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {248E5BC3-64FD-4B0B-A51E-666618CD1EE5}.Release|x64.ActiveCfg = Release|Any CPU
+ {248E5BC3-64FD-4B0B-A51E-666618CD1EE5}.Release|x64.Build.0 = Release|Any CPU
+ {248E5BC3-64FD-4B0B-A51E-666618CD1EE5}.Release|x86.ActiveCfg = Release|Any CPU
+ {248E5BC3-64FD-4B0B-A51E-666618CD1EE5}.Release|x86.Build.0 = Release|Any CPU
+ {EB347292-68B0-4069-932E-FC9CDA9538D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EB347292-68B0-4069-932E-FC9CDA9538D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EB347292-68B0-4069-932E-FC9CDA9538D2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EB347292-68B0-4069-932E-FC9CDA9538D2}.Debug|x64.Build.0 = Debug|Any CPU
+ {EB347292-68B0-4069-932E-FC9CDA9538D2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EB347292-68B0-4069-932E-FC9CDA9538D2}.Debug|x86.Build.0 = Debug|Any CPU
+ {EB347292-68B0-4069-932E-FC9CDA9538D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EB347292-68B0-4069-932E-FC9CDA9538D2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EB347292-68B0-4069-932E-FC9CDA9538D2}.Release|x64.ActiveCfg = Release|Any CPU
+ {EB347292-68B0-4069-932E-FC9CDA9538D2}.Release|x64.Build.0 = Release|Any CPU
+ {EB347292-68B0-4069-932E-FC9CDA9538D2}.Release|x86.ActiveCfg = Release|Any CPU
+ {EB347292-68B0-4069-932E-FC9CDA9538D2}.Release|x86.Build.0 = Release|Any CPU
+ {49EE6ABC-EA0D-4B68-B968-06995EC47CC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {49EE6ABC-EA0D-4B68-B968-06995EC47CC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {49EE6ABC-EA0D-4B68-B968-06995EC47CC7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {49EE6ABC-EA0D-4B68-B968-06995EC47CC7}.Debug|x64.Build.0 = Debug|Any CPU
+ {49EE6ABC-EA0D-4B68-B968-06995EC47CC7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {49EE6ABC-EA0D-4B68-B968-06995EC47CC7}.Debug|x86.Build.0 = Debug|Any CPU
+ {49EE6ABC-EA0D-4B68-B968-06995EC47CC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {49EE6ABC-EA0D-4B68-B968-06995EC47CC7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {49EE6ABC-EA0D-4B68-B968-06995EC47CC7}.Release|x64.ActiveCfg = Release|Any CPU
+ {49EE6ABC-EA0D-4B68-B968-06995EC47CC7}.Release|x64.Build.0 = Release|Any CPU
+ {49EE6ABC-EA0D-4B68-B968-06995EC47CC7}.Release|x86.ActiveCfg = Release|Any CPU
+ {49EE6ABC-EA0D-4B68-B968-06995EC47CC7}.Release|x86.Build.0 = Release|Any CPU
+ {9CCE0D85-7EE2-408B-853E-5D022A009506}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9CCE0D85-7EE2-408B-853E-5D022A009506}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9CCE0D85-7EE2-408B-853E-5D022A009506}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9CCE0D85-7EE2-408B-853E-5D022A009506}.Debug|x64.Build.0 = Debug|Any CPU
+ {9CCE0D85-7EE2-408B-853E-5D022A009506}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9CCE0D85-7EE2-408B-853E-5D022A009506}.Debug|x86.Build.0 = Debug|Any CPU
+ {9CCE0D85-7EE2-408B-853E-5D022A009506}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9CCE0D85-7EE2-408B-853E-5D022A009506}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9CCE0D85-7EE2-408B-853E-5D022A009506}.Release|x64.ActiveCfg = Release|Any CPU
+ {9CCE0D85-7EE2-408B-853E-5D022A009506}.Release|x64.Build.0 = Release|Any CPU
+ {9CCE0D85-7EE2-408B-853E-5D022A009506}.Release|x86.ActiveCfg = Release|Any CPU
+ {9CCE0D85-7EE2-408B-853E-5D022A009506}.Release|x86.Build.0 = Release|Any CPU
+ {DE19027C-218C-49C2-A882-7D66C3E0C381}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DE19027C-218C-49C2-A882-7D66C3E0C381}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DE19027C-218C-49C2-A882-7D66C3E0C381}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DE19027C-218C-49C2-A882-7D66C3E0C381}.Debug|x64.Build.0 = Debug|Any CPU
+ {DE19027C-218C-49C2-A882-7D66C3E0C381}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DE19027C-218C-49C2-A882-7D66C3E0C381}.Debug|x86.Build.0 = Debug|Any CPU
+ {DE19027C-218C-49C2-A882-7D66C3E0C381}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DE19027C-218C-49C2-A882-7D66C3E0C381}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DE19027C-218C-49C2-A882-7D66C3E0C381}.Release|x64.ActiveCfg = Release|Any CPU
+ {DE19027C-218C-49C2-A882-7D66C3E0C381}.Release|x64.Build.0 = Release|Any CPU
+ {DE19027C-218C-49C2-A882-7D66C3E0C381}.Release|x86.ActiveCfg = Release|Any CPU
+ {DE19027C-218C-49C2-A882-7D66C3E0C381}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {248E5BC3-64FD-4B0B-A51E-666618CD1EE5} = {BF45F630-9E4C-45FD-8C9B-18277AD20DF3}
+ {EB347292-68B0-4069-932E-FC9CDA9538D2} = {BF45F630-9E4C-45FD-8C9B-18277AD20DF3}
+ {49EE6ABC-EA0D-4B68-B968-06995EC47CC7} = {BF45F630-9E4C-45FD-8C9B-18277AD20DF3}
+ {9CCE0D85-7EE2-408B-853E-5D022A009506} = {BF45F630-9E4C-45FD-8C9B-18277AD20DF3}
+ {DE19027C-218C-49C2-A882-7D66C3E0C381} = {BF45F630-9E4C-45FD-8C9B-18277AD20DF3}
+ EndGlobalSection
+EndGlobal
diff --git a/Analytics/src/Adriva.Extensions.Analytics.AppInsights/Adriva.Extensions.Analytics.AppInsights.csproj b/Analytics/src/Adriva.Extensions.Analytics.AppInsights/Adriva.Extensions.Analytics.AppInsights.csproj
new file mode 100644
index 00000000..9af4ad22
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.AppInsights/Adriva.Extensions.Analytics.AppInsights.csproj
@@ -0,0 +1,22 @@
+
+
+
+ netstandard2.1
+
+
+
+ Adriva client wrapper for Microsoft ApplicationInsights
+ Client library that can be injected to collect analytics data.
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Analytics/src/Adriva.Extensions.Analytics.AppInsights/AnalyticsBuilder.cs b/Analytics/src/Adriva.Extensions.Analytics.AppInsights/AnalyticsBuilder.cs
new file mode 100644
index 00000000..a388cc4e
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.AppInsights/AnalyticsBuilder.cs
@@ -0,0 +1,23 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Adriva.Extensions.Analytics.AppInsights
+{
+ internal sealed class AnalyticsBuilder : IAnalyticsBuilder
+ {
+ internal Action ConfigureAction;
+ public AnalyticsOptions Options { get; private set; }
+ public IServiceCollection Services { get; private set; }
+
+ public AnalyticsBuilder(IServiceCollection services, AnalyticsOptions options)
+ {
+ this.Services = services;
+ this.Options = options;
+ }
+
+ public void Configure(Action configure)
+ {
+ this.ConfigureAction = configure;
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.AppInsights/AnalyticsExtensions.cs b/Analytics/src/Adriva.Extensions.Analytics.AppInsights/AnalyticsExtensions.cs
new file mode 100644
index 00000000..137ed029
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.AppInsights/AnalyticsExtensions.cs
@@ -0,0 +1,118 @@
+using System;
+using Adriva.Extensions.Analytics.AppInsights;
+using Microsoft.ApplicationInsights.AspNetCore.Extensions;
+using Microsoft.ApplicationInsights.Channel;
+using Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.ApplicationInsights;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// Provides extension methods to use Microsoft AppInsights wrapper services.
+ ///
+ public static class AnalyticsExtensions
+ {
+ private static IServiceCollection AddAppInsightsAnalytics(this IServiceCollection services, Action build)
+ {
+ AnalyticsOptions analyticsOptions = new AnalyticsOptions();
+ AnalyticsBuilder builder = new AnalyticsBuilder(services, analyticsOptions);
+ build?.Invoke(builder);
+ builder.ConfigureAction?.Invoke(analyticsOptions);
+
+ services.Configure(baseOptions =>
+ {
+ baseOptions.BacklogSize = analyticsOptions.BacklogSize;
+ baseOptions.Capacity = analyticsOptions.Capacity;
+ baseOptions.EndPointAddress = analyticsOptions.EndPointAddress;
+ baseOptions.InstrumentationKey = analyticsOptions.InstrumentationKey;
+ baseOptions.IsDeveloperMode = analyticsOptions.IsDeveloperMode;
+ baseOptions.Filter = analyticsOptions.Filter;
+ });
+
+ services.Replace(ServiceDescriptor.Singleton(serviceProvider =>
+ {
+ return new ServerTelemetryChannel()
+ {
+ DeveloperMode = analyticsOptions.IsDeveloperMode,
+ EndpointAddress = analyticsOptions.EndPointAddress,
+ MaxBacklogSize = analyticsOptions.BacklogSize,
+ MaxTelemetryBufferCapacity = analyticsOptions.Capacity,
+ };
+ }));
+
+ services.AddLogging(builder =>
+ {
+ foreach (var logLevelEntry in analyticsOptions.LogLevels)
+ {
+ builder.AddFilter(logLevelEntry.Key, logLevelEntry.Value);
+ }
+ });
+
+ return services;
+ }
+
+ ///
+ /// Adds Microsoft AppInsights for asp.net core services to the specified service collection.
+ ///
+ /// The Microsoft.Extensions.DependencyInjection.IServiceCollection to add services to.
+ /// The AnalyticsOptions configuration delegate.
+ /// The Microsoft.Extensions.DependencyInjection.IServiceCollection so that additional calls can be chained.
+ public static IServiceCollection AddAppInsightsWebAnalytics(this IServiceCollection services, Action configure, Action postConfigure = null)
+ {
+ services.AddAppInsightsAnalytics(builder =>
+ {
+ builder.Configure(configure);
+
+ services.AddApplicationInsightsTelemetry(options =>
+ {
+ options.InstrumentationKey = builder.Options.InstrumentationKey;
+
+ options.DeveloperMode = builder.Options.IsDeveloperMode;
+ options.EndpointAddress = builder.Options.EndPointAddress;
+
+ if (null != postConfigure)
+ {
+ postConfigure(options);
+ }
+ });
+ });
+
+ services.AddSingleton(serviceProvider =>
+ {
+ return new SharedTelemetryProcessorFactory(serviceProvider, typeof(DefaultTelemetryFilter));
+ });
+
+ return services;
+ }
+
+ ///
+ /// Adds Microsoft AppInsights for generic host core services to the specified service collection.
+ ///
+ /// The Microsoft.Extensions.DependencyInjection.IServiceCollection to add services to.
+ /// The AnalyticsOptions configuration delegate.
+ /// The Microsoft.Extensions.DependencyInjection.IServiceCollection so that additional calls can be chained.
+ // public static IServiceCollection AddAppInsightsGenericAnalytics(this IServiceCollection services, Action configure)
+ // {
+
+ // services.AddAppInsightsAnalytics(builder =>
+ // {
+ // builder.Configure(configure);
+ // builder.Services.AddApplicationInsightsTelemetryWorkerService(options =>
+ // {
+ // options.InstrumentationKey = builder.Options.InstrumentationKey;
+ // options.DeveloperMode = builder.Options.IsDeveloperMode;
+ // options.EndpointAddress = builder.Options.EndPointAddress;
+ // });
+ // });
+
+ // services.AddSingleton(serviceProvider =>
+ // {
+ // return new SharedTelemetryProcessorFactory(serviceProvider, typeof(DefaultTelemetryFilter));
+ // });
+
+ // return services;
+ // }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.AppInsights/AnalyticsOptions.cs b/Analytics/src/Adriva.Extensions.Analytics.AppInsights/AnalyticsOptions.cs
new file mode 100644
index 00000000..60387dd8
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.AppInsights/AnalyticsOptions.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.ApplicationInsights.Channel;
+using Microsoft.Extensions.Logging;
+
+namespace Adriva.Extensions.Analytics.AppInsights
+{
+
+ ///
+ /// Defines the custom behavior of analytics services.
+ ///
+ public class AnalyticsOptions
+ {
+ internal readonly IDictionary LogLevels = new Dictionary();
+
+ ///
+ /// Gets the name of the HttpClient instance used to communicate with analytics server.
+ ///
+ ///
+ public static string HttpClientName { get; } = "AdrivaAnalyticsHttpClient";
+
+ ///
+ /// Gets or sets the instrumentation key that uniquely identifies the application.
+ ///
+ /// A string value that represents the instrumentation key.
+ public string InstrumentationKey { get; set; }
+
+ ///
+ /// Gets or sets the address of the server that the analytics data will be sent to.
+ ///
+ /// A string that represents the URI of the analytics server.
+ public string EndPointAddress { get; set; }
+
+ ///
+ /// Gets or sets the maximum number of items that can be stored in the backlog.
+ ///
+ /// An integer value that represents the number of maximum items in the backlog.
+ public int BacklogSize { get; set; } = 100000;
+
+ ///
+ /// Gets or sets the maximum number of items that can be stored in the client buffer.
+ ///
+ /// An integer value that represents the number of maximum items in the client buffer.
+ public int Capacity { get; set; } = 500;
+
+ ///
+ /// Gets or sets a value indicating if the current analytics client is running in developer mode.
+ ///
+ /// A boolean value that represents if the current analytics client is in developer mode.
+ public bool IsDeveloperMode { get; set; }
+
+ ///
+ /// Gets or sets a function that can be used to filter analytics items so that filtered items will not be transferred to the server.
+ ///
+ /// A function predicate that returns a boolean value indicating if the given telemetrry can be transferred to the server.
+ public Func Filter { get; set; }
+
+ ///
+ /// Gets or sets an action that can be used to populate analytics data with extra information.
+ ///
+ /// An action that is called by the system to populate analytics data.
+ public Action Initializer { get; set; } = (sp, t) => { };
+
+ ///
+ /// Sets the logging level per category.
+ ///
+ /// The name of the category to set the logging level for. Empty string for all categories.
+ /// Target logging level for the category.
+ /// The current instance of AnalyticsOptions class.
+ public AnalyticsOptions SetLogLevel(string category, LogLevel logLevel)
+ {
+ this.LogLevels[category] = logLevel;
+
+ return this;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.AppInsights/DefaultTelemetryFilter.cs b/Analytics/src/Adriva.Extensions.Analytics.AppInsights/DefaultTelemetryFilter.cs
new file mode 100644
index 00000000..3346b479
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.AppInsights/DefaultTelemetryFilter.cs
@@ -0,0 +1,42 @@
+using System;
+using Microsoft.ApplicationInsights.Channel;
+using Microsoft.ApplicationInsights.DataContracts;
+using Microsoft.ApplicationInsights.Extensibility;
+using Microsoft.Extensions.Options;
+
+namespace Adriva.Extensions.Analytics.AppInsights
+{
+ internal sealed class DefaultTelemetryFilter : ITelemetryProcessor
+ {
+ private readonly ITelemetryProcessor Next;
+ private readonly Func FilterFunction;
+ private readonly AnalyticsOptions Options;
+
+ public DefaultTelemetryFilter(ITelemetryProcessor next, IOptions optionsAccessor)
+ {
+ this.Next = next;
+ this.Options = optionsAccessor.Value;
+ this.FilterFunction = (telemetry) =>
+ {
+ if (telemetry is RequestTelemetry requestTelemetry)
+ {
+ if (Convert.ToString(requestTelemetry.Url).StartsWith(this.Options.EndPointAddress, StringComparison.OrdinalIgnoreCase))
+ return false;
+ }
+
+ if (null != this.Options.Filter)
+ {
+ return this.Options.Filter.Invoke(telemetry);
+ }
+
+ return true;
+ };
+ }
+
+ public void Process(ITelemetry item)
+ {
+ if (!this.FilterFunction.Invoke(item)) return;
+ this.Next.Process(item);
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.AppInsights/IAnalyticsBuilder.cs b/Analytics/src/Adriva.Extensions.Analytics.AppInsights/IAnalyticsBuilder.cs
new file mode 100644
index 00000000..23dd4f53
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.AppInsights/IAnalyticsBuilder.cs
@@ -0,0 +1,29 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Adriva.Extensions.Analytics.AppInsights
+{
+ ///
+ /// Provides methods and properties that will be used by the system to build an analytics client.
+ ///
+ public interface IAnalyticsBuilder
+ {
+ ///
+ /// Gets the current service collection instance.
+ ///
+ /// A reference to the current services collection.
+ IServiceCollection Services { get; }
+
+ ///
+ /// Gets the current options used by the analytics services.
+ ///
+ /// The current instance of AnalyticsOptions class that is used by the system.
+ AnalyticsOptions Options { get; }
+
+ ///
+ /// Called by the system to configure the analytics options that will be used.
+ ///
+ /// An action that is called by the system to populate analytics options data.
+ void Configure(Action configure);
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.AppInsights/SharedTelemetryProcessorFactory.cs b/Analytics/src/Adriva.Extensions.Analytics.AppInsights/SharedTelemetryProcessorFactory.cs
new file mode 100644
index 00000000..f6c1c401
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.AppInsights/SharedTelemetryProcessorFactory.cs
@@ -0,0 +1,28 @@
+using System;
+using Microsoft.ApplicationInsights.Extensibility;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ internal class SharedTelemetryProcessorFactory : ApplicationInsights.AspNetCore.ITelemetryProcessorFactory
+ {
+ private readonly IServiceProvider serviceProvider;
+ private readonly Type telemetryProcessorType;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The service provider.
+ /// The type of telemetry processor to create.
+ public SharedTelemetryProcessorFactory(IServiceProvider serviceProvider, Type telemetryProcessorType)
+ {
+ this.serviceProvider = serviceProvider;
+ this.telemetryProcessorType = telemetryProcessorType;
+ }
+
+ ///
+ public ITelemetryProcessor Create(ITelemetryProcessor next)
+ {
+ return (ITelemetryProcessor)ActivatorUtilities.CreateInstance(this.serviceProvider, this.telemetryProcessorType, next);
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite/Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite.csproj b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite/Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite.csproj
new file mode 100644
index 00000000..a1732fcb
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite/Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netcoreapp3.1
+ net7.0
+ 1
+
+
+
+
+
+
+
+
+
+
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite/EntityFrameworkRepositoryExtensions.cs b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite/EntityFrameworkRepositoryExtensions.cs
new file mode 100644
index 00000000..8ada80df
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite/EntityFrameworkRepositoryExtensions.cs
@@ -0,0 +1,30 @@
+using Adriva.Extensions.Analytics.Repository.EntityFramework;
+using Adriva.Extensions.Analytics.Repository.EntityFramework.Sqlite;
+using Adriva.Extensions.Analytics.Server;
+using Microsoft.EntityFrameworkCore;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// Provides extension methods to use Microsoft Entity Framework based repositories with the analytics server.
+ ///
+ public static class EntityFrameworkSqliteRepositoryExtensions
+ {
+ ///
+ /// Registers a Sqlite entity framework database to be used with the analytics server.
+ ///
+ /// Analytics server builder delegate.
+ /// The connection string that will be used by the Sqlite provider.
+ /// The Adriva.Extensions.Analytics.Server.IAnalyticsServerBuilder so that additional calls can be chained.
+ public static IAnalyticsServerBuilder UseSqlite(this IAnalyticsServerBuilder builder, string connectionString = "Data Source=./analytics.db")
+ {
+ return builder.UseEntityFrameworkRepository(
+ dbContextBuilder =>
+ {
+ dbContextBuilder.UseSqlite(connectionString);
+ },
+ ServiceLifetime.Scoped,
+ ServiceLifetime.Singleton);
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite/SqliteRepository.cs b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite/SqliteRepository.cs
new file mode 100644
index 00000000..9c609b89
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite/SqliteRepository.cs
@@ -0,0 +1,38 @@
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace Adriva.Extensions.Analytics.Repository.EntityFramework.Sqlite
+{
+ ///
+ /// Provides a default implementation of IAnalyticsRepository that uses Sqlite as the base data store.
+ ///
+ public class SqliteRepository : EntityFrameworkRepository
+ {
+ ///
+ /// Initiates a new instance of SqliteRepository class.
+ ///
+ /// An instance of AnalyticsDatabaseContext that will be used to persist analytics data.
+ /// An implementation of ILogger that will be used to write out log data.
+ public SqliteRepository(AnalyticsDatabaseContext context, ILogger logger)
+ : base(context, logger)
+ {
+ }
+
+ ///
+ /// Initializes the current instance of Adriva.Extensions.Analytics.Repository.EntityFramework.SqlLite.SqlLiteRepository class.
+ ///
+ /// Represents the asynchronous process operation.
+ public override async Task InitializeAsync()
+ {
+ this.Logger.LogInformation("Ensuring database exists.");
+ if (await this.Context.Database.EnsureCreatedAsync())
+ {
+ this.Logger.LogInformation("Analytics database is created.");
+ }
+ else
+ {
+ this.Logger.LogInformation("Analytics database already exists.");
+ }
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/Adriva.Extensions.Analytics.Repository.EntityFramework.csproj b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/Adriva.Extensions.Analytics.Repository.EntityFramework.csproj
new file mode 100644
index 00000000..30131cd7
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/Adriva.Extensions.Analytics.Repository.EntityFramework.csproj
@@ -0,0 +1,17 @@
+
+
+ netcoreapp3.1
+ net7.0
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/AnalyticsDatabaseContext.cs b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/AnalyticsDatabaseContext.cs
new file mode 100644
index 00000000..af0c93e6
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/AnalyticsDatabaseContext.cs
@@ -0,0 +1,197 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.EntityFrameworkCore;
+using Adriva.Extensions.Analytics.Server.Entities;
+using System;
+
+namespace Adriva.Extensions.Analytics.Repository.EntityFramework
+{
+ ///
+ /// Provides the base implementation of an entity framework DbContext that can be used to work with analytics items.
+ ///
+ public class AnalyticsDatabaseContext : DbContext
+ {
+ private readonly IDatabaseModelBuilder DatabaseModelBuilder;
+
+ ///
+ /// Gets or sets a collection of AnalyticsItem objects that can be used to query or save the instances.
+ ///
+ /// An instance of a DbSet class that represents the AnalyticsItem collection.
+ public DbSet AnalyticsItems { get; set; }
+
+ ///
+ /// Gets or sets a collection of RequestItem objects that can be used to query or save the instances.
+ ///
+ /// An instance of a DbSet class that represents the RequestItem collection.
+ public DbSet Requests { get; set; }
+
+ ///
+ /// Gets or sets a collection of ExceptionItem objects that can be used to query or save the instances.
+ ///
+ /// An instance of a DbSet class that represents the ExceptionItem collection.
+ public DbSet Exceptions { get; set; }
+
+ ///
+ /// Gets or sets a collection of MessageItem objects that can be used to query or save the instances.
+ ///
+ /// An instance of a DbSet class that represents the MessageItem collection.
+ public DbSet Messages { get; set; }
+
+ ///
+ /// Gets or sets a collection of MetricItem objects that can be used to query or save the instances.
+ ///
+ /// An instance of a DbSet class that represents the MetricItem collection.
+ public DbSet Metrics { get; set; }
+
+ ///
+ /// Gets or sets a collection of DependencyItem objects that can be used to query or save the instances.
+ ///
+ /// An instance of a DbSet class that represents the DependencyItem collection.
+ public DbSet Dependencies { get; set; }
+
+ ///
+ /// Gets or sets a collection of AvailabilityItem objects that can be used to query or save the instances.
+ ///
+ /// An instance of a DbSet class that represents the AvailabilityItem collection.
+ public DbSet AvailabilityItems { get; set; }
+
+ ///
+ /// Gets or sets a collection of EventItem objects that can be used to query or save the instances.
+ ///
+ /// An instance of a DbSet class that represents the EventItem collection.
+ public DbSet Events { get; set; }
+
+ ///
+ /// Initializes a new instance of the Adriva.Extensions.Analytics.Repository.EntityFramework.AnalyticsDatabaseContext class using the specified options.
+ ///
+ /// The options for this context.
+ /// The IServiceProvider instance that will be used to resolve dependencies.
+ public AnalyticsDatabaseContext(DbContextOptions options, IServiceProvider serviceProvider) : base(options)
+ {
+ this.ChangeTracker.AutoDetectChangesEnabled = false;
+ this.ChangeTracker.LazyLoadingEnabled = false;
+ this.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
+ this.DatabaseModelBuilder = serviceProvider?.GetService();
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity(
+ e =>
+ {
+ e.ToTable("AnalyticsItems");
+ e.HasKey(x => x.Id);
+ e.Property(x => x.Id).ValueGeneratedOnAdd();
+
+ e.HasOne(x => x.RequestItem)
+ .WithOne(x => x.AnalyticsItem)
+ .HasPrincipalKey(x => x.Id)
+ .HasForeignKey(x => x.AnalyticsItemId);
+
+ e.HasOne(x => x.MessageItem)
+ .WithOne(x => x.AnalyticsItem)
+ .HasPrincipalKey(x => x.Id)
+ .HasForeignKey(x => x.AnalyticsItemId);
+
+ e.HasOne(x => x.AvailabilityItem)
+ .WithOne(x => x.AnalyticsItem)
+ .HasPrincipalKey(x => x.Id)
+ .HasForeignKey(x => x.AnalyticsItemId);
+
+ e.HasMany(x => x.Exceptions)
+ .WithOne(x => x.AnalyticsItem)
+ .HasForeignKey(x => x.AnalyticsItemId)
+ .HasPrincipalKey(x => x.Id);
+
+ e.HasMany(x => x.Metrics)
+ .WithOne(x => x.AnalyticsItem)
+ .HasForeignKey(x => x.AnalyticsItemId)
+ .HasPrincipalKey(x => x.Id);
+
+ e.HasMany(x => x.Events)
+ .WithOne(x => x.AnalyticsItem)
+ .HasForeignKey(x => x.AnalyticsItemId)
+ .HasPrincipalKey(x => x.Id);
+
+ e.HasMany(x => x.Dependencies)
+ .WithOne(x => x.AnalyticsItem)
+ .HasForeignKey(x => x.AnalyticsItemId)
+ .HasPrincipalKey(x => x.Id);
+
+ e.HasIndex(x => x.InstrumentationKey);
+ }
+ );
+
+ modelBuilder.Entity(
+ e =>
+ {
+ e.ToTable("Exceptions");
+ e.HasKey(x => x.Id);
+ e.Property(x => x.Id).ValueGeneratedOnAdd();
+ e.HasIndex(x => x.AnalyticsItemId);
+ }
+ );
+
+ modelBuilder.Entity(
+ e =>
+ {
+ e.ToTable("Requests");
+ e.HasKey(x => x.Id);
+ e.Property(x => x.Id).ValueGeneratedOnAdd();
+ e.HasIndex(x => x.AnalyticsItemId);
+ }
+ );
+
+ modelBuilder.Entity(
+ e =>
+ {
+ e.ToTable("Events");
+ e.HasKey(x => x.Id);
+ e.Property(x => x.Id).ValueGeneratedOnAdd();
+ e.HasIndex(x => x.AnalyticsItemId);
+ }
+ );
+
+ modelBuilder.Entity(
+ e =>
+ {
+ e.ToTable("Metrics");
+ e.HasKey(x => x.Id);
+ e.Property(x => x.Id).ValueGeneratedOnAdd();
+ e.HasIndex(x => x.AnalyticsItemId);
+ }
+ );
+
+ modelBuilder.Entity(
+ e =>
+ {
+ e.ToTable("Availability");
+ e.HasKey(x => x.Id);
+ e.Property(x => x.Id).ValueGeneratedOnAdd();
+ e.HasIndex(x => x.AnalyticsItemId);
+ }
+ );
+
+ modelBuilder.Entity(
+ e =>
+ {
+ e.ToTable("Messages");
+ e.HasKey(x => x.Id);
+ e.Property(x => x.Id).ValueGeneratedOnAdd();
+ e.HasIndex(x => x.AnalyticsItemId);
+ }
+ );
+
+ modelBuilder.Entity(
+ e =>
+ {
+ e.ToTable("Dependencies");
+ e.HasKey(x => x.Id);
+ e.Property(x => x.Id).ValueGeneratedOnAdd();
+ e.HasIndex(x => x.AnalyticsItemId);
+ }
+ );
+
+ this.DatabaseModelBuilder?.OnModelCreating(this, modelBuilder);
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/EntityFrameworkRepository.cs b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/EntityFrameworkRepository.cs
new file mode 100644
index 00000000..5655042a
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/EntityFrameworkRepository.cs
@@ -0,0 +1,73 @@
+using System;
+using Adriva.Extensions.Analytics.Server;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using Adriva.Extensions.Analytics.Server.Entities;
+using System.Threading;
+using Microsoft.Extensions.Logging;
+using System.Linq;
+
+namespace Adriva.Extensions.Analytics.Repository.EntityFramework
+{
+ ///
+ /// Represents a base class for an analytics repository that stores data using EF Core.
+ ///
+ public abstract class EntityFrameworkRepository : IAnalyticsRepository
+ {
+ ///
+ /// Gets the logger that can be used to write log output.
+ ///
+ /// A concrete implementation of ILogger interface.
+ protected ILogger Logger { get; private set; }
+
+ ///
+ /// Gets an instance of AnalyticsDatabaseContext class that is used to persist analytics data.
+ ///
+ /// An instance of AnalyticsDatabaseContext class.
+ protected AnalyticsDatabaseContext Context { get; private set; }
+
+ ///
+ /// Initiates a new instance of Adriva.Extensions.Analytics.Repository.EntityFramework.EntityFrameworkRepository class.
+ ///
+ /// The AnalyticsDatabaseContext object that will be used to store data.
+ /// An instance of an ILogger object that is used to write log messages to.
+ protected EntityFrameworkRepository(AnalyticsDatabaseContext analyticsDatabaseContext, ILogger logger)
+ {
+ this.Context = analyticsDatabaseContext;
+ this.Logger = logger;
+ }
+
+ ///
+ /// Initializes the current instance of Adriva.Extensions.Analytics.Repository.EntityFramework.EntityFrameworkRepository class.
+ ///
+ /// Represents the asynchronous process operation.
+ public virtual Task InitializeAsync() => Task.CompletedTask;
+
+ ///
+ /// Called by the system when the server analytics buffer is full to persist items in the repository.
+ ///
+ /// Items that should be persisted in the repository.
+ /// Propagates notification that operations should be canceled.
+ /// Represents the asynchronous process operation.
+ public async virtual Task StoreAsync(IEnumerable items, CancellationToken cancellationToken)
+ {
+ this.Logger.LogTrace($"Persisting {items.Count()} analytics items.");
+ await this.Context.AddRangeAsync(items);
+ await this.Context.SaveChangesAsync(cancellationToken);
+ this.Logger.LogInformation($"Persisted {items.Count()} analytics items.");
+ }
+
+ ///
+ /// Called by the system when the StoreAsync method encounters an exception.
+ /// In-memory repository only writes the error message to the log and returns.
+ ///
+ /// Items that failed to be stored in the repository.
+ /// The exception that is caught by the system.
+ /// Represents the asynchronous process operation.
+ public virtual Task HandleErrorAsync(IEnumerable items, Exception exception)
+ {
+ this.Logger.LogError(exception, "Analytics data repository encountered an exception when persisting analytics data.");
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/EntityFrameworkRepositoryExtensions.cs b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/EntityFrameworkRepositoryExtensions.cs
new file mode 100644
index 00000000..8f2024f6
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/EntityFrameworkRepositoryExtensions.cs
@@ -0,0 +1,73 @@
+using System;
+using Adriva.Extensions.Analytics.Repository.EntityFramework;
+using Adriva.Extensions.Analytics.Server;
+using Microsoft.EntityFrameworkCore;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+
+ ///
+ /// Provides extension methods to use Microsoft Entity Framework based repositories with the analytics server.
+ ///
+ public static class EntityFrameworkRepositoryExtensions
+ {
+ ///
+ /// Registers the required services to use an EF Core based analytics repository.
+ ///
+ /// Analytics server builder delegate.
+ /// Callback method to configure DbContextOptions.
+ /// The lifetime with which to register the DbContext service in the container.
+ /// The lifetime with which to register the DbContextOptions service in the container.
+ /// The concrete implementation of a DbContext to use.
+ /// The concrete implementation of an IAnalyticsRepository to use.
+ /// The concrete implementation of an IDatabaseModelBuilder to configure the model of the DbContext.
+ /// The Adriva.Extensions.Analytics.Server.IAnalyticsServerBuilder so that additional calls can be chained.
+ public static IAnalyticsServerBuilder UseEntityFrameworkRepository(this IAnalyticsServerBuilder builder,
+ Action configureContext,
+ ServiceLifetime contextLifetime = ServiceLifetime.Scoped,
+ ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)
+ where TContext : DbContext
+ where TModelBuilder : class, IDatabaseModelBuilder
+ where TRepository : class, IAnalyticsRepository
+ {
+ builder.Services.AddDbContext(configureContext, contextLifetime, optionsLifetime);
+ builder.UseRepository();
+ builder.Services.AddSingleton();
+ return builder;
+ }
+
+ ///
+ /// Registers the required services to use an EF Core based analytics repository.
+ ///
+ /// Analytics server builder delegate.
+ /// Callback method to configure DbContextOptions.
+ /// The lifetime with which to register the DbContext service in the container.
+ /// The lifetime with which to register the DbContextOptions service in the container.
+ /// The concrete implementation of a DbContext to use.
+ /// The concrete implementation of an IAnalyticsRepository to use.
+ /// The Adriva.Extensions.Analytics.Server.IAnalyticsServerBuilder so that additional calls can be chained.
+ public static IAnalyticsServerBuilder UseEntityFrameworkRepository(this IAnalyticsServerBuilder builder,
+ Action configureContext,
+ ServiceLifetime contextLifetime = ServiceLifetime.Scoped,
+ ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)
+ where TContext : DbContext
+ where TRepository : class, IAnalyticsRepository
+ {
+ builder.Services.AddDbContext(configureContext, contextLifetime, optionsLifetime);
+ builder.UseRepository();
+ return builder;
+ }
+
+ ///
+ /// Registers an in-memory entity framework database to be used with the analytics server.
+ /// In-memory database should only be used for development / testing purposes and not in a production environment.
+ ///
+ /// Analytics server builder delegate.
+ /// The name of the in-memory database.
+ /// The Adriva.Extensions.Analytics.Server.IAnalyticsServerBuilder so that additional calls can be chained.
+ public static IAnalyticsServerBuilder UseInMemoryRepository(this IAnalyticsServerBuilder builder, string databaseName = "AnalyticsDb")
+ {
+ return builder.UseEntityFrameworkRepository(dbContextBuilder => dbContextBuilder.UseInMemoryDatabase(databaseName), ServiceLifetime.Singleton, ServiceLifetime.Singleton);
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/IDatabaseModelBuilder.cs b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/IDatabaseModelBuilder.cs
new file mode 100644
index 00000000..5615fdb5
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/IDatabaseModelBuilder.cs
@@ -0,0 +1,17 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace Adriva.Extensions.Analytics.Repository.EntityFramework
+{
+ ///
+ /// Provides methods to configure the model used by the AnalyticsDbContext for different database providers.
+ ///
+ public interface IDatabaseModelBuilder
+ {
+ ///
+ /// Called by the system once, to let the repository configure its model according to the database provider specifications.
+ ///
+ /// An instance of AnalyticsDatabaseContext that will be used to persist analytics data.
+ /// The ModelBuilder that can be used to configure the EF model.
+ void OnModelCreating(AnalyticsDatabaseContext databaseContext, ModelBuilder modelBuilder);
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/InMemoryRepository.cs b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/InMemoryRepository.cs
new file mode 100644
index 00000000..6d497a00
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Repository.EntityFramework/InMemoryRepository.cs
@@ -0,0 +1,49 @@
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using Adriva.Extensions.Analytics.Server.Entities;
+using System.Threading;
+using Microsoft.Extensions.Logging;
+using System.Linq;
+
+namespace Adriva.Extensions.Analytics.Repository.EntityFramework
+{
+ ///
+ /// Represents an analytics repository that stores data in-memory.
+ ///
+ public class InMemoryRepository : EntityFrameworkRepository
+ {
+ private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
+
+ ///
+ /// Initiates a new instance of Adriva.Extensions.Analytics.Repository.EntityFramework.InMemoryRepository class.
+ ///
+ /// The AnalyticsDatabaseContext object that will be used to store data.
+ /// An instance of an ILogger object that is used to write log messages to.
+ public InMemoryRepository(AnalyticsDatabaseContext analyticsDatabaseContext, ILogger logger)
+ : base(analyticsDatabaseContext, logger)
+ {
+
+ }
+
+ ///
+ /// Called by the system when the server analytics buffer is full to persist items in the repository.
+ ///
+ /// Items that should be persisted in the repository.
+ /// Propagates notification that operations should be canceled.
+ /// Represents the asynchronous process operation.
+ public async override Task StoreAsync(IEnumerable items, CancellationToken cancellationToken)
+ {
+ this.Logger.LogTrace($"Persisting {items.Count()} analytics items in memory.");
+ await this.Semaphore.WaitAsync();
+ try
+ {
+ await base.StoreAsync(items, cancellationToken);
+ }
+ finally
+ {
+ this.Semaphore.Release();
+ }
+ this.Logger.LogInformation($"Persisted {items.Count()} analytics items in memory.");
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Adriva.Extensions.Analytics.Server.AppInsights.csproj b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Adriva.Extensions.Analytics.Server.AppInsights.csproj
new file mode 100644
index 00000000..9b1bb327
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Adriva.Extensions.Analytics.Server.AppInsights.csproj
@@ -0,0 +1,18 @@
+
+
+
+ netcoreapp3.1
+ net7.0
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AnalyticsItemPopulator.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AnalyticsItemPopulator.cs
new file mode 100644
index 00000000..681a033a
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AnalyticsItemPopulator.cs
@@ -0,0 +1,37 @@
+using Adriva.Extensions.Analytics.Server.AppInsights.Contracts;
+using Adriva.Extensions.Analytics.Server.Entities;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights
+{
+ public abstract class AnalyticsItemPopulator
+ {
+ public virtual string TargetKey { get => null; }
+
+ public abstract bool TryPopulate(Envelope envelope, ref AnalyticsItem analyticsItem);
+
+ internal static bool TryPopulateItem(Envelope envelope, out AnalyticsItem analyticsItem)
+ {
+ analyticsItem = null;
+
+ if (null == envelope) return false;
+
+ analyticsItem = new AnalyticsItem();
+
+ analyticsItem.InstrumentationKey = envelope.InstrumentationKey;
+ analyticsItem.Timestamp = envelope.EventDate;
+ if (envelope.Tags.TryGetValue("ai.location.ip", out string ip)) analyticsItem.Ip = ip;
+ if (envelope.Tags.TryGetValue("ai.operation.id", out string operationId)) analyticsItem.OperationId = operationId;
+ if (envelope.Tags.TryGetValue("ai.operation.parentId", out string parentOperationId)) analyticsItem.ParentOperationId = parentOperationId;
+ if (envelope.Tags.TryGetValue("ai.cloud.roleInstance", out string roleInstance)) analyticsItem.RoleInstance = roleInstance;
+ if (envelope.Tags.TryGetValue("ai.internal.sdkVersion", out string sdkVersion)) analyticsItem.SdkVersion = sdkVersion;
+ if (envelope.Tags.TryGetValue("ai.application.ver", out string appVersion)) analyticsItem.ApplicationVersion = appVersion;
+ if (envelope.Tags.TryGetValue("ai.user.id", out string userId)) analyticsItem.UserId = userId;
+ if (envelope.Tags.TryGetValue("ai.user.accountId", out string userAccountId)) analyticsItem.UserAccountId = userAccountId;
+ if (envelope.Tags.TryGetValue("ai.user.authUserId", out string authenticatedUserId)) analyticsItem.AuthenticatedUserId = authenticatedUserId;
+
+ analyticsItem.Type = envelope.Name.Substring(1 + envelope.Name.LastIndexOf('.'));
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AppInsightsAnalyticsServerExtensions.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AppInsightsAnalyticsServerExtensions.cs
new file mode 100644
index 00000000..6c8aa6d4
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AppInsightsAnalyticsServerExtensions.cs
@@ -0,0 +1,30 @@
+using System;
+using Adriva.Extensions.Analytics.Server;
+using Adriva.Extensions.Analytics.Server.AppInsights;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ public static class AppInsightsAnalyticsServerExtensions
+ {
+ public static IServiceCollection AddAppInsightsAnalyticsServer(this IServiceCollection services, Action build)
+ {
+ services.AddAnalyticsServer(builder =>
+ {
+ builder.UseHandler();
+ build.Invoke(builder);
+ });
+
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddSingleton();
+
+ return services;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AppInsightsHandler.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AppInsightsHandler.cs
new file mode 100644
index 00000000..8cccebf1
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AppInsightsHandler.cs
@@ -0,0 +1,108 @@
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Http;
+using AiJsonSerializer = Microsoft.ApplicationInsights.Extensibility.Implementation.JsonSerializer;
+using System.IO;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
+using Microsoft.Extensions.Logging;
+using System.Linq;
+using System.IO.Compression;
+using System;
+using Adriva.Common.Core;
+using Adriva.Extensions.Analytics.Server.AppInsights.Contracts;
+using Newtonsoft.Json;
+using Adriva.Extensions.Analytics.Server.Entities;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights
+{
+
+ public class AppInsightsHandler : IAnalyticsHandler
+ {
+ private readonly ILogger Logger;
+ private readonly Dictionary Populators = new Dictionary();
+ private readonly IAppInsightsValidator Validator;
+ private static readonly JsonSerializerSettings JsonSerializerSettings;
+
+ static AppInsightsHandler()
+ {
+ AppInsightsHandler.JsonSerializerSettings = new JsonSerializerSettings();
+ AppInsightsHandler.JsonSerializerSettings.Converters.Add(new TelemetryConverter());
+ }
+
+ public AppInsightsHandler(IServiceProvider serviceProvider, ILogger logger)
+ {
+ this.Logger = logger;
+ this.Validator = serviceProvider.GetRequiredService();
+ var populatorServices = serviceProvider.GetServices();
+
+ foreach (var populatorService in populatorServices)
+ {
+ this.Populators[populatorService.TargetKey] = populatorService;
+ }
+ }
+
+ public async IAsyncEnumerable HandleAsync(HttpRequest request)
+ {
+ if (0 == string.Compare(request.Method, HttpMethods.Post))
+ {
+ using (Stream inputStream = new AutoStream(32 * 1024))
+ {
+ bool hasPopulatedStream = false;
+
+ if (request.Headers.TryGetValue(HeaderNames.ContentType, out StringValues contentTypeHeader))
+ {
+ if (contentTypeHeader.Any(ct => 0 == string.Compare(ct, AiJsonSerializer.ContentType, StringComparison.OrdinalIgnoreCase)))
+ {
+ this.Logger.LogInformation("Analytics Middleware received compressed JSON data.");
+
+ using (var zipStream = new GZipStream(request.Body, CompressionMode.Decompress))
+ {
+ await zipStream.CopyToAsync(inputStream);
+ hasPopulatedStream = true;
+ }
+ }
+ }
+
+ if (!hasPopulatedStream)
+ {
+ await request.Body.CopyToAsync(inputStream);
+ }
+
+ inputStream.Seek(0, SeekOrigin.Begin);
+
+ this.Logger.LogInformation("Extracting envelope items from the request body.");
+ var envelopeItems = await NdJsonSerializer.DeserializeAsync(inputStream, AppInsightsHandler.JsonSerializerSettings);
+ this.Logger.LogInformation($"Extracted envelope items.");
+
+ if (envelopeItems.Any())
+ {
+ foreach (var envelopeItem in envelopeItems)
+ {
+
+ if (AnalyticsItemPopulator.TryPopulateItem(envelopeItem, out AnalyticsItem analyticsItem))
+ {
+ this.Logger.LogTrace($"Envelope item with data of type '{analyticsItem.Type}' is parsed.");
+ if (this.Populators.TryGetValue(analyticsItem.Type, out AnalyticsItemPopulator populator))
+ {
+ if (populator.TryPopulate(envelopeItem, ref analyticsItem))
+ {
+ this.Logger.LogTrace($"Analytics item of type '{analyticsItem.Type}' is populated.");
+ }
+ }
+ else
+ {
+ this.Logger.LogWarning($"AppInsights handler received a type of '{analyticsItem.Type}' and doesn't have a populator registered for that type.");
+ }
+ if (await this.Validator.ValidateInstrumentationKeyAsync(analyticsItem.InstrumentationKey))
+ {
+ yield return analyticsItem;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AppInsightsValidator.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AppInsightsValidator.cs
new file mode 100644
index 00000000..a3c5adac
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AppInsightsValidator.cs
@@ -0,0 +1,13 @@
+using System.Threading.Tasks;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights
+{
+ public class AppInsightsValidator : IAppInsightsValidator
+ {
+ public virtual ValueTask ValidateInstrumentationKeyAsync(string instrumentationKey)
+ {
+ bool isValid = !string.IsNullOrWhiteSpace(instrumentationKey);
+ return new ValueTask(isValid);
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AvailabilityItemPopulator.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AvailabilityItemPopulator.cs
new file mode 100644
index 00000000..8f1fd208
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/AvailabilityItemPopulator.cs
@@ -0,0 +1,26 @@
+using Adriva.Extensions.Analytics.Server.AppInsights.Contracts;
+using Adriva.Extensions.Analytics.Server.Entities;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights
+{
+ internal sealed class AvailabilityItemPopulator : AnalyticsItemPopulator
+ {
+ public override string TargetKey => "AppAvailabilityResults";
+
+ public override bool TryPopulate(Envelope envelope, ref AnalyticsItem analyticsItem)
+ {
+ if (!(envelope.EventData is AvailabilityData availabilityData)) return false;
+
+ AvailabilityItem availabilityItem = new AvailabilityItem();
+ availabilityItem.Duration = availabilityData.Duration;
+ availabilityItem.Message = availabilityData.Message;
+ availabilityItem.Name = availabilityData.Name;
+ availabilityItem.Success = availabilityData.Success;
+
+ if (availabilityData.Properties.TryGetValue("AspNetCoreEnvironment", out string environment)) availabilityItem.Environment = environment;
+
+ analyticsItem.AvailabilityItem = availabilityItem;
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/AvailabilityData.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/AvailabilityData.cs
new file mode 100644
index 00000000..454e25d1
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/AvailabilityData.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public partial class AvailabilityData
+ : Domain
+ {
+ [JsonProperty("ver")]
+ public int Version { get; set; }
+
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("duration")]
+ public TimeSpan? Duration { get; set; }
+
+ [JsonProperty("success")]
+ public bool Success { get; set; }
+
+ [JsonProperty("runLocation")]
+ public string RunLocation { get; set; }
+
+ [JsonProperty("message")]
+ public string Message { get; set; }
+
+ [JsonProperty("properties")]
+ public Dictionary Properties { get; set; }
+
+ [JsonProperty("measurements")]
+ public Dictionary Measurements { get; set; }
+
+ public AvailabilityData()
+ : this("AI.AvailabilityData", "AvailabilityData")
+ { }
+
+ protected AvailabilityData(string fullName, string name)
+ {
+ this.Version = 2;
+ this.Id = string.Empty;
+ this.Name = string.Empty;
+ this.Duration = null;
+ this.RunLocation = string.Empty;
+ this.Message = string.Empty;
+ this.Properties = new Dictionary();
+ this.Measurements = new Dictionary();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/Base.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/Base.cs
new file mode 100644
index 00000000..bb8add79
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/Base.cs
@@ -0,0 +1,19 @@
+using Newtonsoft.Json;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public partial class Base
+ {
+ [JsonProperty("baseType")]
+ public string BaseType { get; set; }
+
+ public Base()
+ : this("AI.Base", "Base")
+ { }
+
+ protected Base(string fullName, string name)
+ {
+ this.BaseType = string.Empty;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/DataPoint.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/DataPoint.cs
new file mode 100644
index 00000000..e20dd9b5
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/DataPoint.cs
@@ -0,0 +1,39 @@
+using Adriva.Extensions.Analytics.Server.Entities;
+using Newtonsoft.Json;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public partial class DataPoint
+ {
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("kind")]
+ public DataPointType Kind { get; set; }
+
+ [JsonProperty("value")]
+ public double Value { get; set; }
+
+ [JsonProperty("count")]
+ public int? Count { get; set; }
+
+ [JsonProperty("min")]
+ public double? Minimum { get; set; }
+
+ [JsonProperty("max")]
+ public double? Maximum { get; set; }
+
+ [JsonProperty("stdDev")]
+ public double? StandardDeviation { get; set; }
+
+ public DataPoint()
+ : this("AI.DataPoint", "DataPoint")
+ { }
+
+ protected DataPoint(string fullName, string name)
+ {
+ this.Name = string.Empty;
+ this.Kind = DataPointType.Measurement;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/Domain.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/Domain.cs
new file mode 100644
index 00000000..e80d35bb
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/Domain.cs
@@ -0,0 +1,7 @@
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public partial class Domain
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/Envelope.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/Envelope.cs
new file mode 100644
index 00000000..a42c64b3
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/Envelope.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using Newtonsoft.Json;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public class Envelope
+ {
+ [JsonProperty("ver")]
+ public int Version { get; set; }
+
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("Time")]
+ public string Time { get; set; }
+
+ [JsonProperty("sampleRate")]
+ public double SampleRate { get; set; }
+
+ [JsonProperty("seq")]
+ public string Sequence { get; set; }
+
+ [JsonProperty("iKey")]
+ public string InstrumentationKey { get; set; }
+
+ [JsonProperty("time")]
+ public string Timestamp { get; set; }
+
+ [JsonProperty("tags")]
+ public Dictionary Tags { get; set; }
+
+ [JsonProperty("data")]
+ public Base Data { get; set; }
+
+ [JsonIgnore]
+ public Domain EventData { get; internal set; }
+
+ [JsonIgnore]
+ public DateTime EventDate
+ {
+ get
+ {
+ if (DateTime.TryParse(this.Timestamp, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime))
+ {
+ return dateTime;
+ }
+ else if (DateTime.TryParse(this.Timestamp, CultureInfo.CurrentCulture, DateTimeStyles.None, out dateTime))
+ {
+ return dateTime;
+ }
+ else
+ {
+ return DateTime.MinValue;
+ }
+ }
+ set
+ {
+ this.Timestamp = value.ToString("yyyy-MM-dd HH:mm:ss.ffff");
+ }
+ }
+
+ public Envelope()
+ : this("AI.Envelope", "Envelope")
+ { }
+
+ protected Envelope(string fullName, string name)
+ {
+ this.Version = 1;
+ this.Name = string.Empty;
+ this.Time = string.Empty;
+ this.SampleRate = 100.0;
+ this.Sequence = string.Empty;
+ this.InstrumentationKey = string.Empty;
+ this.Tags = new Dictionary();
+ this.Data = new Base();
+ }
+
+ public T Unwrap() where T : Domain
+ {
+ if (this.EventData is T instanceOfT) return instanceOfT;
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/EventData.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/EventData.cs
new file mode 100644
index 00000000..7f1052a4
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/EventData.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public partial class EventData
+ : Domain
+ {
+ [JsonProperty("ver")]
+ public int Version { get; set; }
+
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("properties")]
+ public Dictionary Properties { get; set; }
+
+ [JsonProperty("measurements")]
+ public Dictionary Measurements { get; set; }
+
+ public EventData()
+ : this("AI.EventData", "EventData")
+ { }
+
+ protected EventData(string fullName, string name)
+ {
+ this.Version = 2;
+ this.Name = string.Empty;
+ this.Properties = new Dictionary();
+ this.Measurements = new Dictionary();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/ExceptionData.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/ExceptionData.cs
new file mode 100644
index 00000000..2e535ba6
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/ExceptionData.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+using Adriva.Extensions.Analytics.Server.Entities;
+using Newtonsoft.Json;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public partial class ExceptionData
+ : Domain
+ {
+ [JsonProperty("ver")]
+ public int Version { get; set; }
+
+ [JsonProperty("exceptions")]
+ public List Exceptions { get; set; }
+
+ [JsonProperty("severityLevel")]
+ public Severity? SeverityLevel { get; set; }
+
+ [JsonProperty("problemId")]
+ public string ProblemId { get; set; }
+
+ [JsonProperty("properties")]
+ public Dictionary Properties { get; set; }
+
+ [JsonProperty("measurements")]
+ public Dictionary Measurements { get; set; }
+
+ public ExceptionData()
+ : this("AI.ExceptionData", "ExceptionData")
+ { }
+
+ protected ExceptionData(string fullName, string name)
+ {
+ this.Version = 2;
+ this.Exceptions = new List();
+ this.ProblemId = string.Empty;
+ this.Properties = new Dictionary();
+ this.Measurements = new Dictionary();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/ExceptionDetails.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/ExceptionDetails.cs
new file mode 100644
index 00000000..065963bb
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/ExceptionDetails.cs
@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public partial class ExceptionDetails
+ {
+ [JsonProperty("id")]
+ public int Id { get; set; }
+
+ [JsonProperty("outerId")]
+ public int OuterId { get; set; }
+
+ [JsonProperty("typeName")]
+ public string TypeName { get; set; }
+
+ [JsonProperty("message")]
+ public string Message { get; set; }
+
+ [JsonProperty("hasFullStack")]
+ public bool HasFullStack { get; set; }
+
+ [JsonProperty("stack")]
+ public string Stack { get; set; }
+
+ [JsonProperty("parsedStack")]
+ public List ParsedStack { get; set; }
+
+ public ExceptionDetails()
+ : this("AI.ExceptionDetails", "ExceptionDetails")
+ { }
+
+ protected ExceptionDetails(string fullName, string name)
+ {
+ this.TypeName = string.Empty;
+ this.Message = string.Empty;
+ this.HasFullStack = true;
+ this.Stack = string.Empty;
+ this.ParsedStack = new List();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/MessageData.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/MessageData.cs
new file mode 100644
index 00000000..019564ae
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/MessageData.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using Adriva.Extensions.Analytics.Server.Entities;
+using Newtonsoft.Json;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public partial class MessageData
+ : Domain
+ {
+ [JsonProperty("ver")]
+ public int Version { get; set; }
+
+ [JsonProperty("message")]
+ public string Message { get; set; }
+
+ [JsonProperty("severityLevel")]
+ public Severity SeverityLevel { get; set; }
+
+ [JsonProperty("properties")]
+ public Dictionary Properties { get; set; }
+
+ public MessageData()
+ : this("AI.MessageData", "MessageData")
+ { }
+
+ protected MessageData(string fullName, string name)
+ {
+ this.Version = 2;
+ this.Message = string.Empty;
+ this.Properties = new Dictionary();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/MetricData.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/MetricData.cs
new file mode 100644
index 00000000..631da1a4
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/MetricData.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public partial class MetricData
+ : Domain
+ {
+ [JsonProperty("ver")]
+ public int Version { get; set; }
+
+ [JsonProperty("metrics")]
+ public List Metrics { get; set; }
+
+ [JsonProperty("properties")]
+ public Dictionary Properties { get; set; }
+
+ public MetricData()
+ : this("AI.MetricData", "MetricData")
+ { }
+
+ protected MetricData(string fullName, string name)
+ {
+ this.Version = 2;
+ this.Metrics = new List();
+ this.Properties = new Dictionary();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/PageViewData.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/PageViewData.cs
new file mode 100644
index 00000000..b0c5ae01
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/PageViewData.cs
@@ -0,0 +1,42 @@
+using System;
+using Newtonsoft.Json;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public partial class PageViewData
+ : EventData
+ {
+ [JsonProperty("url")]
+ public string Url { get; set; }
+
+ [JsonProperty("duration")]
+ public string Duration { get; set; }
+
+ [JsonIgnore]
+ public double DurationInMilliseconds
+ {
+ get
+ {
+ if (TimeSpan.TryParse(this.Duration, out TimeSpan timespan))
+ {
+ return timespan.TotalMilliseconds;
+ }
+ return -1;
+ }
+ set
+ {
+ this.Duration = TimeSpan.FromMilliseconds(value).ToString();
+ }
+ }
+
+ public PageViewData()
+ : this("AI.PageViewData", "PageViewData")
+ { }
+
+ protected PageViewData(string fullName, string name)
+ {
+ this.Url = string.Empty;
+ this.Duration = string.Empty;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/PageViewPerfData.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/PageViewPerfData.cs
new file mode 100644
index 00000000..402473d7
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/PageViewPerfData.cs
@@ -0,0 +1,36 @@
+using Newtonsoft.Json;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public partial class PageViewPerfData
+ : PageViewData
+ {
+ [JsonProperty("perfTotal")]
+ public string PerfTotal { get; set; }
+
+ [JsonProperty("networkConnect")]
+ public string NetworkConnect { get; set; }
+
+ [JsonProperty("sentRequest")]
+ public string SentRequest { get; set; }
+
+ [JsonProperty("receivedResponse")]
+ public string ReceivedResponse { get; set; }
+
+ [JsonProperty("domProcessing")]
+ public string DomProcessing { get; set; }
+
+ public PageViewPerfData()
+ : this("AI.PageViewPerfData", "PageViewPerfData")
+ { }
+
+ protected PageViewPerfData(string fullName, string name)
+ {
+ this.PerfTotal = string.Empty;
+ this.NetworkConnect = string.Empty;
+ this.SentRequest = string.Empty;
+ this.ReceivedResponse = string.Empty;
+ this.DomProcessing = string.Empty;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/RemoteDependencyData.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/RemoteDependencyData.cs
new file mode 100644
index 00000000..dbe19416
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/RemoteDependencyData.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public partial class RemoteDependencyData
+ : Domain
+ {
+ [JsonProperty("ver")]
+ public int Version { get; set; }
+
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
+ [JsonProperty("resultCode")]
+ public string ResultCode { get; set; }
+
+ [JsonProperty("duration")]
+ public string Duration { get; set; }
+
+ [JsonProperty("success")]
+ public bool? IsSuccess { get; set; }
+
+ [JsonProperty("data")]
+ public string Data { get; set; }
+
+ [JsonProperty("target")]
+ public string Target { get; set; }
+
+ [JsonProperty("type")]
+ public string Type { get; set; }
+
+ [JsonProperty("properties")]
+ public Dictionary Properties { get; set; }
+
+ [JsonProperty("measurements")]
+ public Dictionary Measurements { get; set; }
+
+ [JsonIgnore]
+ public double DurationInMilliseconds
+ {
+ get
+ {
+ if (TimeSpan.TryParse(this.Duration, out TimeSpan timespan))
+ {
+ return timespan.TotalMilliseconds;
+ }
+ return -1;
+ }
+ set
+ {
+ this.Duration = TimeSpan.FromMilliseconds(value).ToString();
+ }
+ }
+
+ public RemoteDependencyData()
+ : this("AI.RemoteDependencyData", "RemoteDependencyData")
+ { }
+
+ protected RemoteDependencyData(string fullName, string name)
+ {
+ this.Version = 2;
+ this.Name = string.Empty;
+ this.Id = string.Empty;
+ this.ResultCode = string.Empty;
+ this.Duration = string.Empty;
+ this.IsSuccess = true;
+ this.Data = string.Empty;
+ this.Target = string.Empty;
+ this.Type = string.Empty;
+ this.Properties = new Dictionary();
+ this.Measurements = new Dictionary();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/RequestData.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/RequestData.cs
new file mode 100644
index 00000000..add8c72b
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/RequestData.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public partial class RequestData
+ : EventData
+ {
+
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
+ [JsonProperty("source")]
+ public string Source { get; set; }
+
+ [JsonProperty("duration")]
+ public string Duration { get; set; }
+
+ [JsonProperty("responseCode")]
+ public string ResponseCode { get; set; }
+
+ [JsonProperty("success")]
+ public bool IsSuccess { get; set; }
+
+ [JsonProperty("url")]
+ public string Url { get; set; }
+
+
+ [JsonIgnore]
+ public double DurationInMilliseconds
+ {
+ get
+ {
+ if (TimeSpan.TryParse(this.Duration, out TimeSpan timespan))
+ {
+ return timespan.TotalMilliseconds;
+ }
+ return -1;
+ }
+ set
+ {
+ this.Duration = TimeSpan.FromMilliseconds(value).ToString();
+ }
+ }
+
+ public RequestData()
+ : this("AI.RequestData", "RequestData")
+ { }
+
+ protected RequestData(string fullName, string name)
+ {
+ this.Version = 2;
+ this.Id = string.Empty;
+ this.Source = string.Empty;
+ this.Name = string.Empty;
+ this.Duration = string.Empty;
+ this.ResponseCode = string.Empty;
+ this.Url = string.Empty;
+ this.Properties = new Dictionary();
+ this.Measurements = new Dictionary();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/StackFrame.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/StackFrame.cs
new file mode 100644
index 00000000..f4b68604
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/Contracts/StackFrame.cs
@@ -0,0 +1,38 @@
+using Newtonsoft.Json;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights.Contracts
+{
+ public partial class StackFrame
+ {
+ [JsonProperty("level")]
+ public int Level { get; set; }
+
+ [JsonProperty("method")]
+ public string Method { get; set; }
+
+ [JsonProperty("assembly")]
+ public string Assembly { get; set; }
+
+ [JsonProperty("fileName")]
+ public string FileName { get; set; }
+
+ [JsonProperty("line")]
+ public int Line { get; set; }
+
+ public StackFrame()
+ : this("AI.StackFrame", "StackFrame")
+ { }
+
+ protected StackFrame(string fullName, string name)
+ {
+ this.Method = string.Empty;
+ this.Assembly = string.Empty;
+ this.FileName = string.Empty;
+ }
+
+ public override string ToString()
+ {
+ return $"{this.Method} in '{this.Assembly}' at line {this.Line} in file '{this.FileName}'";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/DependencyItemPopulator.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/DependencyItemPopulator.cs
new file mode 100644
index 00000000..ac39904f
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/DependencyItemPopulator.cs
@@ -0,0 +1,32 @@
+using Adriva.Extensions.Analytics.Server.AppInsights.Contracts;
+using Adriva.Extensions.Analytics.Server.Entities;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights
+{
+ internal sealed class DependencyItemPopulator : AnalyticsItemPopulator
+ {
+ public override string TargetKey => "AppDependencies";
+
+ public override bool TryPopulate(Envelope envelope, ref AnalyticsItem analyticsItem)
+ {
+ if (!(envelope.EventData is RemoteDependencyData dependencyData)) return false;
+
+ DependencyItem dependencyItem = new DependencyItem();
+
+ dependencyItem.Name = dependencyData.Name;
+ dependencyItem.Duration = dependencyData.DurationInMilliseconds;
+ dependencyItem.IsSuccess = dependencyData.IsSuccess;
+ dependencyItem.Type = dependencyData.Type;
+ dependencyItem.Target = dependencyData.Target;
+
+ if (dependencyData.Properties.TryGetValue("DeveloperMode", out string developerModeValue) && bool.TryParse(developerModeValue, out bool isDeveloperMode)) dependencyItem.IsDeveloperMode = isDeveloperMode;
+ if (dependencyData.Properties.TryGetValue("AspNetCoreEnvironment", out string environment)) dependencyItem.Environment = environment;
+ if (dependencyData.Properties.TryGetValue("Input", out string input)) dependencyItem.Input = input;
+ if (dependencyData.Properties.TryGetValue("Output", out string output)) dependencyItem.Output = output;
+
+ analyticsItem.Dependencies.Add(dependencyItem);
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/EventItemPopulator.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/EventItemPopulator.cs
new file mode 100644
index 00000000..06538316
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/EventItemPopulator.cs
@@ -0,0 +1,25 @@
+using Adriva.Extensions.Analytics.Server.AppInsights.Contracts;
+using Adriva.Extensions.Analytics.Server.Entities;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights
+{
+ internal sealed class EventItemPopulator : AnalyticsItemPopulator
+ {
+ public override string TargetKey => "AppEvents";
+
+ public override bool TryPopulate(Envelope envelope, ref AnalyticsItem analyticsItem)
+ {
+ if (!(envelope.EventData is EventData eventData)) return false;
+
+ EventItem eventItem = new EventItem();
+
+ eventItem.Name = eventData.Name;
+ if (eventData.Properties.TryGetValue("DeveloperMode", out string developerModeValue) && bool.TryParse(developerModeValue, out bool isDeveloperMode)) eventItem.IsDeveloperMode = isDeveloperMode;
+ if (eventData.Properties.TryGetValue("AspNetCoreEnvironment", out string environment)) eventItem.Environment = environment;
+
+ analyticsItem.Events.Add(eventItem);
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/ExceptionItemPopulator.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/ExceptionItemPopulator.cs
new file mode 100644
index 00000000..556a89ef
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/ExceptionItemPopulator.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Linq;
+using Adriva.Extensions.Analytics.Server.AppInsights.Contracts;
+using Adriva.Extensions.Analytics.Server.Entities;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights
+{
+ internal sealed class ExceptionItemPopulator : AnalyticsItemPopulator
+ {
+ public override string TargetKey => "AppExceptions";
+
+ public override bool TryPopulate(Envelope envelope, ref AnalyticsItem analyticsItem)
+ {
+ if (!(envelope.EventData is ExceptionData exceptionData)) return false;
+ if (null == exceptionData.Exceptions || 0 == exceptionData.Exceptions.Count) return false;
+
+ foreach (var exceptionDetails in exceptionData.Exceptions)
+ {
+ ExceptionItem exceptionItem = new ExceptionItem();
+ if (exceptionData.Properties.TryGetValue("RequestId", out string requestId)) exceptionItem.RequestId = requestId;
+ if (exceptionData.Properties.TryGetValue("CategoryName", out string categoryName)) exceptionItem.Category = categoryName;
+ if (exceptionData.Properties.TryGetValue("RequestPath", out string path)) exceptionItem.Path = path;
+ if (exceptionData.Properties.TryGetValue("ConnectionId", out string connectionId)) exceptionItem.ConnectionId = connectionId;
+ if (exceptionData.Properties.TryGetValue("FormattedMessage", out string message)) exceptionItem.Message = message;
+ if (exceptionData.Properties.TryGetValue("EventName", out string eventName)) exceptionItem.Name = eventName;
+
+ exceptionItem.ExceptionId = exceptionDetails.Id;
+ exceptionItem.ExceptionMessage = exceptionDetails.Message;
+ exceptionItem.ExceptionType = exceptionDetails.TypeName;
+
+ if (null != exceptionDetails.ParsedStack && 0 < exceptionDetails.ParsedStack.Count)
+ {
+ exceptionItem.StackTrace = string.Join(Environment.NewLine, exceptionDetails.ParsedStack.Select(s => s.ToString()));
+ }
+
+ analyticsItem.Exceptions.Add(exceptionItem);
+ }
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/IAppInsightsValidator.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/IAppInsightsValidator.cs
new file mode 100644
index 00000000..db837464
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/IAppInsightsValidator.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights
+{
+ public interface IAppInsightsValidator
+ {
+ ValueTask ValidateInstrumentationKeyAsync(string instrumentationKey);
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/MetricItemPopulator.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/MetricItemPopulator.cs
new file mode 100644
index 00000000..149199f5
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/MetricItemPopulator.cs
@@ -0,0 +1,43 @@
+using Adriva.Extensions.Analytics.Server.AppInsights.Contracts;
+using Adriva.Extensions.Analytics.Server.Entities;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights
+{
+ internal sealed class MetricItemPopulator : AnalyticsItemPopulator
+ {
+ public override string TargetKey => "AppMetrics";
+
+ public override bool TryPopulate(Envelope envelope, ref AnalyticsItem analyticsItem)
+ {
+ if (!(envelope.EventData is MetricData metricData)) return false;
+ if (null == metricData.Metrics || 0 == metricData.Metrics.Count) return false;
+
+ foreach (var metric in metricData.Metrics)
+ {
+ MetricItem metricItem = new MetricItem()
+ {
+ Kind = metric.Kind,
+ Maximum = metric.Maximum ?? 0,
+ Minimum = metric.Minimum ?? 0,
+ SampleRate = envelope.SampleRate,
+ StandardDeviation = metric.StandardDeviation ?? 0,
+ Value = metric.Value,
+ Name = metric.Name,
+ Count = metric.Count ?? 0
+ };
+
+ if (!string.IsNullOrWhiteSpace(metricItem.Name) && -1 < metricItem.Name.IndexOf('\\'))
+ {
+ metricItem.NormalizedName = metricItem.Name.Substring(1 + metricItem.Name.LastIndexOf('\\'));
+ }
+ else
+ {
+ metricItem.NormalizedName = metricItem.Name;
+ }
+ analyticsItem.Metrics.Add(metricItem);
+ }
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/RequestItemPopulator.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/RequestItemPopulator.cs
new file mode 100644
index 00000000..478c5ac1
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/RequestItemPopulator.cs
@@ -0,0 +1,28 @@
+using Adriva.Extensions.Analytics.Server.AppInsights.Contracts;
+using Adriva.Extensions.Analytics.Server.Entities;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights
+{
+ internal sealed class RequestItemPopulator : AnalyticsItemPopulator
+ {
+ public override string TargetKey => "AppRequests";
+
+ public override bool TryPopulate(Envelope envelope, ref AnalyticsItem analyticsItem)
+ {
+ if (!(envelope.EventData is RequestData requestData)) return false;
+
+ analyticsItem.RequestItem = new RequestItem();
+
+ analyticsItem.RequestItem.Name = requestData.Name;
+ if (requestData.Properties.TryGetValue("DeveloperMode", out string developerModeValue) && bool.TryParse(developerModeValue, out bool isDeveloperMode)) analyticsItem.RequestItem.IsDeveloperMode = isDeveloperMode;
+ if (requestData.Properties.TryGetValue("AspNetCoreEnvironment", out string environment)) analyticsItem.RequestItem.Environment = environment;
+ analyticsItem.RequestItem.IsSuccess = requestData.IsSuccess;
+ analyticsItem.RequestItem.Name = requestData.Name;
+ analyticsItem.RequestItem.Duration = requestData.DurationInMilliseconds;
+
+ if (int.TryParse(requestData.ResponseCode, out int responseCode)) analyticsItem.RequestItem.ResponseCode = responseCode;
+ analyticsItem.RequestItem.Url = requestData.Url;
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/TelemetryConverter.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/TelemetryConverter.cs
new file mode 100644
index 00000000..d4903c3f
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/TelemetryConverter.cs
@@ -0,0 +1,69 @@
+using System.Linq;
+using System;
+using Newtonsoft.Json;
+using Adriva.Extensions.Analytics.Server.AppInsights.Contracts;
+using Newtonsoft.Json.Linq;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights
+{
+ public class TelemetryConverter : JsonConverter
+ {
+ private static readonly Type TypeOfTelemetry = typeof(Envelope);
+
+ private static readonly Type[] KnownTypes = new[] {
+ typeof(Contracts.AvailabilityData),
+ typeof(Contracts.EventData),
+ typeof(Contracts.ExceptionData),
+ typeof(Contracts.MessageData),
+ typeof(Contracts.MetricData),
+ typeof(Contracts.PageViewData),
+ typeof(Contracts.PageViewPerfData),
+ typeof(Contracts.RemoteDependencyData),
+ typeof(Contracts.RequestData),
+ };
+
+ public override bool CanConvert(Type objectType)
+ {
+ return TelemetryConverter.TypeOfTelemetry.IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var jObject = serializer.Deserialize(reader);
+
+ var envelope = jObject.ToObject();
+ var typeName = envelope.Data.BaseType;
+
+ var matchinTelemetryType = TelemetryConverter.KnownTypes.FirstOrDefault(kt => 0 == string.Compare(kt.Name, typeName, StringComparison.OrdinalIgnoreCase));
+
+ if (null != matchinTelemetryType)
+ {
+ JProperty dataProperty = jObject.Property("data");
+ if (null != dataProperty && dataProperty.Value is JObject propertyObject)
+ {
+ JProperty baseDataProperty = propertyObject.Property("baseData");
+ if (null != baseDataProperty?.Value)
+ {
+ try
+ {
+ envelope.EventData = (Domain)baseDataProperty.Value.ToObject(matchinTelemetryType);
+ return envelope;
+ }
+ catch
+ {
+ //?
+ return null;
+ }
+ }
+ }
+
+ }
+ return null;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/TraceItemPopulator.cs b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/TraceItemPopulator.cs
new file mode 100644
index 00000000..9a925dfb
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server.AppInsights/TraceItemPopulator.cs
@@ -0,0 +1,26 @@
+using Adriva.Extensions.Analytics.Server.AppInsights.Contracts;
+using Adriva.Extensions.Analytics.Server.Entities;
+
+namespace Adriva.Extensions.Analytics.Server.AppInsights
+{
+ internal sealed class TraceItemPopulator : AnalyticsItemPopulator
+ {
+ public override string TargetKey => "AppTraces";
+
+ public override bool TryPopulate(Envelope envelope, ref AnalyticsItem analyticsItem)
+ {
+ if (!(envelope.EventData is MessageData messageData)) return false;
+
+ analyticsItem.MessageItem = new MessageItem();
+
+ analyticsItem.MessageItem.Message = messageData.Message;
+ analyticsItem.MessageItem.Severity = messageData.SeverityLevel;
+
+ if (messageData.Properties.TryGetValue("DeveloperMode", out string developerModeValue) && bool.TryParse(developerModeValue, out bool isDeveloperMode)) analyticsItem.MessageItem.IsDeveloperMode = isDeveloperMode;
+ if (messageData.Properties.TryGetValue("AspNetCoreEnvironment", out string environmentName)) analyticsItem.MessageItem.Environment = environmentName;
+ if (messageData.Properties.TryGetValue("CategoryName", out string categoryName)) analyticsItem.MessageItem.Category = categoryName;
+
+ return !string.IsNullOrWhiteSpace(analyticsItem.MessageItem.Message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/Adriva.Extensions.Analytics.Server.csproj b/Analytics/src/Adriva.Extensions.Analytics.Server/Adriva.Extensions.Analytics.Server.csproj
new file mode 100644
index 00000000..9d90caf5
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/Adriva.Extensions.Analytics.Server.csproj
@@ -0,0 +1,13 @@
+
+
+
+ netcoreapp3.1
+ net7.0
+ 1
+
+
+
+
+
+
+
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/AnalyticsServerBuilder.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/AnalyticsServerBuilder.cs
new file mode 100644
index 00000000..61be2055
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/AnalyticsServerBuilder.cs
@@ -0,0 +1,77 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+
+namespace Adriva.Extensions.Analytics.Server
+{
+ internal sealed class AnalyticsServerBuilder : IAnalyticsServerBuilder, IConfigureOptions
+ {
+ private readonly IServiceCollection ServiceCollection;
+ private readonly AnalyticsServerOptions Options = new AnalyticsServerOptions();
+ private Type RepositoryType;
+ private Type HandlerType;
+
+ public IServiceCollection Services => this.ServiceCollection;
+
+ public AnalyticsServerBuilder(IServiceCollection services)
+ {
+ this.ServiceCollection = services;
+ }
+
+ public IAnalyticsServerBuilder UseRepository() where TRepository : class, IAnalyticsRepository
+ {
+ this.RepositoryType = typeof(TRepository);
+ return this;
+ }
+
+ public IAnalyticsServerBuilder UseHandler() where THandler : IAnalyticsHandler
+ {
+ this.HandlerType = typeof(THandler);
+ return this;
+ }
+
+ public IAnalyticsServerBuilder SetProcessorThreadCount(int threadCount)
+ {
+ this.Options.ProcessorThreadCount = Math.Max(1, threadCount);
+ return this;
+ }
+
+ public IAnalyticsServerBuilder SetBufferCapacity(int capacity)
+ {
+ this.Options.BufferCapacity = capacity;
+ return this;
+ }
+
+ public IAnalyticsServerBuilder SetStorageTimeout(TimeSpan timeout)
+ {
+ this.Options.StorageTimeout = timeout;
+ return this;
+ }
+
+ public void Build()
+ {
+ this.ServiceCollection.AddSingleton(serviceProvider =>
+ {
+ return (IAnalyticsHandler)ActivatorUtilities.CreateInstance(serviceProvider, this.HandlerType);
+ });
+
+ this.ServiceCollection.AddScoped(serviceProvider =>
+ {
+ if (null == this.RepositoryType)
+ {
+ throw new ArgumentException($"Analytics repository type is not set.");
+ }
+ return (IAnalyticsRepository)ActivatorUtilities.CreateInstance(serviceProvider, this.RepositoryType);
+ });
+
+ this.ServiceCollection.ConfigureOptions(this);
+ }
+
+ public void Configure(AnalyticsServerOptions options)
+ {
+ options.ProcessorThreadCount = Math.Max(1, this.Options.ProcessorThreadCount);
+ options.BufferCapacity = Math.Max(1, this.Options.BufferCapacity);
+ options.StorageTimeout = this.Options.StorageTimeout;
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/AnalyticsServerExtensions.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/AnalyticsServerExtensions.cs
new file mode 100644
index 00000000..3b0b4482
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/AnalyticsServerExtensions.cs
@@ -0,0 +1,46 @@
+using System;
+using Adriva.Extensions.Analytics.Server;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// Provides extension methods to use Microsoft AppInsights server side services.
+ ///
+ public static class AnalyticsServerExtensions
+ {
+ ///
+ /// Adds services required to host an analytics server to the service collection.
+ ///
+ /// The Microsoft.Extensions.DependencyInjection.IServiceCollection to add services to.
+ /// Analytics server builder delegate.
+ /// The Microsoft.Extensions.DependencyInjection.IServiceCollection so that additional calls can be chained.
+ public static IServiceCollection AddAnalyticsServer(this IServiceCollection services, Action build)
+ {
+ AnalyticsServerBuilder analyticsBuilder = new AnalyticsServerBuilder(services);
+ analyticsBuilder.UseHandler();
+ build.Invoke(analyticsBuilder);
+ analyticsBuilder.Build();
+
+ services.AddSingleton();
+ services.AddHostedService();
+ return services;
+ }
+
+ ///
+ /// Adds analytics server to the Http pipeline of a web application.
+ ///
+ /// The Microsoft.AspNetCore.Builder.IApplicationBuilder instance that the analytics server will be added to.
+ /// Base path of the analytics server to capture incoming requests.
+ /// The Microsoft.AspNetCore.Builder.IApplicationBuilder so that additional calls can be chained.
+ public static IApplicationBuilder UseAnalyticsServer(this IApplicationBuilder app, PathString basePath)
+ {
+ if (string.IsNullOrWhiteSpace(basePath)) basePath = "/analytics";
+
+ app.Map(basePath.Add("/track"), appBuilder => appBuilder.UseMiddleware());
+
+ return app;
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/AnalyticsServerOptions.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/AnalyticsServerOptions.cs
new file mode 100644
index 00000000..0a04739c
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/AnalyticsServerOptions.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace Adriva.Extensions.Analytics.Server
+{
+ ///
+ /// Defines the custom behavior of analytics server services.
+ ///
+ public sealed class AnalyticsServerOptions
+ {
+ ///
+ /// Gets or sets the maximum number of items that can be stored in the server buffer.
+ ///
+ /// An integer value that represents the number of maximum items in the server buffer.
+ public int BufferCapacity { get; set; }
+
+ ///
+ /// Gets or sets the maximum number of worker threads that will process the analytics data.
+ /// The minimum number of allowed thread count is 1.
+ ///
+ /// An integer value represeting the number of worker threads.
+ public int ProcessorThreadCount { get; set; }
+
+ ///
+ /// Gets or sets the maximum time given to the repository to persist analytics data buffer.
+ ///
+ /// A TimeSpan value indicating the timeout.
+ public TimeSpan StorageTimeout { get; set; } = TimeSpan.FromSeconds(5);
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/AnalyticsTrackingMiddleware.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/AnalyticsTrackingMiddleware.cs
new file mode 100644
index 00000000..d3ca2c9b
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/AnalyticsTrackingMiddleware.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Adriva.Extensions.Analytics.Server.Entities;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+
+namespace Adriva.Extensions.Analytics.Server
+{
+ internal class AnalyticsTrackingMiddleware
+ {
+ private readonly RequestDelegate Next;
+ private readonly IAnalyticsHandler Handler;
+ private readonly IQueueingService QueueingService;
+ private readonly ILogger Logger;
+
+ public AnalyticsTrackingMiddleware(RequestDelegate next, IAnalyticsHandler handler, IQueueingService queueingService, ILogger logger)
+ {
+ this.Next = next;
+ this.Handler = handler;
+ this.QueueingService = queueingService;
+ this.Logger = logger;
+ }
+
+ public async Task Invoke(HttpContext context)
+ {
+ IAsyncEnumerable items = this.Handler.HandleAsync(context.Request);
+
+ if (null != items)
+ {
+ await foreach (var item in items)
+ {
+ this.QueueingService.Enqueue(item);
+ }
+ }
+
+ context.Response.StatusCode = 204;
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/AnalyticsItem.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/AnalyticsItem.cs
new file mode 100644
index 00000000..64fdd10c
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/AnalyticsItem.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+
+namespace Adriva.Extensions.Analytics.Server.Entities
+{
+
+ public class AnalyticsItem : IAnalyticsObject
+ {
+ private readonly IDictionary CustomProperties = new Dictionary();
+
+ public long Id { get; set; }
+
+ public string InstrumentationKey { get; set; }
+
+ public DateTime Timestamp { get; set; }
+
+ public string ApplicationVersion { get; set; }
+
+ public string RoleInstance { get; set; }
+
+ public string OperationId { get; set; }
+
+ public string ParentOperationId { get; set; }
+
+ public string Ip { get; set; }
+
+ public string SdkVersion { get; set; }
+
+ public string Type { get; set; }
+
+ public string UserId { get; set; }
+
+ public string UserAccountId { get; set; }
+
+ public string AuthenticatedUserId { get; set; }
+
+ public List Exceptions { get; set; } = new List();
+
+ public RequestItem RequestItem { get; set; }
+
+ public MessageItem MessageItem { get; set; }
+
+ public AvailabilityItem AvailabilityItem { get; set; }
+
+ public List Metrics { get; set; } = new List();
+
+ public List Events { get; set; } = new List();
+
+ public List Dependencies { get; set; } = new List();
+
+ public IDictionary Properties => this.CustomProperties;
+
+ public override string ToString() => $"AnalyticsItem, [{this.Type}]";
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/AvailabilityItem.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/AvailabilityItem.cs
new file mode 100644
index 00000000..be5e7ab4
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/AvailabilityItem.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace Adriva.Extensions.Analytics.Server.Entities
+{
+ public class AvailabilityItem : IAnalyticsObject
+ {
+ public long Id { get; set; }
+
+ public long AnalyticsItemId { get; set; }
+
+ public string Name { get; set; }
+
+ public TimeSpan? Duration { get; set; }
+
+ public bool Success { get; set; }
+
+ public string Environment { get; set; }
+
+ public string Message { get; set; }
+
+ public AnalyticsItem AnalyticsItem { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/DependencyItem.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/DependencyItem.cs
new file mode 100644
index 00000000..938a22d4
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/DependencyItem.cs
@@ -0,0 +1,29 @@
+namespace Adriva.Extensions.Analytics.Server.Entities
+{
+ public class DependencyItem : IAnalyticsObject
+ {
+ public long Id { get; set; }
+
+ public long AnalyticsItemId { get; set; }
+
+ public string Name { get; set; }
+
+ public string Target { get; set; }
+
+ public double Duration { get; set; }
+
+ public bool? IsSuccess { get; set; }
+
+ public string Type { get; set; }
+
+ public string Environment { get; set; }
+
+ public bool? IsDeveloperMode { get; set; }
+
+ public string Input { get; set; }
+
+ public string Output { get; set; }
+
+ public AnalyticsItem AnalyticsItem { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/Enums.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/Enums.cs
new file mode 100644
index 00000000..fe578911
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/Enums.cs
@@ -0,0 +1,35 @@
+namespace Adriva.Extensions.Analytics.Server.Entities
+{
+ ///
+ /// Represents the severity level of an analytics item.
+ ///
+ public enum Severity
+ {
+ //
+ // Summary:
+ // Verbose severity level.
+ Verbose = 0,
+ //
+ // Summary:
+ // Information severity level.
+ Information = 1,
+ //
+ // Summary:
+ // Warning severity level.
+ Warning = 2,
+ //
+ // Summary:
+ // Error severity level.
+ Error = 3,
+ //
+ // Summary:
+ // Critical severity level.
+ Critical = 4
+ }
+
+ public enum DataPointType
+ {
+ Measurement,
+ Aggregation,
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/EventItem.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/EventItem.cs
new file mode 100644
index 00000000..b00b3283
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/EventItem.cs
@@ -0,0 +1,17 @@
+namespace Adriva.Extensions.Analytics.Server.Entities
+{
+ public class EventItem : IAnalyticsObject
+ {
+ public long Id { get; set; }
+
+ public long AnalyticsItemId { get; set; }
+
+ public string Name { get; set; }
+
+ public string Environment { get; set; }
+
+ public bool? IsDeveloperMode { get; set; }
+
+ public AnalyticsItem AnalyticsItem { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/ExceptionItem.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/ExceptionItem.cs
new file mode 100644
index 00000000..9ca2a443
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/ExceptionItem.cs
@@ -0,0 +1,32 @@
+namespace Adriva.Extensions.Analytics.Server.Entities
+{
+ public class ExceptionItem : IAnalyticsObject
+ {
+ public long Id { get; set; }
+
+ public long AnalyticsItemId { get; set; }
+
+ public string Name { get; set; }
+
+ public string RequestId { get; set; }
+
+ public string Category { get; set; }
+
+ public string ConnectionId { get; set; }
+
+ public string Message { get; set; }
+
+ public string Path { get; set; }
+
+ public string ExceptionType { get; set; }
+
+ public int ExceptionId { get; set; }
+
+ public string ExceptionMessage { get; set; }
+
+ public string StackTrace { get; set; }
+
+ public AnalyticsItem AnalyticsItem { get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/IAnalyticsObject.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/IAnalyticsObject.cs
new file mode 100644
index 00000000..9980d081
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/IAnalyticsObject.cs
@@ -0,0 +1,7 @@
+namespace Adriva.Extensions.Analytics.Server.Entities
+{
+ public interface IAnalyticsObject
+ {
+ long Id { get; }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/MessageItem.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/MessageItem.cs
new file mode 100644
index 00000000..7a94a443
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/MessageItem.cs
@@ -0,0 +1,21 @@
+namespace Adriva.Extensions.Analytics.Server.Entities
+{
+ public class MessageItem : IAnalyticsObject
+ {
+ public long Id { get; set; }
+
+ public long AnalyticsItemId { get; set; }
+
+ public string Category { get; set; }
+
+ public string Message { get; set; }
+
+ public string Environment { get; set; }
+
+ public bool? IsDeveloperMode { get; set; }
+
+ public Severity? Severity { get; set; }
+
+ public AnalyticsItem AnalyticsItem { get; set; }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/MetricItem.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/MetricItem.cs
new file mode 100644
index 00000000..01298e0c
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/MetricItem.cs
@@ -0,0 +1,29 @@
+namespace Adriva.Extensions.Analytics.Server.Entities
+{
+ public class MetricItem : IAnalyticsObject
+ {
+ public long Id { get; set; }
+
+ public long AnalyticsItemId { get; set; }
+
+ public string Name { get; set; }
+
+ public string NormalizedName { get; set; }
+
+ public double SampleRate { get; set; }
+
+ public DataPointType Kind { get; set; }
+
+ public int Count { get; set; }
+
+ public double Maximum { get; set; }
+
+ public double Minimum { get; set; }
+
+ public double StandardDeviation { get; set; }
+
+ public double Value { get; set; }
+
+ public AnalyticsItem AnalyticsItem { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/RequestItem.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/RequestItem.cs
new file mode 100644
index 00000000..8dd19600
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/Entities/RequestItem.cs
@@ -0,0 +1,29 @@
+namespace Adriva.Extensions.Analytics.Server.Entities
+{
+ public class RequestItem : IAnalyticsObject
+ {
+ public long Id { get; set; }
+
+ public long AnalyticsItemId { get; set; }
+
+ public string Name { get; set; }
+
+ public string Environment { get; set; }
+
+ public bool? IsDeveloperMode { get; set; }
+
+ ///
+ /// Gets or sets the duration of the request in milliseconds.
+ ///
+ /// A double value representing the duration of the request.
+ public double Duration { get; set; }
+
+ public bool IsSuccess { get; set; }
+
+ public int ResponseCode { get; set; }
+
+ public string Url { get; set; }
+
+ public AnalyticsItem AnalyticsItem { get; set; }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/IAnalyticsHandler.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/IAnalyticsHandler.cs
new file mode 100644
index 00000000..c2e41ec3
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/IAnalyticsHandler.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using Adriva.Extensions.Analytics.Server.Entities;
+using Microsoft.AspNetCore.Http;
+
+namespace Adriva.Extensions.Analytics.Server
+{
+ ///
+ /// Provides methods to handle the HttpRequest and extract AnalyticsItems from the request.
+ ///
+ public interface IAnalyticsHandler
+ {
+ ///
+ /// Processes the HttpRequest and extracts AnalyticsItems from it.
+ ///
+ /// Represents the current HttpRequest.
+ /// An IAsyncEnumerable of AnalyticsItems that stores the extracted analytics item data.
+ IAsyncEnumerable HandleAsync(HttpRequest request);
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/IAnalyticsRepository.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/IAnalyticsRepository.cs
new file mode 100644
index 00000000..604abea5
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/IAnalyticsRepository.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Adriva.Extensions.Analytics.Server.Entities;
+
+namespace Adriva.Extensions.Analytics.Server
+{
+ ///
+ /// Provides methods to persist analytics data in a repository.
+ ///
+ public interface IAnalyticsRepository
+ {
+ ///
+ /// Called by the system once to let the repository do some initial work before accepting data.
+ ///
+ /// Represents the asynchronous process operation.
+ Task InitializeAsync();
+
+ ///
+ /// Called by the system when the server analytics buffer is full to persist items in the repository.
+ ///
+ /// Items that should be persisted in the repository.
+ /// Propagates notification that operations should be canceled.
+ /// Represents the asynchronous process operation.
+ Task StoreAsync(IEnumerable items, CancellationToken cancellationToken);
+
+ ///
+ /// Called by the system when the StoreAsync method encounters an exception.
+ /// This method should never throw any exceptions. Even if it does, it will be swallowed and ignored by the system.
+ ///
+ /// Items that failed to be stored in the repository.
+ /// The exception that is caught by the system.
+ /// Represents the asynchronous process operation.
+ Task HandleErrorAsync(IEnumerable items, Exception exception);
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/IAnalyticsServerBuilder.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/IAnalyticsServerBuilder.cs
new file mode 100644
index 00000000..ee563429
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/IAnalyticsServerBuilder.cs
@@ -0,0 +1,52 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Adriva.Extensions.Analytics.Server
+{
+ ///
+ /// Provides methods and properties that will be used by the system to build an analytics server.
+ ///
+ public interface IAnalyticsServerBuilder
+ {
+
+ ///
+ /// Gets the IServiceCollection that is used by the current builder.
+ ///
+ /// The current Microsoft.Extensions.DependencyInjection.IServiceCollection.
+ IServiceCollection Services { get; }
+
+ ///
+ /// Registers an IAnalyticsRepository to be used by the system to persist analytics data.
+ ///
+ /// Type of the analytics repository.
+ /// The Adriva.Extensions.Analytics.Server.IAnalyticsServerBuilder so that additional calls can be chained.
+ IAnalyticsServerBuilder UseRepository() where TRepository : class, IAnalyticsRepository;
+
+ ///
+ /// Registers an IAnalyticsHandler to be used by the system to parse and extract analyics data from incoming requests.
+ ///
+ /// Type of analytics handler.
+ /// The Adriva.Extensions.Analytics.Server.IAnalyticsServerBuilder so that additional calls can be chained.
+ IAnalyticsServerBuilder UseHandler() where THandler : IAnalyticsHandler;
+
+ ///
+ /// Sets the count of processor threads that will be used to extract and persist analytics data.
+ /// Number of threads cannot be less than 1. If so, system will override the value and set it to 1 automatically.
+ ///
+ /// The number of threads that will be made available to the system.
+ /// The Adriva.Extensions.Analytics.Server.IAnalyticsServerBuilder so that additional calls can be chained.
+ IAnalyticsServerBuilder SetProcessorThreadCount(int threadCount);
+
+ ///
+ /// Sets the maximum number of items that can be stored in the memory buffer before persisted in the repository.
+ /// The value set in this method declares an intention and not a hard limit.async System may flush the buffer before it reaches its capacity.
+ ///
+ /// Maximum number of items that can be stored in the buffer.
+ /// The Adriva.Extensions.Analytics.Server.IAnalyticsServerBuilder so that additional calls can be chained.
+ IAnalyticsServerBuilder SetBufferCapacity(int capacity);
+
+ ///
+ /// Called by the system to build the analytics server with the configuration provided.
+ ///
+ void Build();
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/IQueueingService.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/IQueueingService.cs
new file mode 100644
index 00000000..ff849daa
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/IQueueingService.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Threading;
+using Adriva.Extensions.Analytics.Server.Entities;
+
+namespace Adriva.Extensions.Analytics.Server
+{
+ ///
+ /// Provides methods and properties to queue analytics items in a server buffer and share it with processors to persist in an analytics repository.
+ ///
+ public interface IQueueingService
+ {
+ ///
+ /// Returns a value indicating if adding to the queue is completed and no other items will be added.
+ /// When the applicaion about to shutdown, this property should return True to indicate the consumers that no more items will be added to the queue.
+ ///
+ /// A boolean value indicating if the adding to the queue is completed.
+ bool IsCompleted { get; }
+
+ ///
+ /// Adds an analytics item to the queue to be processed by the system.
+ ///
+ /// An instance of AnalyticsItem class to be processed.
+ void Enqueue(AnalyticsItem analyticsItem);
+
+ ///
+ /// Tries to get the next item from the queue in a given time period.
+ ///
+ /// Maximum time to wait in milliseconds before timing out.
+ /// The AnalyticsItem instance that is retrieved from the queue, if possible.
+ /// A boolean value indicating if the AnalyticsItem could be retrieved or not.
+ bool TryGetNext(int millisecondsTimeout, out AnalyticsItem analyticsItem);
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/NullHandler.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/NullHandler.cs
new file mode 100644
index 00000000..ff4915a7
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/NullHandler.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Adriva.Extensions.Analytics.Server.Entities;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+
+namespace Adriva.Extensions.Analytics.Server
+{
+ internal sealed class NullHandler : IAnalyticsHandler
+ {
+ private readonly ILogger Logger;
+
+ public NullHandler(ILogger logger)
+ {
+ this.Logger = logger;
+ }
+
+ public async IAsyncEnumerable HandleAsync(HttpRequest request)
+ {
+ this.Logger.LogTrace($"Null Analytics handler received: {request.Method} {request.Path} with ContentType = {request.ContentType} and ContentLength = {request.ContentLength}");
+ await Task.CompletedTask;
+ yield break;
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/NullRepository.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/NullRepository.cs
new file mode 100644
index 00000000..d8e2cd8c
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/NullRepository.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Adriva.Extensions.Analytics.Server.Entities;
+
+namespace Adriva.Extensions.Analytics.Server
+{
+ ///
+ /// Provides an empty implementation of an analytics repository that does not persist data or handle any exceptions.
+ ///
+ public sealed class NullRepository : IAnalyticsRepository
+ {
+ ///
+ /// Initializes the current instance of Adriva.Extensions.Analytics.Server.NullRepository class.
+ ///
+ /// Represents the asynchronous process operation.
+ public Task InitializeAsync() => Task.CompletedTask;
+
+ ///
+ /// Called by the system when the server analytics buffer is full to persist items in the repository.
+ ///
+ /// Items that should be persisted in the repository.
+ /// Propagates notification that operations should be canceled.
+ /// Represents the asynchronous process operation.
+ public Task HandleErrorAsync(IEnumerable items, Exception exception)
+ {
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Called by the system when the StoreAsync method encounters an exception.
+ /// This method should never throw any exceptions. Even if it does, it will be swallowed and ignored by the system.
+ ///
+ /// Items that failed to be stored in the repository.
+ /// The exception that is caught by the system.
+ /// Represents the asynchronous process operation.
+ public Task StoreAsync(IEnumerable items, CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/QueueProcessorService.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/QueueProcessorService.cs
new file mode 100644
index 00000000..01e82a09
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/QueueProcessorService.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Adriva.Extensions.Analytics.Server.Entities;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Adriva.Extensions.Analytics.Server
+{
+ ///
+ /// Provides the queueing (buffering) services required to store parsed analytics data temporarily until it's sent to the repository.
+ ///
+ internal class QueueProcessorService : IHostedService, IDisposable
+ {
+ private readonly ILogger Logger;
+ private readonly AnalyticsServerOptions Options;
+ private readonly IServiceProvider ServiceProvider;
+ private readonly IQueueingService QueueingService;
+ private readonly CancellationTokenSource StopTokenSource = new CancellationTokenSource();
+ private readonly Task[] ProcessorTasks;
+ private bool IsDisposed;
+
+ public QueueProcessorService(
+ IServiceProvider serviceProvider,
+ IQueueingService queueingService,
+ IOptions optionsAccessor,
+ ILogger logger)
+ {
+ this.ServiceProvider = serviceProvider;
+ this.Options = optionsAccessor.Value;
+ this.QueueingService = queueingService;
+ this.Logger = logger;
+
+ this.ProcessorTasks = new Task[this.Options.ProcessorThreadCount];
+ }
+
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ using (IServiceScope serviceScope = this.ServiceProvider.CreateScope())
+ {
+ IAnalyticsRepository repository = serviceScope.ServiceProvider.GetRequiredService();
+ await repository.InitializeAsync();
+ }
+
+ for (int loop = 0; loop < this.Options.ProcessorThreadCount; loop++)
+ {
+ this.Logger.LogInformation($"Starting queue processor instance {loop}.");
+ this.ProcessorTasks[loop] = Task.Run(async () => { await this.ProcessItemsAsync(); });
+ }
+ }
+
+ private async Task PersistBufferAsync(List buffer)
+ {
+ if (0 == buffer.Count) return;
+
+
+ using (IServiceScope serviceScope = this.ServiceProvider.CreateScope())
+ {
+ IAnalyticsRepository repository = serviceScope.ServiceProvider.GetRequiredService();
+
+ try
+ {
+ await repository.StoreAsync(buffer, CancellationToken.None);
+ this.Logger.LogTrace($"AnalyticsItems persisted in repository.");
+ }
+ catch (Exception storageError)
+ {
+ try
+ {
+ await repository?.HandleErrorAsync(buffer, storageError);
+ }
+ catch (Exception errorHandlerError)
+ {
+ this.Logger.LogError(errorHandlerError, $"Repository '{repository.GetType().FullName}' failed to handle error.");
+ }
+ }
+ finally
+ {
+ buffer.Clear();
+ }
+ }
+ }
+
+ private async Task ProcessItemsAsync()
+ {
+ while (!this.QueueingService.IsCompleted)
+ {
+ List buffer = new List(1 + this.Options.BufferCapacity);
+
+ while (this.QueueingService.TryGetNext(10000, out AnalyticsItem analyticsItem))
+ {
+ buffer.Add(analyticsItem);
+
+ if (this.Options.BufferCapacity <= buffer.Count)
+ {
+ await this.PersistBufferAsync(buffer);
+ }
+ }
+
+ await this.PersistBufferAsync(buffer);
+ }
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ this.StopTokenSource.Cancel();
+ Task.WaitAll(this.ProcessorTasks);
+ return Task.CompletedTask;
+ }
+
+ #region IDisposable Implementation
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!this.IsDisposed)
+ {
+ if (disposing)
+ {
+ this.StopTokenSource.Dispose();
+ }
+
+ this.IsDisposed = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ this.Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+ #endregion
+}
diff --git a/Analytics/src/Adriva.Extensions.Analytics.Server/QueueingService.cs b/Analytics/src/Adriva.Extensions.Analytics.Server/QueueingService.cs
new file mode 100644
index 00000000..ee450b8d
--- /dev/null
+++ b/Analytics/src/Adriva.Extensions.Analytics.Server/QueueingService.cs
@@ -0,0 +1,42 @@
+using System.Collections.Concurrent;
+using Adriva.Extensions.Analytics.Server.Entities;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace Adriva.Extensions.Analytics.Server
+{
+ internal class QueueingService : IQueueingService
+ {
+ private readonly BlockingCollection Items = new BlockingCollection();
+ private readonly ILogger Logger;
+
+ public bool IsCompleted => this.Items.IsCompleted;
+
+ public QueueingService(IHostApplicationLifetime applicationLifetime, ILogger logger)
+ {
+ this.Logger = logger;
+ applicationLifetime.ApplicationStopping.Register(this.CompleteAdding);
+ }
+
+ public void CompleteAdding()
+ {
+ this.Items.CompleteAdding();
+ }
+
+ public void Enqueue(AnalyticsItem analyticsItem)
+ {
+ if (null == analyticsItem) return;
+ if (this.Items.IsCompleted) return;
+
+ if (!this.Items.TryAdd(analyticsItem, 50))
+ {
+ this.Logger.LogWarning("Failed to queue analytics item.");
+ }
+ }
+
+ public bool TryGetNext(int millisecondsTimeout, out AnalyticsItem analyticsItem)
+ {
+ return this.Items.TryTake(out analyticsItem, millisecondsTimeout);
+ }
+ }
+}
diff --git a/Caching/Caching.sln b/Caching/Caching.sln
new file mode 100644
index 00000000..93560ae0
--- /dev/null
+++ b/Caching/Caching.sln
@@ -0,0 +1,69 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{87D3DCC1-4E18-47D3-B395-8D7E913C1888}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adriva.Extensions.Caching.Memory", "src\Adriva.Extensions.Caching.Memory\Adriva.Extensions.Caching.Memory.csproj", "{0815D0C2-7B10-47B9-B903-65E869C0556C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adriva.Extensions.Caching.Abstractions", "src\Adriva.Extensions.Caching.Abstractions\Adriva.Extensions.Caching.Abstractions.csproj", "{29B32D35-AB58-468A-BDB8-C4ACA8A8949C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adriva.Extensions.Caching.SqlServer", "src\Adriva.Extensions.Caching.SqlServer\Adriva.Extensions.Caching.SqlServer.csproj", "{F608EDFD-33F2-48A1-A4B4-73B016A3FE28}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {0815D0C2-7B10-47B9-B903-65E869C0556C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0815D0C2-7B10-47B9-B903-65E869C0556C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0815D0C2-7B10-47B9-B903-65E869C0556C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0815D0C2-7B10-47B9-B903-65E869C0556C}.Debug|x64.Build.0 = Debug|Any CPU
+ {0815D0C2-7B10-47B9-B903-65E869C0556C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0815D0C2-7B10-47B9-B903-65E869C0556C}.Debug|x86.Build.0 = Debug|Any CPU
+ {0815D0C2-7B10-47B9-B903-65E869C0556C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0815D0C2-7B10-47B9-B903-65E869C0556C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0815D0C2-7B10-47B9-B903-65E869C0556C}.Release|x64.ActiveCfg = Release|Any CPU
+ {0815D0C2-7B10-47B9-B903-65E869C0556C}.Release|x64.Build.0 = Release|Any CPU
+ {0815D0C2-7B10-47B9-B903-65E869C0556C}.Release|x86.ActiveCfg = Release|Any CPU
+ {0815D0C2-7B10-47B9-B903-65E869C0556C}.Release|x86.Build.0 = Release|Any CPU
+ {29B32D35-AB58-468A-BDB8-C4ACA8A8949C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {29B32D35-AB58-468A-BDB8-C4ACA8A8949C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {29B32D35-AB58-468A-BDB8-C4ACA8A8949C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {29B32D35-AB58-468A-BDB8-C4ACA8A8949C}.Debug|x64.Build.0 = Debug|Any CPU
+ {29B32D35-AB58-468A-BDB8-C4ACA8A8949C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {29B32D35-AB58-468A-BDB8-C4ACA8A8949C}.Debug|x86.Build.0 = Debug|Any CPU
+ {29B32D35-AB58-468A-BDB8-C4ACA8A8949C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {29B32D35-AB58-468A-BDB8-C4ACA8A8949C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {29B32D35-AB58-468A-BDB8-C4ACA8A8949C}.Release|x64.ActiveCfg = Release|Any CPU
+ {29B32D35-AB58-468A-BDB8-C4ACA8A8949C}.Release|x64.Build.0 = Release|Any CPU
+ {29B32D35-AB58-468A-BDB8-C4ACA8A8949C}.Release|x86.ActiveCfg = Release|Any CPU
+ {29B32D35-AB58-468A-BDB8-C4ACA8A8949C}.Release|x86.Build.0 = Release|Any CPU
+ {F608EDFD-33F2-48A1-A4B4-73B016A3FE28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F608EDFD-33F2-48A1-A4B4-73B016A3FE28}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F608EDFD-33F2-48A1-A4B4-73B016A3FE28}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F608EDFD-33F2-48A1-A4B4-73B016A3FE28}.Debug|x64.Build.0 = Debug|Any CPU
+ {F608EDFD-33F2-48A1-A4B4-73B016A3FE28}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F608EDFD-33F2-48A1-A4B4-73B016A3FE28}.Debug|x86.Build.0 = Debug|Any CPU
+ {F608EDFD-33F2-48A1-A4B4-73B016A3FE28}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F608EDFD-33F2-48A1-A4B4-73B016A3FE28}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F608EDFD-33F2-48A1-A4B4-73B016A3FE28}.Release|x64.ActiveCfg = Release|Any CPU
+ {F608EDFD-33F2-48A1-A4B4-73B016A3FE28}.Release|x64.Build.0 = Release|Any CPU
+ {F608EDFD-33F2-48A1-A4B4-73B016A3FE28}.Release|x86.ActiveCfg = Release|Any CPU
+ {F608EDFD-33F2-48A1-A4B4-73B016A3FE28}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {0815D0C2-7B10-47B9-B903-65E869C0556C} = {87D3DCC1-4E18-47D3-B395-8D7E913C1888}
+ {29B32D35-AB58-468A-BDB8-C4ACA8A8949C} = {87D3DCC1-4E18-47D3-B395-8D7E913C1888}
+ {F608EDFD-33F2-48A1-A4B4-73B016A3FE28} = {87D3DCC1-4E18-47D3-B395-8D7E913C1888}
+ EndGlobalSection
+EndGlobal
diff --git a/Caching/src/Adriva.Extensions.Caching.Abstractions/Adriva.Extensions.Caching.Abstractions.csproj b/Caching/src/Adriva.Extensions.Caching.Abstractions/Adriva.Extensions.Caching.Abstractions.csproj
new file mode 100644
index 00000000..c137d24c
--- /dev/null
+++ b/Caching/src/Adriva.Extensions.Caching.Abstractions/Adriva.Extensions.Caching.Abstractions.csproj
@@ -0,0 +1,17 @@
+
+
+
+ netstandard2.1
+
+
+
+ Adriva Caching Abstractions Library
+ Base library providing abstractions and common components to implement and consume caching services.
+ 6
+
+
+
+
+
+
+
diff --git a/Caching/src/Adriva.Extensions.Caching.Abstractions/CacheExtensions.cs b/Caching/src/Adriva.Extensions.Caching.Abstractions/CacheExtensions.cs
new file mode 100644
index 00000000..a2cbc5e6
--- /dev/null
+++ b/Caching/src/Adriva.Extensions.Caching.Abstractions/CacheExtensions.cs
@@ -0,0 +1,23 @@
+using Adriva.Extensions.Caching.Abstractions;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ public static class CacheExtensions
+ {
+ public static IServiceCollection AddCache(this IServiceCollection services) where TCache : class, ICache
+ {
+ services.AddSingleton();
+ services.AddSingleton(serviceProvider =>
+ {
+ return serviceProvider.GetRequiredService();
+ });
+ services.AddSingleton>(serviceProvider =>
+ {
+ var cacheInstance = serviceProvider.GetRequiredService();
+ var cacheWrapper = new CacheWrapper(cacheInstance);
+ return cacheWrapper;
+ });
+ return services;
+ }
+ }
+}
diff --git a/Caching/src/Adriva.Extensions.Caching.Abstractions/CacheWrapper.cs b/Caching/src/Adriva.Extensions.Caching.Abstractions/CacheWrapper.cs
new file mode 100644
index 00000000..a3f78b58
--- /dev/null
+++ b/Caching/src/Adriva.Extensions.Caching.Abstractions/CacheWrapper.cs
@@ -0,0 +1,12 @@
+namespace Adriva.Extensions.Caching.Abstractions
+{
+ internal sealed class CacheWrapper : ICache where TCache : ICache
+ {
+ public TCache Instance { get; private set; }
+
+ internal CacheWrapper(TCache instance)
+ {
+ this.Instance = instance;
+ }
+ }
+}
diff --git a/Caching/src/Adriva.Extensions.Caching.Abstractions/ICache.cs b/Caching/src/Adriva.Extensions.Caching.Abstractions/ICache.cs
new file mode 100644
index 00000000..90ee21d9
--- /dev/null
+++ b/Caching/src/Adriva.Extensions.Caching.Abstractions/ICache.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Adriva.Extensions.Caching.Abstractions
+{
+ public delegate void EvictionCallback(string key, object value, ICache cache);
+
+ ///
+ /// Represents a cache type.
+ ///
+ public interface ICache
+ {
+ ///
+ /// Gets or creates a new cache entry in the cache asynchronously.
+ ///
+ /// The identifier of the cache item.
+ /// A factory method that creates the cache data if it already does not exist in the cache.
+ /// A callback function that will be called when the cached item is evicted.
+ /// List of dependency keys, so that if any of the dependencies expire the cached item will be removed from the cache.
+ /// Type of the data that will be cached.
+ /// A task that represents the asynchronous cache operation. The value of the TResult parameter contains the data retrieved from the cache if exists, or the result of the factory method.
+ Task GetOrCreateAsync(string key, Func> factory, EvictionCallback evictionCallback = null, params string[] dependencyMonikers);
+
+ ///
+ /// Gets or creates a new cache entry in the cache asynchronously.
+ ///
+ /// The identifier of the cache item.
+ /// A factory method that creates the cache data if it already does not exist in the cache.
+ /// List of dependency keys, so that if any of the dependencies expire the cached item will be removed from the cache.
+ /// Type of the data that will be cached.
+ /// A task that represents the asynchronous cache operation. The value of the TResult parameter contains the data retrieved from the cache if exists, or the result of the factory method.
+ Task GetOrCreateAsync(string key, Func> factory, params string[] dependencyMonikers);
+
+ ///
+ /// Removes the item with the given key from the cache.
+ ///
+ /// The unique key of the item to be removed.
+ /// A task that represents the asynchronous cache operation.
+ ValueTask RemoveAsync(string key);
+
+ ///
+ /// Notifies the given dependency item of a change so that all cache dependencies are expired in the cache.
+ ///
+ /// The identifier of the item that triggered the change notification.
+ /// The identifier of the dependency.
+ void NotifyChanged(string key, string dependencyMoniker);
+
+ ///
+ /// Notifies the given dependency item of a change so that all cached items depending on this moniker are evicted.
+ ///
+ /// The identifier of the item that triggered the change notification.
+ /// The identifier of the dependency.
+ /// A task that represents the asynchronous operation.
+ Task NotifyChangedAsync(string key, string dependencyMoniker);
+ }
+}
diff --git a/Caching/src/Adriva.Extensions.Caching.Abstractions/ICacheItem.cs b/Caching/src/Adriva.Extensions.Caching.Abstractions/ICacheItem.cs
new file mode 100644
index 00000000..2de568b1
--- /dev/null
+++ b/Caching/src/Adriva.Extensions.Caching.Abstractions/ICacheItem.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Adriva.Extensions.Caching.Abstractions
+{
+ public interface ICacheItem
+ {
+ DateTimeOffset? AbsoluteExpiration { get; set; }
+
+ TimeSpan? AbsoluteExpirationRelativeToNow { get; set; }
+
+ TimeSpan? SlidingExpiration { get; set; }
+ }
+}
diff --git a/Caching/src/Adriva.Extensions.Caching.Abstractions/ICacheOfT.cs b/Caching/src/Adriva.Extensions.Caching.Abstractions/ICacheOfT.cs
new file mode 100644
index 00000000..fab7b1fb
--- /dev/null
+++ b/Caching/src/Adriva.Extensions.Caching.Abstractions/ICacheOfT.cs
@@ -0,0 +1,7 @@
+namespace Adriva.Extensions.Caching.Abstractions
+{
+ public interface ICache where TCache : ICache
+ {
+ TCache Instance { get; }
+ }
+}
diff --git a/Caching/src/Adriva.Extensions.Caching.Abstractions/NullCache.cs b/Caching/src/Adriva.Extensions.Caching.Abstractions/NullCache.cs
new file mode 100644
index 00000000..b9c3b89b
--- /dev/null
+++ b/Caching/src/Adriva.Extensions.Caching.Abstractions/NullCache.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+
+namespace Adriva.Extensions.Caching.Abstractions
+{
+ public sealed class NullCache : ICache
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task GetOrCreateAsync(string key, Func> factory, EvictionCallback evictionCallback = null, params string[] dependencyMonikers)
+ {
+ NullCacheItem nullCacheItem = new NullCacheItem();
+ return factory.Invoke(nullCacheItem);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task GetOrCreateAsync(string key, Func> factory, params string[] dependencyMonikers)
+ => this.GetOrCreateAsync(key, factory, null, dependencyMonikers);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void NotifyChanged(string key, string dependencyMoniker)
+ {
+
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task NotifyChangedAsync(string key, string dependencyMoniker) => Task.CompletedTask;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ValueTask RemoveAsync(string key) => new ValueTask();
+ }
+}
diff --git a/Caching/src/Adriva.Extensions.Caching.Abstractions/NullCacheItem.cs b/Caching/src/Adriva.Extensions.Caching.Abstractions/NullCacheItem.cs
new file mode 100644
index 00000000..2794c854
--- /dev/null
+++ b/Caching/src/Adriva.Extensions.Caching.Abstractions/NullCacheItem.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Adriva.Extensions.Caching.Abstractions
+{
+ public sealed class NullCacheItem : ICacheItem
+ {
+ public DateTimeOffset? AbsoluteExpiration
+ {
+ get => null;
+ set { }
+ }
+
+ public TimeSpan? AbsoluteExpirationRelativeToNow
+ {
+ get => null;
+ set { }
+ }
+
+ public TimeSpan? SlidingExpiration
+ {
+ get => null;
+ set { }
+ }
+ }
+}
diff --git a/Caching/src/Adriva.Extensions.Caching.Memory/Adriva.Extensions.Caching.Memory.csproj b/Caching/src/Adriva.Extensions.Caching.Memory/Adriva.Extensions.Caching.Memory.csproj
new file mode 100644
index 00000000..d2d52c0f
--- /dev/null
+++ b/Caching/src/Adriva.Extensions.Caching.Memory/Adriva.Extensions.Caching.Memory.csproj
@@ -0,0 +1,19 @@
+
+
+ netstandard2.1
+
+
+
+ Adriva In-Memory Cache Storage
+ Provides an implementation of cache storage that stores cached items in memory.
+ 6
+
+
+
+
+
+
+
+
+
+
diff --git a/Caching/src/Adriva.Extensions.Caching.Memory/DependencyChangeToken.cs b/Caching/src/Adriva.Extensions.Caching.Memory/DependencyChangeToken.cs
new file mode 100644
index 00000000..62d9ef38
--- /dev/null
+++ b/Caching/src/Adriva.Extensions.Caching.Memory/DependencyChangeToken.cs
@@ -0,0 +1,22 @@
+using System;
+using Microsoft.Extensions.Primitives;
+
+namespace Adriva.Extensions.Caching.Memory
+{
+ internal sealed class DependencyChangeToken : IChangeToken
+ {
+ public bool HasChanged { get; private set; }
+
+ public bool ActiveChangeCallbacks => false;
+
+ public IDisposable RegisterChangeCallback(Action