├── .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 |
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 |
--------------------------------------------------------------------------------