├── .github └── workflows │ └── deploy-web-app-to-azure.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── images ├── bastion.png ├── demo.png ├── redis-cache-in-a-vnet.png └── redis-cache-private-endpoint.png ├── scripts └── deploy.sh ├── sql └── ProductsDb.sql ├── src ├── Controllers │ ├── HomeController.cs │ └── ProductsController.cs ├── Helpers │ ├── AzureAdAuthenticationDbConnectionInterceptor.cs │ ├── MessageHelper.cs │ └── StackExchangeExtensions.cs ├── Models │ ├── ErrorViewModel.cs │ ├── Product.cs │ └── ProductsContext.cs ├── Products.csproj ├── Products.sln ├── Program.cs ├── Properties │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── ServiceDependencies │ │ ├── WebAppRedisCacheSqlDb - Web Deploy │ │ │ └── profile.arm.json │ │ └── local │ │ │ └── appInsights1.arm.json │ ├── launchSettings.json │ ├── serviceDependencies.json │ └── serviceDependencies.local.json ├── Startup.cs ├── Views │ ├── Home │ │ ├── Index.cshtml │ │ └── Privacy.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── _Layout.cshtml │ │ └── _ValidationScriptsPartial.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── appsettings.Development.json ├── appsettings.json ├── libman.json └── wwwroot │ ├── css │ ├── site.css │ └── themes │ │ └── base │ │ ├── accordion.css │ │ ├── all.css │ │ ├── autocomplete.css │ │ ├── base.css │ │ ├── button.css │ │ ├── core.css │ │ ├── datepicker.css │ │ ├── dialog.css │ │ ├── draggable.css │ │ ├── images │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ ├── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ ├── ui-icons_222222_256x240.png │ │ ├── ui-icons_2e83ff_256x240.png │ │ ├── ui-icons_444444_256x240.png │ │ ├── ui-icons_454545_256x240.png │ │ ├── ui-icons_555555_256x240.png │ │ ├── ui-icons_777620_256x240.png │ │ ├── ui-icons_777777_256x240.png │ │ ├── ui-icons_888888_256x240.png │ │ ├── ui-icons_cc0000_256x240.png │ │ ├── ui-icons_cd0a0a_256x240.png │ │ └── ui-icons_ffffff_256x240.png │ │ ├── jquery-ui.css │ │ ├── jquery-ui.min.css │ │ ├── menu.css │ │ ├── progressbar.css │ │ ├── resizable.css │ │ ├── selectable.css │ │ ├── selectmenu.css │ │ ├── slider.css │ │ ├── sortable.css │ │ ├── spinner.css │ │ ├── tabs.css │ │ ├── theme.css │ │ └── tooltip.css │ ├── favicon.ico │ ├── images │ ├── botleft.png │ ├── botright.png │ ├── far-clouds.png │ ├── left.png │ ├── near-clouds.png │ ├── pattern.gif │ ├── right.png │ ├── sky.png │ └── title.png │ ├── js │ ├── _references.js │ ├── jquery-2.0.3.intellisense.js │ ├── jquery-2.0.3.js │ ├── jquery-2.0.3.min.js │ ├── jquery-3.1.1.intellisense.js │ ├── jquery-3.1.1.js │ ├── jquery-3.1.1.min.js │ ├── jquery-3.1.1.min.map │ ├── jquery-3.1.1.slim.js │ ├── jquery-3.1.1.slim.min.js │ ├── jquery-3.1.1.slim.min.map │ ├── jquery-ui-1.10.3.js │ ├── jquery-ui-1.10.3.min.js │ ├── jquery-ui-1.12.1.js │ ├── jquery-ui-1.12.1.min.js │ ├── jquery.spritely-0.6.js │ ├── jquery.unobtrusive-ajax.js │ ├── jquery.unobtrusive-ajax.min.js │ ├── jquery.validate-vsdoc.js │ ├── jquery.validate.js │ ├── jquery.validate.min.js │ ├── jquery.validate.unobtrusive.js │ ├── jquery.validate.unobtrusive.min.js │ ├── knockout-2.3.0.debug.js │ ├── knockout-2.3.0.js │ ├── knockout-3.4.2.debug.js │ ├── knockout-3.4.2.js │ ├── modernizr-2.6.2.js │ ├── modernizr-2.8.3.js │ ├── page.js │ └── site.js │ └── lib │ ├── bootstrap │ ├── LICENSE │ └── dist │ │ ├── css │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.css.map │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ │ └── js │ │ ├── bootstrap.bundle.js │ │ ├── bootstrap.bundle.js.map │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ ├── bootstrap.js │ │ ├── bootstrap.js.map │ │ ├── bootstrap.min.js │ │ └── bootstrap.min.js.map │ ├── jquery-validation-unobtrusive │ ├── LICENSE.txt │ ├── jquery.validate.unobtrusive.js │ └── jquery.validate.unobtrusive.min.js │ ├── jquery-validation │ ├── LICENSE.md │ └── dist │ │ ├── additional-methods.js │ │ ├── additional-methods.min.js │ │ ├── jquery.validate.js │ │ └── jquery.validate.min.js │ └── jquery │ ├── LICENSE.txt │ └── dist │ ├── jquery.js │ ├── jquery.min.js │ └── jquery.min.map ├── templates ├── azuredeploy.endpoint.json ├── azuredeploy.endpoint.parameters.json ├── azuredeploy.vnet.json └── azuredeploy.vnet.parameters.json └── visio └── architecture.vsdx /.github/workflows/deploy-web-app-to-azure.yml: -------------------------------------------------------------------------------- 1 | name: Build and deploy .NET Core app to a Windows Web App on Azure 2 | on: 3 | workflow_dispatch: 4 | paths: 5 | - 'src/**' 6 | inputs: 7 | name: 8 | description: 'Name of the user that manually starts the workflow' 9 | required: true 10 | default: 'Paolo Salvatori' 11 | push: 12 | paths: 13 | - 'src/**' 14 | branches: 15 | - master 16 | pull_request: 17 | paths: 18 | - 'src/**' 19 | branches: 20 | - main 21 | env: 22 | AZURE_WEBAPP_NAME: RedisCachePrivateLink 23 | AZURE_WEBAPP_PACKAGE_PATH: ./publish 24 | AZURE_WEBAPP_PUBLISH_PROFILE: ${{ secrets.RedisCachePrivateLink_f3dd }} 25 | CONFIGURATION: Release 26 | DOTNET_CORE_VERSION: 3.1.x 27 | WORKING_DIRECTORY: src 28 | jobs: 29 | build-and-deploy: 30 | runs-on: windows-latest 31 | steps: 32 | - uses: actions/checkout@v2 33 | - name: Setup .NET Core 34 | uses: actions/setup-dotnet@v1 35 | with: 36 | dotnet-version: ${{ env.DOTNET_CORE_VERSION }} 37 | - name: Restore 38 | run: dotnet restore "${{ env.WORKING_DIRECTORY }}" 39 | - name: Build 40 | run: dotnet build "${{ env.WORKING_DIRECTORY }}" --configuration ${{ env.CONFIGURATION }} --no-restore 41 | - name: Test 42 | run: dotnet test "${{ env.WORKING_DIRECTORY }}" --no-build 43 | - name: Publish 44 | run: dotnet publish "${{ env.WORKING_DIRECTORY }}" --configuration ${{ env.CONFIGURATION }} --no-build --output "${{ env.AZURE_WEBAPP_PACKAGE_PATH }}" 45 | - name: Deploy to Azure WebApp 46 | uses: azure/webapps-deploy@v2 47 | with: 48 | app-name: ${{ env.AZURE_WEBAPP_NAME }} 49 | package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} 50 | publish-profile: ${{ env.AZURE_WEBAPP_PUBLISH_PROFILE }} 51 | - name: Publish Artifacts 52 | uses: actions/upload-artifact@v1.0.0 53 | with: 54 | name: webapp 55 | path: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # Azure Functions localsettings file 7 | local.settings.json 8 | 9 | # User-specific files 10 | *.rsuser 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Mono auto generated files 20 | mono_crash.* 21 | 22 | # Build results 23 | [Dd]ebug/ 24 | [Dd]ebugPublic/ 25 | [Rr]elease/ 26 | [Rr]eleases/ 27 | x64/ 28 | x86/ 29 | [Aa][Rr][Mm]/ 30 | [Aa][Rr][Mm]64/ 31 | bld/ 32 | [Bb]in/ 33 | [Oo]bj/ 34 | [Ll]og/ 35 | [Ll]ogs/ 36 | 37 | # Visual Studio 2015/2017 cache/options directory 38 | .vs/ 39 | # Uncomment if you have tasks that create the project's static files in wwwroot 40 | #wwwroot/ 41 | 42 | # Visual Studio 2017 auto generated files 43 | Generated\ Files/ 44 | 45 | # MSTest test Results 46 | [Tt]est[Rr]esult*/ 47 | [Bb]uild[Ll]og.* 48 | 49 | # NUnit 50 | *.VisualState.xml 51 | TestResult.xml 52 | nunit-*.xml 53 | 54 | # Build Results of an ATL Project 55 | [Dd]ebugPS/ 56 | [Rr]eleasePS/ 57 | dlldata.c 58 | 59 | # Benchmark Results 60 | BenchmarkDotNet.Artifacts/ 61 | 62 | # .NET Core 63 | project.lock.json 64 | project.fragment.lock.json 65 | artifacts/ 66 | 67 | # StyleCop 68 | StyleCopReport.xml 69 | 70 | # Files built by Visual Studio 71 | *_i.c 72 | *_p.c 73 | *_h.h 74 | *.ilk 75 | *.meta 76 | *.obj 77 | *.iobj 78 | *.pch 79 | *.pdb 80 | *.ipdb 81 | *.pgc 82 | *.pgd 83 | *.rsp 84 | *.sbr 85 | *.tlb 86 | *.tli 87 | *.tlh 88 | *.tmp 89 | *.tmp_proj 90 | *_wpftmp.csproj 91 | *.log 92 | *.vspscc 93 | *.vssscc 94 | .builds 95 | *.pidb 96 | *.svclog 97 | *.scc 98 | 99 | # Chutzpah Test files 100 | _Chutzpah* 101 | 102 | # Visual C++ cache files 103 | ipch/ 104 | *.aps 105 | *.ncb 106 | *.opendb 107 | *.opensdf 108 | *.sdf 109 | *.cachefile 110 | *.VC.db 111 | *.VC.VC.opendb 112 | 113 | # Visual Studio profiler 114 | *.psess 115 | *.vsp 116 | *.vspx 117 | *.sap 118 | 119 | # Visual Studio Trace Files 120 | *.e2e 121 | 122 | # TFS 2012 Local Workspace 123 | $tf/ 124 | 125 | # Guidance Automation Toolkit 126 | *.gpState 127 | 128 | # ReSharper is a .NET coding add-in 129 | _ReSharper*/ 130 | *.[Rr]e[Ss]harper 131 | *.DotSettings.user 132 | 133 | # TeamCity is a build add-in 134 | _TeamCity* 135 | 136 | # DotCover is a Code Coverage Tool 137 | *.dotCover 138 | 139 | # AxoCover is a Code Coverage Tool 140 | .axoCover/* 141 | !.axoCover/settings.json 142 | 143 | # Visual Studio code coverage results 144 | *.coverage 145 | *.coveragexml 146 | 147 | # NCrunch 148 | _NCrunch_* 149 | .*crunch*.local.xml 150 | nCrunchTemp_* 151 | 152 | # MightyMoose 153 | *.mm.* 154 | AutoTest.Net/ 155 | 156 | # Web workbench (sass) 157 | .sass-cache/ 158 | 159 | # Installshield output folder 160 | [Ee]xpress/ 161 | 162 | # DocProject is a documentation generator add-in 163 | DocProject/buildhelp/ 164 | DocProject/Help/*.HxT 165 | DocProject/Help/*.HxC 166 | DocProject/Help/*.hhc 167 | DocProject/Help/*.hhk 168 | DocProject/Help/*.hhp 169 | DocProject/Help/Html2 170 | DocProject/Help/html 171 | 172 | # Click-Once directory 173 | publish/ 174 | 175 | # Publish Web Output 176 | *.[Pp]ublish.xml 177 | *.azurePubxml 178 | # Note: Comment the next line if you want to checkin your web deploy settings, 179 | # but database connection strings (with potential passwords) will be unencrypted 180 | *.pubxml 181 | *.publishproj 182 | 183 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 184 | # checkin your Azure Web App publish settings, but sensitive information contained 185 | # in these scripts will be unencrypted 186 | PublishScripts/ 187 | 188 | # NuGet Packages 189 | *.nupkg 190 | # NuGet Symbol Packages 191 | *.snupkg 192 | # The packages folder can be ignored because of Package Restore 193 | **/[Pp]ackages/* 194 | # except build/, which is used as an MSBuild target. 195 | !**/[Pp]ackages/build/ 196 | # Uncomment if necessary however generally it will be regenerated when needed 197 | #!**/[Pp]ackages/repositories.config 198 | # NuGet v3's project.json files produces more ignorable files 199 | *.nuget.props 200 | *.nuget.targets 201 | 202 | # Microsoft Azure Build Output 203 | csx/ 204 | *.build.csdef 205 | 206 | # Microsoft Azure Emulator 207 | ecf/ 208 | rcf/ 209 | 210 | # Windows Store app package directories and files 211 | AppPackages/ 212 | BundleArtifacts/ 213 | Package.StoreAssociation.xml 214 | _pkginfo.txt 215 | *.appx 216 | *.appxbundle 217 | *.appxupload 218 | 219 | # Visual Studio cache files 220 | # files ending in .cache can be ignored 221 | *.[Cc]ache 222 | # but keep track of directories ending in .cache 223 | !?*.[Cc]ache/ 224 | 225 | # Others 226 | ClientBin/ 227 | ~$* 228 | *~ 229 | *.dbmdl 230 | *.dbproj.schemaview 231 | *.jfm 232 | *.pfx 233 | *.publishsettings 234 | orleans.codegen.cs 235 | 236 | # Including strong name files can present a security risk 237 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 238 | #*.snk 239 | 240 | # Since there are multiple workflows, uncomment next line to ignore bower_components 241 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 242 | #bower_components/ 243 | 244 | # RIA/Silverlight projects 245 | Generated_Code/ 246 | 247 | # Backup & report files from converting an old project file 248 | # to a newer Visual Studio version. Backup files are not needed, 249 | # because we have git ;-) 250 | _UpgradeReport_Files/ 251 | Backup*/ 252 | UpgradeLog*.XML 253 | UpgradeLog*.htm 254 | ServiceFabricBackup/ 255 | *.rptproj.bak 256 | 257 | # SQL Server files 258 | *.mdf 259 | *.ldf 260 | *.ndf 261 | 262 | # Business Intelligence projects 263 | *.rdl.data 264 | *.bim.layout 265 | *.bim_*.settings 266 | *.rptproj.rsuser 267 | *- [Bb]ackup.rdl 268 | *- [Bb]ackup ([0-9]).rdl 269 | *- [Bb]ackup ([0-9][0-9]).rdl 270 | 271 | # Microsoft Fakes 272 | FakesAssemblies/ 273 | 274 | # GhostDoc plugin setting file 275 | *.GhostDoc.xml 276 | 277 | # Node.js Tools for Visual Studio 278 | .ntvs_analysis.dat 279 | node_modules/ 280 | 281 | # Visual Studio 6 build log 282 | *.plg 283 | 284 | # Visual Studio 6 workspace options file 285 | *.opt 286 | 287 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 288 | *.vbw 289 | 290 | # Visual Studio LightSwitch build output 291 | **/*.HTMLClient/GeneratedArtifacts 292 | **/*.DesktopClient/GeneratedArtifacts 293 | **/*.DesktopClient/ModelManifest.xml 294 | **/*.Server/GeneratedArtifacts 295 | **/*.Server/ModelManifest.xml 296 | _Pvt_Extensions 297 | 298 | # Paket dependency manager 299 | .paket/paket.exe 300 | paket-files/ 301 | 302 | # FAKE - F# Make 303 | .fake/ 304 | 305 | # CodeRush personal settings 306 | .cr/personal 307 | 308 | # Python Tools for Visual Studio (PTVS) 309 | __pycache__/ 310 | *.pyc 311 | 312 | # Cake - Uncomment if you are using it 313 | # tools/** 314 | # !tools/packages.config 315 | 316 | # Tabs Studio 317 | *.tss 318 | 319 | # Telerik's JustMock configuration file 320 | *.jmconfig 321 | 322 | # BizTalk build output 323 | *.btp.cs 324 | *.btm.cs 325 | *.odx.cs 326 | *.xsd.cs 327 | 328 | # OpenCover UI analysis results 329 | OpenCover/ 330 | 331 | # Azure Stream Analytics local run output 332 | ASALocalRun/ 333 | 334 | # MSBuild Binary and Structured Log 335 | *.binlog 336 | 337 | # NVidia Nsight GPU debugger configuration file 338 | *.nvuser 339 | 340 | # MFractors (Xamarin productivity tool) working folder 341 | .mfractor/ 342 | 343 | # Local History for Visual Studio 344 | .localhistory/ 345 | 346 | # BeatPulse healthcheck temp database 347 | healthchecksdb 348 | 349 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 350 | MigrationBackup/ 351 | 352 | # Ionide (cross platform F# VS Code tools) working folder 353 | .ionide/ 354 | 355 | 356 | # Azure Functions Core Tools 357 | src/azure-functions-core-tools/* 358 | template/azure-functions-core-tools/* 359 | azure-functions-core-tools/* -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | // Use IntelliSense to find out which attributes exist for C# debugging 6 | // Use hover for the description of the existing attributes 7 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/src/bin/Debug/netcoreapp3.1/Products.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/src", 16 | "stopAtEntry": false, 17 | // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser 18 | "serverReadyAction": { 19 | "action": "openExternally", 20 | "pattern": "\\\\bNow listening on:\\\\s+(https?://\\\\S+)" 21 | }, 22 | "env": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "sourceFileMap": { 26 | "/Views": "${workspaceFolder}/Views" 27 | } 28 | }, 29 | { 30 | "name": ".NET Core Attach", 31 | "type": "coreclr", 32 | "request": "attach", 33 | "processId": "${command:pickProcess}" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/src/Products.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/src/Products.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/src/Products.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | | Date | Notes | 4 | |------------|-------------------| 5 | | 2021-02-15 | Initial release. | 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /images/bastion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/images/bastion.png -------------------------------------------------------------------------------- /images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/images/demo.png -------------------------------------------------------------------------------- /images/redis-cache-in-a-vnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/images/redis-cache-in-a-vnet.png -------------------------------------------------------------------------------- /images/redis-cache-private-endpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/images/redis-cache-private-endpoint.png -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Clear the screen 4 | clear 5 | 6 | # Print the menu 7 | echo "=================================================" 8 | echo "Install Demo. Choose an option (1-3): " 9 | echo "=================================================" 10 | options=("Inject Premium Azure Cache for Redis in a VNET" 11 | "Azure Cache for Redis with Azure Private Link" 12 | "Quit") 13 | 14 | # Select an option 15 | COLUMNS=0 16 | select opt in "${options[@]}"; do 17 | case $opt in 18 | "Inject Premium Azure Cache for Redis in a VNET") 19 | template="../templates/azuredeploy.vnet.json" 20 | parameters="../templates/azuredeploy.vnet.parameters.json" 21 | resourceGroupName="RedisCacheInVnetRG" 22 | break 23 | ;; 24 | "Azure Cache for Redis with Azure Private Link") 25 | template="../templates/azuredeploy.endpoint.json" 26 | parameters="../templates/azuredeploy.endpoint.parameters.json" 27 | resourceGroupName="RedisCachePrivateLinkRG" 28 | break 29 | ;; 30 | "Quit") 31 | exit 32 | ;; 33 | *) echo "invalid option $REPLY" ;; 34 | esac 35 | done 36 | 37 | # Variables 38 | location="WestEurope" 39 | 40 | # SubscriptionId of the current subscription 41 | subscriptionId=$(az account show --query id --output tsv) 42 | subscriptionName=$(az account show --query name --output tsv) 43 | 44 | # Check if the resource group already exists 45 | createResourceGroup() { 46 | local resourceGroupName=$1 47 | local location=$2 48 | 49 | # Parameters validation 50 | if [[ -z $resourceGroupName ]]; then 51 | echo "The resource group name parameter cannot be null" 52 | exit 53 | fi 54 | 55 | if [[ -z $location ]]; then 56 | echo "The location parameter cannot be null" 57 | exit 58 | fi 59 | 60 | echo "Checking if [$resourceGroupName] resource group actually exists in the [$subscriptionName] subscription..." 61 | 62 | if ! az group show --name "$resourceGroupName" &>/dev/null; then 63 | echo "No [$resourceGroupName] resource group actually exists in the [$subscriptionName] subscription" 64 | echo "Creating [$resourceGroupName] resource group in the [$subscriptionName] subscription..." 65 | 66 | # Create the resource group 67 | if az group create --name "$resourceGroupName" --location "$location" 1>/dev/null; then 68 | echo "[$resourceGroupName] resource group successfully created in the [$subscriptionName] subscription" 69 | else 70 | echo "Failed to create [$resourceGroupName] resource group in the [$subscriptionName] subscription" 71 | exit 72 | fi 73 | else 74 | echo "[$resourceGroupName] resource group already exists in the [$subscriptionName] subscription" 75 | fi 76 | } 77 | 78 | # Validate the ARM template 79 | validateTemplate() { 80 | local resourceGroupName=$1 81 | local template=$2 82 | local parameters=$3 83 | local arguments=$4 84 | 85 | # Parameters validation 86 | if [[ -z $resourceGroupName ]]; then 87 | echo "The resource group name parameter cannot be null" 88 | fi 89 | 90 | if [[ -z $template ]]; then 91 | echo "The template parameter cannot be null" 92 | fi 93 | 94 | if [[ -z $parameters ]]; then 95 | echo "The parameters parameter cannot be null" 96 | fi 97 | 98 | echo "Validating [$template] ARM template..." 99 | 100 | if [[ -z $arguments ]]; then 101 | error=$(az deployment group validate \ 102 | --resource-group "$resourceGroupName" \ 103 | --template-file "$template" \ 104 | --parameters "$parameters" 2>&1 | grep 'ERROR:') 105 | else 106 | error=$(az deployment group validate \ 107 | --resource-group "$resourceGroupName" \ 108 | --template-file "$template" \ 109 | --parameters "$parameters" \ 110 | --arguments $arguments 2>&1 | grep 'ERROR:') 111 | fi 112 | 113 | if [[ -z $error ]]; then 114 | echo "[$template] ARM template successfully validated" 115 | else 116 | echo "Failed to validate the [$template] ARM template" 117 | echo "$error" 118 | exit 1 119 | fi 120 | } 121 | 122 | # Deploy ARM template 123 | deployTemplate() { 124 | local resourceGroupName=$1 125 | local template=$2 126 | local parameters=$3 127 | local arguments=$4 128 | 129 | # Parameters validation 130 | if [[ -z $resourceGroupName ]]; then 131 | echo "The resource group name parameter cannot be null" 132 | exit 133 | fi 134 | 135 | if [[ -z $template ]]; then 136 | echo "The template parameter cannot be null" 137 | exit 138 | fi 139 | 140 | if [[ -z $parameters ]]; then 141 | echo "The parameters parameter cannot be null" 142 | exit 143 | fi 144 | 145 | # Deploy the ARM template 146 | echo "Deploying [$template] ARM template..." 147 | 148 | if [[ -z $arguments ]]; then 149 | az deployment group create \ 150 | --resource-group $resourceGroupName \ 151 | --template-file $template \ 152 | --parameters $parameters 1>/dev/null 153 | else 154 | az deployment group create \ 155 | --resource-group $resourceGroupName \ 156 | --template-file $template \ 157 | --parameters $parameters \ 158 | --parameters $arguments 1>/dev/null 159 | fi 160 | 161 | if [[ $? == 0 ]]; then 162 | echo "[$template] ARM template successfully provisioned" 163 | else 164 | echo "Failed to provision the [$template$] ARM template" 165 | exit -1 166 | fi 167 | } 168 | 169 | # Create Resource Group 170 | createResourceGroup \ 171 | "$resourceGroupName" \ 172 | "$location" 173 | 174 | # Validate ARM Template 175 | validateTemplate \ 176 | "$resourceGroupName" \ 177 | "$template" \ 178 | "$parameters" 179 | 180 | # Deploy ARM Template 181 | deployTemplate \ 182 | "$resourceGroupName" \ 183 | "$template" \ 184 | "$parameters" -------------------------------------------------------------------------------- /sql/ProductsDb.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('Products') > 0 DROP TABLE [Products] 2 | GO 3 | -- Create Products table 4 | CREATE TABLE [Products] 5 | ( 6 | [ProductID] [int] IDENTITY(1,1) NOT NULL , 7 | [Name] [nvarchar](50) NOT NULL , 8 | [Category] [nvarchar](50) NOT NULL , 9 | [Price] [smallmoney] NOT NULL 10 | CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED 11 | ( 12 | [ProductID] 13 | ) 14 | ) 15 | GO 16 | -- Create stored procedures 17 | IF OBJECT_ID('GetProduct') > 0 DROP PROCEDURE [GetProduct] 18 | GO 19 | CREATE PROCEDURE GetProduct 20 | @ProductID int 21 | AS 22 | SELECT [ProductID], [Name], [Category], [Price] 23 | FROM [Products] 24 | WHERE [ProductID] = @ProductID 25 | GO 26 | IF OBJECT_ID('GetProducts') > 0 DROP PROCEDURE [GetProducts] 27 | GO 28 | CREATE PROCEDURE GetProducts 29 | AS 30 | SELECT [ProductID], [Name], [Category], [Price] 31 | FROM [Products] 32 | GO 33 | IF OBJECT_ID('GetProductsByCategory') > 0 DROP PROCEDURE [GetProductsByCategory] 34 | GO 35 | CREATE PROCEDURE GetProductsByCategory 36 | @Category [nvarchar](50) 37 | AS 38 | SELECT [ProductID], [Name], [Category], [Price] 39 | FROM [Products] 40 | WHERE [Category] = @Category 41 | GO 42 | IF OBJECT_ID('AddProduct') > 0 DROP PROCEDURE [AddProduct] 43 | GO 44 | CREATE PROCEDURE AddProduct 45 | @ProductID int OUTPUT, 46 | @Name [nvarchar](50), 47 | @Category [nvarchar](50), 48 | @Price [smallmoney] 49 | AS 50 | INSERT INTO Products 51 | VALUES 52 | (@Name, @Category, @Price) 53 | SET @ProductID = @@IDENTITY 54 | GO 55 | IF OBJECT_ID('UpdateProduct') > 0 DROP PROCEDURE [UpdateProduct] 56 | GO 57 | CREATE PROCEDURE UpdateProduct 58 | @ProductID int, 59 | @Name [nvarchar](50), 60 | @Category [nvarchar](50), 61 | @Price [smallmoney] 62 | AS 63 | UPDATE Products 64 | SET [Name] = @Name, 65 | [Category] = @Category, 66 | [Price] = @Price 67 | WHERE [ProductID] = @ProductID 68 | GO 69 | IF OBJECT_ID('DeleteProduct') > 0 DROP PROCEDURE [DeleteProduct] 70 | GO 71 | CREATE PROCEDURE DeleteProduct 72 | @ProductID int 73 | AS 74 | DELETE [Products] 75 | WHERE [ProductID] = @ProductID 76 | GO 77 | -- Create test data 78 | SET NOCOUNT ON 79 | GO 80 | INSERT INTO Products 81 | VALUES 82 | (N'Tomato soup', N'Groceries', 1.39) 83 | GO 84 | INSERT INTO Products 85 | VALUES 86 | (N'Babo', N'Toys', 19.99) 87 | GO 88 | INSERT INTO Products 89 | VALUES 90 | (N'Hammer', N'Hardware', 16.49) 91 | GO -------------------------------------------------------------------------------- /src/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2 | //======================================================================================= 3 | // Author: Paolo Salvatori 4 | // GitHub: https://github.com/paolosalvatori 5 | //======================================================================================= 6 | // Copyright © 2021 Microsoft Corporation. All rights reserved. 7 | // 8 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER 9 | // EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT. 11 | //======================================================================================= 12 | #endregion 13 | 14 | #region MyRegion 15 | using Microsoft.AspNetCore.Mvc; 16 | using Microsoft.Extensions.Logging; 17 | using Products.Models; 18 | using System.Diagnostics; 19 | #endregion 20 | 21 | namespace Products.Controllers 22 | { 23 | public class HomeController : Controller 24 | { 25 | private readonly ILogger logger; 26 | 27 | public HomeController(ILogger logger) 28 | { 29 | this.logger = logger; 30 | } 31 | 32 | public IActionResult Index() 33 | { 34 | return View(); 35 | } 36 | 37 | public IActionResult Privacy() 38 | { 39 | return View(); 40 | } 41 | 42 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 43 | public IActionResult Error() 44 | { 45 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Helpers/AzureAdAuthenticationDbConnectionInterceptor.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2 | //======================================================================================= 3 | // Author: Paolo Salvatori 4 | // GitHub: https://github.com/paolosalvatori 5 | //======================================================================================= 6 | // Copyright © 2021 Microsoft Corporation. All rights reserved. 7 | // 8 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER 9 | // EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT. 11 | //======================================================================================= 12 | #endregion 13 | 14 | #region Using Directives 15 | using System; 16 | using System.Data.Common; 17 | using System.Threading; 18 | using System.Threading.Tasks; 19 | using Azure.Core; 20 | using Azure.Identity; 21 | using Microsoft.Data.SqlClient; 22 | using Microsoft.EntityFrameworkCore.Diagnostics; 23 | #endregion 24 | 25 | namespace Products.Helpers 26 | { 27 | public class AzureAdAuthenticationDbConnectionInterceptor : DbConnectionInterceptor 28 | { 29 | // For more information, see: 30 | // https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/services-support-managed-identities#azure-sql 31 | // https://devblogs.microsoft.com/azure-sdk/azure-identity-with-sql-graph-ef/ 32 | private static readonly string[] _azureSqlScopes = new[] 33 | { 34 | "https://database.windows.net//.default" 35 | }; 36 | 37 | private static readonly TokenCredential _credential = new ChainedTokenCredential( 38 | new ManagedIdentityCredential(), 39 | new VisualStudioCredential(), 40 | new EnvironmentCredential()); 41 | 42 | public override InterceptionResult ConnectionOpening( 43 | DbConnection connection, 44 | ConnectionEventData eventData, 45 | InterceptionResult result) 46 | { 47 | var sqlConnection = (SqlConnection)connection; 48 | if (DoesConnectionNeedAccessToken(sqlConnection)) 49 | { 50 | var tokenRequestContext = new TokenRequestContext(_azureSqlScopes); 51 | var token = _credential.GetToken(tokenRequestContext, default); 52 | 53 | sqlConnection.AccessToken = token.Token; 54 | } 55 | 56 | return base.ConnectionOpening(connection, eventData, result); 57 | } 58 | 59 | public override async ValueTask ConnectionOpeningAsync( 60 | DbConnection connection, 61 | ConnectionEventData eventData, 62 | InterceptionResult result, 63 | CancellationToken cancellationToken = default) 64 | { 65 | var sqlConnection = (SqlConnection)connection; 66 | if (DoesConnectionNeedAccessToken(sqlConnection)) 67 | { 68 | var tokenRequestContext = new TokenRequestContext(_azureSqlScopes); 69 | var token = await _credential.GetTokenAsync(tokenRequestContext, cancellationToken); 70 | 71 | sqlConnection.AccessToken = token.Token; 72 | } 73 | 74 | return await base.ConnectionOpeningAsync(connection, eventData, result, cancellationToken); 75 | } 76 | 77 | private static bool DoesConnectionNeedAccessToken(SqlConnection connection) 78 | { 79 | // 80 | // Only try to get a token from AAD if 81 | // - We connect to an Azure SQL instance; and 82 | // - The connection doesn't specify a username. 83 | // 84 | var connectionStringBuilder = new SqlConnectionStringBuilder(connection.ConnectionString); 85 | 86 | return connectionStringBuilder.DataSource.Contains("database.windows.net", StringComparison.OrdinalIgnoreCase) && string.IsNullOrEmpty(connectionStringBuilder.UserID); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Helpers/MessageHelper.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2 | //======================================================================================= 3 | // Author: Paolo Salvatori 4 | // GitHub: https://github.com/paolosalvatori 5 | //======================================================================================= 6 | // Copyright © 2021 Microsoft Corporation. All rights reserved. 7 | // 8 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER 9 | // EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT. 11 | //======================================================================================= 12 | #endregion 13 | 14 | #region Using Directives 15 | using System; 16 | #endregion 17 | 18 | namespace Products.Helpers 19 | { 20 | /// 21 | /// This static class contains helper methods for exception messages 22 | /// 23 | public static class MessageHelper 24 | { 25 | public static string FormatException(Exception ex) 26 | { 27 | if (ex == null) 28 | { 29 | return string.Empty; 30 | } 31 | var baseException = ex.GetBaseException(); 32 | if (string.Compare(ex.Message, baseException?.Message, true) == 0) 33 | { 34 | return $"An error occurred: Exception=[{ex.Message}]"; 35 | } 36 | return $"An error occurred: Exception=[{ex.Message}] BaseException=[{baseException?.Message ?? "NULL"}]"; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Helpers/StackExchangeExtensions.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2 | //======================================================================================= 3 | // Author: Paolo Salvatori 4 | // GitHub: https://github.com/paolosalvatori 5 | //======================================================================================= 6 | // Copyright © 2021 Microsoft Corporation. All rights reserved. 7 | // 8 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER 9 | // EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT. 11 | //======================================================================================= 12 | #endregion 13 | 14 | #region Using Directives 15 | 16 | using System.Collections.Generic; 17 | using System.IO; 18 | using System.Linq; 19 | using System.Runtime.Serialization.Formatters.Binary; 20 | using System.Threading.Tasks; 21 | using StackExchange.Redis; 22 | #endregion 23 | 24 | namespace Products.Helpers 25 | { 26 | public static class StackExchangeExtensions 27 | { 28 | #region Public Instance Methods 29 | public static T Get(this IDatabase cache, string key) 30 | { 31 | return Deserialize(cache.StringGet(key)); 32 | } 33 | 34 | public async static Task GetAsync(this IDatabase cache, string key) 35 | { 36 | return Deserialize(await cache.StringGetAsync(key)); 37 | } 38 | 39 | public static IEnumerable Get(this IDatabase cache, string[] keys) 40 | { 41 | return Deserialize(cache.StringGet(keys.Select(key => (RedisKey)key).ToArray())); 42 | } 43 | 44 | public async static Task> GetAsync(this IDatabase cache, string[] keys) 45 | { 46 | return Deserialize(await cache.StringGetAsync(keys.Select(key => (RedisKey)key).ToArray())); 47 | } 48 | 49 | public static object Get(this IDatabase cache, string key) 50 | { 51 | return Deserialize(cache.StringGet(key)); 52 | } 53 | 54 | public async static Task GetAsync(this IDatabase cache, string key) 55 | { 56 | return Deserialize(await cache.StringGetAsync(key)); 57 | } 58 | 59 | public static object Get(this IDatabase cache, string[] keys) 60 | { 61 | return Deserialize(cache.StringGet(keys.Select(key => (RedisKey)key).ToArray())); 62 | } 63 | 64 | public async static Task GetAsync(this IDatabase cache, string[] keys) 65 | { 66 | return Deserialize(await cache.StringGetAsync(keys.Select(key => (RedisKey)key).ToArray())); 67 | } 68 | 69 | public static void Set(this IDatabase cache, string key, object value) 70 | { 71 | cache.StringSet(key, Serialize(value)); 72 | } 73 | 74 | public static Task SetAsync(this IDatabase cache, string key, object value) 75 | { 76 | return cache.StringSetAsync(key, Serialize(value)); 77 | } 78 | #endregion 79 | 80 | #region Private Static Methods 81 | private static byte[] Serialize(object o) 82 | { 83 | if (o == null) 84 | { 85 | return null; 86 | } 87 | 88 | var binaryFormatter = new BinaryFormatter(); 89 | using (var memoryStream = new MemoryStream()) 90 | { 91 | binaryFormatter.Serialize(memoryStream, o); 92 | byte[] objectDataAsStream = memoryStream.ToArray(); 93 | return objectDataAsStream; 94 | } 95 | } 96 | 97 | private static IEnumerable Deserialize(IEnumerable values) 98 | { 99 | return values.Select(v => Deserialize(v)); 100 | } 101 | 102 | private static T Deserialize(byte[] stream) 103 | { 104 | if (stream == null) 105 | { 106 | return default(T); 107 | } 108 | 109 | var binaryFormatter = new BinaryFormatter(); 110 | using (var memoryStream = new MemoryStream(stream)) 111 | { 112 | var result = (T)binaryFormatter.Deserialize(memoryStream); 113 | return result; 114 | } 115 | } 116 | #endregion 117 | } 118 | } -------------------------------------------------------------------------------- /src/Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/Models/ErrorViewModel.cs -------------------------------------------------------------------------------- /src/Models/Product.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2 | //======================================================================================= 3 | // Author: Paolo Salvatori 4 | // GitHub: https://github.com/paolosalvatori 5 | //======================================================================================= 6 | // Copyright © 2021 Microsoft Corporation. All rights reserved. 7 | // 8 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER 9 | // EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT. 11 | //======================================================================================= 12 | #endregion 13 | 14 | #region Using Directives 15 | using System; 16 | using System.ComponentModel.DataAnnotations; 17 | using System.Runtime.Serialization; 18 | using Newtonsoft.Json; 19 | #endregion 20 | 21 | namespace Products.Models 22 | { 23 | [Serializable] 24 | [DataContract(Name = "product", Namespace = "http://github.com/paolosalvatori/samples")] 25 | public class Product 26 | { 27 | #region Public Properties 28 | [Key] 29 | [JsonProperty(PropertyName = "productId", Order = 1)] 30 | [DataMember(Name = "productId", Order = 1)] 31 | public int ProductId { get; set; } 32 | 33 | [JsonProperty(PropertyName = "name", Order = 2)] 34 | [DataMember(Name = "name", Order = 2)] 35 | public string Name { get; set; } 36 | 37 | [JsonProperty(PropertyName = "category", Order = 3)] 38 | [DataMember(Name = "category", Order = 3)] 39 | public string Category { get; set; } 40 | 41 | [JsonProperty(PropertyName = "price", Order = 4)] 42 | [DataMember(Name = "price", Order = 4)] 43 | public decimal Price { get; set; } 44 | #endregion 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Models/ProductsContext.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2 | //======================================================================================= 3 | // Author: Paolo Salvatori 4 | // GitHub: https://github.com/paolosalvatori 5 | //======================================================================================= 6 | // Copyright © 2021 Microsoft Corporation. All rights reserved. 7 | // 8 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER 9 | // EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT. 11 | //======================================================================================= 12 | #endregion 13 | 14 | #region Using Directives 15 | using Microsoft.EntityFrameworkCore; 16 | using System; 17 | #endregion 18 | 19 | namespace Products.Models 20 | { 21 | /// 22 | /// ProductsDbContext class 23 | /// 24 | public class ProductsContext : DbContext 25 | { 26 | #region Public Constructor 27 | /// 28 | /// Public Constructor 29 | /// 30 | /// DbContextOptions object 31 | public ProductsContext(DbContextOptions options) 32 | : base(options) 33 | { 34 | } 35 | #endregion 36 | 37 | #region Public Properties 38 | /// 39 | /// Gets or sets the Products property 40 | /// 41 | public DbSet Products { get; set; } 42 | #endregion 43 | 44 | #region Protected Methods 45 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 46 | => optionsBuilder 47 | .EnableSensitiveDataLogging() 48 | .EnableDetailedErrors() 49 | .LogTo(Console.WriteLine); 50 | #endregion 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Products.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | false 6 | d268c7b1-505a-41d0-a81e-bce866dfd28f 7 | /subscriptions/1a45a694-ae23-4650-9774-89a571c462f6/resourceGroups/WebAppSqlDbRedisCacheRG/providers/microsoft.insights/components/WebAppSqlDbRedisApplicationInsights 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | all 83 | runtime; build; native; contentfiles; analyzers; buildtransitive 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | True 97 | True 98 | Resources.resx 99 | 100 | 101 | 102 | 103 | 104 | ResXFileCodeGenerator 105 | Resources.Designer.cs 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/Products.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31105.61 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Products", "Products.csproj", "{040B0C91-5349-4675-BA72-4EB0B745E503}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {040B0C91-5349-4675-BA72-4EB0B745E503}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {040B0C91-5349-4675-BA72-4EB0B745E503}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {040B0C91-5349-4675-BA72-4EB0B745E503}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {040B0C91-5349-4675-BA72-4EB0B745E503}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {05FC7653-5823-4625-A9B2-4F31990E023D} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/Program.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2 | //======================================================================================= 3 | // Author: Paolo Salvatori 4 | // GitHub: https://github.com/paolosalvatori 5 | //======================================================================================= 6 | // Copyright � 2021 Microsoft Corporation. All rights reserved. 7 | // 8 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER 9 | // EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT. 11 | //======================================================================================= 12 | #endregion 13 | 14 | #region Using Directives 15 | using System; 16 | using Microsoft.AspNetCore.Hosting; 17 | using Microsoft.Extensions.Configuration; 18 | using Microsoft.Extensions.Hosting; 19 | using Azure.Identity; 20 | using Azure.Security.KeyVault.Secrets; 21 | using Azure.Extensions.AspNetCore.Configuration.Secrets; 22 | using Products.Properties; 23 | #endregion 24 | 25 | namespace Products 26 | { 27 | public class Program 28 | { 29 | public static void Main(string[] args) 30 | { 31 | CreateHostBuilder(args).Build().Run(); 32 | } 33 | 34 | public static IHostBuilder CreateHostBuilder(string[] args) => 35 | Host.CreateDefaultBuilder(args) 36 | .ConfigureAppConfiguration((context, config) => 37 | { 38 | var builtConfig = config.Build(); 39 | var keyVaultUri = builtConfig[Resources.KeyVaultUri]; 40 | if (string.IsNullOrEmpty(keyVaultUri)) 41 | { 42 | throw new Exception("KeyVaultUri parameter in the appsettings.json cannot be null or empty"); 43 | } 44 | var secretClient = new SecretClient( 45 | new Uri(keyVaultUri), 46 | new DefaultAzureCredential()); 47 | config.AddAzureKeyVault(secretClient, new KeyVaultSecretManager()); 48 | }) 49 | .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Products.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Products.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to EXEC [dbo].[AddProduct] @ProductID OUTPUT, @Name, @Category, @Price. 65 | /// 66 | internal static string AddProduct { 67 | get { 68 | return ResourceManager.GetString("AddProduct", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized string similar to APPINSIGHTS_CONNECTIONSTRING. 74 | /// 75 | internal static string ApplicationInsightsConnectionString { 76 | get { 77 | return ResourceManager.GetString("ApplicationInsightsConnectionString", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// Looks up a localized string similar to EXEC [dbo].[DeleteProduct] @ProductID. 83 | /// 84 | internal static string DeleteProduct { 85 | get { 86 | return ResourceManager.GetString("DeleteProduct", resourceCulture); 87 | } 88 | } 89 | 90 | /// 91 | /// Looks up a localized string similar to {0} Exception: [{1}] InnerException: [{2}]. 92 | /// 93 | internal static string ExceptionFormat { 94 | get { 95 | return ResourceManager.GetString("ExceptionFormat", resourceCulture); 96 | } 97 | } 98 | 99 | /// 100 | /// Looks up a localized string similar to EXEC [dbo].[GetProduct] @ProductID. 101 | /// 102 | internal static string GetProduct { 103 | get { 104 | return ResourceManager.GetString("GetProduct", resourceCulture); 105 | } 106 | } 107 | 108 | /// 109 | /// Looks up a localized string similar to EXEC [dbo].[GetProducts]. 110 | /// 111 | internal static string GetProducts { 112 | get { 113 | return ResourceManager.GetString("GetProducts", resourceCulture); 114 | } 115 | } 116 | 117 | /// 118 | /// Looks up a localized string similar to In. 119 | /// 120 | internal static string In { 121 | get { 122 | return ResourceManager.GetString("In", resourceCulture); 123 | } 124 | } 125 | 126 | /// 127 | /// Looks up a localized string similar to KeyVaultUri. 128 | /// 129 | internal static string KeyVaultUri { 130 | get { 131 | return ResourceManager.GetString("KeyVaultUri", resourceCulture); 132 | } 133 | } 134 | 135 | /// 136 | /// Looks up a localized string similar to [{0}.{1}]. 137 | /// 138 | internal static string MethodNameFormat { 139 | get { 140 | return ResourceManager.GetString("MethodNameFormat", resourceCulture); 141 | } 142 | } 143 | 144 | /// 145 | /// Looks up a localized string similar to Null. 146 | /// 147 | internal static string Null { 148 | get { 149 | return ResourceManager.GetString("Null", resourceCulture); 150 | } 151 | } 152 | 153 | /// 154 | /// Looks up a localized string similar to Out. 155 | /// 156 | internal static string Out { 157 | get { 158 | return ResourceManager.GetString("Out", resourceCulture); 159 | } 160 | } 161 | 162 | /// 163 | /// Looks up a localized string similar to RedisCacheConnectionString. 164 | /// 165 | internal static string RedisCacheConnectionString { 166 | get { 167 | return ResourceManager.GetString("RedisCacheConnectionString", resourceCulture); 168 | } 169 | } 170 | 171 | /// 172 | /// Looks up a localized string similar to RedisKeys. 173 | /// 174 | internal static string RedisKeys { 175 | get { 176 | return ResourceManager.GetString("RedisKeys", resourceCulture); 177 | } 178 | } 179 | 180 | /// 181 | /// Looks up a localized string similar to SqlServerConnectionString. 182 | /// 183 | internal static string SqlServerConnectionString { 184 | get { 185 | return ResourceManager.GetString("SqlServerConnectionString", resourceCulture); 186 | } 187 | } 188 | 189 | /// 190 | /// Looks up a localized string similar to {0} {1}. 191 | /// 192 | internal static string TraceInFormat { 193 | get { 194 | return ResourceManager.GetString("TraceInFormat", resourceCulture); 195 | } 196 | } 197 | 198 | /// 199 | /// Looks up a localized string similar to {0} {1}. 200 | /// 201 | internal static string TraceOutFormat { 202 | get { 203 | return ResourceManager.GetString("TraceOutFormat", resourceCulture); 204 | } 205 | } 206 | 207 | /// 208 | /// Looks up a localized string similar to This TraceSwitch is used to control which information to trace at runtime.. 209 | /// 210 | internal static string TraceSwitchDescription { 211 | get { 212 | return ResourceManager.GetString("TraceSwitchDescription", resourceCulture); 213 | } 214 | } 215 | 216 | /// 217 | /// Looks up a localized string similar to TraceSwitch. 218 | /// 219 | internal static string TraceSwitchName { 220 | get { 221 | return ResourceManager.GetString("TraceSwitchName", resourceCulture); 222 | } 223 | } 224 | 225 | /// 226 | /// Looks up a localized string similar to Unknown. 227 | /// 228 | internal static string Unknown { 229 | get { 230 | return ResourceManager.GetString("Unknown", resourceCulture); 231 | } 232 | } 233 | 234 | /// 235 | /// Looks up a localized string similar to EXEC [dbo].[UpdateProduct] @ProductID, @Name, @Category, @Price. 236 | /// 237 | internal static string UpdateProduct { 238 | get { 239 | return ResourceManager.GetString("UpdateProduct", resourceCulture); 240 | } 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | EXEC [dbo].[AddProduct] @ProductID OUTPUT, @Name, @Category, @Price 122 | 123 | 124 | APPINSIGHTS_CONNECTIONSTRING 125 | 126 | 127 | EXEC [dbo].[DeleteProduct] @ProductID 128 | 129 | 130 | {0} Exception: [{1}] InnerException: [{2}] 131 | 132 | 133 | EXEC [dbo].[GetProduct] @ProductID 134 | 135 | 136 | EXEC [dbo].[GetProducts] 137 | 138 | 139 | In 140 | 141 | 142 | KeyVaultUri 143 | 144 | 145 | [{0}.{1}] 146 | 147 | 148 | Null 149 | 150 | 151 | Out 152 | 153 | 154 | RedisCacheConnectionString 155 | 156 | 157 | RedisKeys 158 | 159 | 160 | SqlServerConnectionString 161 | 162 | 163 | {0} {1} 164 | 165 | 166 | {0} {1} 167 | 168 | 169 | This TraceSwitch is used to control which information to trace at runtime. 170 | 171 | 172 | TraceSwitch 173 | 174 | 175 | Unknown 176 | 177 | 178 | EXEC [dbo].[UpdateProduct] @ProductID, @Name, @Category, @Price 179 | 180 | -------------------------------------------------------------------------------- /src/Properties/ServiceDependencies/WebAppRedisCacheSqlDb - Web Deploy/profile.arm.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "_dependencyType": "appService.windows" 6 | }, 7 | "parameters": { 8 | "resourceGroupName": { 9 | "type": "string", 10 | "defaultValue": "WebAppRedisCacheSqlDb", 11 | "metadata": { 12 | "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking." 13 | } 14 | }, 15 | "resourceGroupLocation": { 16 | "type": "string", 17 | "defaultValue": "westeurope", 18 | "metadata": { 19 | "description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support." 20 | } 21 | }, 22 | "resourceName": { 23 | "type": "string", 24 | "defaultValue": "WebAppRedisCacheSqlDb", 25 | "metadata": { 26 | "description": "Name of the main resource to be created by this template." 27 | } 28 | }, 29 | "resourceLocation": { 30 | "type": "string", 31 | "defaultValue": "[parameters('resourceGroupLocation')]", 32 | "metadata": { 33 | "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there." 34 | } 35 | } 36 | }, 37 | "variables": { 38 | "appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", 39 | "appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]" 40 | }, 41 | "resources": [ 42 | { 43 | "type": "Microsoft.Resources/resourceGroups", 44 | "name": "[parameters('resourceGroupName')]", 45 | "location": "[parameters('resourceGroupLocation')]", 46 | "apiVersion": "2019-10-01" 47 | }, 48 | { 49 | "type": "Microsoft.Resources/deployments", 50 | "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", 51 | "resourceGroup": "[parameters('resourceGroupName')]", 52 | "apiVersion": "2019-10-01", 53 | "dependsOn": [ 54 | "[parameters('resourceGroupName')]" 55 | ], 56 | "properties": { 57 | "mode": "Incremental", 58 | "template": { 59 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 60 | "contentVersion": "1.0.0.0", 61 | "resources": [ 62 | { 63 | "location": "[parameters('resourceLocation')]", 64 | "name": "[parameters('resourceName')]", 65 | "type": "Microsoft.Web/sites", 66 | "apiVersion": "2015-08-01", 67 | "tags": { 68 | "[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty" 69 | }, 70 | "dependsOn": [ 71 | "[variables('appServicePlan_ResourceId')]" 72 | ], 73 | "kind": "app", 74 | "properties": { 75 | "name": "[parameters('resourceName')]", 76 | "kind": "app", 77 | "httpsOnly": true, 78 | "reserved": false, 79 | "serverFarmId": "[variables('appServicePlan_ResourceId')]", 80 | "siteConfig": { 81 | "metadata": [ 82 | { 83 | "name": "CURRENT_STACK", 84 | "value": "dotnetcore" 85 | } 86 | ] 87 | } 88 | }, 89 | "identity": { 90 | "type": "SystemAssigned" 91 | } 92 | }, 93 | { 94 | "location": "[parameters('resourceLocation')]", 95 | "name": "[variables('appServicePlan_name')]", 96 | "type": "Microsoft.Web/serverFarms", 97 | "apiVersion": "2015-08-01", 98 | "sku": { 99 | "name": "S1", 100 | "tier": "Standard", 101 | "family": "S", 102 | "size": "S1" 103 | }, 104 | "properties": { 105 | "name": "[variables('appServicePlan_name')]" 106 | } 107 | } 108 | ] 109 | } 110 | } 111 | } 112 | ] 113 | } -------------------------------------------------------------------------------- /src/Properties/ServiceDependencies/local/appInsights1.arm.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "resourceGroupName": { 6 | "type": "string", 7 | "defaultValue": "WebAppSqlDbRedisCacheRG", 8 | "metadata": { 9 | "_parameterType": "resourceGroup", 10 | "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking." 11 | } 12 | }, 13 | "resourceGroupLocation": { 14 | "type": "string", 15 | "defaultValue": "westeurope", 16 | "metadata": { 17 | "_parameterType": "location", 18 | "description": "Location of the resource group. Resource groups could have different location than resources." 19 | } 20 | }, 21 | "resourceLocation": { 22 | "type": "string", 23 | "defaultValue": "[parameters('resourceGroupLocation')]", 24 | "metadata": { 25 | "_parameterType": "location", 26 | "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there." 27 | } 28 | } 29 | }, 30 | "resources": [ 31 | { 32 | "type": "Microsoft.Resources/resourceGroups", 33 | "name": "[parameters('resourceGroupName')]", 34 | "location": "[parameters('resourceGroupLocation')]", 35 | "apiVersion": "2019-10-01" 36 | }, 37 | { 38 | "type": "Microsoft.Resources/deployments", 39 | "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat('WebAppSqlDbRedisApplicationInsights', subscription().subscriptionId)))]", 40 | "resourceGroup": "[parameters('resourceGroupName')]", 41 | "apiVersion": "2019-10-01", 42 | "dependsOn": [ 43 | "[parameters('resourceGroupName')]" 44 | ], 45 | "properties": { 46 | "mode": "Incremental", 47 | "template": { 48 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 49 | "contentVersion": "1.0.0.0", 50 | "resources": [ 51 | { 52 | "name": "WebAppSqlDbRedisApplicationInsights", 53 | "type": "microsoft.insights/components", 54 | "location": "[parameters('resourceLocation')]", 55 | "kind": "web", 56 | "properties": {}, 57 | "apiVersion": "2015-05-01" 58 | } 59 | ] 60 | } 61 | } 62 | } 63 | ], 64 | "metadata": { 65 | "_dependencyType": "appInsights.azure" 66 | } 67 | } -------------------------------------------------------------------------------- /src/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:39472", 7 | "sslPort": 44359 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development", 16 | "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" 17 | } 18 | }, 19 | "Products": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development", 25 | "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Properties/serviceDependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "secrets1": { 4 | "type": "secrets" 5 | }, 6 | "appInsights1": { 7 | "type": "appInsights", 8 | "connectionId": "APPINSIGHTS_CONNECTIONSTRING" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Properties/serviceDependencies.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "secrets1": { 4 | "type": "secrets.user" 5 | }, 6 | "appInsights1": { 7 | "resourceId": "/subscriptions/[parameters('subscriptionId')]/resourceGroups/[parameters('resourceGroupName')]/providers/microsoft.insights/components/WebAppSqlDbRedisApplicationInsights", 8 | "type": "appInsights.azure", 9 | "connectionId": "APPINSIGHTS_CONNECTIONSTRING", 10 | "secretStore": "LocalSecretsFile" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Startup.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2 | //======================================================================================= 3 | // Author: Paolo Salvatori 4 | // GitHub: https://github.com/paolosalvatori 5 | //======================================================================================= 6 | // Copyright � 2021 Microsoft Corporation. All rights reserved. 7 | // 8 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER 9 | // EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT. 11 | //======================================================================================= 12 | #endregion 13 | 14 | #region Using Directives 15 | using System; 16 | using Microsoft.OpenApi.Models; 17 | using Microsoft.AspNetCore.Builder; 18 | using Microsoft.AspNetCore.Hosting; 19 | using Microsoft.Extensions.Configuration; 20 | using Microsoft.Extensions.DependencyInjection; 21 | using Microsoft.Extensions.Hosting; 22 | using Microsoft.EntityFrameworkCore; 23 | using StackExchange.Redis; 24 | using Products.Properties; 25 | using Products.Models; 26 | using Products.Helpers; 27 | #endregion 28 | 29 | namespace Products 30 | { 31 | public class Startup 32 | { 33 | /// 34 | /// Creates an instance of the Startup class 35 | /// 36 | /// The configuration created by the CreateDefaultBuilder. 37 | public Startup(IConfiguration configuration) 38 | { 39 | Configuration = configuration; 40 | } 41 | 42 | /// 43 | /// Gets or sets the Configuration property. 44 | /// 45 | public IConfiguration Configuration { get; } 46 | 47 | /// 48 | /// This method gets called by the runtime. Use this method to add services to the container. 49 | /// 50 | /// The services collection. 51 | public void ConfigureServices(IServiceCollection services) 52 | { 53 | services.AddControllersWithViews(); 54 | services.AddApplicationInsightsTelemetry(); 55 | services.AddOptions(); 56 | services.AddMvc(); 57 | services.AddSingleton(ConnectionMultiplexer.Connect(Configuration.GetConnectionString(Resources.RedisCacheConnectionString))); 58 | services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString(Resources.SqlServerConnectionString))); 59 | 60 | // Register the Swagger generator, defining one or more Swagger documents 61 | services.AddSwaggerGen(c => 62 | { 63 | c.SwaggerDoc("v1", new OpenApiInfo 64 | { 65 | Version = "v1", 66 | Title = "Products API", 67 | Description = "A simple example ASP.NET Core Web API", 68 | TermsOfService = new Uri("https://www.apache.org/licenses/LICENSE-2.0"), 69 | Contact = new OpenApiContact 70 | { 71 | Name = "Paolo Salvatori", 72 | Email = "paolos@microsoft.com", 73 | Url = new Uri("https://github.com/paolosalvatori") 74 | }, 75 | License = new OpenApiLicense 76 | { 77 | Name = "Use under Apache License 2.0", 78 | Url = new Uri("https://www.apache.org/licenses/LICENSE-2.0") 79 | } 80 | }); 81 | }); 82 | } 83 | 84 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 85 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 86 | { 87 | if (env.IsDevelopment()) 88 | { 89 | app.UseDeveloperExceptionPage(); 90 | } 91 | else 92 | { 93 | app.UseExceptionHandler("/Home/Error"); 94 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 95 | app.UseHsts(); 96 | } 97 | 98 | // Enable middleware to serve generated Swagger as a JSON endpoint. 99 | app.UseSwagger(); 100 | 101 | // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), specifying the Swagger JSON endpoint. 102 | app.UseSwaggerUI(c => 103 | { 104 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "TodoList API V1"); 105 | c.RoutePrefix = "swagger"; 106 | }); 107 | 108 | app.UseHttpsRedirection(); 109 | app.UseStaticFiles(); 110 | 111 | app.UseRouting(); 112 | 113 | app.UseAuthorization(); 114 | 115 | app.UseEndpoints(endpoints => 116 | { 117 | endpoints.MapControllerRoute( 118 | name: "default", 119 | pattern: "{controller=Home}/{action=Index}/{id?}"); 120 | }); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 |  2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Title 11 |

Enter a new product

12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
Name
Category
Price
32 |
33 |

Products

34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
IdNameCategoryPriceCommands
      
57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 |
65 |

Loading...

66 |

Author: Paolo Salvatori (@@babosbird)

67 |
68 |
69 |
70 |
    71 |
    72 |
    73 | 74 | 75 | -------------------------------------------------------------------------------- /src/Views/Home/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Privacy Policy"; 3 | } 4 |

    @ViewData["Title"]

    5 | 6 |

    Use this page to detail your site's privacy policy.

    7 | -------------------------------------------------------------------------------- /src/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = null; 3 | } 4 | 5 | 6 | 7 | 8 | 9 | Error 10 | 11 | 12 |
    13 |

    Error.

    14 |

    An error occurred while processing your request.

    15 |
    16 | 17 | -------------------------------------------------------------------------------- /src/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Products 6 | 7 | 8 | 9 | @Html.Raw(JavaScriptSnippet.FullScript) 10 | 11 | 12 | @RenderBody() 13 | 14 | -------------------------------------------------------------------------------- /src/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /src/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Products 2 | @using Products.Models 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | @inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet 5 | -------------------------------------------------------------------------------- /src/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/_Layout.cshtml"; 3 | } -------------------------------------------------------------------------------- /src/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | }, 8 | "ApplicationInsights": { 9 | "LogLevel": { 10 | "Default": "Information" 11 | } 12 | } 13 | }, 14 | "KeyVaultUri": "https://babokeyvault.vault.azure.net/", 15 | "APPINSIGHTS_INSTRUMENTATIONKEY": "1b4df8d0-4d3f-4abe-9ade-3d5eb4141955", 16 | "APPLICATIONINSIGHTS_CONNECTION_STRING": "InstrumentationKey=1b4df8d0-4d3f-4abe-9ade-3d5eb4141955;IngestionEndpoint=https://westeurope-5.in.applicationinsights.azure.com/" 17 | } 18 | -------------------------------------------------------------------------------- /src/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "ApplicationInsights": { 4 | "LogLevel": { 5 | "Default": "Information" 6 | } 7 | }, 8 | "LogLevel": { 9 | "Default": "Information", 10 | "Microsoft": "Warning", 11 | "Microsoft.Hosting.Lifetime": "Information" 12 | } 13 | }, 14 | "AllowedHosts": "*" 15 | } -------------------------------------------------------------------------------- /src/libman.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "defaultProvider": "cdnjs", 4 | "libraries": [] 5 | } -------------------------------------------------------------------------------- /src/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | /* Reset and define common styles */ 2 | * { box-sizing: border-box; -moz-box-sizing: border-box; margin: 0; padding: 0; } 3 | body { font-family: Arial, Helvetica; background: #ffffff url(../images/sky.png) 0 0 repeat-x;} 4 | button { 5 | border: 1px solid #88a0f4; 6 | color: white; 7 | background-color: #b9c9fe; 8 | float: right; 9 | margin-left: 10px; 10 | padding: 0 10px; 11 | border-radius: 4px; 12 | width: 68px 13 | } 14 | button:hover { background-color: white; color: #88a0f4; } 15 | input[type=text], input:not([type]) { padding: 0 10px; text-overflow: ellipsis; } 16 | 17 | /* Main page structure and masthead style */ 18 | #wrapper { max-width: 800px; margin: auto; padding: 24px; } 19 | article { 20 | background-color: white; 21 | box-shadow: 0 0 12px #000000; 22 | box-shadow: 0 0 12px rgba(0, 0, 0, 0.75); border-radius: 10px; 23 | } 24 | 25 | header { background-color: #FFFFFF; padding: 20px; border-top-left-radius: 10px; border-top-right-radius: 10px; } 26 | header h1 { 27 | text-transform: uppercase; 28 | font-weight: normal; 29 | font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif; 30 | font-size: 14px; 31 | margin: auto; 32 | color: #88a0f4; 33 | margin-bottom: 5px; 34 | } 35 | 36 | h2 { text-transform: uppercase; font-weight: normal; color: white; font-size: 20px; } 37 | #summary { text-align: center; font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif;font-size: 13px; padding: 8px 0; } 38 | #author { text-align: center; font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif;font-size: 13px; padding: 8px 0; } 39 | 40 | /* "Add new" form */ 41 | #add-product table tbody tr td 42 | { 43 | font-weight: normal; 44 | font-size: 13px; 45 | color: #88a0f4; 46 | text-align: left; 47 | height: 30px; 48 | padding: 0; 49 | } 50 | #add-product button { float: right; margin-top: 10px; padding: 0 10px; height: 24px; border-radius: 4px; } 51 | #add-product input { width: 510px; height: 24px; border: 1px solid #DDDDDD; font-size: 1em; border-radius: 4px; margin-left: 10px;} 52 | #add-product-headline { 53 | font-family: Arial, Helvetica; 54 | margin-top: 5px; 55 | margin-bottom: 10px; 56 | font-weight: normal; 57 | font-size: 13px; 58 | } 59 | 60 | #refresh-products {width: 610px;margin: auto} 61 | 62 | #refresh-products button { 63 | float: right; 64 | margin-left: 10px; 65 | padding: 0 10px; 66 | height: 24px; 67 | border-radius: 4px; 68 | } 69 | .product-header { width: 100px;} 70 | 71 | .button-update {margin: 0;height: 24px; } 72 | .button-delete {margin: 0;height: 24px; } 73 | .button-refresh {margin: 0;height: 24px; margin-top: 10px} 74 | 75 | /* Textboxes in list of products */ 76 | .product-text { width: 100%; height: 24px;line-height: 24px; border: 1px solid transparent; background-color: transparent; border-radius: 4px;} 77 | .product-text:focus, .product-text:hover { border-color: #aaa; background-color: #FFC; } 78 | 79 | /* Footer */ 80 | footer { text-align: center; font-size: 0.8em; margin-top: 20px; } 81 | footer a { color: #666; display: block; } 82 | #errorlog { color: red; font-weight: bold; padding: 8px; display: inline-block; text-align: left; } 83 | 84 | #new-product 85 | { 86 | font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif; 87 | font-size: 12px; 88 | margin: auto; 89 | text-align: left; 90 | border-collapse: collapse; 91 | width: 610px; 92 | } 93 | 94 | #products 95 | { 96 | font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif; 97 | font-size: 12px; 98 | margin: auto; 99 | text-align: left; 100 | border-collapse: collapse; 101 | width: 610px; 102 | } 103 | #products thead th.rounded-upper-left 104 | { 105 | background: #b9c9fe url('../images/left.png') left -1px no-repeat; 106 | } 107 | #products thead th.rounded-upper-right 108 | { 109 | background: #b9c9fe url('../images/right.png') right -1px no-repeat; 110 | } 111 | #products th 112 | { 113 | padding: 8px; 114 | font-weight: normal; 115 | font-size: 13px; 116 | color: white; 117 | background: #b9c9fe; 118 | text-align: left; 119 | height: 24px; 120 | } 121 | #products th span 122 | { 123 | margin-left: 10px; 124 | } 125 | #products td 126 | { 127 | padding: 8px; 128 | background: #e8edff; 129 | border-top: 1px solid #fff; 130 | color: #669; 131 | height: 24px; 132 | } 133 | #products td button 134 | { 135 | margin-right: 10px; 136 | } 137 | #products tfoot td.rounded-foot-left 138 | { 139 | background: #e8edff url('../images/botleft.png') left bottom no-repeat; 140 | } 141 | #products tfoot td.rounded-foot-right 142 | { 143 | background: #e8edff url('../images/botright.png') right bottom no-repeat; 144 | } 145 | #products tbody tr:hover td 146 | { 147 | background: #d0dafd; 148 | } 149 | #title-image {margin-bottom: 20px} 150 | .stage { 151 | position: absolute; 152 | top: 0; 153 | left: 0; 154 | width: 100%; 155 | min-width: 900px; 156 | height: 100%; 157 | overflow: hidden; 158 | z-index: 100; 159 | } 160 | .far-clouds { background: transparent url(../images/far-clouds.png) 305px 102px repeat-x; } 161 | .near-clouds { background: transparent url(../images/near-clouds.png) 305px 300px repeat-x; } 162 | .mainContent { 163 | z-index: 101; 164 | width: 650px; 165 | position: relative; 166 | transform: translateX(12%) 167 | } 168 | 169 | .container { 170 | min-width:900px; 171 | padding-top:359px; 172 | position:relative; 173 | width:100%; 174 | } 175 | 176 | #select-repository tbody tr td 177 | { 178 | font-weight: normal; 179 | font-size: 13px; 180 | color: #88a0f4; 181 | text-align: left; 182 | height: 30px; 183 | padding: 0; 184 | } 185 | 186 | #select-repository select { width: 150px; height: 24px; border: 1px solid #DDDDDD; font-size: 1em; border-radius: 4px;} -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/accordion.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Accordion 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/accordion/#theming 10 | */ 11 | .ui-accordion .ui-accordion-header { 12 | display: block; 13 | cursor: pointer; 14 | position: relative; 15 | margin: 2px 0 0 0; 16 | padding: .5em .5em .5em .7em; 17 | min-height: 0; /* support: IE7 */ 18 | font-size: 100%; 19 | } 20 | .ui-accordion .ui-accordion-icons { 21 | padding-left: 2.2em; 22 | } 23 | .ui-accordion .ui-accordion-icons .ui-accordion-icons { 24 | padding-left: 2.2em; 25 | } 26 | .ui-accordion .ui-accordion-header .ui-accordion-header-icon { 27 | position: absolute; 28 | left: .5em; 29 | top: 50%; 30 | margin-top: -8px; 31 | } 32 | .ui-accordion .ui-accordion-content { 33 | padding: 1em 2.2em; 34 | border-top: 0; 35 | overflow: auto; 36 | } 37 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/all.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI CSS Framework 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/category/theming/ 10 | */ 11 | @import "base.css"; 12 | @import "theme.css"; 13 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/autocomplete.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Autocomplete 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/autocomplete/#theming 10 | */ 11 | .ui-autocomplete { 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | cursor: default; 16 | } 17 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/base.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI CSS Framework 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/category/theming/ 10 | */ 11 | @import url("core.css"); 12 | 13 | @import url("accordion.css"); 14 | @import url("autocomplete.css"); 15 | @import url("button.css"); 16 | @import url("datepicker.css"); 17 | @import url("dialog.css"); 18 | @import url("draggable.css"); 19 | @import url("menu.css"); 20 | @import url("progressbar.css"); 21 | @import url("resizable.css"); 22 | @import url("selectable.css"); 23 | @import url("selectmenu.css"); 24 | @import url("sortable.css"); 25 | @import url("slider.css"); 26 | @import url("spinner.css"); 27 | @import url("tabs.css"); 28 | @import url("tooltip.css"); 29 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/button.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Button 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/button/#theming 10 | */ 11 | .ui-button { 12 | display: inline-block; 13 | position: relative; 14 | padding: 0; 15 | line-height: normal; 16 | margin-right: .1em; 17 | cursor: pointer; 18 | vertical-align: middle; 19 | text-align: center; 20 | overflow: visible; /* removes extra width in IE */ 21 | } 22 | .ui-button, 23 | .ui-button:link, 24 | .ui-button:visited, 25 | .ui-button:hover, 26 | .ui-button:active { 27 | text-decoration: none; 28 | } 29 | /* to make room for the icon, a width needs to be set here */ 30 | .ui-button-icon-only { 31 | width: 2.2em; 32 | } 33 | /* button elements seem to need a little more width */ 34 | button.ui-button-icon-only { 35 | width: 2.4em; 36 | } 37 | .ui-button-icons-only { 38 | width: 3.4em; 39 | } 40 | button.ui-button-icons-only { 41 | width: 3.7em; 42 | } 43 | 44 | /* button text element */ 45 | .ui-button .ui-button-text { 46 | display: block; 47 | line-height: normal; 48 | } 49 | .ui-button-text-only .ui-button-text { 50 | padding: .4em 1em; 51 | } 52 | .ui-button-icon-only .ui-button-text, 53 | .ui-button-icons-only .ui-button-text { 54 | padding: .4em; 55 | text-indent: -9999999px; 56 | } 57 | .ui-button-text-icon-primary .ui-button-text, 58 | .ui-button-text-icons .ui-button-text { 59 | padding: .4em 1em .4em 2.1em; 60 | } 61 | .ui-button-text-icon-secondary .ui-button-text, 62 | .ui-button-text-icons .ui-button-text { 63 | padding: .4em 2.1em .4em 1em; 64 | } 65 | .ui-button-text-icons .ui-button-text { 66 | padding-left: 2.1em; 67 | padding-right: 2.1em; 68 | } 69 | /* no icon support for input elements, provide padding by default */ 70 | input.ui-button { 71 | padding: .4em 1em; 72 | } 73 | 74 | /* button icon element(s) */ 75 | .ui-button-icon-only .ui-icon, 76 | .ui-button-text-icon-primary .ui-icon, 77 | .ui-button-text-icon-secondary .ui-icon, 78 | .ui-button-text-icons .ui-icon, 79 | .ui-button-icons-only .ui-icon { 80 | position: absolute; 81 | top: 50%; 82 | margin-top: -8px; 83 | } 84 | .ui-button-icon-only .ui-icon { 85 | left: 50%; 86 | margin-left: -8px; 87 | } 88 | .ui-button-text-icon-primary .ui-button-icon-primary, 89 | .ui-button-text-icons .ui-button-icon-primary, 90 | .ui-button-icons-only .ui-button-icon-primary { 91 | left: .5em; 92 | } 93 | .ui-button-text-icon-secondary .ui-button-icon-secondary, 94 | .ui-button-text-icons .ui-button-icon-secondary, 95 | .ui-button-icons-only .ui-button-icon-secondary { 96 | right: .5em; 97 | } 98 | 99 | /* button sets */ 100 | .ui-buttonset { 101 | margin-right: 7px; 102 | } 103 | .ui-buttonset .ui-button { 104 | margin-left: 0; 105 | margin-right: -.3em; 106 | } 107 | 108 | /* workarounds */ 109 | /* reset extra padding in Firefox, see h5bp.com/l */ 110 | input.ui-button::-moz-focus-inner, 111 | button.ui-button::-moz-focus-inner { 112 | border: 0; 113 | padding: 0; 114 | } 115 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/core.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI CSS Framework 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/category/theming/ 10 | */ 11 | 12 | /* Layout helpers 13 | ----------------------------------*/ 14 | .ui-helper-hidden { 15 | display: none; 16 | } 17 | .ui-helper-hidden-accessible { 18 | border: 0; 19 | clip: rect(0 0 0 0); 20 | height: 1px; 21 | margin: -1px; 22 | overflow: hidden; 23 | padding: 0; 24 | position: absolute; 25 | width: 1px; 26 | } 27 | .ui-helper-reset { 28 | margin: 0; 29 | padding: 0; 30 | border: 0; 31 | outline: 0; 32 | line-height: 1.3; 33 | text-decoration: none; 34 | font-size: 100%; 35 | list-style: none; 36 | } 37 | .ui-helper-clearfix:before, 38 | .ui-helper-clearfix:after { 39 | content: ""; 40 | display: table; 41 | border-collapse: collapse; 42 | } 43 | .ui-helper-clearfix:after { 44 | clear: both; 45 | } 46 | .ui-helper-clearfix { 47 | min-height: 0; /* support: IE7 */ 48 | } 49 | .ui-helper-zfix { 50 | width: 100%; 51 | height: 100%; 52 | top: 0; 53 | left: 0; 54 | position: absolute; 55 | opacity: 0; 56 | filter:Alpha(Opacity=0); /* support: IE8 */ 57 | } 58 | 59 | .ui-front { 60 | z-index: 100; 61 | } 62 | 63 | 64 | /* Interaction Cues 65 | ----------------------------------*/ 66 | .ui-state-disabled { 67 | cursor: default !important; 68 | } 69 | 70 | 71 | /* Icons 72 | ----------------------------------*/ 73 | 74 | /* states and images */ 75 | .ui-icon { 76 | display: block; 77 | text-indent: -99999px; 78 | overflow: hidden; 79 | background-repeat: no-repeat; 80 | } 81 | 82 | 83 | /* Misc visuals 84 | ----------------------------------*/ 85 | 86 | /* Overlays */ 87 | .ui-widget-overlay { 88 | position: fixed; 89 | top: 0; 90 | left: 0; 91 | width: 100%; 92 | height: 100%; 93 | } 94 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/datepicker.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Datepicker 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/datepicker/#theming 10 | */ 11 | .ui-datepicker { 12 | width: 17em; 13 | padding: .2em .2em 0; 14 | display: none; 15 | } 16 | .ui-datepicker .ui-datepicker-header { 17 | position: relative; 18 | padding: .2em 0; 19 | } 20 | .ui-datepicker .ui-datepicker-prev, 21 | .ui-datepicker .ui-datepicker-next { 22 | position: absolute; 23 | top: 2px; 24 | width: 1.8em; 25 | height: 1.8em; 26 | } 27 | .ui-datepicker .ui-datepicker-prev-hover, 28 | .ui-datepicker .ui-datepicker-next-hover { 29 | top: 1px; 30 | } 31 | .ui-datepicker .ui-datepicker-prev { 32 | left: 2px; 33 | } 34 | .ui-datepicker .ui-datepicker-next { 35 | right: 2px; 36 | } 37 | .ui-datepicker .ui-datepicker-prev-hover { 38 | left: 1px; 39 | } 40 | .ui-datepicker .ui-datepicker-next-hover { 41 | right: 1px; 42 | } 43 | .ui-datepicker .ui-datepicker-prev span, 44 | .ui-datepicker .ui-datepicker-next span { 45 | display: block; 46 | position: absolute; 47 | left: 50%; 48 | margin-left: -8px; 49 | top: 50%; 50 | margin-top: -8px; 51 | } 52 | .ui-datepicker .ui-datepicker-title { 53 | margin: 0 2.3em; 54 | line-height: 1.8em; 55 | text-align: center; 56 | } 57 | .ui-datepicker .ui-datepicker-title select { 58 | font-size: 1em; 59 | margin: 1px 0; 60 | } 61 | .ui-datepicker select.ui-datepicker-month, 62 | .ui-datepicker select.ui-datepicker-year { 63 | width: 45%; 64 | } 65 | .ui-datepicker table { 66 | width: 100%; 67 | font-size: .9em; 68 | border-collapse: collapse; 69 | margin: 0 0 .4em; 70 | } 71 | .ui-datepicker th { 72 | padding: .7em .3em; 73 | text-align: center; 74 | font-weight: bold; 75 | border: 0; 76 | } 77 | .ui-datepicker td { 78 | border: 0; 79 | padding: 1px; 80 | } 81 | .ui-datepicker td span, 82 | .ui-datepicker td a { 83 | display: block; 84 | padding: .2em; 85 | text-align: right; 86 | text-decoration: none; 87 | } 88 | .ui-datepicker .ui-datepicker-buttonpane { 89 | background-image: none; 90 | margin: .7em 0 0 0; 91 | padding: 0 .2em; 92 | border-left: 0; 93 | border-right: 0; 94 | border-bottom: 0; 95 | } 96 | .ui-datepicker .ui-datepicker-buttonpane button { 97 | float: right; 98 | margin: .5em .2em .4em; 99 | cursor: pointer; 100 | padding: .2em .6em .3em .6em; 101 | width: auto; 102 | overflow: visible; 103 | } 104 | .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { 105 | float: left; 106 | } 107 | 108 | /* with multiple calendars */ 109 | .ui-datepicker.ui-datepicker-multi { 110 | width: auto; 111 | } 112 | .ui-datepicker-multi .ui-datepicker-group { 113 | float: left; 114 | } 115 | .ui-datepicker-multi .ui-datepicker-group table { 116 | width: 95%; 117 | margin: 0 auto .4em; 118 | } 119 | .ui-datepicker-multi-2 .ui-datepicker-group { 120 | width: 50%; 121 | } 122 | .ui-datepicker-multi-3 .ui-datepicker-group { 123 | width: 33.3%; 124 | } 125 | .ui-datepicker-multi-4 .ui-datepicker-group { 126 | width: 25%; 127 | } 128 | .ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, 129 | .ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { 130 | border-left-width: 0; 131 | } 132 | .ui-datepicker-multi .ui-datepicker-buttonpane { 133 | clear: left; 134 | } 135 | .ui-datepicker-row-break { 136 | clear: both; 137 | width: 100%; 138 | font-size: 0; 139 | } 140 | 141 | /* RTL support */ 142 | .ui-datepicker-rtl { 143 | direction: rtl; 144 | } 145 | .ui-datepicker-rtl .ui-datepicker-prev { 146 | right: 2px; 147 | left: auto; 148 | } 149 | .ui-datepicker-rtl .ui-datepicker-next { 150 | left: 2px; 151 | right: auto; 152 | } 153 | .ui-datepicker-rtl .ui-datepicker-prev:hover { 154 | right: 1px; 155 | left: auto; 156 | } 157 | .ui-datepicker-rtl .ui-datepicker-next:hover { 158 | left: 1px; 159 | right: auto; 160 | } 161 | .ui-datepicker-rtl .ui-datepicker-buttonpane { 162 | clear: right; 163 | } 164 | .ui-datepicker-rtl .ui-datepicker-buttonpane button { 165 | float: left; 166 | } 167 | .ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, 168 | .ui-datepicker-rtl .ui-datepicker-group { 169 | float: right; 170 | } 171 | .ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, 172 | .ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { 173 | border-right-width: 0; 174 | border-left-width: 1px; 175 | } 176 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/dialog.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Dialog 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/dialog/#theming 10 | */ 11 | .ui-dialog { 12 | overflow: hidden; 13 | position: absolute; 14 | top: 0; 15 | left: 0; 16 | padding: .2em; 17 | outline: 0; 18 | } 19 | .ui-dialog .ui-dialog-titlebar { 20 | padding: .4em 1em; 21 | position: relative; 22 | } 23 | .ui-dialog .ui-dialog-title { 24 | float: left; 25 | margin: .1em 0; 26 | white-space: nowrap; 27 | width: 90%; 28 | overflow: hidden; 29 | text-overflow: ellipsis; 30 | } 31 | .ui-dialog .ui-dialog-titlebar-close { 32 | position: absolute; 33 | right: .3em; 34 | top: 50%; 35 | width: 20px; 36 | margin: -10px 0 0 0; 37 | padding: 1px; 38 | height: 20px; 39 | } 40 | .ui-dialog .ui-dialog-content { 41 | position: relative; 42 | border: 0; 43 | padding: .5em 1em; 44 | background: none; 45 | overflow: auto; 46 | } 47 | .ui-dialog .ui-dialog-buttonpane { 48 | text-align: left; 49 | border-width: 1px 0 0 0; 50 | background-image: none; 51 | margin-top: .5em; 52 | padding: .3em 1em .5em .4em; 53 | } 54 | .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { 55 | float: right; 56 | } 57 | .ui-dialog .ui-dialog-buttonpane button { 58 | margin: .5em .4em .5em 0; 59 | cursor: pointer; 60 | } 61 | .ui-dialog .ui-resizable-se { 62 | width: 12px; 63 | height: 12px; 64 | right: -5px; 65 | bottom: -5px; 66 | background-position: 16px 16px; 67 | } 68 | .ui-draggable .ui-dialog-titlebar { 69 | cursor: move; 70 | } 71 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/draggable.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Draggable 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | */ 9 | .ui-draggable-handle { 10 | -ms-touch-action: none; 11 | touch-action: none; 12 | } 13 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-icons_444444_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-icons_444444_256x240.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-icons_555555_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-icons_555555_256x240.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-icons_777620_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-icons_777620_256x240.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-icons_777777_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-icons_777777_256x240.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-icons_cc0000_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-icons_cc0000_256x240.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/images/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/css/themes/base/images/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/menu.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Menu 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/menu/#theming 10 | */ 11 | .ui-menu { 12 | list-style: none; 13 | padding: 0; 14 | margin: 0; 15 | display: block; 16 | outline: none; 17 | } 18 | .ui-menu .ui-menu { 19 | position: absolute; 20 | } 21 | .ui-menu .ui-menu-item { 22 | position: relative; 23 | margin: 0; 24 | padding: 3px 1em 3px .4em; 25 | cursor: pointer; 26 | min-height: 0; /* support: IE7 */ 27 | /* support: IE10, see #8844 */ 28 | list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"); 29 | } 30 | .ui-menu .ui-menu-divider { 31 | margin: 5px 0; 32 | height: 0; 33 | font-size: 0; 34 | line-height: 0; 35 | border-width: 1px 0 0 0; 36 | } 37 | .ui-menu .ui-state-focus, 38 | .ui-menu .ui-state-active { 39 | margin: -1px; 40 | } 41 | 42 | /* icon support */ 43 | .ui-menu-icons { 44 | position: relative; 45 | } 46 | .ui-menu-icons .ui-menu-item { 47 | padding-left: 2em; 48 | } 49 | 50 | /* left-aligned */ 51 | .ui-menu .ui-icon { 52 | position: absolute; 53 | top: 0; 54 | bottom: 0; 55 | left: .2em; 56 | margin: auto 0; 57 | } 58 | 59 | /* right-aligned */ 60 | .ui-menu .ui-menu-icon { 61 | left: auto; 62 | right: 0; 63 | } 64 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/progressbar.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Progressbar 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/progressbar/#theming 10 | */ 11 | .ui-progressbar { 12 | height: 2em; 13 | text-align: left; 14 | overflow: hidden; 15 | } 16 | .ui-progressbar .ui-progressbar-value { 17 | margin: -1px; 18 | height: 100%; 19 | } 20 | .ui-progressbar .ui-progressbar-overlay { 21 | background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw=="); 22 | height: 100%; 23 | filter: alpha(opacity=25); /* support: IE8 */ 24 | opacity: 0.25; 25 | } 26 | .ui-progressbar-indeterminate .ui-progressbar-value { 27 | background-image: none; 28 | } 29 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/resizable.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Resizable 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | */ 9 | .ui-resizable { 10 | position: relative; 11 | } 12 | .ui-resizable-handle { 13 | position: absolute; 14 | font-size: 0.1px; 15 | display: block; 16 | -ms-touch-action: none; 17 | touch-action: none; 18 | } 19 | .ui-resizable-disabled .ui-resizable-handle, 20 | .ui-resizable-autohide .ui-resizable-handle { 21 | display: none; 22 | } 23 | .ui-resizable-n { 24 | cursor: n-resize; 25 | height: 7px; 26 | width: 100%; 27 | top: -5px; 28 | left: 0; 29 | } 30 | .ui-resizable-s { 31 | cursor: s-resize; 32 | height: 7px; 33 | width: 100%; 34 | bottom: -5px; 35 | left: 0; 36 | } 37 | .ui-resizable-e { 38 | cursor: e-resize; 39 | width: 7px; 40 | right: -5px; 41 | top: 0; 42 | height: 100%; 43 | } 44 | .ui-resizable-w { 45 | cursor: w-resize; 46 | width: 7px; 47 | left: -5px; 48 | top: 0; 49 | height: 100%; 50 | } 51 | .ui-resizable-se { 52 | cursor: se-resize; 53 | width: 12px; 54 | height: 12px; 55 | right: 1px; 56 | bottom: 1px; 57 | } 58 | .ui-resizable-sw { 59 | cursor: sw-resize; 60 | width: 9px; 61 | height: 9px; 62 | left: -5px; 63 | bottom: -5px; 64 | } 65 | .ui-resizable-nw { 66 | cursor: nw-resize; 67 | width: 9px; 68 | height: 9px; 69 | left: -5px; 70 | top: -5px; 71 | } 72 | .ui-resizable-ne { 73 | cursor: ne-resize; 74 | width: 9px; 75 | height: 9px; 76 | right: -5px; 77 | top: -5px; 78 | } 79 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/selectable.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Selectable 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | */ 9 | .ui-selectable { 10 | -ms-touch-action: none; 11 | touch-action: none; 12 | } 13 | .ui-selectable-helper { 14 | position: absolute; 15 | z-index: 100; 16 | border: 1px dotted black; 17 | } 18 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/selectmenu.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Selectmenu 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/selectmenu/#theming 10 | */ 11 | .ui-selectmenu-menu { 12 | padding: 0; 13 | margin: 0; 14 | position: absolute; 15 | top: 0; 16 | left: 0; 17 | display: none; 18 | } 19 | .ui-selectmenu-menu .ui-menu { 20 | overflow: auto; 21 | /* Support: IE7 */ 22 | overflow-x: hidden; 23 | padding-bottom: 1px; 24 | } 25 | .ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup { 26 | font-size: 1em; 27 | font-weight: bold; 28 | line-height: 1.5; 29 | padding: 2px 0.4em; 30 | margin: 0.5em 0 0 0; 31 | height: auto; 32 | border: 0; 33 | } 34 | .ui-selectmenu-open { 35 | display: block; 36 | } 37 | .ui-selectmenu-button { 38 | display: inline-block; 39 | overflow: hidden; 40 | position: relative; 41 | text-decoration: none; 42 | cursor: pointer; 43 | } 44 | .ui-selectmenu-button span.ui-icon { 45 | right: 0.5em; 46 | left: auto; 47 | margin-top: -8px; 48 | position: absolute; 49 | top: 50%; 50 | } 51 | .ui-selectmenu-button span.ui-selectmenu-text { 52 | text-align: left; 53 | padding: 0.4em 2.1em 0.4em 1em; 54 | display: block; 55 | line-height: 1.4; 56 | overflow: hidden; 57 | text-overflow: ellipsis; 58 | white-space: nowrap; 59 | } 60 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/slider.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Slider 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/slider/#theming 10 | */ 11 | .ui-slider { 12 | position: relative; 13 | text-align: left; 14 | } 15 | .ui-slider .ui-slider-handle { 16 | position: absolute; 17 | z-index: 2; 18 | width: 1.2em; 19 | height: 1.2em; 20 | cursor: default; 21 | -ms-touch-action: none; 22 | touch-action: none; 23 | } 24 | .ui-slider .ui-slider-range { 25 | position: absolute; 26 | z-index: 1; 27 | font-size: .7em; 28 | display: block; 29 | border: 0; 30 | background-position: 0 0; 31 | } 32 | 33 | /* support: IE8 - See #6727 */ 34 | .ui-slider.ui-state-disabled .ui-slider-handle, 35 | .ui-slider.ui-state-disabled .ui-slider-range { 36 | filter: inherit; 37 | } 38 | 39 | .ui-slider-horizontal { 40 | height: .8em; 41 | } 42 | .ui-slider-horizontal .ui-slider-handle { 43 | top: -.3em; 44 | margin-left: -.6em; 45 | } 46 | .ui-slider-horizontal .ui-slider-range { 47 | top: 0; 48 | height: 100%; 49 | } 50 | .ui-slider-horizontal .ui-slider-range-min { 51 | left: 0; 52 | } 53 | .ui-slider-horizontal .ui-slider-range-max { 54 | right: 0; 55 | } 56 | 57 | .ui-slider-vertical { 58 | width: .8em; 59 | height: 100px; 60 | } 61 | .ui-slider-vertical .ui-slider-handle { 62 | left: -.3em; 63 | margin-left: 0; 64 | margin-bottom: -.6em; 65 | } 66 | .ui-slider-vertical .ui-slider-range { 67 | left: 0; 68 | width: 100%; 69 | } 70 | .ui-slider-vertical .ui-slider-range-min { 71 | bottom: 0; 72 | } 73 | .ui-slider-vertical .ui-slider-range-max { 74 | top: 0; 75 | } 76 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/sortable.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Sortable 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | */ 9 | .ui-sortable-handle { 10 | -ms-touch-action: none; 11 | touch-action: none; 12 | } 13 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/spinner.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Spinner 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/spinner/#theming 10 | */ 11 | .ui-spinner { 12 | position: relative; 13 | display: inline-block; 14 | overflow: hidden; 15 | padding: 0; 16 | vertical-align: middle; 17 | } 18 | .ui-spinner-input { 19 | border: none; 20 | background: none; 21 | color: inherit; 22 | padding: 0; 23 | margin: .2em 0; 24 | vertical-align: middle; 25 | margin-left: .4em; 26 | margin-right: 22px; 27 | } 28 | .ui-spinner-button { 29 | width: 16px; 30 | height: 50%; 31 | font-size: .5em; 32 | padding: 0; 33 | margin: 0; 34 | text-align: center; 35 | position: absolute; 36 | cursor: default; 37 | display: block; 38 | overflow: hidden; 39 | right: 0; 40 | } 41 | /* more specificity required here to override default borders */ 42 | .ui-spinner a.ui-spinner-button { 43 | border-top: none; 44 | border-bottom: none; 45 | border-right: none; 46 | } 47 | /* vertically center icon */ 48 | .ui-spinner .ui-icon { 49 | position: absolute; 50 | margin-top: -8px; 51 | top: 50%; 52 | left: 0; 53 | } 54 | .ui-spinner-up { 55 | top: 0; 56 | } 57 | .ui-spinner-down { 58 | bottom: 0; 59 | } 60 | 61 | /* TR overrides */ 62 | .ui-spinner .ui-icon-triangle-1-s { 63 | /* need to fix icons sprite */ 64 | background-position: -65px -16px; 65 | } 66 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/tabs.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Tabs 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/tabs/#theming 10 | */ 11 | .ui-tabs { 12 | position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ 13 | padding: .2em; 14 | } 15 | .ui-tabs .ui-tabs-nav { 16 | margin: 0; 17 | padding: .2em .2em 0; 18 | } 19 | .ui-tabs .ui-tabs-nav li { 20 | list-style: none; 21 | float: left; 22 | position: relative; 23 | top: 0; 24 | margin: 1px .2em 0 0; 25 | border-bottom-width: 0; 26 | padding: 0; 27 | white-space: nowrap; 28 | } 29 | .ui-tabs .ui-tabs-nav .ui-tabs-anchor { 30 | float: left; 31 | padding: .5em 1em; 32 | text-decoration: none; 33 | } 34 | .ui-tabs .ui-tabs-nav li.ui-tabs-active { 35 | margin-bottom: -1px; 36 | padding-bottom: 1px; 37 | } 38 | .ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor, 39 | .ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor, 40 | .ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor { 41 | cursor: text; 42 | } 43 | .ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor { 44 | cursor: pointer; 45 | } 46 | .ui-tabs .ui-tabs-panel { 47 | display: block; 48 | border-width: 0; 49 | padding: 1em 1.4em; 50 | background: none; 51 | } 52 | -------------------------------------------------------------------------------- /src/wwwroot/css/themes/base/tooltip.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Tooltip 1.11.4 3 | * http://jqueryui.com 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/tooltip/#theming 10 | */ 11 | .ui-tooltip { 12 | padding: 8px; 13 | position: absolute; 14 | z-index: 9999; 15 | max-width: 300px; 16 | -webkit-box-shadow: 0 0 5px #aaa; 17 | box-shadow: 0 0 5px #aaa; 18 | } 19 | body .ui-tooltip { 20 | border-width: 2px; 21 | } 22 | -------------------------------------------------------------------------------- /src/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/wwwroot/images/botleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/images/botleft.png -------------------------------------------------------------------------------- /src/wwwroot/images/botright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/images/botright.png -------------------------------------------------------------------------------- /src/wwwroot/images/far-clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/images/far-clouds.png -------------------------------------------------------------------------------- /src/wwwroot/images/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/images/left.png -------------------------------------------------------------------------------- /src/wwwroot/images/near-clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/images/near-clouds.png -------------------------------------------------------------------------------- /src/wwwroot/images/pattern.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/images/pattern.gif -------------------------------------------------------------------------------- /src/wwwroot/images/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/images/right.png -------------------------------------------------------------------------------- /src/wwwroot/images/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/images/sky.png -------------------------------------------------------------------------------- /src/wwwroot/images/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paolosalvatori/web-app-redis-sql-db/d73029cc711df8f581d0cfe042ae87e85b979052/src/wwwroot/images/title.png -------------------------------------------------------------------------------- /src/wwwroot/js/_references.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | /// 7 | -------------------------------------------------------------------------------- /src/wwwroot/js/jquery.unobtrusive-ajax.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /*! 16 | ** Unobtrusive Ajax support library for jQuery 17 | ** Copyright (C) Microsoft Corporation. All rights reserved. 18 | */ 19 | 20 | /*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */ 21 | /*global window: false, jQuery: false */ 22 | 23 | (function ($) { 24 | var data_click = "unobtrusiveAjaxClick", 25 | data_target = "unobtrusiveAjaxClickTarget", 26 | data_validation = "unobtrusiveValidation"; 27 | 28 | function getFunction(code, argNames) { 29 | var fn = window, parts = (code || "").split("."); 30 | while (fn && parts.length) { 31 | fn = fn[parts.shift()]; 32 | } 33 | if (typeof (fn) === "function") { 34 | return fn; 35 | } 36 | argNames.push(code); 37 | return Function.constructor.apply(null, argNames); 38 | } 39 | 40 | function isMethodProxySafe(method) { 41 | return method === "GET" || method === "POST"; 42 | } 43 | 44 | function asyncOnBeforeSend(xhr, method) { 45 | if (!isMethodProxySafe(method)) { 46 | xhr.setRequestHeader("X-HTTP-Method-Override", method); 47 | } 48 | } 49 | 50 | function asyncOnSuccess(element, data, contentType) { 51 | var mode; 52 | 53 | if (contentType.indexOf("application/x-javascript") !== -1) { // jQuery already executes JavaScript for us 54 | return; 55 | } 56 | 57 | mode = (element.getAttribute("data-ajax-mode") || "").toUpperCase(); 58 | $(element.getAttribute("data-ajax-update")).each(function (i, update) { 59 | var top; 60 | 61 | switch (mode) { 62 | case "BEFORE": 63 | top = update.firstChild; 64 | $("
    ").html(data).contents().each(function () { 65 | update.insertBefore(this, top); 66 | }); 67 | break; 68 | case "AFTER": 69 | $("
    ").html(data).contents().each(function () { 70 | update.appendChild(this); 71 | }); 72 | break; 73 | case "REPLACE-WITH": 74 | $(update).replaceWith(data); 75 | break; 76 | default: 77 | $(update).html(data); 78 | break; 79 | } 80 | }); 81 | } 82 | 83 | function asyncRequest(element, options) { 84 | var confirm, loading, method, duration; 85 | 86 | confirm = element.getAttribute("data-ajax-confirm"); 87 | if (confirm && !window.confirm(confirm)) { 88 | return; 89 | } 90 | 91 | loading = $(element.getAttribute("data-ajax-loading")); 92 | duration = parseInt(element.getAttribute("data-ajax-loading-duration"), 10) || 0; 93 | 94 | $.extend(options, { 95 | type: element.getAttribute("data-ajax-method") || undefined, 96 | url: element.getAttribute("data-ajax-url") || undefined, 97 | cache: !!element.getAttribute("data-ajax-cache"), 98 | beforeSend: function (xhr) { 99 | var result; 100 | asyncOnBeforeSend(xhr, method); 101 | result = getFunction(element.getAttribute("data-ajax-begin"), ["xhr"]).apply(element, arguments); 102 | if (result !== false) { 103 | loading.show(duration); 104 | } 105 | return result; 106 | }, 107 | complete: function () { 108 | loading.hide(duration); 109 | getFunction(element.getAttribute("data-ajax-complete"), ["xhr", "status"]).apply(element, arguments); 110 | }, 111 | success: function (data, status, xhr) { 112 | asyncOnSuccess(element, data, xhr.getResponseHeader("Content-Type") || "text/html"); 113 | getFunction(element.getAttribute("data-ajax-success"), ["data", "status", "xhr"]).apply(element, arguments); 114 | }, 115 | error: function () { 116 | getFunction(element.getAttribute("data-ajax-failure"), ["xhr", "status", "error"]).apply(element, arguments); 117 | } 118 | }); 119 | 120 | options.data.push({ name: "X-Requested-With", value: "XMLHttpRequest" }); 121 | 122 | method = options.type.toUpperCase(); 123 | if (!isMethodProxySafe(method)) { 124 | options.type = "POST"; 125 | options.data.push({ name: "X-HTTP-Method-Override", value: method }); 126 | } 127 | 128 | $.ajax(options); 129 | } 130 | 131 | function validate(form) { 132 | var validationInfo = $(form).data(data_validation); 133 | return !validationInfo || !validationInfo.validate || validationInfo.validate(); 134 | } 135 | 136 | $(document).on("click", "a[data-ajax=true]", function (evt) { 137 | evt.preventDefault(); 138 | asyncRequest(this, { 139 | url: this.href, 140 | type: "GET", 141 | data: [] 142 | }); 143 | }); 144 | 145 | $(document).on("click", "form[data-ajax=true] input[type=image]", function (evt) { 146 | var name = evt.target.name, 147 | target = $(evt.target), 148 | form = $(target.parents("form")[0]), 149 | offset = target.offset(); 150 | 151 | form.data(data_click, [ 152 | { name: name + ".x", value: Math.round(evt.pageX - offset.left) }, 153 | { name: name + ".y", value: Math.round(evt.pageY - offset.top) } 154 | ]); 155 | 156 | setTimeout(function () { 157 | form.removeData(data_click); 158 | }, 0); 159 | }); 160 | 161 | $(document).on("click", "form[data-ajax=true] :submit", function (evt) { 162 | var name = evt.currentTarget.name, 163 | target = $(evt.target), 164 | form = $(target.parents("form")[0]); 165 | 166 | form.data(data_click, name ? [{ name: name, value: evt.currentTarget.value }] : []); 167 | form.data(data_target, target); 168 | 169 | setTimeout(function () { 170 | form.removeData(data_click); 171 | form.removeData(data_target); 172 | }, 0); 173 | }); 174 | 175 | $(document).on("submit", "form[data-ajax=true]", function (evt) { 176 | var clickInfo = $(this).data(data_click) || [], 177 | clickTarget = $(this).data(data_target), 178 | isCancel = clickTarget && clickTarget.hasClass("cancel"); 179 | evt.preventDefault(); 180 | if (!isCancel && !validate(this)) { 181 | return; 182 | } 183 | asyncRequest(this, { 184 | url: this.action, 185 | type: this.method || "GET", 186 | data: clickInfo.concat($(this).serializeArray()) 187 | }); 188 | }); 189 | }(jQuery)); -------------------------------------------------------------------------------- /src/wwwroot/js/jquery.unobtrusive-ajax.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /* 16 | ** Unobtrusive Ajax support library for jQuery 17 | ** Copyright (C) Microsoft Corporation. All rights reserved. 18 | */ 19 | (function(a){var b="unobtrusiveAjaxClick",d="unobtrusiveAjaxClickTarget",h="unobtrusiveValidation";function c(d,b){var a=window,c=(d||"").split(".");while(a&&c.length)a=a[c.shift()];if(typeof a==="function")return a;b.push(d);return Function.constructor.apply(null,b)}function e(a){return a==="GET"||a==="POST"}function g(b,a){!e(a)&&b.setRequestHeader("X-HTTP-Method-Override",a)}function i(c,b,e){var d;if(e.indexOf("application/x-javascript")!==-1)return;d=(c.getAttribute("data-ajax-mode")||"").toUpperCase();a(c.getAttribute("data-ajax-update")).each(function(f,c){var e;switch(d){case"BEFORE":e=c.firstChild;a("
    ").html(b).contents().each(function(){c.insertBefore(this,e)});break;case"AFTER":a("
    ").html(b).contents().each(function(){c.appendChild(this)});break;case"REPLACE-WITH":a(c).replaceWith(b);break;default:a(c).html(b)}})}function f(b,d){var j,k,f,h;j=b.getAttribute("data-ajax-confirm");if(j&&!window.confirm(j))return;k=a(b.getAttribute("data-ajax-loading"));h=parseInt(b.getAttribute("data-ajax-loading-duration"),10)||0;a.extend(d,{type:b.getAttribute("data-ajax-method")||undefined,url:b.getAttribute("data-ajax-url")||undefined,cache:!!b.getAttribute("data-ajax-cache"),beforeSend:function(d){var a;g(d,f);a=c(b.getAttribute("data-ajax-begin"),["xhr"]).apply(b,arguments);a!==false&&k.show(h);return a},complete:function(){k.hide(h);c(b.getAttribute("data-ajax-complete"),["xhr","status"]).apply(b,arguments)},success:function(a,e,d){i(b,a,d.getResponseHeader("Content-Type")||"text/html");c(b.getAttribute("data-ajax-success"),["data","status","xhr"]).apply(b,arguments)},error:function(){c(b.getAttribute("data-ajax-failure"),["xhr","status","error"]).apply(b,arguments)}});d.data.push({name:"X-Requested-With",value:"XMLHttpRequest"});f=d.type.toUpperCase();if(!e(f)){d.type="POST";d.data.push({name:"X-HTTP-Method-Override",value:f})}a.ajax(d)}function j(c){var b=a(c).data(h);return!b||!b.validate||b.validate()}a(document).on("click","a[data-ajax=true]",function(a){a.preventDefault();f(this,{url:this.href,type:"GET",data:[]})});a(document).on("click","form[data-ajax=true] input[type=image]",function(c){var g=c.target.name,e=a(c.target),f=a(e.parents("form")[0]),d=e.offset();f.data(b,[{name:g+".x",value:Math.round(c.pageX-d.left)},{name:g+".y",value:Math.round(c.pageY-d.top)}]);setTimeout(function(){f.removeData(b)},0)});a(document).on("click","form[data-ajax=true] :submit",function(e){var g=e.currentTarget.name,f=a(e.target),c=a(f.parents("form")[0]);c.data(b,g?[{name:g,value:e.currentTarget.value}]:[]);c.data(d,f);setTimeout(function(){c.removeData(b);c.removeData(d)},0)});a(document).on("submit","form[data-ajax=true]",function(h){var e=a(this).data(b)||[],c=a(this).data(d),g=c&&c.hasClass("cancel");h.preventDefault();if(!g&&!j(this))return;f(this,{url:this.action,type:this.method||"GET",data:e.concat(a(this).serializeArray())})})})(jQuery); -------------------------------------------------------------------------------- /src/wwwroot/js/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /* 16 | ** Unobtrusive validation support library for jQuery and jQuery Validate 17 | ** Copyright (C) Microsoft Corporation. All rights reserved. 18 | */ 19 | (function(a){var d=a.validator,b,e="unobtrusiveValidation";function c(a,b,c){a.rules[b]=c;if(a.message)a.messages[b]=a.message}function j(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function f(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function h(a){return a.substr(0,a.lastIndexOf(".")+1)}function g(a,b){if(a.indexOf("*.")===0)a=a.replace("*.",b);return a}function m(c,e){var b=a(this).find("[data-valmsg-for='"+f(e[0].name)+"']"),d=b.attr("data-valmsg-replace"),g=d?a.parseJSON(d)!==false:null;b.removeClass("field-validation-valid").addClass("field-validation-error");c.data("unobtrusiveContainer",b);if(g){b.empty();c.removeClass("input-validation-error").appendTo(b)}else c.hide()}function l(e,d){var c=a(this).find("[data-valmsg-summary=true]"),b=c.find("ul");if(b&&b.length&&d.errorList.length){b.empty();c.addClass("validation-summary-errors").removeClass("validation-summary-valid");a.each(d.errorList,function(){a("
  • ").html(this.message).appendTo(b)})}}function k(d){var b=d.data("unobtrusiveContainer"),c=b.attr("data-valmsg-replace"),e=c?a.parseJSON(c):null;if(b){b.addClass("field-validation-valid").removeClass("field-validation-error");d.removeData("unobtrusiveContainer");e&&b.empty()}}function n(){var b=a(this),c="__jquery_unobtrusive_validation_form_reset";if(b.data(c))return;b.data(c,true);try{b.data("validator").resetForm()}finally{b.removeData(c)}b.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors");b.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}function i(b){var c=a(b),f=c.data(e),i=a.proxy(n,b),g=d.unobtrusive.options||{},h=function(e,d){var c=g[e];c&&a.isFunction(c)&&c.apply(b,d)};if(!f){f={options:{errorClass:g.errorClass||"input-validation-error",errorElement:g.errorElement||"span",errorPlacement:function(){m.apply(b,arguments);h("errorPlacement",arguments)},invalidHandler:function(){l.apply(b,arguments);h("invalidHandler",arguments)},messages:{},rules:{},success:function(){k.apply(b,arguments);h("success",arguments)}},attachValidation:function(){c.off("reset."+e,i).on("reset."+e,i).validate(this.options)},validate:function(){c.validate();return c.valid()}};c.data(e,f)}return f}d.unobtrusive={adapters:[],parseElement:function(b,h){var d=a(b),f=d.parents("form")[0],c,e,g;if(!f)return;c=i(f);c.options.rules[b.name]=e={};c.options.messages[b.name]=g={};a.each(this.adapters,function(){var c="data-val-"+this.name,i=d.attr(c),h={};if(i!==undefined){c+="-";a.each(this.params,function(){h[this]=d.attr(c+this)});this.adapt({element:b,form:f,message:i,params:h,rules:e,messages:g})}});a.extend(e,{__dummy__:true});!h&&c.attachValidation()},parse:function(c){var b=a(c),e=b.parents().addBack().filter("form").add(b.find("form")).has("[data-val=true]");b.find("[data-val=true]").each(function(){d.unobtrusive.parseElement(this,true)});e.each(function(){var a=i(this);a&&a.attachValidation()})}};b=d.unobtrusive.adapters;b.add=function(c,a,b){if(!b){b=a;a=[]}this.push({name:c,params:a,adapt:b});return this};b.addBool=function(a,b){return this.add(a,function(d){c(d,b||a,true)})};b.addMinMax=function(e,g,f,a,d,b){return this.add(e,[d||"min",b||"max"],function(b){var e=b.params.min,d=b.params.max;if(e&&d)c(b,a,[e,d]);else if(e)c(b,g,e);else d&&c(b,f,d)})};b.addSingleVal=function(a,b,d){return this.add(a,[b||"val"],function(e){c(e,d||a,e.params[b])})};d.addMethod("__dummy__",function(){return true});d.addMethod("regex",function(b,c,d){var a;if(this.optional(c))return true;a=(new RegExp(d)).exec(b);return a&&a.index===0&&a[0].length===b.length});d.addMethod("nonalphamin",function(c,d,b){var a;if(b){a=c.match(/\W/g);a=a&&a.length>=b}return a});if(d.methods.extension){b.addSingleVal("accept","mimtype");b.addSingleVal("extension","extension")}else b.addSingleVal("extension","extension","accept");b.addSingleVal("regex","pattern");b.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");b.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range");b.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength");b.add("equalto",["other"],function(b){var i=h(b.element.name),j=b.params.other,d=g(j,i),e=a(b.form).find(":input").filter("[name='"+f(d)+"']")[0];c(b,"equalTo",e)});b.add("required",function(a){(a.element.tagName.toUpperCase()!=="INPUT"||a.element.type.toUpperCase()!=="CHECKBOX")&&c(a,"required",true)});b.add("remote",["url","type","additionalfields"],function(b){var d={url:b.params.url,type:b.params.type||"GET",data:{}},e=h(b.element.name);a.each(j(b.params.additionalfields||b.element.name),function(i,h){var c=g(h,e);d.data[c]=function(){var d=a(b.form).find(":input").filter("[name='"+f(c)+"']");return d.is(":checkbox")?d.filter(":checked").val()||d.filter(":hidden").val()||"":d.is(":radio")?d.filter(":checked").val()||"":d.val()}});c(b,"remote",d)});b.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&c(a,"minlength",a.params.min);a.params.nonalphamin&&c(a,"nonalphamin",a.params.nonalphamin);a.params.regex&&c(a,"regex",a.params.regex)});a(function(){d.unobtrusive.parse(document)})})(jQuery); -------------------------------------------------------------------------------- /src/wwwroot/js/page.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var productsServiceUrl = 'api/products'; 3 | 4 | // Cloud Animation 5 | $('#far-clouds1').pan({ fps: 30, speed: 0.7, dir: 'left', depth: 30 }); 6 | $('#near-clouds1').pan({ fps: 30, speed: 1, dir: 'left', depth: 70 }); 7 | $('#far-clouds2').pan({ fps: 30, speed: 0.7, dir: 'left', depth: 100 }); 8 | $('#near-clouds2').pan({ fps: 30, speed: 1, dir: 'left', depth: 130 }); 9 | 10 | // Read current data and rebuild UI. 11 | // If you plan to generate complex UIs like this, consider using a JavaScript templating library. 12 | function refreshProducts() { 13 | $('#summary').html('Retrieving products...'); 14 | $.ajax({ 15 | url: productsServiceUrl, 16 | type: 'GET', 17 | dataType: 'json', 18 | tryCount: 0, 19 | retryLimit: 3, 20 | success: function (data) { 21 | if (Array.isArray(data) && data.length > 0) { 22 | var products = $.map(data, function (product) { 23 | return $('').append($('')) 24 | .append($('')) 25 | .append($('')) 26 | .append($('')) 27 | .append($('')) 28 | .append($('')).append(''); 29 | }); 30 | $('#products > tbody').empty().append(products).toggle(products.length > 0); 31 | $('#summary').html('' + products.length + ' product(s) successfully retrieved.'); 32 | } else { 33 | $('#summary').html('No product retrieved.'); 34 | } 35 | }, 36 | error: function (xhr, textStatus, error) { 37 | if (textStatus == 'timeout') { 38 | this.tryCount++; 39 | if (this.tryCount <= this.retryLimit) { 40 | //try again 41 | $.ajax(this); 42 | return; 43 | } 44 | return; 45 | } 46 | var errorMessage = JSON.parse(xhr.responseText); 47 | var text = errorMessage.error; 48 | $('#errorlog').append($('
  • ').text(text)); 49 | } 50 | }); 51 | } 52 | 53 | // Handle insert 54 | $('#add-product').submit(function(event) { 55 | if ($('#product-name').val() === '') { 56 | $('#summary').html('The Name field cannot be null.'); 57 | event.preventDefault(); 58 | return; 59 | } 60 | if ($('#product-category').val() === '') { 61 | $('#summary').html('The Category field cannot be null.'); 62 | event.preventDefault(); 63 | return; 64 | } 65 | if ($('#product-price').val() === '') { 66 | $('#summary').html('The Price field cannot be null.'); 67 | event.preventDefault(); 68 | return; 69 | } 70 | var product = { 71 | 'productId': 0, 72 | 'name': $('#product-name').val(), 73 | 'category': $('#product-category').val(), 74 | 'price': parseFloat($('#product-price').val()) 75 | }; 76 | var data = JSON.stringify(product); 77 | $('#summary').html('Adding ' + product.name + '...'); 78 | $.ajax({ 79 | url: productsServiceUrl, 80 | type: 'POST', 81 | cache: false, 82 | data: data, 83 | dataType: 'json', 84 | tryCount: 0, 85 | retryLimit: 3, 86 | contentType: 'application/json; charset=utf-8', 87 | success: function (data) { 88 | if (data) { 89 | var row = $('').append($('')) 90 | .append($('')) 91 | .append($('')) 92 | .append($('')) 93 | .append($('')) 94 | .append($('')).append(''); 95 | $('#products > tbody').append(row).toggle(true); 96 | $('#summary').html('' + data.name + ' successfully added.'); 97 | } 98 | }, 99 | error: function (xhr, textStatus, error) { 100 | if (textStatus == 'timeout') { 101 | this.tryCount++; 102 | if (this.tryCount <= this.retryLimit) { 103 | //try again 104 | $.ajax(this); 105 | return; 106 | } 107 | return; 108 | } 109 | var errorMessage = JSON.parse(xhr.responseText); 110 | var text = errorMessage.error; 111 | $('#errorlog').append($('
  • ').text(text)); 112 | } 113 | }); 114 | $('#product-name').val(''); 115 | $('#product-category').val(''); 116 | $('#product-price').val(''); 117 | event.preventDefault(); 118 | }); 119 | 120 | // Handle update 121 | $(document.body).on('click', '.button-update', function (event) { 122 | var row = $(this).closest('tr'); 123 | var productId = row.find('td:nth-child(1) input').val(); 124 | if (!productId) { 125 | $('#summary').html('The product id field cannot be null.'); 126 | event.preventDefault(); 127 | return; 128 | } 129 | var name = row.find('td:nth-child(2) input').val(); 130 | if (!name) { 131 | $('#summary').html('The name field cannot be null.'); 132 | event.preventDefault(); 133 | return; 134 | } 135 | var category = row.find('td:nth-child(3) input').val(); 136 | if (!category) { 137 | $('#summary').html('The category field cannot be null.'); 138 | event.preventDefault(); 139 | return; 140 | } 141 | var price = row.find('td:nth-child(4) input').val(); 142 | if (!price) { 143 | $('#summary').html('The price field cannot be null.'); 144 | event.preventDefault(); 145 | return; 146 | } 147 | var product = { 148 | 'productId': parseInt(productId), 149 | 'name': name, 150 | 'category': category, 151 | 'price': parseFloat(price) 152 | }; 153 | var data = JSON.stringify(product); 154 | $('#summary').html('Updating ' + product.name + '...'); 155 | $.ajax({ 156 | url: productsServiceUrl + '/' + productId, 157 | type: 'PUT', 158 | cache: false, 159 | data: data, 160 | dataType: 'json', 161 | tryCount: 0, 162 | retryLimit: 3, 163 | contentType: 'application/json; charset=utf-8', 164 | success: function () { 165 | $('#summary').html('' + product.name + ' successfully updated.'); 166 | }, 167 | error: function (xhr, textStatus, error) { 168 | if (textStatus == 'timeout') { 169 | this.tryCount++; 170 | if (this.tryCount <= this.retryLimit) { 171 | //try again 172 | $.ajax(this); 173 | return; 174 | } 175 | return; 176 | } 177 | var errorMessage = JSON.parse(xhr.responseText); 178 | var text = errorMessage.error; 179 | $('#errorlog').append($('
  • ').text(text)); 180 | } 181 | }); 182 | event.preventDefault(); 183 | }); 184 | 185 | // Handle delete 186 | $(document.body).on('click', '.button-delete', function (event) { 187 | var productId = $(this).closest('tr').find('td:nth-child(1) input').val(); 188 | var name = $(this).closest('tr').find('td:nth-child(2) input').val(); 189 | var row = $(this).parents('tr').first(); 190 | if (productId) { 191 | $('#summary').html('Deleting ' + name + '...'); 192 | $.ajax({ 193 | url: productsServiceUrl + '/' + productId, 194 | type: 'DELETE', 195 | dataType: 'json', 196 | tryCount: 0, 197 | retryLimit: 3, 198 | success: function () { 199 | row.remove(); 200 | $('#summary').html('' + name + ' successfully deleted.'); 201 | }, 202 | error: function (xhr, textStatus, error) { 203 | if (textStatus == 'timeout') { 204 | this.tryCount++; 205 | if (this.tryCount <= this.retryLimit) { 206 | //try again 207 | $.ajax(this); 208 | return; 209 | } 210 | return; 211 | } 212 | var errorMessage = JSON.parse(xhr.responseText); 213 | var text = errorMessage.error; 214 | $('#errorlog').append($('
  • ').text(text)); 215 | } 216 | }); 217 | event.preventDefault(); 218 | } 219 | }); 220 | 221 | // Handle refresh 222 | $(document.body).on('click', '.button-refresh', function (event) { 223 | refreshProducts(); 224 | event.preventDefault(); 225 | $('#errorlog').body = ''; 226 | }); 227 | 228 | // On initial load, start by fetching the current data 229 | refreshProducts(); 230 | }); -------------------------------------------------------------------------------- /src/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /src/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2018 Twitter, Inc. 4 | Copyright (c) 2011-2018 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors 4 | * Copyright 2011-2019 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */ 8 | *, 9 | *::before, 10 | *::after { 11 | box-sizing: border-box; 12 | } 13 | 14 | html { 15 | font-family: sans-serif; 16 | line-height: 1.15; 17 | -webkit-text-size-adjust: 100%; 18 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 19 | } 20 | 21 | article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { 22 | display: block; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 28 | font-size: 1rem; 29 | font-weight: 400; 30 | line-height: 1.5; 31 | color: #212529; 32 | text-align: left; 33 | background-color: #fff; 34 | } 35 | 36 | [tabindex="-1"]:focus { 37 | outline: 0 !important; 38 | } 39 | 40 | hr { 41 | box-sizing: content-box; 42 | height: 0; 43 | overflow: visible; 44 | } 45 | 46 | h1, h2, h3, h4, h5, h6 { 47 | margin-top: 0; 48 | margin-bottom: 0.5rem; 49 | } 50 | 51 | p { 52 | margin-top: 0; 53 | margin-bottom: 1rem; 54 | } 55 | 56 | abbr[title], 57 | abbr[data-original-title] { 58 | text-decoration: underline; 59 | -webkit-text-decoration: underline dotted; 60 | text-decoration: underline dotted; 61 | cursor: help; 62 | border-bottom: 0; 63 | -webkit-text-decoration-skip-ink: none; 64 | text-decoration-skip-ink: none; 65 | } 66 | 67 | address { 68 | margin-bottom: 1rem; 69 | font-style: normal; 70 | line-height: inherit; 71 | } 72 | 73 | ol, 74 | ul, 75 | dl { 76 | margin-top: 0; 77 | margin-bottom: 1rem; 78 | } 79 | 80 | ol ol, 81 | ul ul, 82 | ol ul, 83 | ul ol { 84 | margin-bottom: 0; 85 | } 86 | 87 | dt { 88 | font-weight: 700; 89 | } 90 | 91 | dd { 92 | margin-bottom: .5rem; 93 | margin-left: 0; 94 | } 95 | 96 | blockquote { 97 | margin: 0 0 1rem; 98 | } 99 | 100 | b, 101 | strong { 102 | font-weight: bolder; 103 | } 104 | 105 | small { 106 | font-size: 80%; 107 | } 108 | 109 | sub, 110 | sup { 111 | position: relative; 112 | font-size: 75%; 113 | line-height: 0; 114 | vertical-align: baseline; 115 | } 116 | 117 | sub { 118 | bottom: -.25em; 119 | } 120 | 121 | sup { 122 | top: -.5em; 123 | } 124 | 125 | a { 126 | color: #007bff; 127 | text-decoration: none; 128 | background-color: transparent; 129 | } 130 | 131 | a:hover { 132 | color: #0056b3; 133 | text-decoration: underline; 134 | } 135 | 136 | a:not([href]):not([tabindex]) { 137 | color: inherit; 138 | text-decoration: none; 139 | } 140 | 141 | a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { 142 | color: inherit; 143 | text-decoration: none; 144 | } 145 | 146 | a:not([href]):not([tabindex]):focus { 147 | outline: 0; 148 | } 149 | 150 | pre, 151 | code, 152 | kbd, 153 | samp { 154 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 155 | font-size: 1em; 156 | } 157 | 158 | pre { 159 | margin-top: 0; 160 | margin-bottom: 1rem; 161 | overflow: auto; 162 | } 163 | 164 | figure { 165 | margin: 0 0 1rem; 166 | } 167 | 168 | img { 169 | vertical-align: middle; 170 | border-style: none; 171 | } 172 | 173 | svg { 174 | overflow: hidden; 175 | vertical-align: middle; 176 | } 177 | 178 | table { 179 | border-collapse: collapse; 180 | } 181 | 182 | caption { 183 | padding-top: 0.75rem; 184 | padding-bottom: 0.75rem; 185 | color: #6c757d; 186 | text-align: left; 187 | caption-side: bottom; 188 | } 189 | 190 | th { 191 | text-align: inherit; 192 | } 193 | 194 | label { 195 | display: inline-block; 196 | margin-bottom: 0.5rem; 197 | } 198 | 199 | button { 200 | border-radius: 0; 201 | } 202 | 203 | button:focus { 204 | outline: 1px dotted; 205 | outline: 5px auto -webkit-focus-ring-color; 206 | } 207 | 208 | input, 209 | button, 210 | select, 211 | optgroup, 212 | textarea { 213 | margin: 0; 214 | font-family: inherit; 215 | font-size: inherit; 216 | line-height: inherit; 217 | } 218 | 219 | button, 220 | input { 221 | overflow: visible; 222 | } 223 | 224 | button, 225 | select { 226 | text-transform: none; 227 | } 228 | 229 | select { 230 | word-wrap: normal; 231 | } 232 | 233 | button, 234 | [type="button"], 235 | [type="reset"], 236 | [type="submit"] { 237 | -webkit-appearance: button; 238 | } 239 | 240 | button:not(:disabled), 241 | [type="button"]:not(:disabled), 242 | [type="reset"]:not(:disabled), 243 | [type="submit"]:not(:disabled) { 244 | cursor: pointer; 245 | } 246 | 247 | button::-moz-focus-inner, 248 | [type="button"]::-moz-focus-inner, 249 | [type="reset"]::-moz-focus-inner, 250 | [type="submit"]::-moz-focus-inner { 251 | padding: 0; 252 | border-style: none; 253 | } 254 | 255 | input[type="radio"], 256 | input[type="checkbox"] { 257 | box-sizing: border-box; 258 | padding: 0; 259 | } 260 | 261 | input[type="date"], 262 | input[type="time"], 263 | input[type="datetime-local"], 264 | input[type="month"] { 265 | -webkit-appearance: listbox; 266 | } 267 | 268 | textarea { 269 | overflow: auto; 270 | resize: vertical; 271 | } 272 | 273 | fieldset { 274 | min-width: 0; 275 | padding: 0; 276 | margin: 0; 277 | border: 0; 278 | } 279 | 280 | legend { 281 | display: block; 282 | width: 100%; 283 | max-width: 100%; 284 | padding: 0; 285 | margin-bottom: .5rem; 286 | font-size: 1.5rem; 287 | line-height: inherit; 288 | color: inherit; 289 | white-space: normal; 290 | } 291 | 292 | progress { 293 | vertical-align: baseline; 294 | } 295 | 296 | [type="number"]::-webkit-inner-spin-button, 297 | [type="number"]::-webkit-outer-spin-button { 298 | height: auto; 299 | } 300 | 301 | [type="search"] { 302 | outline-offset: -2px; 303 | -webkit-appearance: none; 304 | } 305 | 306 | [type="search"]::-webkit-search-decoration { 307 | -webkit-appearance: none; 308 | } 309 | 310 | ::-webkit-file-upload-button { 311 | font: inherit; 312 | -webkit-appearance: button; 313 | } 314 | 315 | output { 316 | display: inline-block; 317 | } 318 | 319 | summary { 320 | display: list-item; 321 | cursor: pointer; 322 | } 323 | 324 | template { 325 | display: none; 326 | } 327 | 328 | [hidden] { 329 | display: none !important; 330 | } 331 | /*# sourceMappingURL=bootstrap-reboot.css.map */ -------------------------------------------------------------------------------- /src/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors 4 | * Copyright 2011-2019 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /src/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /src/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | // Unobtrusive validation support library for jQuery and jQuery Validate 2 | // Copyright (c) .NET Foundation. All rights reserved. 3 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 4 | // @version v3.2.11 5 | !function(a){"function"==typeof define&&define.amd?define("jquery.validate.unobtrusive",["jquery-validation"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery-validation")):jQuery.validator.unobtrusive=a(jQuery)}(function(a){function e(a,e,n){a.rules[e]=n,a.message&&(a.messages[e]=a.message)}function n(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function t(a){return a.replace(/([!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function r(a){return a.substr(0,a.lastIndexOf(".")+1)}function i(a,e){return 0===a.indexOf("*.")&&(a=a.replace("*.",e)),a}function o(e,n){var r=a(this).find("[data-valmsg-for='"+t(n[0].name)+"']"),i=r.attr("data-valmsg-replace"),o=i?a.parseJSON(i)!==!1:null;r.removeClass("field-validation-valid").addClass("field-validation-error"),e.data("unobtrusiveContainer",r),o?(r.empty(),e.removeClass("input-validation-error").appendTo(r)):e.hide()}function d(e,n){var t=a(this).find("[data-valmsg-summary=true]"),r=t.find("ul");r&&r.length&&n.errorList.length&&(r.empty(),t.addClass("validation-summary-errors").removeClass("validation-summary-valid"),a.each(n.errorList,function(){a("
  • ").html(this.message).appendTo(r)}))}function s(e){var n=e.data("unobtrusiveContainer");if(n){var t=n.attr("data-valmsg-replace"),r=t?a.parseJSON(t):null;n.addClass("field-validation-valid").removeClass("field-validation-error"),e.removeData("unobtrusiveContainer"),r&&n.empty()}}function l(e){var n=a(this),t="__jquery_unobtrusive_validation_form_reset";if(!n.data(t)){n.data(t,!0);try{n.data("validator").resetForm()}finally{n.removeData(t)}n.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors"),n.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}}function u(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=f.unobtrusive.options||{},u=function(n,t){var r=i[n];r&&a.isFunction(r)&&r.apply(e,t)};return t||(t={options:{errorClass:i.errorClass||"input-validation-error",errorElement:i.errorElement||"span",errorPlacement:function(){o.apply(e,arguments),u("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),u("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),u("success",arguments)}},attachValidation:function(){n.off("reset."+v,r).on("reset."+v,r).validate(this.options)},validate:function(){return n.validate(),n.valid()}},n.data(v,t)),t}var m,f=a.validator,v="unobtrusiveValidation";return f.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=u(d),t.options.rules[e.name]=r={},t.options.messages[e.name]=i={},a.each(this.adapters,function(){var n="data-val-"+this.name,t=o.attr(n),s={};void 0!==t&&(n+="-",a.each(this.params,function(){s[this]=o.attr(n+this)}),this.adapt({element:e,form:d,message:t,params:s,rules:r,messages:i}))}),a.extend(r,{__dummy__:!0}),n||t.attachValidation())},parse:function(e){var n=a(e),t=n.parents().addBack().filter("form").add(n.find("form")).has("[data-val=true]");n.find("[data-val=true]").each(function(){f.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=u(this);a&&a.attachValidation()})}},m=f.unobtrusive.adapters,m.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},m.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},m.addMinMax=function(a,n,t,r,i,o){return this.add(a,[i||"min",o||"max"],function(a){var i=a.params.min,o=a.params.max;i&&o?e(a,r,[i,o]):i?e(a,n,i):o&&e(a,t,o)})},m.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},f.addMethod("__dummy__",function(a,e,n){return!0}),f.addMethod("regex",function(a,e,n){var t;return!!this.optional(e)||(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),f.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),f.methods.extension?(m.addSingleVal("accept","mimtype"),m.addSingleVal("extension","extension")):m.addSingleVal("extension","extension","accept"),m.addSingleVal("regex","pattern"),m.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),m.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),m.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),m.add("equalto",["other"],function(n){var o=r(n.element.name),d=n.params.other,s=i(d,o),l=a(n.form).find(":input").filter("[name='"+t(s)+"']")[0];e(n,"equalTo",l)}),m.add("required",function(a){"INPUT"===a.element.tagName.toUpperCase()&&"CHECKBOX"===a.element.type.toUpperCase()||e(a,"required",!0)}),m.add("remote",["url","type","additionalfields"],function(o){var d={url:o.params.url,type:o.params.type||"GET",data:{}},s=r(o.element.name);a.each(n(o.params.additionalfields||o.element.name),function(e,n){var r=i(n,s);d.data[r]=function(){var e=a(o.form).find(":input").filter("[name='"+t(r)+"']");return e.is(":checkbox")?e.filter(":checked").val()||e.filter(":hidden").val()||"":e.is(":radio")?e.filter(":checked").val()||"":e.val()}}),e(o,"remote",d)}),m.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&e(a,"minlength",a.params.min),a.params.nonalphamin&&e(a,"nonalphamin",a.params.nonalphamin),a.params.regex&&e(a,"regex",a.params.regex)}),m.add("fileextensions",["extensions"],function(a){e(a,"extension",a.params.extensions)}),a(function(){f.unobtrusive.parse(document)}),f.unobtrusive}); -------------------------------------------------------------------------------- /src/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/wwwroot/lib/jquery-validation/dist/additional-methods.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery Validation Plugin - v1.17.0 - 7/29/2017 2 | * https://jqueryvalidation.org/ 3 | * Copyright (c) 2017 Jörn Zaefferer; Licensed MIT */ 4 | !function(a){"function"==typeof define&&define.amd?define(["jquery","./jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return function(){function b(a){return a.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," ").replace(/[.(),;:!?%#$'\"_+=\/\-“”’]*/g,"")}a.validator.addMethod("maxWords",function(a,c,d){return this.optional(c)||b(a).match(/\b\w+\b/g).length<=d},a.validator.format("Please enter {0} words or less.")),a.validator.addMethod("minWords",function(a,c,d){return this.optional(c)||b(a).match(/\b\w+\b/g).length>=d},a.validator.format("Please enter at least {0} words.")),a.validator.addMethod("rangeWords",function(a,c,d){var e=b(a),f=/\b\w+\b/g;return this.optional(c)||e.match(f).length>=d[0]&&e.match(f).length<=d[1]},a.validator.format("Please enter between {0} and {1} words."))}(),a.validator.addMethod("accept",function(b,c,d){var e,f,g,h="string"==typeof d?d.replace(/\s/g,""):"image/*",i=this.optional(c);if(i)return i;if("file"===a(c).attr("type")&&(h=h.replace(/[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g,"\\$&").replace(/,/g,"|").replace(/\/\*/g,"/.*"),c.files&&c.files.length))for(g=new RegExp(".?("+h+")$","i"),e=0;e9?"0":f,g="JABCDEFGHI".substr(f,1).toString(),i.match(/[ABEH]/)?k===f:i.match(/[KPQS]/)?k===g:k===f||k===g},"Please specify a valid CIF number."),a.validator.addMethod("cpfBR",function(a){if(a=a.replace(/([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g,""),11!==a.length)return!1;var b,c,d,e,f=0;if(b=parseInt(a.substring(9,10),10),c=parseInt(a.substring(10,11),10),d=function(a,b){var c=10*a%11;return 10!==c&&11!==c||(c=0),c===b},""===a||"00000000000"===a||"11111111111"===a||"22222222222"===a||"33333333333"===a||"44444444444"===a||"55555555555"===a||"66666666666"===a||"77777777777"===a||"88888888888"===a||"99999999999"===a)return!1;for(e=1;e<=9;e++)f+=parseInt(a.substring(e-1,e),10)*(11-e);if(d(f,b)){for(f=0,e=1;e<=10;e++)f+=parseInt(a.substring(e-1,e),10)*(12-e);return d(f,c)}return!1},"Please specify a valid CPF number"),a.validator.addMethod("creditcard",function(a,b){if(this.optional(b))return"dependency-mismatch";if(/[^0-9 \-]+/.test(a))return!1;var c,d,e=0,f=0,g=!1;if(a=a.replace(/\D/g,""),a.length<13||a.length>19)return!1;for(c=a.length-1;c>=0;c--)d=a.charAt(c),f=parseInt(d,10),g&&(f*=2)>9&&(f-=9),e+=f,g=!g;return e%10===0},"Please enter a valid credit card number."),a.validator.addMethod("creditcardtypes",function(a,b,c){if(/[^0-9\-]+/.test(a))return!1;a=a.replace(/\D/g,"");var d=0;return c.mastercard&&(d|=1),c.visa&&(d|=2),c.amex&&(d|=4),c.dinersclub&&(d|=8),c.enroute&&(d|=16),c.discover&&(d|=32),c.jcb&&(d|=64),c.unknown&&(d|=128),c.all&&(d=255),1&d&&/^(5[12345])/.test(a)?16===a.length:2&d&&/^(4)/.test(a)?16===a.length:4&d&&/^(3[47])/.test(a)?15===a.length:8&d&&/^(3(0[012345]|[68]))/.test(a)?14===a.length:16&d&&/^(2(014|149))/.test(a)?15===a.length:32&d&&/^(6011)/.test(a)?16===a.length:64&d&&/^(3)/.test(a)?16===a.length:64&d&&/^(2131|1800)/.test(a)?15===a.length:!!(128&d)},"Please enter a valid credit card number."),a.validator.addMethod("currency",function(a,b,c){var d,e="string"==typeof c,f=e?c:c[0],g=!!e||c[1];return f=f.replace(/,/g,""),f=g?f+"]":f+"]?",d="^["+f+"([1-9]{1}[0-9]{0,2}(\\,[0-9]{3})*(\\.[0-9]{0,2})?|[1-9]{1}[0-9]{0,}(\\.[0-9]{0,2})?|0(\\.[0-9]{0,2})?|(\\.[0-9]{1,2})?)$",d=new RegExp(d),this.optional(b)||d.test(a)},"Please specify a valid currency"),a.validator.addMethod("dateFA",function(a,b){return this.optional(b)||/^[1-4]\d{3}\/((0?[1-6]\/((3[0-1])|([1-2][0-9])|(0?[1-9])))|((1[0-2]|(0?[7-9]))\/(30|([1-2][0-9])|(0?[1-9]))))$/.test(a)},a.validator.messages.date),a.validator.addMethod("dateITA",function(a,b){var c,d,e,f,g,h=!1,i=/^\d{1,2}\/\d{1,2}\/\d{4}$/;return i.test(a)?(c=a.split("/"),d=parseInt(c[0],10),e=parseInt(c[1],10),f=parseInt(c[2],10),g=new Date(Date.UTC(f,e-1,d,12,0,0,0)),h=g.getUTCFullYear()===f&&g.getUTCMonth()===e-1&&g.getUTCDate()===d):h=!1,this.optional(b)||h},a.validator.messages.date),a.validator.addMethod("dateNL",function(a,b){return this.optional(b)||/^(0?[1-9]|[12]\d|3[01])[\.\/\-](0?[1-9]|1[012])[\.\/\-]([12]\d)?(\d\d)$/.test(a)},a.validator.messages.date),a.validator.addMethod("extension",function(a,b,c){return c="string"==typeof c?c.replace(/,/g,"|"):"png|jpe?g|gif",this.optional(b)||a.match(new RegExp("\\.("+c+")$","i"))},a.validator.format("Please enter a value with a valid extension.")),a.validator.addMethod("giroaccountNL",function(a,b){return this.optional(b)||/^[0-9]{1,7}$/.test(a)},"Please specify a valid giro account number"),a.validator.addMethod("iban",function(a,b){if(this.optional(b))return!0;var c,d,e,f,g,h,i,j,k,l=a.replace(/ /g,"").toUpperCase(),m="",n=!0,o="",p="",q=5;if(l.length9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)7(?:[1345789]\d{2}|624)\s?\d{3}\s?\d{3})$/)},"Please specify a valid mobile number"),a.validator.addMethod("netmask",function(a,b){return this.optional(b)||/^(254|252|248|240|224|192|128)\.0\.0\.0|255\.(254|252|248|240|224|192|128|0)\.0\.0|255\.255\.(254|252|248|240|224|192|128|0)\.0|255\.255\.255\.(254|252|248|240|224|192|128|0)/i.test(a)},"Please enter a valid netmask."),a.validator.addMethod("nieES",function(a,b){"use strict";if(this.optional(b))return!0;var c,d=new RegExp(/^[MXYZ]{1}[0-9]{7,8}[TRWAGMYFPDXBNJZSQVHLCKET]{1}$/gi),e="TRWAGMYFPDXBNJZSQVHLCKET",f=a.substr(a.length-1).toUpperCase();return a=a.toString().toUpperCase(),!(a.length>10||a.length<9||!d.test(a))&&(a=a.replace(/^[X]/,"0").replace(/^[Y]/,"1").replace(/^[Z]/,"2"),c=9===a.length?a.substr(0,8):a.substr(0,9),e.charAt(parseInt(c,10)%23)===f)},"Please specify a valid NIE number."),a.validator.addMethod("nifES",function(a,b){"use strict";return!!this.optional(b)||(a=a.toUpperCase(),!!a.match("((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)")&&(/^[0-9]{8}[A-Z]{1}$/.test(a)?"TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.substring(8,0)%23)===a.charAt(8):!!/^[KLM]{1}/.test(a)&&a[8]==="TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.substring(8,1)%23)))},"Please specify a valid NIF number."),a.validator.addMethod("nipPL",function(a){"use strict";if(a=a.replace(/[^0-9]/g,""),10!==a.length)return!1;for(var b=[6,5,7,2,3,4,5,6,7],c=0,d=0;d<9;d++)c+=b[d]*a[d];var e=c%11,f=10===e?0:e;return f===parseInt(a[9],10)},"Please specify a valid NIP number."),a.validator.addMethod("notEqualTo",function(b,c,d){return this.optional(c)||!a.validator.methods.equalTo.call(this,b,c,d)},"Please enter a different value, values must not be the same."),a.validator.addMethod("nowhitespace",function(a,b){return this.optional(b)||/^\S+$/i.test(a)},"No white space please"),a.validator.addMethod("pattern",function(a,b,c){return!!this.optional(b)||("string"==typeof c&&(c=new RegExp("^(?:"+c+")$")),c.test(a))},"Invalid format."),a.validator.addMethod("phoneNL",function(a,b){return this.optional(b)||/^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)[1-9]((\s|\s?\-\s?)?[0-9]){8}$/.test(a)},"Please specify a valid phone number."),a.validator.addMethod("phonesUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)(?:1\d{8,9}|[23]\d{9}|7(?:[1345789]\d{8}|624\d{6})))$/)},"Please specify a valid uk phone number"),a.validator.addMethod("phoneUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?)|(?:\(?0))(?:\d{2}\)?\s?\d{4}\s?\d{4}|\d{3}\)?\s?\d{3}\s?\d{3,4}|\d{4}\)?\s?(?:\d{5}|\d{3}\s?\d{3})|\d{5}\)?\s?\d{4,5})$/)},"Please specify a valid phone number"),a.validator.addMethod("phoneUS",function(a,b){return a=a.replace(/\s+/g,""),this.optional(b)||a.length>9&&a.match(/^(\+?1-?)?(\([2-9]([02-9]\d|1[02-9])\)|[2-9]([02-9]\d|1[02-9]))-?[2-9]([02-9]\d|1[02-9])-?\d{4}$/)},"Please specify a valid phone number"),a.validator.addMethod("postalcodeBR",function(a,b){return this.optional(b)||/^\d{2}.\d{3}-\d{3}?$|^\d{5}-?\d{3}?$/.test(a)},"Informe um CEP válido."),a.validator.addMethod("postalCodeCA",function(a,b){return this.optional(b)||/^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ] *\d[ABCEGHJKLMNPRSTVWXYZ]\d$/i.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postalcodeIT",function(a,b){return this.optional(b)||/^\d{5}$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postalcodeNL",function(a,b){return this.optional(b)||/^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postcodeUK",function(a,b){return this.optional(b)||/^((([A-PR-UWYZ][0-9])|([A-PR-UWYZ][0-9][0-9])|([A-PR-UWYZ][A-HK-Y][0-9])|([A-PR-UWYZ][A-HK-Y][0-9][0-9])|([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY]))\s?([0-9][ABD-HJLNP-UW-Z]{2})|(GIR)\s?(0AA))$/i.test(a)},"Please specify a valid UK postcode"),a.validator.addMethod("require_from_group",function(b,c,d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_req_grp")?f.data("valid_req_grp"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length>=d[0];return f.data("valid_req_grp",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),h},a.validator.format("Please fill at least {0} of these fields.")),a.validator.addMethod("skip_or_fill_minimum",function(b,c,d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_skip")?f.data("valid_skip"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length,i=0===h||h>=d[0];return f.data("valid_skip",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),i},a.validator.format("Please either skip these fields or fill at least {0} of them.")),a.validator.addMethod("stateUS",function(a,b,c){var d,e="undefined"==typeof c,f=!e&&"undefined"!=typeof c.caseSensitive&&c.caseSensitive,g=!e&&"undefined"!=typeof c.includeTerritories&&c.includeTerritories,h=!e&&"undefined"!=typeof c.includeMilitary&&c.includeMilitary;return d=g||h?g&&h?"^(A[AEKLPRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":g?"^(A[KLRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":"^(A[AEKLPRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$":"^(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$",d=f?new RegExp(d):new RegExp(d,"i"),this.optional(b)||d.test(a)},"Please specify a valid state"),a.validator.addMethod("strippedminlength",function(b,c,d){return a(b).text().length>=d},a.validator.format("Please enter at least {0} characters")),a.validator.addMethod("time",function(a,b){return this.optional(b)||/^([01]\d|2[0-3]|[0-9])(:[0-5]\d){1,2}$/.test(a)},"Please enter a valid time, between 00:00 and 23:59"),a.validator.addMethod("time12h",function(a,b){return this.optional(b)||/^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test(a)},"Please enter a valid time in 12-hour am/pm format"),a.validator.addMethod("url2",function(a,b){return this.optional(b)||/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)*(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(a)},a.validator.messages.url),a.validator.addMethod("vinUS",function(a){if(17!==a.length)return!1;var b,c,d,e,f,g,h=["A","B","C","D","E","F","G","H","J","K","L","M","N","P","R","S","T","U","V","W","X","Y","Z"],i=[1,2,3,4,5,6,7,8,1,2,3,4,5,7,9,2,3,4,5,6,7,8,9],j=[8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2],k=0;for(b=0;b<17;b++){if(e=j[b],d=a.slice(b,b+1),8===b&&(g=d),isNaN(d)){for(c=0;c