├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ ├── config.yml │ ├── documentation.yml │ └── feature-request.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── closed-issue-message.yml │ ├── handle-stale-discussions.yml │ ├── issue-regression-labeler.yml │ └── stale_issues.yml ├── .gitignore ├── .nuget ├── NuGet.Config ├── NuGet.exe └── NuGet.targets ├── AWS.SessionProvider.nuspec ├── AWS.SessionProvider.sln ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── License.txt ├── README.md ├── SampleWebApp ├── Default.aspx ├── Default.aspx.cs ├── Default.aspx.designer.cs ├── Properties │ └── AssemblyInfo.cs ├── SampleWebApp.csproj ├── Web.Debug.config ├── Web.Release.config ├── Web.config └── packages.config ├── aws-sessionprovider-sample.config ├── build.proj ├── icon.png ├── src ├── AWS.SessionProvider.Net35.csproj ├── AWS.SessionProvider.Net45.csproj ├── DynamoDBSessionStateStore.cs ├── Properties │ └── AssemblyInfo.cs ├── packages.config └── public.snk └── test ├── AWS.SessionProvider.Test.csproj ├── Properties └── AssemblyInfo.cs ├── SessionStoreTest.cs └── packages.config /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐛 Bug Report" 3 | description: Report a bug 4 | title: "(short issue description)" 5 | labels: [bug, needs-triage] 6 | assignees: [] 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Describe the bug 12 | description: What is the problem? A clear and concise description of the bug. 13 | validations: 14 | required: true 15 | - type: checkboxes 16 | id: regression 17 | attributes: 18 | label: Regression Issue 19 | description: What is a regression? If it worked in a previous version but doesn't in the latest version, it's considered a regression. In this case, please provide specific version number in the report. 20 | options: 21 | - label: Select this option if this issue appears to be a regression. 22 | required: false 23 | - type: textarea 24 | id: expected 25 | attributes: 26 | label: Expected Behavior 27 | description: | 28 | What did you expect to happen? 29 | validations: 30 | required: true 31 | - type: textarea 32 | id: current 33 | attributes: 34 | label: Current Behavior 35 | description: | 36 | What actually happened? 37 | 38 | Please include full errors, uncaught exceptions, stack traces, and relevant logs. 39 | If service responses are relevant, please include wire logs. 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: reproduction 44 | attributes: 45 | label: Reproduction Steps 46 | description: | 47 | Provide a self-contained, concise snippet of code that can be used to reproduce the issue. 48 | For more complex issues provide a repo with the smallest sample that reproduces the bug. 49 | 50 | Avoid including business logic or unrelated code, it makes diagnosis more difficult. 51 | The code sample should be an SSCCE. See http://sscce.org/ for details. In short, please provide a code sample that we can copy/paste, run and reproduce. 52 | validations: 53 | required: true 54 | - type: textarea 55 | id: solution 56 | attributes: 57 | label: Possible Solution 58 | description: | 59 | Suggest a fix/reason for the bug 60 | validations: 61 | required: false 62 | - type: textarea 63 | id: context 64 | attributes: 65 | label: Additional Information/Context 66 | description: | 67 | Anything else that might be relevant for troubleshooting this bug. Providing context helps us come up with a solution that is most useful in the real world. 68 | validations: 69 | required: false 70 | 71 | - type: textarea 72 | id: dotnet-sdk-version 73 | attributes: 74 | label: AWS .NET SDK and/or Package version used 75 | description: NuGet Packages used 76 | placeholder: AWSSDK.S3 3.7.8.13 77 | validations: 78 | required: true 79 | 80 | - type: input 81 | id: platform-used 82 | attributes: 83 | label: Targeted .NET Platform 84 | description: "Example: .NET Framework 4.7, .NET Core 3.1, .NET 6, etc." 85 | placeholder: .NET Framework 4.7, .NET Core 3.1, .NET 6, etc. 86 | validations: 87 | required: true 88 | 89 | - type: input 90 | id: operating-system 91 | attributes: 92 | label: Operating System and version 93 | description: "Example: Windows 10, OSX Mojave, Ubuntu, AmazonLinux, etc." 94 | placeholder: Windows 10, OSX Mojave, Ubuntu, AmazonLinux, etc. 95 | validations: 96 | required: true 97 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | blank_issues_enabled: false 3 | contact_links: 4 | - name: 💬 General Question 5 | url: https://github.com/aws/aws-dotnet-session-provider/discussions/categories/q-a 6 | about: Please ask and answer questions as a discussion thread -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "📕 Documentation Issue" 3 | description: Report an issue in the API Reference documentation or Developer Guide 4 | title: "(short issue description)" 5 | labels: [documentation, needs-triage] 6 | assignees: [] 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Describe the issue 12 | description: A clear and concise description of the issue. 13 | validations: 14 | required: true 15 | 16 | - type: textarea 17 | id: links 18 | attributes: 19 | label: Links 20 | description: | 21 | Include links to affected documentation page(s). 22 | validations: 23 | required: true 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature Request 3 | description: Suggest an idea for this project 4 | title: "(short issue description)" 5 | labels: [feature-request, needs-triage] 6 | assignees: [] 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Describe the feature 12 | description: A clear and concise description of the feature you are proposing. 13 | validations: 14 | required: true 15 | - type: textarea 16 | id: use-case 17 | attributes: 18 | label: Use Case 19 | description: | 20 | Why do you need this feature? For example: "I'm always frustrated when..." 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: solution 25 | attributes: 26 | label: Proposed Solution 27 | description: | 28 | Suggest how to implement the addition or change. Please include prototype/workaround/sketch/reference implementation. 29 | validations: 30 | required: false 31 | - type: textarea 32 | id: other 33 | attributes: 34 | label: Other Information 35 | description: | 36 | Any alternative solutions or features you considered, a more detailed explanation, stack traces, related issues, links for context, etc. 37 | validations: 38 | required: false 39 | - type: checkboxes 40 | id: ack 41 | attributes: 42 | label: Acknowledgements 43 | options: 44 | - label: I may be able to implement this feature request 45 | required: false 46 | - label: This feature might incur a breaking change 47 | required: false 48 | 49 | - type: textarea 50 | id: dotnet-sdk-version 51 | attributes: 52 | label: AWS .NET SDK and/or Package version used 53 | description: NuGet Packages used 54 | placeholder: AWSSDK.S3 3.7.8.13 55 | validations: 56 | required: true 57 | 58 | - type: input 59 | id: platform-used 60 | attributes: 61 | label: Targeted .NET Platform 62 | description: "Example: .NET Framework 4.7, .NET Core 3.1, .NET 6, etc." 63 | placeholder: .NET Framework 4.7, .NET Core 3.1, .NET 6, etc. 64 | validations: 65 | required: true 66 | 67 | - type: input 68 | id: operating-system 69 | attributes: 70 | label: Operating System and version 71 | description: "Example: Windows 10, OSX Mojave, Ubuntu, AmazonLinux, etc." 72 | placeholder: Windows 10, OSX Mojave, Ubuntu, AmazonLinux, etc. 73 | validations: 74 | required: true 75 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /.github/workflows/closed-issue-message.yml: -------------------------------------------------------------------------------- 1 | name: Closed Issue Message 2 | on: 3 | issues: 4 | types: [closed] 5 | jobs: 6 | auto_comment: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: aws-actions/closed-issue-message@v1 10 | with: 11 | # These inputs are both required 12 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 13 | message: | 14 | ### ⚠️COMMENT VISIBILITY WARNING⚠️ 15 | Comments on closed issues are hard for our team to see. 16 | If you need more assistance, please either tag a team member or open a new issue that references this one. 17 | If you wish to keep having a conversation with other community members under this issue feel free to do so. 18 | -------------------------------------------------------------------------------- /.github/workflows/handle-stale-discussions.yml: -------------------------------------------------------------------------------- 1 | name: HandleStaleDiscussions 2 | on: 3 | schedule: 4 | - cron: '0 */4 * * *' 5 | discussion_comment: 6 | types: [created] 7 | 8 | jobs: 9 | handle-stale-discussions: 10 | name: Handle stale discussions 11 | runs-on: ubuntu-latest 12 | permissions: 13 | discussions: write 14 | steps: 15 | - name: Stale discussions action 16 | uses: aws-github-ops/handle-stale-discussions@v1 17 | env: 18 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 19 | -------------------------------------------------------------------------------- /.github/workflows/issue-regression-labeler.yml: -------------------------------------------------------------------------------- 1 | # Apply potential regression label on issues 2 | name: issue-regression-label 3 | on: 4 | issues: 5 | types: [opened, edited] 6 | jobs: 7 | add-regression-label: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | steps: 12 | - name: Fetch template body 13 | id: check_regression 14 | uses: actions/github-script@v7 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | TEMPLATE_BODY: ${{ github.event.issue.body }} 18 | with: 19 | script: | 20 | const regressionPattern = /\[x\] Select this option if this issue appears to be a regression\./i; 21 | const template = `${process.env.TEMPLATE_BODY}` 22 | const match = regressionPattern.test(template); 23 | core.setOutput('is_regression', match); 24 | - name: Manage regression label 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | run: | 28 | if [ "${{ steps.check_regression.outputs.is_regression }}" == "true" ]; then 29 | gh issue edit ${{ github.event.issue.number }} --add-label "potential-regression" -R ${{ github.repository }} 30 | else 31 | gh issue edit ${{ github.event.issue.number }} --remove-label "potential-regression" -R ${{ github.repository }} 32 | fi 33 | -------------------------------------------------------------------------------- /.github/workflows/stale_issues.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues" 2 | 3 | # Controls when the action will run. 4 | on: 5 | schedule: 6 | - cron: "0 0 * * *" 7 | 8 | jobs: 9 | cleanup: 10 | runs-on: ubuntu-latest 11 | name: Stale issue job 12 | steps: 13 | - uses: aws-actions/stale-issue-cleanup@v6 14 | with: 15 | # Setting messages to an empty string will cause the automation to skip 16 | # that category 17 | ancient-issue-message: We have noticed this issue has not received attention in 1 year. We will close this issue for now. If you think this is in error, please feel free to comment and reopen the issue. 18 | stale-issue-message: This issue has not received a response in 5 days. If you want to keep this issue open, please just leave a comment below and auto-close will be canceled. 19 | 20 | # These labels are required 21 | stale-issue-label: closing-soon 22 | exempt-issue-labels: no-autoclose 23 | stale-pr-label: no-pr-activity 24 | exempt-pr-labels: awaiting-approval 25 | response-requested-label: response-requested 26 | 27 | # Don't set closed-for-staleness label to skip closing very old issues 28 | # regardless of label 29 | closed-for-staleness-label: closed-for-staleness 30 | 31 | # Issue timing 32 | days-before-stale: 5 33 | days-before-close: 2 34 | days-before-ancient: 36500 35 | 36 | # If you don't want to mark a issue as being ancient based on a 37 | # threshold of "upvotes", you can set this here. An "upvote" is 38 | # the total number of +1, heart, hooray, and rocket reactions 39 | # on an issue. 40 | minimum-upvotes-to-exempt: 10 41 | 42 | repo-token: ${{ secrets.GITHUB_TOKEN }} 43 | #loglevel: DEBUG 44 | # Set dry-run to true to not perform label or close actions. 45 | #dry-run: true 46 | 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ###################################### 2 | # Visual Studio per-user settings data 3 | ###################################### 4 | *.suo 5 | *.user 6 | .vs 7 | 8 | #################### 9 | # Build/Test folders 10 | #################### 11 | 12 | **/bin/ 13 | **/obj/ 14 | **/TestResults/ 15 | **/Temp/ 16 | **/NuGet.exe 17 | **/buildlogs/ 18 | 19 | sdk/packages/** 20 | 21 | Deployment/** 22 | 23 | 24 | packages/** -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-dotnet-session-provider/9d80ddf025b34e06503febe59ca9e93234bb6d5d/.nuget/NuGet.exe -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | 32 | 33 | 34 | 35 | $(SolutionDir).nuget 36 | 37 | 38 | 39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config 40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config 41 | 42 | 43 | 44 | $(MSBuildProjectDirectory)\packages.config 45 | $(PackagesProjectConfig) 46 | 47 | 48 | 49 | 50 | $(NuGetToolsPath)\NuGet.exe 51 | @(PackageSource) 52 | 53 | "$(NuGetExePath)" 54 | mono --runtime=v4.0.30319 "$(NuGetExePath)" 55 | 56 | $(TargetDir.Trim('\\')) 57 | 58 | -RequireConsent 59 | -NonInteractive 60 | 61 | "$(SolutionDir)\" 62 | "$(SolutionDir)" 63 | "$(SolutionDir)" 64 | 65 | 66 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 67 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 68 | 69 | 70 | 71 | RestorePackages; 72 | $(BuildDependsOn); 73 | 74 | 75 | 76 | 77 | $(BuildDependsOn); 78 | BuildPackage; 79 | 80 | 81 | 82 | 83 | 84 | 85 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 100 | 101 | 104 | 105 | 106 | 107 | 109 | 110 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /AWS.SessionProvider.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | AWS.SessionProvider 4 | AWS SDK for .NET: SessionProvider 5 | 4.1.2.0 6 | Amazon Web Services 7 | This contains a session state provider using Amazon DynamoDB. 8 | en-US 9 | 10 | http://aws.amazon.com/apache2.0/ 11 | 12 | https://github.com/aws/aws-dotnet-session-provider/ 13 | AWS Amazon cloud 14 | 15 | http://media.amazonwebservices.com/aws_singlebox_01.png 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /AWS.SessionProvider.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.SessionProvider.Net35", "src\AWS.SessionProvider.Net35.csproj", "{65DD8109-6DA4-46A7-A29E-DE712F85010A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebApp", "SampleWebApp\SampleWebApp.csproj", "{3B69B216-24F4-4220-978B-DE3DC28E6D9A}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.SessionProvider.Net45", "src\AWS.SessionProvider.Net45.csproj", "{C73B3233-CB6F-4FA7-9FFF-CC6FD64100C3}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{9283708F-A3BA-4D04-8801-982C3AAF5E55}" 13 | ProjectSection(SolutionItems) = preProject 14 | .nuget\NuGet.Config = .nuget\NuGet.Config 15 | .nuget\NuGet.exe = .nuget\NuGet.exe 16 | .nuget\NuGet.targets = .nuget\NuGet.targets 17 | EndProjectSection 18 | EndProject 19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.SessionProvider.Test", "test\AWS.SessionProvider.Test.csproj", "{2418CFD0-AC81-4425-9CDC-9CAF450A9855}" 20 | EndProject 21 | Global 22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 23 | Debug|Any CPU = Debug|Any CPU 24 | Release|Any CPU = Release|Any CPU 25 | EndGlobalSection 26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 27 | {65DD8109-6DA4-46A7-A29E-DE712F85010A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {65DD8109-6DA4-46A7-A29E-DE712F85010A}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {65DD8109-6DA4-46A7-A29E-DE712F85010A}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {65DD8109-6DA4-46A7-A29E-DE712F85010A}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {3B69B216-24F4-4220-978B-DE3DC28E6D9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {3B69B216-24F4-4220-978B-DE3DC28E6D9A}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {3B69B216-24F4-4220-978B-DE3DC28E6D9A}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {3B69B216-24F4-4220-978B-DE3DC28E6D9A}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {C73B3233-CB6F-4FA7-9FFF-CC6FD64100C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {C73B3233-CB6F-4FA7-9FFF-CC6FD64100C3}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {C73B3233-CB6F-4FA7-9FFF-CC6FD64100C3}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {C73B3233-CB6F-4FA7-9FFF-CC6FD64100C3}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {2418CFD0-AC81-4425-9CDC-9CAF450A9855}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {2418CFD0-AC81-4425-9CDC-9CAF450A9855}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {2418CFD0-AC81-4425-9CDC-9CAF450A9855}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {2418CFD0-AC81-4425-9CDC-9CAF450A9855}.Release|Any CPU.Build.0 = Release|Any CPU 43 | EndGlobalSection 44 | GlobalSection(SolutionProperties) = preSolution 45 | HideSolutionNode = FALSE 46 | EndGlobalSection 47 | EndGlobal 48 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 4.1.2.0 (2024-04-23) 2 | * Update User-Agent string 3 | 4 | ### 4.1.1.0 (2023-04-21) 5 | * Add option to enable On-Demand read/write capacity for session table on creation 6 | 7 | ### 4.1.0.0 (2021-03-31) 8 | * Update AWS SDK dependencies to 3.7 9 | 10 | ### 4.0.0.0 (2020-11-06) 11 | * Update AWS SDK dependencies to 3.5, and update version to 4.0.0 12 | 13 | ### 3.3.0.0 (2017-03-10) 14 | * Added DynamoDB Time-to-Live support. 15 | 16 | ### 3.1.0.3 (2016-03-18) 17 | * Fix issue with the reading of ATTRIBUTE_LOCK_DATE attribute 18 | 19 | ### 3.1.0.2 (2015-10-14) 20 | * Accept pull request 2 which fixes issues with DeleteExpiredSessions utility method. 21 | 22 | ### 3.1.0.1 (2015-08-05) 23 | * Fix issue with ReleaseItemExclusive not doing a consistent read 24 | * Add logging 25 | 26 | ### 3.1.0.0 (2015-07-28) 27 | * The GA release of the session provider -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws/aws-dotnet-session-provider/issues), or [recently closed](https://github.com/aws/aws-dotnet-session-provider/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws/aws-dotnet-session-provider/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws/aws-dotnet-session-provider/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-dotnet-session-provider/9d80ddf025b34e06503febe59ca9e93234bb6d5d/License.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS DynamoDB Session State Provider 2 | 3 | > [!Warning] 4 | > We are announcing the **deprecation** of the AWS DynamoDB Session State Provider for .NET. Support for this library will continue for the next six months and will officially end on **November 14, 2025**. After that date, we will no longer publish updates to the library, including security or critical bug fixes. 5 | > You can learn more on the deprecation announcement via the [Announcing the end of support for AWS DynamoDB Session State Provider](https://aws.amazon.com/blogs/developer/announcing-the-end-of-support-for-aws-dynamodb-session-state-provider/) blog post. 6 | 7 | The **Amazon DynamoDB Session State Provider** allows ASP.NET applications store their sessions inside DynamoDB. This helps applications scale across multiple application servers while maintaining session state across the system. 8 | 9 | If you are looking to cache session state in DynamoDB from an _ASP.NET Core_ application, try the [AWS .NET Distributed Cache Provider](https://github.com/awslabs/aws-dotnet-distributed-cache-provider) instead. 10 | 11 | ## Change Log 12 | 13 | The change log for the can be found in the [CHANGELOG.md](https://github.com/aws/aws-dotnet-session-provider/blob/master/CHANGELOG.md) file. 14 | 15 | 16 | ## Usage Information 17 | 18 | This project builds a ASP.NET Session State provider that stores session in a DynamoDB table. The session state provider can retrieved from [NuGet][nuget-package]. 19 | 20 | For more information on using the session manager, see the session manager section in the [AWS SDK for .NET Developer Guide][developer-guide]. 21 | 22 | 23 | ## Links 24 | 25 | * [AWS DynamoDB Session State Provider Deprecation Announcement][deprecation-announcement] 26 | * [AWS Session State Provider NuGet package][nuget-package] 27 | * [AWS Session Provider Developer Guide][developer-guide] 28 | * [AWS .NET Developer Blog][dotnet-blog] 29 | * [AWS SDK for .NET GitHub Repository][github-awssdk] 30 | * [AWS SDK for .NET SDK][sdk-website] 31 | 32 | 33 | [deprecation-announcement]: https://aws.amazon.com/blogs/developer/announcing-the-end-of-support-for-aws-dynamodb-session-state-provider/ 34 | [developer-guide]: https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/dynamodb-session-net-sdk.html 35 | [nuget-package]: http://www.nuget.org/packages/AWS.SessionProvider/ 36 | [github-awssdk]: https://github.com/aws/aws-sdk-net 37 | [sdk-website]: http://aws.amazon.com/sdkfornet 38 | [dotnet-blog]: http://blogs.aws.amazon.com/net/ 39 | -------------------------------------------------------------------------------- /SampleWebApp/Default.aspx: -------------------------------------------------------------------------------- 1 | <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="SampleWebApp.Default" %> 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

13 | This simple web application is configured in the web.config to use the AWS Session Provider for session state. The app will increment the page count 14 | for every click on the refresh button and store the current value in the session. To confirm this an entry will be written to 15 | the ASP.NET_SessionState table and can be viewed with the DynamoDB table browser. 16 |

17 |

18 | The application will use the default profile for credentials and the us-west-2 region. When first launched it will 19 | create the ASP.NET_SessionState table if it doesn't exist. Be sure to delete this table when done testing to avoid charges. 20 |

21 |

22 | Number of page views <%=this.PageCount%> 23 |

24 | 25 |
26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /SampleWebApp/Default.aspx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.UI; 6 | using System.Web.UI.WebControls; 7 | 8 | namespace SampleWebApp 9 | { 10 | public partial class Default : System.Web.UI.Page 11 | { 12 | const string PageCountKey = "PageCount"; 13 | 14 | protected void Page_Load(object sender, EventArgs e) 15 | { 16 | 17 | } 18 | 19 | public int PageCount 20 | { 21 | get 22 | { 23 | if (this.Session[PageCountKey] == null) 24 | { 25 | this.Session[PageCountKey] = 0; 26 | } 27 | 28 | int value = Convert.ToInt32(this.Session[PageCountKey]); 29 | value++; 30 | this.Session[PageCountKey] = value; 31 | return value; 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /SampleWebApp/Default.aspx.designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | namespace SampleWebApp 11 | { 12 | 13 | 14 | public partial class Default 15 | { 16 | 17 | /// 18 | /// form1 control. 19 | /// 20 | /// 21 | /// Auto-generated field. 22 | /// To modify move field declaration from designer file to code-behind file. 23 | /// 24 | protected global::System.Web.UI.HtmlControls.HtmlForm form1; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SampleWebApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SampleWebApp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SampleWebApp")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f1dbf565-0dbc-4399-a5d3-af8ab24bb50e")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /SampleWebApp/SampleWebApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | 2.0 10 | {3B69B216-24F4-4220-978B-DE3DC28E6D9A} 11 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 12 | Library 13 | Properties 14 | SampleWebApp 15 | SampleWebApp 16 | v4.5 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | true 27 | full 28 | false 29 | bin\ 30 | DEBUG;TRACE 31 | prompt 32 | 4 33 | 34 | 35 | pdbonly 36 | true 37 | bin\ 38 | TRACE 39 | prompt 40 | 4 41 | 42 | 43 | 44 | ..\packages\AWSSDK.Core.3.7.0.2\lib\net45\AWSSDK.Core.dll 45 | 46 | 47 | ..\packages\AWSSDK.DynamoDBv2.3.7.0.2\lib\net45\AWSSDK.DynamoDBv2.dll 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | Web.config 71 | 72 | 73 | Web.config 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | Default.aspx 83 | ASPXCodeBehind 84 | 85 | 86 | Default.aspx 87 | 88 | 89 | 90 | 91 | 92 | {c73b3233-cb6f-4fa7-9fff-cc6fd64100c3} 93 | AWS.SessionProvider.Net45 94 | 95 | 96 | 97 | 98 | 99 | 100 | 10.0 101 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | True 111 | True 112 | 62103 113 | / 114 | http://localhost:62103/ 115 | False 116 | False 117 | 118 | 119 | False 120 | 121 | 122 | 123 | 124 | 131 | -------------------------------------------------------------------------------- /SampleWebApp/Web.Debug.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /SampleWebApp/Web.Release.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /SampleWebApp/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /SampleWebApp/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /aws-sessionprovider-sample.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /build.proj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Release 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-dotnet-session-provider/9d80ddf025b34e06503febe59ca9e93234bb6d5d/icon.png -------------------------------------------------------------------------------- /src/AWS.SessionProvider.Net35.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {65DD8109-6DA4-46A7-A29E-DE712F85010A} 9 | Library 10 | Properties 11 | Amazon 12 | AWS.SessionProvider 13 | v3.5 14 | 512 15 | obj\net35 16 | 17 | ..\ 18 | true 19 | 20 | C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client 21 | 22 | 23 | 24 | 25 | true 26 | 27 | 28 | 29 | public.snk 30 | 31 | 32 | 33 | true 34 | full 35 | false 36 | bin\net35\$(Configuration) $(TargetFrameworkVersion)\ 37 | DEBUG;TRACE 38 | prompt 39 | 4 40 | bin\net35\$(Configuration) $(TargetFrameworkVersion)\AWS.SessionProvider.xml 41 | 1591 42 | 43 | 44 | pdbonly 45 | true 46 | bin\net35\$(Configuration) $(TargetFrameworkVersion)\ 47 | TRACE 48 | prompt 49 | 4 50 | bin\net35\$(Configuration) $(TargetFrameworkVersion)\AWS.SessionProvider.xml 51 | 1591 52 | 53 | 54 | NET35 55 | 3.5 56 | 57 | 58 | NET40 59 | 4.0 60 | 61 | 62 | NET45 63 | 4.5 64 | 65 | 66 | 67 | ..\packages\AWSSDK.Core.3.7.303.14\lib\net35\AWSSDK.Core.dll 68 | True 69 | 70 | 71 | ..\packages\AWSSDK.DynamoDBv2.3.7.302.15\lib\net35\AWSSDK.DynamoDBv2.dll 72 | True 73 | 74 | 75 | 76 | 77 | 78 | ..\packages\Microsoft.NETFramework.ReferenceAssemblies.net20.1.0.0\build\.NETFramework\v2.0\System.Web.dll 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 98 | 99 | 100 | 101 | 102 | 103 | 110 | -------------------------------------------------------------------------------- /src/AWS.SessionProvider.Net45.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {C73B3233-CB6F-4FA7-9FFF-CC6FD64100C3} 9 | Library 10 | Properties 11 | Amazon 12 | AWS.SessionProvider 13 | v4.5 14 | 512 15 | obj\net45 16 | 17 | ..\ 18 | true 19 | 20 | 21 | true 22 | 23 | 24 | 25 | public.snk 26 | 27 | 28 | 29 | true 30 | full 31 | false 32 | bin\net45\$(Configuration) $(TargetFrameworkVersion)\ 33 | DEBUG;TRACE 34 | prompt 35 | 4 36 | bin\net45\$(Configuration) $(TargetFrameworkVersion)\AWS.SessionProvider.xml 37 | 1591 38 | 39 | 40 | pdbonly 41 | true 42 | bin\net45\$(Configuration) $(TargetFrameworkVersion)\ 43 | TRACE 44 | prompt 45 | 4 46 | bin\net45\$(Configuration) $(TargetFrameworkVersion)\AWS.SessionProvider.xml 47 | 1591 48 | 49 | 50 | NET35 51 | 3.5 52 | 53 | 54 | NET40 55 | 4.0 56 | 57 | 58 | NET45 59 | 4.5 60 | 61 | 62 | 63 | ..\packages\AWSSDK.Core.3.7.303.14\lib\net45\AWSSDK.Core.dll 64 | 65 | 66 | ..\packages\AWSSDK.DynamoDBv2.3.7.302.15\lib\net45\AWSSDK.DynamoDBv2.dll 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 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 93 | 94 | 95 | 96 | 103 | -------------------------------------------------------------------------------- /src/DynamoDBSessionStateStore.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | using System; 16 | using System.IO; 17 | using System.Web; 18 | using System.Web.Configuration; 19 | using System.Configuration; 20 | using System.Collections.Generic; 21 | using System.Collections.Specialized; 22 | using System.Web.SessionState; 23 | using System.Threading; 24 | 25 | using Amazon.DynamoDBv2; 26 | using Amazon.DynamoDBv2.Model; 27 | using Amazon.DynamoDBv2.DocumentModel; 28 | using Amazon.Runtime; 29 | using Amazon.Runtime.Internal.Util; 30 | using System.Reflection; 31 | 32 | namespace Amazon.SessionProvider 33 | { 34 | /// 35 | /// DynamoDBSessionStateStore is a custom session state provider that can be used inside of an ASP.NET application. Session state is saved 36 | /// inside a DynamoDB table that can be configured in the web.config. If the table does not exist the provider will create 37 | /// it during initialization with default read and write units set to 10 and 5 unless configured otherwise. If the table is created 38 | /// the application startup will block for about a minute while the table is being created. 39 | /// 40 | /// Example web.config entry setting up the session state provider. 41 | /// 42 | /// <sessionState 43 | /// mode="Custom" 44 | /// customProvider="DynamoDBSessionStoreProvider"> 45 | /// <providers> 46 | /// <add name="DynamoDBSessionStoreProvider" 47 | /// type="Amazon.SessionProvider.DynamoDBSessionStateStore, AWS.SessionProvider" 48 | /// AWSProfileName="default" 49 | /// AWSProfilesLocation=".aws/credentials" 50 | /// Region="us-east-1" 51 | /// Table="ASP.NET_SessionState" 52 | /// TTLAttributeName="ExpirationTime" 53 | /// TTLExpiredSessionsSeconds="86400" 54 | /// /> 55 | /// </providers> 56 | /// </sessionState> 57 | /// 58 | /// 59 | /// 60 | /// The schema for the table used to store session requires a string hash key with no range key. The provider will look up the name of the hash key during 61 | /// initialization so any name can be given for the hash key. 62 | /// 63 | /// 64 | /// 65 | /// Below is a list of configuration attributes that can specified in the provider element in the web.config. 66 | /// 67 | /// 68 | /// Config Constant 69 | /// Use 70 | /// 71 | /// 72 | /// AWSProfileName 73 | /// Profile used. This can be set at either the provider or in the appSettings. 74 | /// 75 | /// 76 | /// AWSProfilesLocation 77 | /// Location of the credentials file. This can be set at either the provider or in the appSettings. 78 | /// 79 | /// 80 | /// Region 81 | /// Required string attribute. The region to use DynamoDB in. Possible values are us-east-1, us-west-1, us-west-2, eu-west-1, ap-northeast-1, ap-southeast-1. 82 | /// 83 | /// 84 | /// Service URL 85 | /// The URL of the DynamoDB endpoint. This can be used instead of region. This property is commonly used for connecting to DynamoDB Local (e.g. http://localhost:8000/) 86 | /// 87 | /// 88 | /// Application 89 | /// Optional string attribute. Application is used to partition the session data in the table so it can be used for more than one application. 90 | /// 91 | /// 92 | /// Table 93 | /// Optional string attribute. The table used to store session data. The default is ASP.NET_SessionState. 94 | /// 95 | /// 96 | /// ReadCapacityUnits 97 | /// Optional int attribute. The read capacity units if the table is created. The default is 10. 98 | /// 99 | /// 100 | /// WriteCapacityUnits 101 | /// Optional int attribute. The write capacity units if the table is created. The default is 5. 102 | /// 103 | /// 104 | /// UseOnDemandReadWriteCapacity 105 | /// Optional boolean attribute. UseOnDemandReadWriteCapacity controls whether the table will be created with its read/write capacity set to On-Demand. Default is false. 106 | /// 107 | /// 108 | /// CreateIfNotExist 109 | /// Optional boolean attribute. CreateIfNotExist controls whether the table will be auto created if it doesn't exist. Default is true. 110 | /// 111 | /// 112 | /// StrictDisableSession 113 | /// Optional boolean attribute. If EnabledSessionState is False globally or on an individual page/view/controller, ASP.NET will still send a keepalive request to dynamo. Setting this to true disables keepalive requests when EnableSessionState is False. Default is false. 114 | /// 115 | /// 116 | /// TTLAttributeName 117 | /// Optional string attribute. The name of the TTL attribute for the table. This must be specified for session items to contain TTL-compatible data. 118 | /// 119 | /// 120 | /// TTLExpiredSessionsSeconds 121 | /// Optional int attribute. The minimum number of seconds after session expiration before sessions are eligible for TTL. By default this is 0. This value must be non-negative. 122 | /// 123 | /// 124 | /// 125 | /// 126 | public class DynamoDBSessionStateStore : SessionStateStoreProviderBase 127 | { 128 | private const string CURRENT_RECORD_FORMAT_VERSION = "1"; 129 | private const int DESCRIBE_INTERVAL = 5000; 130 | private const string ACTIVE_STATUS = "Active"; 131 | 132 | private static readonly GetItemOperationConfig CONSISTENT_READ_GET = new GetItemOperationConfig(); 133 | private static readonly UpdateItemOperationConfig LOCK_UPDATE_CONFIG = new UpdateItemOperationConfig(); 134 | 135 | private static readonly ILogger _logger = Logger.GetLogger(typeof(DynamoDBSessionStateStore)); 136 | 137 | private static readonly string _assemblyFileVersion = GetAssemblyFileVersion(); 138 | private static readonly string _userAgentSuffix = $"lib/SessionStateProvider#{_assemblyFileVersion}"; 139 | 140 | static DynamoDBSessionStateStore() 141 | { 142 | CONSISTENT_READ_GET.ConsistentRead = true; 143 | 144 | LOCK_UPDATE_CONFIG.Expected = new Document(); 145 | LOCK_UPDATE_CONFIG.Expected[ATTRIBUTE_LOCKED] = false; 146 | LOCK_UPDATE_CONFIG.ReturnValues = ReturnValues.AllNewAttributes; 147 | } 148 | 149 | // Possible config names set in the web.config. 150 | public const string CONFIG_ACCESSKEY = "AWSAccessKey"; 151 | public const string CONFIG_SECRETKEY = "AWSSecretKey"; 152 | public const string CONFIG_PROFILENAME = "AWSProfileName"; 153 | public const string CONFIG_PROFILESLOCATION = "AWSProfilesLocation"; 154 | public const string CONFIG_APPLICATION = "Application"; 155 | public const string CONFIG_TABLE = "Table"; 156 | public const string CONFIG_REGION = "Region"; 157 | public const string CONFIG_SERVICE_URL = "ServiceURL"; 158 | public const string CONFIG_INITIAL_READ_UNITS = "ReadCapacityUnits"; 159 | public const string CONFIG_INITIAL_WRITE_UNITS = "WriteCapacityUnits"; 160 | public const string CONFIG_ON_DEMAND_READ_WRITE_CAPACITY = "UseOnDemandReadWriteCapacity"; 161 | public const string CONFIG_CREATE_TABLE_IF_NOT_EXIST = "CreateIfNotExist"; 162 | public const string CONFIG_STRICT_DISABLE_SESSION = "StrictDisableSession"; 163 | public const string CONFIG_TTL_ATTRIBUTE = "TTLAttributeName"; 164 | public const string CONFIG_TTL_EXPIRED_SESSIONS_SECONDS = "TTLExpiredSessionsSeconds"; 165 | 166 | // This is not const because we will use whatever is the hash key defined for 167 | // the table as long as it is a string. 168 | private static string ATTRIBUTE_SESSION_ID = "SessionId"; 169 | 170 | // The attribute names stored for the session record. 171 | public const string ATTRIBUTE_CREATE_DATE = "CreateDate"; 172 | public const string ATTRIBUTE_LOCKED = "Locked"; 173 | public const string ATTRIBUTE_LOCK_DATE = "LockDate"; 174 | public const string ATTRIBUTE_LOCK_ID = "LockId"; 175 | public const string ATTRIBUTE_EXPIRES = "Expires"; 176 | public const string ATTRIBUTE_SESSION_ITEMS = "SessionItems"; 177 | public const string ATTRIBUTE_FLAGS = "Flags"; 178 | public const string ATTRIBUTE_RECORD_FORMAT_VERSION = "Ver"; 179 | 180 | const string DEFAULT_TABLENAME = "ASP.NET_SessionState"; 181 | 182 | // Fields that come from the web.config 183 | string _accessKey; 184 | string _secretKey; 185 | string _profileName; 186 | string _profilesLocation; 187 | string _tableName = DEFAULT_TABLENAME; 188 | string _regionName; 189 | string _serviceURL; 190 | string _application = ""; 191 | int _initialReadUnits = 10; 192 | int _initialWriteUnits = 5; 193 | bool _useOnDemandReadWriteCapacity = false; 194 | bool _createIfNotExist = true; 195 | bool _strictDisableSession = false; 196 | uint _ttlExtraSeconds = 0; 197 | string _ttlAttributeName = null; 198 | 199 | IAmazonDynamoDB _ddbClient; 200 | Table _table; 201 | TimeSpan _timeout = new TimeSpan(0, 20, 0); 202 | 203 | /// 204 | /// Default Constructor 205 | /// 206 | public DynamoDBSessionStateStore() 207 | { 208 | } 209 | 210 | /// 211 | /// Constructor for testing. 212 | /// 213 | /// 214 | public DynamoDBSessionStateStore(IAmazonDynamoDB ddbClient) 215 | { 216 | this._ddbClient = ddbClient; 217 | SetupTable(); 218 | } 219 | 220 | /// 221 | /// Constructor for testing. 222 | /// 223 | /// 224 | /// 225 | public DynamoDBSessionStateStore(string name, NameValueCollection config) 226 | { 227 | Initialize(name, config); 228 | } 229 | 230 | /// 231 | /// Gets the name of the table used to store session data. 232 | /// 233 | public string TableName 234 | { 235 | get { return this._tableName; } 236 | } 237 | 238 | /// 239 | /// Initializes the provider by pulling the config info from the web.config and validate/create the DynamoDB table. 240 | /// If the table is being created this method will block until the table is active. 241 | /// 242 | /// 243 | /// 244 | public override void Initialize(string name, NameValueCollection config) 245 | { 246 | _logger.InfoFormat("Initialize : Initializing Session provider {0}", name); 247 | 248 | if (config == null) 249 | throw new ArgumentNullException("config"); 250 | 251 | base.Initialize(name, config); 252 | 253 | GetConfigSettings(config); 254 | 255 | 256 | RegionEndpoint region = null; 257 | if(!string.IsNullOrEmpty(this._regionName)) 258 | region = RegionEndpoint.GetBySystemName(this._regionName); 259 | 260 | AWSCredentials credentials = null; 261 | if (!string.IsNullOrEmpty(this._accessKey)) 262 | { 263 | credentials = new BasicAWSCredentials(this._accessKey, this._secretKey); 264 | } 265 | else if (!string.IsNullOrEmpty(this._profileName)) 266 | { 267 | if (string.IsNullOrEmpty(this._profilesLocation)) 268 | credentials = new StoredProfileAWSCredentials(this._profileName); 269 | else 270 | credentials = new StoredProfileAWSCredentials(this._profileName, this._profilesLocation); 271 | } 272 | 273 | AmazonDynamoDBConfig ddbConfig = new AmazonDynamoDBConfig(); 274 | 275 | if (region != null) 276 | ddbConfig.RegionEndpoint = region; 277 | if (!string.IsNullOrEmpty(this._serviceURL)) 278 | ddbConfig.ServiceURL = this._serviceURL; 279 | 280 | if (credentials != null) 281 | { 282 | this._ddbClient = new AmazonDynamoDBClient(credentials, ddbConfig); 283 | } 284 | else 285 | { 286 | this._ddbClient = new AmazonDynamoDBClient(ddbConfig); 287 | } 288 | 289 | ((AmazonDynamoDBClient)this._ddbClient).BeforeRequestEvent += DynamoDBSessionStateStore_BeforeRequestEvent; 290 | 291 | SetupTable(); 292 | } 293 | 294 | const string UserAgentHeader = "User-Agent"; 295 | void DynamoDBSessionStateStore_BeforeRequestEvent(object sender, RequestEventArgs e) 296 | { 297 | Amazon.Runtime.WebServiceRequestEventArgs args = e as Amazon.Runtime.WebServiceRequestEventArgs; 298 | if (args == null || !args.Headers.ContainsKey(UserAgentHeader) || args.Headers[UserAgentHeader].Contains(_userAgentSuffix)) 299 | return; 300 | 301 | args.Headers[UserAgentHeader] = args.Headers[UserAgentHeader] + " " + _userAgentSuffix; 302 | } 303 | 304 | private static string GetAssemblyFileVersion() 305 | { 306 | var assembly = typeof(DynamoDBSessionStateStore).Assembly; 307 | var attribute = (AssemblyFileVersionAttribute)Attribute.GetCustomAttribute(assembly, typeof(AssemblyFileVersionAttribute)); 308 | 309 | var version = attribute == null ? "Unknown" : attribute.Version; 310 | return version; 311 | } 312 | 313 | private void SetupTable() 314 | { 315 | try 316 | { 317 | var tableConfig = CreateTableConfig(); 318 | this._table = Table.LoadTable(this._ddbClient, tableConfig); 319 | } 320 | catch (ResourceNotFoundException) { } 321 | 322 | if (this._table == null) 323 | { 324 | if (this._createIfNotExist) 325 | this._table = CreateTable(); 326 | else 327 | throw new AmazonDynamoDBException(string.Format("Table {0} was not found to be used to store session state and autocreate is turned off.", this._tableName)); 328 | } 329 | else 330 | { 331 | ValidateTable(); 332 | } 333 | } 334 | 335 | private void GetConfigSettings(NameValueCollection config) 336 | { 337 | this._accessKey = config[CONFIG_ACCESSKEY]; 338 | this._secretKey = config[CONFIG_SECRETKEY]; 339 | this._profileName= config[CONFIG_PROFILENAME]; 340 | this._profilesLocation = config[CONFIG_PROFILESLOCATION]; 341 | this._regionName = config[CONFIG_REGION]; 342 | this._serviceURL = config[CONFIG_SERVICE_URL]; 343 | 344 | if (!string.IsNullOrEmpty(config[CONFIG_TABLE])) 345 | { 346 | this._tableName = config[CONFIG_TABLE]; 347 | } 348 | 349 | if (!string.IsNullOrEmpty(config[CONFIG_APPLICATION])) 350 | { 351 | this._application = config[CONFIG_APPLICATION]; 352 | } 353 | 354 | if (!string.IsNullOrEmpty(config[CONFIG_CREATE_TABLE_IF_NOT_EXIST])) 355 | { 356 | this._createIfNotExist = bool.Parse(config[CONFIG_CREATE_TABLE_IF_NOT_EXIST]); 357 | } 358 | 359 | if (!string.IsNullOrEmpty(config[CONFIG_INITIAL_READ_UNITS])) 360 | { 361 | this._initialReadUnits = int.Parse(config[CONFIG_INITIAL_READ_UNITS]); 362 | } 363 | 364 | if (!string.IsNullOrEmpty(config[CONFIG_INITIAL_WRITE_UNITS])) 365 | { 366 | this._initialWriteUnits = int.Parse(config[CONFIG_INITIAL_WRITE_UNITS]); 367 | } 368 | 369 | if (!string.IsNullOrEmpty(config[CONFIG_ON_DEMAND_READ_WRITE_CAPACITY])) 370 | { 371 | this._useOnDemandReadWriteCapacity = bool.Parse(config[CONFIG_ON_DEMAND_READ_WRITE_CAPACITY]); 372 | } 373 | 374 | if (!string.IsNullOrEmpty(config[CONFIG_STRICT_DISABLE_SESSION])) 375 | { 376 | this._strictDisableSession = bool.Parse(config[CONFIG_STRICT_DISABLE_SESSION]); 377 | } 378 | 379 | if (!string.IsNullOrEmpty(config[CONFIG_TTL_ATTRIBUTE])) 380 | { 381 | this._ttlAttributeName = config[CONFIG_TTL_ATTRIBUTE]; 382 | } 383 | 384 | if (!string.IsNullOrEmpty(config[CONFIG_TTL_EXPIRED_SESSIONS_SECONDS])) 385 | { 386 | this._ttlExtraSeconds = uint.Parse(config[CONFIG_TTL_EXPIRED_SESSIONS_SECONDS]); 387 | } 388 | 389 | string applicationName = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath; 390 | if (applicationName != null) 391 | { 392 | Configuration cfg = WebConfigurationManager.OpenWebConfiguration(applicationName); 393 | if (cfg != null) 394 | { 395 | SessionStateSection sessionConfig = cfg.GetSection("system.web/sessionState") as SessionStateSection; 396 | if (sessionConfig != null) 397 | { 398 | this._timeout = sessionConfig.Timeout; 399 | } 400 | } 401 | } 402 | } 403 | 404 | /// 405 | /// Provider returns false for this method. 406 | /// 407 | /// 408 | /// 409 | public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback) 410 | { 411 | return false; 412 | } 413 | 414 | /// 415 | /// Returns read-only session-state data from the DynamoDB table. 416 | /// 417 | /// 418 | /// 419 | /// 420 | /// 421 | /// 422 | /// 423 | /// 424 | public override SessionStateStoreData GetItem(HttpContext context, 425 | string sessionId, 426 | out bool locked, 427 | out TimeSpan lockAge, 428 | out object lockId, 429 | out SessionStateActions actionFlags) 430 | { 431 | LogInfo("GetItem", sessionId, context); 432 | 433 | return GetSessionStoreItem(false, context, sessionId, out locked, 434 | out lockAge, out lockId, out actionFlags); 435 | } 436 | 437 | /// 438 | /// Returns session-state data from the DynamoDB table. 439 | /// 440 | /// 441 | /// 442 | /// 443 | /// 444 | /// 445 | /// 446 | /// 447 | public override SessionStateStoreData GetItemExclusive(HttpContext context, 448 | string sessionId, 449 | out bool locked, 450 | out TimeSpan lockAge, 451 | out object lockId, 452 | out SessionStateActions actionFlags) 453 | { 454 | LogInfo("GetItemExclusive", sessionId, context); 455 | return GetSessionStoreItem(true, context, sessionId, out locked, 456 | out lockAge, out lockId, out actionFlags); 457 | } 458 | 459 | /// 460 | /// Get the session for DynamoDB and optionally lock the record. 461 | /// 462 | /// 463 | /// 464 | /// 465 | /// 466 | /// 467 | /// 468 | /// 469 | /// 470 | private SessionStateStoreData GetSessionStoreItem(bool lockRecord, 471 | HttpContext context, 472 | string sessionId, 473 | out bool locked, 474 | out TimeSpan lockAge, 475 | out object lockId, 476 | out SessionStateActions actionFlags) 477 | { 478 | LogInfo("GetSessionStoreItem", sessionId, lockRecord, context); 479 | 480 | // Initial values for return value and out parameters. 481 | SessionStateStoreData item = null; 482 | lockAge = TimeSpan.Zero; 483 | lockId = Guid.NewGuid().ToString(); 484 | locked = false; 485 | actionFlags = SessionStateActions.None; 486 | 487 | bool foundRecord = false; 488 | bool deleteData = false; 489 | 490 | DateTime newLockedDate = DateTime.Now; 491 | 492 | 493 | Document session = null; 494 | if (lockRecord) 495 | { 496 | Document lockDoc = new Document(); 497 | lockDoc[ATTRIBUTE_SESSION_ID] = GetHashKey(sessionId); 498 | lockDoc[ATTRIBUTE_LOCK_ID] = lockId.ToString(); 499 | lockDoc[ATTRIBUTE_LOCKED] = true; 500 | lockDoc[ATTRIBUTE_LOCK_DATE] = DateTime.Now; 501 | 502 | try 503 | { 504 | session = this._table.UpdateItem(lockDoc, LOCK_UPDATE_CONFIG); 505 | locked = false; 506 | } 507 | catch (ConditionalCheckFailedException) 508 | { 509 | // This means the record is already locked by another request. 510 | locked = true; 511 | } 512 | } 513 | 514 | if (session == null) 515 | { 516 | session = this._table.GetItem(GetHashKey(sessionId), CONSISTENT_READ_GET); 517 | if (session == null && lockRecord) 518 | { 519 | locked = true; 520 | } 521 | } 522 | 523 | string serializedItems = null; 524 | if (session != null) 525 | { 526 | DateTime expire = (DateTime)session[ATTRIBUTE_EXPIRES]; 527 | 528 | if (expire < DateTime.Now) 529 | { 530 | deleteData = true; 531 | locked = false; 532 | } 533 | else 534 | { 535 | foundRecord = true; 536 | 537 | DynamoDBEntry entry; 538 | if (session.TryGetValue(ATTRIBUTE_SESSION_ITEMS, out entry)) 539 | { 540 | serializedItems = (string)entry; 541 | } 542 | 543 | if (session.Contains(ATTRIBUTE_LOCK_ID)) 544 | lockId = (string)session[ATTRIBUTE_LOCK_ID]; 545 | 546 | 547 | if (session.Contains(ATTRIBUTE_FLAGS)) 548 | actionFlags = (SessionStateActions)((int)session[ATTRIBUTE_FLAGS]); 549 | 550 | if (session.Contains(ATTRIBUTE_LOCK_DATE) && session[ATTRIBUTE_LOCK_DATE] != null) 551 | { 552 | DateTime lockDate = (DateTime)session[ATTRIBUTE_LOCK_DATE]; 553 | lockAge = DateTime.Now.Subtract(lockDate); 554 | } 555 | } 556 | } 557 | 558 | if (deleteData) 559 | { 560 | this.deleteItem(sessionId); 561 | } 562 | 563 | // The record was not found. Ensure that locked is false. 564 | if (!foundRecord) 565 | { 566 | locked = false; 567 | lockId = null; 568 | } 569 | 570 | // If the record was found and you obtained a lock, then clear the actionFlags, 571 | // and create the SessionStateStoreItem to return. 572 | if (foundRecord && !locked) 573 | { 574 | if (actionFlags == SessionStateActions.InitializeItem) 575 | { 576 | Document updateDoc = new Document(); 577 | updateDoc[ATTRIBUTE_SESSION_ID] = GetHashKey(sessionId); 578 | updateDoc[ATTRIBUTE_FLAGS] = 0; 579 | this._table.UpdateItem(updateDoc); 580 | 581 | item = CreateNewStoreData(context, (int)this._timeout.TotalMinutes); 582 | } 583 | else 584 | { 585 | item = deserialize(context, serializedItems, (int)this._timeout.TotalMinutes); 586 | } 587 | } 588 | 589 | return item; 590 | } 591 | 592 | /// 593 | /// Updates the session-item information in the session-state data store with values from the current request, and clears the lock on the data. 594 | /// 595 | /// The HttpContext for the current request. 596 | /// The session identifier for the current request. 597 | /// The SessionStateStoreData object that contains the current session values to be stored. 598 | /// The lock identifier for the current request. 599 | /// true to identify the session item as a new item; false to identify the session item as an existing item. 600 | public override void SetAndReleaseItemExclusive(HttpContext context, 601 | string sessionId, 602 | SessionStateStoreData item, 603 | object lockId, 604 | bool newItem) 605 | { 606 | LogInfo("SetAndReleaseItemExclusive", sessionId, lockId, newItem, context); 607 | 608 | string serialized = serialize(item.Items as SessionStateItemCollection); 609 | var expiration = DateTime.Now.Add(this._timeout); 610 | 611 | Document newValues = new Document(); 612 | newValues[ATTRIBUTE_SESSION_ID] = GetHashKey(sessionId); 613 | newValues[ATTRIBUTE_LOCKED] = false; 614 | newValues[ATTRIBUTE_LOCK_ID] = null; 615 | newValues[ATTRIBUTE_LOCK_DATE] = DateTime.Now; 616 | newValues[ATTRIBUTE_EXPIRES] = expiration; 617 | newValues[ATTRIBUTE_FLAGS] = 0; 618 | newValues[ATTRIBUTE_SESSION_ITEMS] = serialized; 619 | newValues[ATTRIBUTE_RECORD_FORMAT_VERSION] = CURRENT_RECORD_FORMAT_VERSION; 620 | SetTTLAttribute(newValues, expiration); 621 | 622 | if (newItem) 623 | { 624 | newValues[ATTRIBUTE_CREATE_DATE] = DateTime.Now; 625 | this._table.PutItem(newValues); 626 | } 627 | else 628 | { 629 | Document expected = new Document(); 630 | expected[ATTRIBUTE_LOCK_ID] = lockId.ToString(); 631 | 632 | // Not really any reason the condition should fail unless we get in some sort of weird 633 | // app pool reset mode. 634 | try 635 | { 636 | this._table.UpdateItem(newValues, new UpdateItemOperationConfig() { Expected = expected }); 637 | } 638 | catch (ConditionalCheckFailedException) { LogInfo("(SetAndReleaseItemExclusive) Conditional check failed for update.", sessionId, context); } 639 | } 640 | } 641 | 642 | /// 643 | /// Releases a lock on an item in the session data store. 644 | /// 645 | /// The HttpContext for the current request. 646 | /// The session identifier for the current request. 647 | /// The lock identifier for the current request. 648 | public override void ReleaseItemExclusive(HttpContext context, string sessionId, object lockId) 649 | { 650 | LogInfo("ReleaseItemExclusive", sessionId, lockId, context); 651 | 652 | Document doc = this._table.GetItem(GetHashKey(sessionId), CONSISTENT_READ_GET); 653 | if (doc == null) 654 | { 655 | LogError("ReleaseItemExclusive Failed to retrieve state for session id: " + sessionId, sessionId, lockId, context); 656 | return; 657 | } 658 | 659 | var expiration = DateTime.Now.Add(this._timeout); 660 | doc[ATTRIBUTE_LOCKED] = false; 661 | doc[ATTRIBUTE_EXPIRES] = expiration; 662 | SetTTLAttribute(doc, expiration); 663 | 664 | Document expected = new Document(); 665 | expected[ATTRIBUTE_LOCK_ID] = lockId.ToString(); 666 | 667 | try 668 | { 669 | this._table.UpdateItem(doc, new UpdateItemOperationConfig() { Expected = expected }); 670 | } 671 | catch (ConditionalCheckFailedException) { LogInfo("(ReleaseItemExclusive) Conditional check failed for update.", sessionId, context); } 672 | } 673 | 674 | /// 675 | /// Removes the session record for DynamoDB. 676 | /// 677 | /// 678 | /// 679 | /// 680 | /// 681 | public override void RemoveItem(HttpContext context, string sessionId, object lockId, SessionStateStoreData item) 682 | { 683 | LogInfo("RemoveItem", sessionId, lockId, context); 684 | 685 | if (lockId == null) 686 | { 687 | deleteItem(sessionId); 688 | } 689 | else 690 | { 691 | Document doc = this._table.GetItem(GetHashKey(sessionId), CONSISTENT_READ_GET); 692 | if (doc.Contains(ATTRIBUTE_LOCK_ID)) 693 | { 694 | string currentLockId = (string)doc[ATTRIBUTE_LOCK_ID]; 695 | if (string.Equals(currentLockId, lockId)) 696 | { 697 | deleteItem(sessionId); 698 | } 699 | } 700 | } 701 | } 702 | 703 | /// 704 | /// Creates an initial session record in the DynamoDB table. 705 | /// 706 | /// 707 | /// 708 | /// 709 | public override void CreateUninitializedItem(HttpContext context, string sessionId, int timeout) 710 | { 711 | LogInfo("CreateUninitializedItem", sessionId, timeout, context); 712 | 713 | var expiration = DateTime.Now.Add(this._timeout); 714 | Document session = new Document(); 715 | session[ATTRIBUTE_SESSION_ID] = GetHashKey(sessionId); 716 | session[ATTRIBUTE_LOCKED] = false; 717 | session[ATTRIBUTE_CREATE_DATE] = DateTime.Now; 718 | session[ATTRIBUTE_EXPIRES] = expiration; 719 | session[ATTRIBUTE_FLAGS] = 1; 720 | session[ATTRIBUTE_RECORD_FORMAT_VERSION] = CURRENT_RECORD_FORMAT_VERSION; 721 | SetTTLAttribute(session, expiration); 722 | this._table.PutItem(session); 723 | } 724 | 725 | /// 726 | /// Creates a new SessionStateStoreData object to be used for the current request. 727 | /// 728 | /// 729 | /// 730 | /// 731 | public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout) 732 | { 733 | LogInfo("CreateNewStoreData", timeout, context); 734 | 735 | HttpStaticObjectsCollection sessionStatics = null; 736 | if (context != null) 737 | sessionStatics = SessionStateUtility.GetSessionStaticObjects(context); 738 | return new SessionStateStoreData(new SessionStateItemCollection(), sessionStatics, timeout); 739 | } 740 | 741 | /// 742 | /// Updates the expiration date and time of an item in the DynamoDB table. 743 | /// 744 | /// 745 | /// 746 | public override void ResetItemTimeout(HttpContext context, string sessionId) 747 | { 748 | LogInfo("ResetItemTimeout", sessionId, context); 749 | 750 | var suppressKeepalive = _strictDisableSession && context.Session == null; 751 | if (suppressKeepalive) 752 | return; 753 | 754 | var expiration = DateTime.Now.Add(this._timeout); 755 | Document doc = new Document(); 756 | doc[ATTRIBUTE_SESSION_ID] = GetHashKey(sessionId); 757 | doc[ATTRIBUTE_LOCKED] = false; 758 | doc[ATTRIBUTE_EXPIRES] = expiration; 759 | SetTTLAttribute(doc, expiration); 760 | this._table.UpdateItem(doc); 761 | } 762 | 763 | /// 764 | /// A utility method for cleaning up expired sessions that IIS failed to delete. The method performs a scan on the ASP.NET_SessionState table 765 | /// with a condition that the expiration date is in the past and calls delete on all the keys returned. Scans can be costly on performance 766 | /// so use this method sparingly like a nightly or weekly clean job. 767 | /// 768 | /// The AmazonDynamoDB client used to find a delete expired sessions. 769 | public static void DeleteExpiredSessions(IAmazonDynamoDB dbClient) 770 | { 771 | LogInfo("DeleteExpiredSessions"); 772 | DeleteExpiredSessions(dbClient, DEFAULT_TABLENAME); 773 | } 774 | 775 | /// 776 | /// A utility method for cleaning up expired sessions that IIS failed to delete. The method performs a scan on the table 777 | /// with a condition that the expiration date is in the past and calls delete on all the keys returned. Scans can be costly on performance 778 | /// so use this method sparingly like a nightly or weekly clean job. 779 | /// 780 | /// The AmazonDynamoDB client used to find a delete expired sessions. 781 | /// The table to search. 782 | public static void DeleteExpiredSessions(IAmazonDynamoDB dbClient, string tableName) 783 | { 784 | LogInfo("DeleteExpiredSessions"); 785 | var tableConfig = CreateTableConfig(tableName); 786 | Table table = Table.LoadTable(dbClient, tableConfig); 787 | 788 | 789 | ScanFilter filter = new ScanFilter(); 790 | filter.AddCondition(ATTRIBUTE_EXPIRES, ScanOperator.LessThan, DateTime.Now); 791 | 792 | ScanOperationConfig config = new ScanOperationConfig(); 793 | config.AttributesToGet = new List { ATTRIBUTE_SESSION_ID }; 794 | config.Select = SelectValues.SpecificAttributes; 795 | config.Filter = filter; 796 | 797 | Search search = table.Scan(config); 798 | 799 | do 800 | { 801 | DocumentBatchWrite batchWrite = table.CreateBatchWrite(); 802 | List page = search.GetNextSet(); 803 | foreach (var document in page) 804 | { 805 | batchWrite.AddItemToDelete(document); 806 | } 807 | 808 | batchWrite.Execute(); 809 | } while (!search.IsDone); 810 | } 811 | 812 | /// 813 | /// Empty implementation of the override. 814 | /// 815 | public override void Dispose() 816 | { 817 | } 818 | 819 | /// 820 | /// Empty implementation of the override. 821 | /// 822 | /// 823 | public override void InitializeRequest(HttpContext context) 824 | { 825 | } 826 | 827 | /// 828 | /// Empty implementation of the override. 829 | /// 830 | /// 831 | public override void EndRequest(HttpContext context) 832 | { 833 | } 834 | 835 | 836 | private Table CreateTable() 837 | { 838 | CreateTableRequest createRequest = new CreateTableRequest 839 | { 840 | TableName = this._tableName, 841 | KeySchema = new List 842 | { 843 | new KeySchemaElement 844 | { 845 | AttributeName = ATTRIBUTE_SESSION_ID, KeyType = "HASH" 846 | } 847 | }, 848 | AttributeDefinitions = new List 849 | { 850 | new AttributeDefinition 851 | { 852 | AttributeName = ATTRIBUTE_SESSION_ID, AttributeType = "S" 853 | } 854 | } 855 | }; 856 | 857 | if (this._useOnDemandReadWriteCapacity) 858 | { 859 | createRequest.BillingMode = BillingMode.PAY_PER_REQUEST; 860 | } 861 | else 862 | { 863 | createRequest.ProvisionedThroughput = new ProvisionedThroughput 864 | { 865 | ReadCapacityUnits = this._initialReadUnits, 866 | WriteCapacityUnits = this._initialWriteUnits 867 | }; 868 | } 869 | 870 | CreateTableResponse response = this._ddbClient.CreateTable(createRequest); 871 | 872 | DescribeTableRequest descRequest = new DescribeTableRequest 873 | { 874 | TableName = this._tableName 875 | }; 876 | 877 | // Wait till table is active 878 | bool isActive = false; 879 | while (!isActive) 880 | { 881 | Thread.Sleep(DESCRIBE_INTERVAL); 882 | DescribeTableResponse descResponse = this._ddbClient.DescribeTable(descRequest); 883 | string tableStatus = descResponse.Table.TableStatus; 884 | 885 | if (string.Equals(tableStatus, ACTIVE_STATUS, StringComparison.InvariantCultureIgnoreCase)) 886 | isActive = true; 887 | } 888 | 889 | if (!string.IsNullOrEmpty(this._ttlAttributeName)) 890 | { 891 | this._ddbClient.UpdateTimeToLive(new UpdateTimeToLiveRequest 892 | { 893 | TableName = this._tableName, 894 | TimeToLiveSpecification = new TimeToLiveSpecification 895 | { 896 | AttributeName = this._ttlAttributeName, 897 | Enabled = true 898 | } 899 | }); 900 | } 901 | 902 | var tableConfig = CreateTableConfig(); 903 | Table table = Table.LoadTable(this._ddbClient, tableConfig); 904 | return table; 905 | } 906 | private TableConfig CreateTableConfig() 907 | { 908 | var tableConfig = CreateTableConfig(this._tableName); 909 | if (!string.IsNullOrEmpty(this._ttlAttributeName)) 910 | { 911 | tableConfig.AttributesToStoreAsEpoch.Add(this._ttlAttributeName); 912 | } 913 | return tableConfig; 914 | } 915 | private static TableConfig CreateTableConfig(string tableName) 916 | { 917 | var tableConfig = new TableConfig(tableName) 918 | { 919 | Conversion = DynamoDBEntryConversion.V1 920 | }; 921 | return tableConfig; 922 | } 923 | 924 | /// 925 | /// Make sure existing table is valid to be used as a session store. 926 | /// 927 | private void ValidateTable() 928 | { 929 | if (this._table.HashKeys.Count != 1) 930 | throw new AmazonDynamoDBException(string.Format("Table {0} cannot be used to store session data because it does not define a single hash key", this._tableName)); 931 | string hashKey = this._table.HashKeys[0]; 932 | KeyDescription hashKeyDescription = this._table.Keys[hashKey]; 933 | if (hashKeyDescription.Type != DynamoDBEntryType.String) 934 | throw new AmazonDynamoDBException(string.Format("Table {0} cannot be used to store session data because hash key is not a string.", this._tableName)); 935 | 936 | if (this._table.RangeKeys.Count > 0) 937 | throw new AmazonDynamoDBException(string.Format("Table {0} cannot be used to store session data because it contains a range key in its schema.", this._tableName)); 938 | 939 | ATTRIBUTE_SESSION_ID = hashKey; 940 | } 941 | 942 | private void SetTTLAttribute(Document doc, DateTime expiration) 943 | { 944 | if (!string.IsNullOrEmpty(this._ttlAttributeName)) 945 | doc[this._ttlAttributeName] = expiration.AddSeconds(_ttlExtraSeconds); 946 | } 947 | 948 | private void deleteItem(string sessionId) 949 | { 950 | Document doc = new Document(); 951 | doc[ATTRIBUTE_SESSION_ID] = GetHashKey(sessionId); 952 | this._table.DeleteItem(doc); 953 | } 954 | 955 | private string serialize(SessionStateItemCollection items) 956 | { 957 | MemoryStream ms = new MemoryStream(); 958 | BinaryWriter writer = new BinaryWriter(ms); 959 | 960 | if (items != null) 961 | items.Serialize(writer); 962 | 963 | writer.Close(); 964 | 965 | return Convert.ToBase64String(ms.ToArray()); 966 | } 967 | 968 | private SessionStateStoreData deserialize(HttpContext context, string serializedItems, int timeout) 969 | { 970 | SessionStateItemCollection sessionItems = new SessionStateItemCollection(); 971 | if (serializedItems != null) 972 | { 973 | MemoryStream ms = 974 | new MemoryStream(Convert.FromBase64String(serializedItems)); 975 | 976 | if (ms.Length > 0) 977 | { 978 | BinaryReader reader = new BinaryReader(ms); 979 | sessionItems = SessionStateItemCollection.Deserialize(reader); 980 | } 981 | } 982 | 983 | HttpStaticObjectsCollection statics = null; 984 | if (context != null) 985 | statics = SessionStateUtility.GetSessionStaticObjects(context); 986 | return new SessionStateStoreData(sessionItems, statics, timeout); 987 | } 988 | 989 | /// 990 | /// Combine application and session id for hash key. 991 | /// 992 | /// 993 | /// 994 | private string GetHashKey(string sessionId) 995 | { 996 | if (string.IsNullOrEmpty(this._application)) 997 | return sessionId; 998 | 999 | return string.Format("{0}-{1}", this._application, sessionId); 1000 | } 1001 | 1002 | private static void LogInfo(string methodName) 1003 | { 1004 | _logger.InfoFormat("{0}", methodName); 1005 | } 1006 | 1007 | private static void LogInfo(string methodName, string sessionId, HttpContext context) 1008 | { 1009 | _logger.InfoFormat("{0} : SessionId {1}, Context {2}", methodName, sessionId ?? "NULL", 1010 | context == null ? "NULL" : "HttpContext"); 1011 | } 1012 | 1013 | private static void LogInfo(string methodName, string sessionId, bool lockRecord, HttpContext context) 1014 | { 1015 | _logger.InfoFormat("{0} : SessionId {1}, LockRecord {2}, Context {3} ", 1016 | methodName, sessionId ?? "NULL", lockRecord, 1017 | context == null ? "NULL" : "HttpContext"); 1018 | } 1019 | 1020 | private static void LogInfo(string methodName, string sessionId, object lockId, bool newItem, HttpContext context) 1021 | { 1022 | _logger.InfoFormat("{0} : SessionId {1}, LockId {2}, NewItem {3}, Context {4} ", 1023 | methodName, sessionId ?? "NULL", 1024 | lockId == null ? "NULL" : lockId.ToString(), newItem, 1025 | context == null ? "NULL" : "HttpContext"); 1026 | } 1027 | 1028 | private static void LogInfo(string methodName, string sessionId, object lockId, HttpContext context) 1029 | { 1030 | _logger.InfoFormat("{0} : SessionId {1}, LockId {2}, Context {3} ", methodName, sessionId ?? "NULL", 1031 | lockId == null ? "NULL" : lockId.ToString(), 1032 | context == null ? "NULL" : "HttpContext"); 1033 | } 1034 | 1035 | private static void LogInfo(string methodName, string sessionId, int timeout, HttpContext context) 1036 | { 1037 | _logger.InfoFormat("{0} : SessionId {1}, Timeout {2}, Context {3} ", methodName, sessionId ?? "NULL", 1038 | timeout, context == null ? "NULL" : "HttpContext"); 1039 | } 1040 | 1041 | private static void LogInfo(string methodName, int timeout, HttpContext context) 1042 | { 1043 | _logger.InfoFormat("{0} : Timeout {1}, Context {2} ", methodName, 1044 | timeout, context == null ? "NULL" : "HttpContext"); 1045 | } 1046 | 1047 | private static void LogError(string methodName, string sessionId, object lockId, HttpContext context) 1048 | { 1049 | string message = string.Format("{0} : SessionId {1}, LockId {2}, Context {3} ", methodName, sessionId ?? "NULL", 1050 | lockId == null ? "NULL" : lockId.ToString(), 1051 | context == null ? "NULL" : "HttpContext"); 1052 | _logger.Error(new Exception(message), message); 1053 | } 1054 | 1055 | private static void LogError(string methodName, Exception exception) 1056 | { 1057 | _logger.Error(exception, "{0} : {1}", methodName, exception.Message); 1058 | } 1059 | } 1060 | } 1061 | -------------------------------------------------------------------------------- /src/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | 9 | #if (NET35 ||NET40) 10 | [assembly: AssemblyTitle("AWS.SessionProvider (.NET 3.5)")] 11 | #else 12 | [assembly: AssemblyTitle("AWS.SessionProvider (.NET 4.5)")] 13 | #endif 14 | [assembly: AssemblyDescription("Amazon Web Services Session Provider Extensions")] 15 | [assembly: AssemblyConfiguration("")] 16 | [assembly: AssemblyCompany("Amazon.com, Inc")] 17 | [assembly: AssemblyProduct("Amazon Web Services Session Provider Extensions")] 18 | [assembly: AssemblyCopyright("Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.")] 19 | [assembly: AssemblyTrademark("")] 20 | [assembly: AssemblyCulture("")] 21 | 22 | // Setting ComVisible to false makes the types in this assembly not visible 23 | // to COM components. If you need to access a type in this assembly from 24 | // COM, set the ComVisible attribute to true on that type. 25 | [assembly: ComVisible(false)] 26 | 27 | // The following GUID is for the ID of the typelib if this project is exposed to COM 28 | [assembly: Guid("972e230e-a3b8-4854-9b6e-805a4e73398d")] 29 | 30 | // Version information for an assembly consists of the following four values: 31 | // 32 | // Major Version 33 | // Minor Version 34 | // Build Number 35 | // Revision 36 | // 37 | // You can specify all the values or you can default the Build and Revision Numbers 38 | // by using the '*' as shown below: 39 | // [assembly: AssemblyVersion("1.0.*")] 40 | [assembly: AssemblyVersion("4.0")] 41 | [assembly: AssemblyFileVersion("4.1.2.0")] 42 | -------------------------------------------------------------------------------- /src/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/public.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-dotnet-session-provider/9d80ddf025b34e06503febe59ca9e93234bb6d5d/src/public.snk -------------------------------------------------------------------------------- /test/AWS.SessionProvider.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Debug 10 | AnyCPU 11 | {2418CFD0-AC81-4425-9CDC-9CAF450A9855} 12 | Library 13 | Properties 14 | AWS.SessionProvider.Test 15 | AWS.SessionProvider.Test 16 | v4.8 17 | 512 18 | ..\ 19 | true 20 | 21 | 22 | 23 | 24 | 25 | true 26 | full 27 | false 28 | bin\Debug\ 29 | DEBUG;TRACE 30 | prompt 31 | 4 32 | 33 | 34 | pdbonly 35 | true 36 | bin\Release\ 37 | TRACE 38 | prompt 39 | 4 40 | 41 | 42 | 43 | ..\packages\AWSSDK.Core.3.7.303.14\lib\net45\AWSSDK.Core.dll 44 | 45 | 46 | ..\packages\AWSSDK.DynamoDBv2.3.7.302.15\lib\net45\AWSSDK.DynamoDBv2.dll 47 | 48 | 49 | ..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll 50 | 51 | 52 | ..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll 65 | 66 | 67 | ..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll 68 | 69 | 70 | ..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll 71 | 72 | 73 | ..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | {c73b3233-cb6f-4fa7-9fff-cc6fd64100c3} 83 | AWS.SessionProvider.Net45 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 119 | -------------------------------------------------------------------------------- /test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AWS.SessionProvider.Test")] 9 | [assembly: AssemblyDescription("Amazon Web Services Session Provider Extensions Tests")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Amazon.com, Inc")] 12 | [assembly: AssemblyProduct("Amazon Web Services Session Provider Extensions Tests")] 13 | [assembly: AssemblyCopyright("Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("d1476e36-5859-4ad5-8785-0b67fff938eb")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /test/SessionStoreTest.cs: -------------------------------------------------------------------------------- 1 | using Amazon; 2 | using Amazon.DynamoDBv2; 3 | using Amazon.DynamoDBv2.DocumentModel; 4 | using Amazon.DynamoDBv2.Model; 5 | using Amazon.SessionProvider; 6 | using Amazon.Util; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Collections.Specialized; 10 | using System.Linq; 11 | using System.Reflection; 12 | using System.Threading; 13 | using System.Web.SessionState; 14 | using Xunit; 15 | 16 | namespace AWS.SessionProvider.Test 17 | { 18 | public class SessionStoreTest : IDisposable 19 | { 20 | private static DynamoDBSessionStateStore store; 21 | private string tableName; 22 | private const string ttlAttributeName = "TTL"; 23 | private readonly int ttlExpiredSessionsSeconds = (int)TimeSpan.FromDays(7).TotalSeconds; 24 | private static string sessionId = DateTime.Now.ToFileTime().ToString(); 25 | private static TimeSpan newTimeout = TimeSpan.FromSeconds(5); 26 | private static FieldInfo timeoutField; 27 | private static TimeSpan waitPeriod = TimeSpan.FromSeconds(10); 28 | private static TimeSpan tableActiveMaxTime = TimeSpan.FromMinutes(5); 29 | private static RegionEndpoint region = RegionEndpoint.USEast1; 30 | 31 | private static AmazonDynamoDBClient CreateClient() 32 | { 33 | var client = new AmazonDynamoDBClient(region); 34 | return client; 35 | } 36 | 37 | public SessionStoreTest() 38 | { 39 | timeoutField = typeof(DynamoDBSessionStateStore).GetField("_timeout", BindingFlags.NonPublic | BindingFlags.Instance); 40 | Assert.NotNull(timeoutField); 41 | } 42 | 43 | public void Dispose() 44 | { 45 | using (var client = CreateClient()) 46 | { 47 | client.DeleteTable(new DeleteTableRequest 48 | { 49 | TableName = this.tableName 50 | }); 51 | WaitUntilTableReady(client, null); 52 | } 53 | } 54 | 55 | [Fact] 56 | public void DynamoDBSessionStateStoreTest() 57 | { 58 | var config = new NameValueCollection(); 59 | config.Add(DynamoDBSessionStateStore.CONFIG_REGION, region.SystemName); 60 | config.Add(DynamoDBSessionStateStore.CONFIG_TABLE, "SessionStoreWithUserSpecifiedCapacity"); 61 | config.Add(DynamoDBSessionStateStore.CONFIG_APPLICATION, "IntegTest"); 62 | config.Add(DynamoDBSessionStateStore.CONFIG_INITIAL_READ_UNITS, "10"); 63 | config.Add(DynamoDBSessionStateStore.CONFIG_INITIAL_WRITE_UNITS, "10"); 64 | config.Add(DynamoDBSessionStateStore.CONFIG_CREATE_TABLE_IF_NOT_EXIST, "true"); 65 | Test(config); 66 | 67 | config.Add(DynamoDBSessionStateStore.CONFIG_TTL_ATTRIBUTE, ttlAttributeName); 68 | config.Add(DynamoDBSessionStateStore.CONFIG_TTL_EXPIRED_SESSIONS_SECONDS, ttlExpiredSessionsSeconds.ToString()); 69 | Test(config); 70 | } 71 | 72 | [TestMethod] 73 | public void DynamoDBOnDemandCapacityTest() 74 | { 75 | var config = new NameValueCollection(); 76 | config.Add(DynamoDBSessionStateStore.CONFIG_REGION, region.SystemName); 77 | config.Add(DynamoDBSessionStateStore.CONFIG_TABLE, "SessionStoreWithOnDemandCapacity"); 78 | config.Add(DynamoDBSessionStateStore.CONFIG_APPLICATION, "IntegTest2"); 79 | config.Add(DynamoDBSessionStateStore.CONFIG_ON_DEMAND_READ_WRITE_CAPACITY, "true"); 80 | config.Add(DynamoDBSessionStateStore.CONFIG_CREATE_TABLE_IF_NOT_EXIST, "true"); 81 | Test(config); 82 | } 83 | 84 | private void Test(NameValueCollection config) 85 | { 86 | using (var client = CreateClient()) 87 | { 88 | store = new DynamoDBSessionStateStore("TestSessionProvider", config); 89 | timeoutField.SetValue(store, newTimeout); 90 | 91 | this.tableName = config[DynamoDBSessionStateStore.CONFIG_TABLE]; 92 | 93 | WaitUntilTableReady(client, TableStatus.ACTIVE); 94 | var table = Table.LoadTable(client, this.tableName); 95 | 96 | var creationTime = DateTime.Now; 97 | store.CreateUninitializedItem(null, sessionId, 10); 98 | var items = GetAllItems(table); 99 | Assert.Single(items); 100 | var testTtl = config.AllKeys.Contains(DynamoDBSessionStateStore.CONFIG_TTL_ATTRIBUTE); 101 | var firstItem =items[0]; 102 | Assert.Equal(testTtl, firstItem.ContainsKey(ttlAttributeName)); 103 | if (testTtl) 104 | { 105 | var epochSeconds = firstItem[ttlAttributeName].AsInt(); 106 | Assert.NotEqual(0, epochSeconds); 107 | var expiresDateTime = AWSSDKUtils.ConvertFromUnixEpochSeconds(epochSeconds); 108 | var expectedExpiresDateTime = (creationTime + newTimeout).AddSeconds(ttlExpiredSessionsSeconds); 109 | Assert.True((expiresDateTime - expectedExpiresDateTime) < TimeSpan.FromMinutes(1)); 110 | } 111 | 112 | var hasOnDemandReadWriteCapacity = config.AllKeys.Contains(DynamoDBSessionStateStore.CONFIG_ON_DEMAND_READ_WRITE_CAPACITY); 113 | if (hasOnDemandReadWriteCapacity) 114 | { 115 | var mode = client.DescribeTable(tableName).Table.BillingModeSummary.BillingMode; 116 | Assert.AreEqual(mode, BillingMode.PAY_PER_REQUEST); 117 | } 118 | 119 | bool locked; 120 | TimeSpan lockAge; 121 | object lockId; 122 | SessionStateActions actionFlags; 123 | store.GetItem(null, sessionId, out locked, out lockAge, out lockId, out actionFlags); 124 | 125 | Thread.Sleep(newTimeout); 126 | 127 | DynamoDBSessionStateStore.DeleteExpiredSessions(client, tableName); 128 | items = GetAllItems(table); 129 | Assert.Empty(items); 130 | } 131 | } 132 | 133 | private static List GetAllItems(Table table) 134 | { 135 | Assert.NotNull(table); 136 | 137 | var allItems = table.Scan(new ScanFilter()).GetRemaining().ToList(); 138 | return allItems; 139 | } 140 | private void WaitUntilTableReady(AmazonDynamoDBClient client, TableStatus targetStatus) 141 | { 142 | var startTime = DateTime.Now; 143 | TableStatus status; 144 | while ((DateTime.Now - startTime) < tableActiveMaxTime) 145 | { 146 | try 147 | { 148 | status = client.DescribeTable(tableName).Table.TableStatus; 149 | } 150 | catch(ResourceNotFoundException) 151 | { 152 | status = null; 153 | } 154 | 155 | if (status == targetStatus) 156 | return; 157 | Thread.Sleep(waitPeriod); 158 | } 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /test/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------