├── .gitignore
├── .nuget
├── NuGet.exe
├── NuGet.Config
└── NuGet.targets
├── MemcachedSessionProvider
├── packages.config
├── MemcachedSessionProvider.nuspec
├── Properties
│ └── AssemblyInfo.cs
├── SessionNodeLocator.cs
├── SessionData.cs
├── SessionKeyFormat.cs
├── MemcachedSessionProvider.csproj
├── SessionCacheWithBackup.cs
├── SessionNodeLocatorImpl.cs
└── SessionProvider.cs
├── MemcachedSessionProvider.Tests
├── packages.config
├── SessionDataTests.cs
├── Properties
│ └── AssemblyInfo.cs
├── App.config
├── SessionCacheWithBackupTests.cs
├── MemcachedSessionProvider.Tests.csproj
├── SessionBackupTests.cs
├── SessionKeyFormatTests.cs
├── MockMemcachedClient.cs
└── SessionNodeLocatorTests.cs
├── MemcachedSessionProvider.sln
├── .gitattributes
├── README.markdown
└── License.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | [Oo]bj
2 | [Bb]in
3 | packages
4 | *.user
5 | *.suo
6 | *.cache
7 | *.nupkg
--------------------------------------------------------------------------------
/.nuget/NuGet.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohita/MemcachedSessionProvider/HEAD/.nuget/NuGet.exe
--------------------------------------------------------------------------------
/MemcachedSessionProvider/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.nuget/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider.Tests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider/MemcachedSessionProvider.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $id$
5 | $version$
6 | $title$
7 | $author$
8 | $author$
9 | http://www.apache.org/licenses/LICENSE-2.0
10 | https://github.com/rohita/MemcachedSessionProvider
11 | false
12 | $description$
13 | Copyright 2014 Rohit Agarwal
14 | asp.net session provider sessionstate memcached
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider.Tests/SessionDataTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Web;
7 | using System.Web.Hosting;
8 | using System.Web.SessionState;
9 | using NUnit.Framework;
10 |
11 | namespace MemcachedSessionProvider.Tests
12 | {
13 | [TestFixture]
14 | public class SessionDataTests
15 | {
16 | [Test]
17 | public void TestDeserializeHandlesNull()
18 | {
19 | var request = new SimpleWorkerRequest("", "", "", null, new StringWriter());
20 | var context = new HttpContext(request);
21 | var s = new SessionData(SessionStateActions.None, 10);
22 | Assert.DoesNotThrow(() => s.Deserialize(context));
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider.Tests/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("MemcachedSessionProvider.Tests")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("MemcachedSessionProvider.Tests")]
13 | [assembly: AssemblyCopyright("Copyright © Rohit Agarwal 2014")]
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("35c30248-2cdd-4a34-ac18-4749aeeb350b")]
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 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider/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("MemcachedSessionProvider")]
9 | [assembly: AssemblyDescription("A highly available, high performance ASP.NET session state store provider using Memcached.")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Rohit Agarwal")]
12 | [assembly: AssemblyProduct("MemcachedSessionProvider")]
13 | [assembly: AssemblyCopyright("Copyright © Rohit Agarwal 2014")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 | [assembly: InternalsVisibleTo("MemcachedSessionProvider.Tests")]
17 |
18 | // Setting ComVisible to false makes the types in this assembly not visible
19 | // to COM components. If you need to access a type in this assembly from
20 | // COM, set the ComVisible attribute to true on that type.
21 | [assembly: ComVisible(false)]
22 |
23 | // The following GUID is for the ID of the typelib if this project is exposed to COM
24 | [assembly: Guid("c2af4fab-dc61-43d7-824e-fd4a26be2098")]
25 |
26 | // Version information for an assembly consists of the following four values:
27 | //
28 | // Major Version
29 | // Minor Version
30 | // Build Number
31 | // Revision
32 | //
33 | // You can specify all the values or you can default the Build and Revision Numbers
34 | // by using the '*' as shown below:
35 | // [assembly: AssemblyVersion("1.0.*")]
36 | [assembly: AssemblyVersion("1.0.5.0")]
37 | [assembly: AssemblyFileVersion("1.0.5.0")]
38 |
39 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider.Tests/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2012
4 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EAF584A0-A5F1-47BF-BC54-004C3978ACE8}"
5 | ProjectSection(SolutionItems) = preProject
6 | License.txt = License.txt
7 | README.markdown = README.markdown
8 | EndProjectSection
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MemcachedSessionProvider", "MemcachedSessionProvider\MemcachedSessionProvider.csproj", "{B062D12C-EFB7-4CB5-B560-FB2387468E82}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MemcachedSessionProvider.Tests", "MemcachedSessionProvider.Tests\MemcachedSessionProvider.Tests.csproj", "{B1948D1F-D924-4E91-AAEA-41D47EF602D0}"
13 | EndProject
14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{74C320A6-C406-4280-9813-5B411CAA1EB1}"
15 | ProjectSection(SolutionItems) = preProject
16 | .nuget\NuGet.Config = .nuget\NuGet.Config
17 | .nuget\NuGet.exe = .nuget\NuGet.exe
18 | .nuget\NuGet.targets = .nuget\NuGet.targets
19 | EndProjectSection
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 | {B062D12C-EFB7-4CB5-B560-FB2387468E82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {B062D12C-EFB7-4CB5-B560-FB2387468E82}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {B062D12C-EFB7-4CB5-B560-FB2387468E82}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {B062D12C-EFB7-4CB5-B560-FB2387468E82}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {B1948D1F-D924-4E91-AAEA-41D47EF602D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {B1948D1F-D924-4E91-AAEA-41D47EF602D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {B1948D1F-D924-4E91-AAEA-41D47EF602D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {B1948D1F-D924-4E91-AAEA-41D47EF602D0}.Release|Any CPU.Build.0 = Release|Any CPU
35 | EndGlobalSection
36 | GlobalSection(SolutionProperties) = preSolution
37 | HideSolutionNode = FALSE
38 | EndGlobalSection
39 | EndGlobal
40 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider/SessionNodeLocator.cs:
--------------------------------------------------------------------------------
1 | #region [License]
2 | /* ************************************************************
3 | *
4 | * Copyright (c) 2014 Rohit Agarwal
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | * ************************************************************/
19 | #endregion
20 |
21 | using System.Collections.Generic;
22 | using Enyim.Caching.Memcached;
23 |
24 | namespace MemcachedSessionProvider
25 | {
26 | ///
27 | /// This is a custom implementation of the interface
28 | /// in the Enyim.Caching.Memcached library. This handles keys with prefix "bak:".
29 | /// These backup keys are stored on the "next" available server.
30 | ///
31 | internal class SessionNodeLocator : IMemcachedNodeLocator
32 | {
33 |
34 | public void Initialize(IList nodes)
35 | {
36 | SessionCacheWithBackup.Instance.InitializeLocator(nodes);
37 | }
38 |
39 | public IMemcachedNode Locate(string key)
40 | {
41 | return SessionCacheWithBackup.Instance.Locate(key);
42 | }
43 |
44 | public IEnumerable GetWorkingNodes()
45 | {
46 | return SessionCacheWithBackup.Instance.GetWorkingNodes();
47 | }
48 |
49 | internal void AssignPrimaryBackupNodes(string key)
50 | {
51 | SessionCacheWithBackup.Instance.AssignPrimaryBackupNodes(key);
52 | }
53 |
54 | internal void Reset()
55 | {
56 | SessionCacheWithBackup.Instance.ResetLocator();
57 | }
58 |
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider/SessionData.cs:
--------------------------------------------------------------------------------
1 | #region [License]
2 | /* ************************************************************
3 | *
4 | * Copyright (c) 2014 Rohit Agarwal
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | * ************************************************************/
19 | #endregion
20 |
21 | using System;
22 | using System.IO;
23 | using System.Web;
24 | using System.Web.SessionState;
25 |
26 | namespace MemcachedSessionProvider
27 | {
28 | [Serializable]
29 | internal class SessionData
30 | {
31 | private readonly int _actionFlag;
32 | private readonly int _timeout;
33 | private byte[] _serializedSessionData;
34 |
35 | public int Timeout { get { return _timeout; } }
36 | public long SavedAt { get; set; }
37 |
38 | public SessionData(SessionStateActions actionFlag, int timeout)
39 | {
40 | _actionFlag = (int) actionFlag;
41 | _timeout = timeout;
42 | _serializedSessionData = null;
43 | }
44 |
45 | public void Serialize(SessionStateItemCollection items)
46 | {
47 | var ms = new MemoryStream();
48 | var writer = new BinaryWriter(ms);
49 |
50 | if (items != null)
51 | items.Serialize(writer);
52 |
53 | writer.Close();
54 |
55 | _serializedSessionData = ms.ToArray();
56 | }
57 |
58 | public SessionStateStoreData Deserialize(HttpContext context)
59 | {
60 | var ms = _serializedSessionData == null
61 | ? new MemoryStream()
62 | : new MemoryStream(_serializedSessionData);
63 |
64 | var sessionItems = new SessionStateItemCollection();
65 |
66 | if (ms.Length > 0)
67 | {
68 | var reader = new BinaryReader(ms);
69 | sessionItems = SessionStateItemCollection.Deserialize(reader);
70 | }
71 |
72 | return new SessionStateStoreData(sessionItems,
73 | SessionStateUtility.GetSessionStaticObjects(context),
74 | _timeout);
75 | }
76 |
77 | public SessionStateActions GetActionFlag()
78 | {
79 | return (SessionStateActions) _actionFlag;
80 | }
81 |
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider/SessionKeyFormat.cs:
--------------------------------------------------------------------------------
1 | #region [License]
2 | /* ************************************************************
3 | *
4 | * Copyright (c) 2014 Rohit Agarwal
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | * ************************************************************/
19 | #endregion
20 |
21 | using System;
22 | using System.Collections.Generic;
23 | using System.Linq;
24 | using System.Text;
25 | using System.Web;
26 |
27 | namespace MemcachedSessionProvider
28 | {
29 | internal class SessionKeyFormat
30 | {
31 |
32 | private string _applicationName;
33 | private const string AspSessionPrefix = "__AspSession_";
34 | private const string BackupPrefix = "bak:";
35 | private const string Format = "{0}{1}{2}{3}";
36 |
37 | public SessionKeyFormat()
38 | {
39 | if (!string.IsNullOrEmpty(HttpRuntime.AppDomainAppId))
40 | {
41 | _applicationName = string.Concat(HttpRuntime.AppDomainAppId, "_");
42 | }
43 |
44 | }
45 |
46 | public SessionKeyFormat(string applicationName)
47 | {
48 | if (!string.IsNullOrEmpty(applicationName))
49 | {
50 | _applicationName = string.Concat(applicationName, "_");
51 | }
52 | }
53 |
54 | public String GetBackupKey(String key)
55 | {
56 | if (IsBackupKey(key))
57 | {
58 | return key;
59 | }
60 |
61 | if (IsPrimaryKey(key))
62 | {
63 | return String.Format("{0}{1}", BackupPrefix, key);
64 | }
65 |
66 | return String.Format(Format, BackupPrefix, AspSessionPrefix, _applicationName, key);
67 | }
68 |
69 | public bool IsBackupKey(String key)
70 | {
71 | return key.StartsWith(BackupPrefix);
72 | }
73 |
74 | public bool IsPrimaryKey(string key)
75 | {
76 | return key.StartsWith(AspSessionPrefix);
77 | }
78 |
79 | public String GetPrimaryKey(String key)
80 | {
81 | if (IsPrimaryKey(key))
82 | {
83 | return key;
84 | }
85 |
86 | if (IsBackupKey(key))
87 | {
88 | return key.Substring(BackupPrefix.Length);
89 | }
90 |
91 | return String.Format(Format, string.Empty, AspSessionPrefix, _applicationName, key);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider.Tests/SessionCacheWithBackupTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Text;
6 | using Enyim.Caching.Memcached;
7 | using NUnit.Framework;
8 | using System.Web.SessionState;
9 |
10 | namespace MemcachedSessionProvider.Tests
11 | {
12 | [TestFixture]
13 | public class SessionCacheWithBackupTests
14 | {
15 | private SessionCacheWithBackup cache = SessionCacheWithBackup.Instance;
16 | private const string SessionId = "abc";
17 | private string _primaryKey = new SessionKeyFormat(null).GetPrimaryKey(SessionId);
18 | private string _backupKey = new SessionKeyFormat(null).GetBackupKey(SessionId);
19 |
20 |
21 |
22 | [Test]
23 | public void StoreSessionTest()
24 | {
25 | cache.ResetMemcachedClient("sessionManagement/memcached");
26 | cache.Remove(SessionId);
27 |
28 | cache.Store(SessionId, new SessionData(SessionStateActions.None, 30), TimeSpan.FromMinutes(30));
29 |
30 | var data = cache.GetByCacheKey(_primaryKey);
31 | Assert.NotNull(data);
32 |
33 | var data2 = cache.GetByCacheKey(_backupKey);
34 | Assert.NotNull(data2);
35 |
36 | var firstStore = data.SavedAt;
37 | cache.Store(SessionId, data, TimeSpan.FromMinutes(30));
38 | data = cache.GetByCacheKey(_primaryKey);
39 | var secondStore = data.SavedAt;
40 | Assert.Greater(secondStore, firstStore);
41 |
42 | cache.Remove(SessionId);
43 | }
44 |
45 | [Test]
46 | public void RemoveSessionTest()
47 | {
48 | cache.ResetMemcachedClient("sessionManagement/memcached");
49 |
50 | cache.Store(SessionId, new SessionData(SessionStateActions.None, 30), TimeSpan.FromMinutes(30));
51 |
52 | var data = cache.GetByCacheKey(_primaryKey);
53 | var data2 = cache.GetByCacheKey(_backupKey);
54 | Assert.NotNull(data);
55 | Assert.NotNull(data2);
56 |
57 | cache.Remove(SessionId);
58 |
59 | data = cache.GetByCacheKey(_primaryKey);
60 | data2 = cache.GetByCacheKey(_backupKey);
61 | Assert.IsNull(data);
62 | Assert.IsNull(data2);
63 | }
64 |
65 | [Test]
66 | public void DefaultLocatorTest()
67 | {
68 | cache.ResetMemcachedClient("test/defaultLocator");
69 |
70 | cache.Store(SessionId, new SessionData(SessionStateActions.None, 30), TimeSpan.FromMinutes(30));
71 |
72 | var data = cache.GetByCacheKey(_primaryKey);
73 | var data2 = cache.GetByCacheKey(_backupKey);
74 | Assert.NotNull(data);
75 | Assert.IsNull(data2, "Don't backup sessions if not using backup node locator");
76 |
77 | cache.Remove(SessionId);
78 |
79 | }
80 |
81 | [Test]
82 | public void SingleNodeTest()
83 | {
84 | cache.ResetMemcachedClient("test/singleServer");
85 |
86 | cache.Store(SessionId, new SessionData(SessionStateActions.None, 30), TimeSpan.FromMinutes(30));
87 |
88 | var data = cache.GetByCacheKey(_primaryKey);
89 | var data2 = cache.GetByCacheKey(_backupKey);
90 | Assert.NotNull(data);
91 | Assert.IsNull(data2, "Don't backup sessions if using single node");
92 |
93 | cache.Remove(SessionId);
94 |
95 | }
96 |
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider/MemcachedSessionProvider.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Release
5 | AnyCPU
6 | 9.0.21022
7 | 2.0
8 | {B062D12C-EFB7-4CB5-B560-FB2387468E82}
9 | Library
10 | Properties
11 | MemcachedSessionProvider
12 | MemcachedSessionProvider
13 | v3.5
14 | 512
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 3.5
28 |
29 |
30 | ..\
31 | true
32 |
33 |
34 | true
35 | full
36 | false
37 | bin\Debug\
38 | DEBUG;TRACE
39 | prompt
40 | 4
41 |
42 |
43 | false
44 |
45 |
46 | pdbonly
47 | true
48 | bin\Release\
49 | TRACE
50 | prompt
51 | 4
52 | false
53 | bin\Release\MemcachedSessionProvider.xml
54 |
55 |
56 |
57 | False
58 | ..\packages\EnyimMemcached.2.12\lib\net35\Enyim.Caching.dll
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
85 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider.Tests/MemcachedSessionProvider.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {B1948D1F-D924-4E91-AAEA-41D47EF602D0}
8 | Library
9 | Properties
10 | MemcachedSessionProvider.Tests
11 | MemcachedSessionProvider.Tests
12 | v3.5
13 | 512
14 |
15 | ..\
16 | true
17 |
18 |
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 |
27 |
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 | False
38 | ..\packages\EnyimMemcached.2.12\lib\net35\Enyim.Caching.dll
39 |
40 |
41 | ..\packages\NUnit.2.6.3\lib\nunit.framework.dll
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | {b062d12c-efb7-4cb5-b560-fb2387468e82}
68 | MemcachedSessionProvider
69 |
70 |
71 |
72 |
73 |
80 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | # Memcached Session Provider
2 |
3 | This is a highly available, high performance ASP.NET session state store provider using Memcached.
4 |
5 | Features:
6 |
7 | * Handles Memcached node failures
8 | * No session locking per request
9 |
10 | ### Handling node failures
11 | In a pool of memcached nodes, sessions are distributed equally across all the nodes. Additionally, for every session, a backup
12 | copy is stored on a secondary memcached node. This is similar to the way [Memcached session manager for Tomcat](https://code.google.com/p/memcached-session-manager/) is implemented.
13 |
14 | Example: In a pool of 2 memcached nodes, if a session `S1` gets stored on memcached node `M1`, the backup session `bak:S1` is stored
15 | on node `M2`. If node `M1` goes down, session is not lost. It will be retrived from the `M2` node without any interruption.
16 | Similarly, `M1` acts as backup node for `M2`, in case `M2` goes down.
17 | ```
18 |
19 | S1 S2
20 | bak:S2 bak:S1
21 | ```
22 | Note that if only 1 memcached node is configured then there is no backup.
23 |
24 | ### No session locking
25 | [Session locking in ASP.NET](http://msdn.microsoft.com/en-us/library/ms178587.aspx) can cause a few
26 | [performance problems](http://stackoverflow.com/questions/3629709/i-just-discovered-why-all-asp-net-websites-are-slow-and-i-am-trying-to-work-out).
27 | This custom implementation of Session provider does not lock any Session. This should not be a problem in most cases. But if
28 | you need locked, consistent data as part of the session, you can implement a lock/concurrency check in your application,
29 | or use a different Memcached provider (see [here](https://github.com/enyim/memcached-providers) and [here](http://memcachedproviders.codeplex.com/)).
30 |
31 | ## Requirements
32 | You'll need .NET Framework 3.5 or later to use the precompiled binaries. To build client, you'll need Visual Studio 2012.
33 |
34 | ## Install
35 | In your web project, include the assembly via [NuGet Package](https://www.nuget.org/packages/MemcachedSessionProvider/).
36 |
37 | ## Web.config
38 | This library uses the [Enyim Memcached client](https://github.com/enyim/EnyimMemcached). Make the following changes in
39 | the web.config file for Enyim client
40 | ```xml
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | ```
61 | #### memcached/locator
62 | The `memcached/locator` is used to map objects to servers in the pool. Replace the default implementation with the
63 | type `MemcachedSessionProvider.SessionNodeLocator, MemcachedSessionProvider`. This handles the session backup.
64 |
65 | See here for [more configuration options for Enyim Memcached](https://github.com/enyim/EnyimMemcached/wiki/MemcachedClient-Configuration)
66 |
67 | Also make the following change in web.config to use the custom Session State provider
68 | ```xml
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | ```
83 |
84 | ## Questions?
85 | If you have questions, bugs reports, or feature requests, please submit them via the [Issue Tracker](https://github.com/rohita/MemcachedSessionProvider/issues).
86 |
87 | ## Reference
88 | This implementation is based on the [sample provided by Microsoft](http://msdn.microsoft.com/en-us/library/ms178588.aspx).
89 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider.Tests/SessionBackupTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Text;
6 | using System.Web.SessionState;
7 | using Enyim.Caching.Configuration;
8 | using Enyim.Caching.Memcached;
9 | using NUnit.Framework;
10 |
11 | namespace MemcachedSessionProvider.Tests
12 | {
13 | [TestFixture]
14 | public class SessionBackupTests
15 | {
16 | private const string N2Key = "abcdefgh_34567890";
17 | private const string N1Key = "abcdefgh";
18 | private const string N2Key2Node = "345678906789";
19 | ISocketPoolConfiguration _s = new SocketPoolConfiguration();
20 | private SessionCacheWithBackup cache = SessionCacheWithBackup.Instance;
21 |
22 | [Test]
23 | public void TestSingleClientPrimaryDown()
24 | {
25 | var n1 = GetNode(1);
26 | var n2 = GetNode(2);
27 | var n3 = GetNode(3);
28 | var n4 = GetNode(4);
29 |
30 | var newClient = new MockMemcachedClient(new List { n1, n2, n3, n4 });
31 | cache.ResetMemcachedClient(newClient, null);
32 |
33 | cache.Store(N1Key, new SessionData(SessionStateActions.None, 23), TimeSpan.FromMinutes(30));
34 |
35 | newClient.SetNodeDead(n1, new List { n2, n3, n4 });
36 |
37 | var data = cache.Get(N1Key);
38 | Assert.AreEqual(23, data.Timeout);
39 | }
40 |
41 | [Test]
42 | public void TestSingleClientPrimaryUp()
43 | {
44 | var n1 = GetNode(1);
45 | var n2 = GetNode(2);
46 | var n3 = GetNode(3);
47 | var n4 = GetNode(4);
48 | var nodes = new List {n1, n2, n3, n4};
49 |
50 | var newClient = new MockMemcachedClient(nodes);
51 | cache.ResetMemcachedClient(newClient, null);
52 |
53 | cache.Store(N1Key, new SessionData(SessionStateActions.None, 23), TimeSpan.FromMinutes(30));
54 |
55 | newClient.SetNodeDead(n1, new List { n2, n3, n4 });
56 |
57 | var data = cache.Get(N1Key);
58 | Assert.AreEqual(23, data.Timeout);
59 |
60 | newClient.SetNodeAlive(n1, new List { n1, n2, n3, n4 });
61 |
62 | data = cache.Get(N1Key);
63 | Assert.AreEqual(23, data.Timeout);
64 | }
65 |
66 | [Test]
67 | public void TestSingleClientPrimaryDown2NdStore()
68 | {
69 | var n1 = GetNode(1);
70 | var n2 = GetNode(2);
71 | var n3 = GetNode(3);
72 | var n4 = GetNode(4);
73 |
74 | var newClient = new MockMemcachedClient(new List { n1, n2, n3, n4 });
75 | cache.ResetMemcachedClient(newClient, null);
76 |
77 | cache.Store(N1Key, new SessionData(SessionStateActions.None, 23), TimeSpan.FromMinutes(30));
78 |
79 | newClient.SetNodeDead(n1, new List { n2, n3, n4 });
80 |
81 | cache.Store(N1Key, new SessionData(SessionStateActions.None, 25), TimeSpan.FromMinutes(30));
82 |
83 | var data = cache.Get(N1Key);
84 | Assert.AreEqual(25, data.Timeout);
85 | }
86 |
87 |
88 | [Test]
89 | public void TestTwoClientsPrimaryDown()
90 | {
91 | var n1 = GetNode(1);
92 | var n2 = GetNode(2);
93 | var n3 = GetNode(3);
94 | var n4 = GetNode(4);
95 |
96 | var s1 = new SessionNodeLocatorImpl();
97 | s1.Initialize(new List { n1, n2, n3, n4 });
98 | var newClient = new MockMemcachedClient(new List { n1, n2, n3, n4 });
99 | cache.ResetMemcachedClient(newClient, s1);
100 |
101 | cache.Store(N1Key, new SessionData(SessionStateActions.None, 23), TimeSpan.FromMinutes(30));
102 |
103 | newClient.SetNodeDead(n1, new List { n2, n3, n4 });
104 |
105 | cache.Store(N1Key, new SessionData(SessionStateActions.None, 25), TimeSpan.FromMinutes(30));
106 |
107 | var s2 = new SessionNodeLocatorImpl();
108 | s2.Initialize(new List { n1, n2, n3, n4 });
109 | cache.ResetMemcachedClient(newClient, s2);
110 | newClient.SetNodeDead(n1, new List { n2, n3, n4 });
111 |
112 | var data = cache.Get(N1Key);
113 | Assert.AreEqual(25, data.Timeout);
114 | }
115 |
116 | private IMemcachedNode GetNode(int num)
117 | {
118 | var p1 = new IPEndPoint(IPAddress.Parse(string.Format("10.10.10.{0}", num)), 11211);
119 | return new MemcachedNode(p1, _s);
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider.Tests/SessionKeyFormatTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Web;
7 | using NUnit.Framework;
8 |
9 | namespace MemcachedSessionProvider.Tests
10 | {
11 | [TestFixture]
12 | public class SessionKeyFormatTests
13 | {
14 | [TestCase("bak:__AspSession_testapp_abcd456", "bak:__AspSession_testapp_abcd456")]
15 | [TestCase("bak:__AspSession_testapp_35c30248-2cdd-4a34-ac18-4749aeeb350b", "35c30248-2cdd-4a34-ac18-4749aeeb350b")]
16 | [TestCase("bak:__AspSession_testapp_abcd456", "__AspSession_testapp_abcd456")]
17 | [TestCase("bak:__AspSession_testapp_", "")]
18 | [TestCase("bak:__AspSession_testapp_", "bak:__AspSession_testapp_")]
19 | public void TestGetBackupKey(string expected, string sessionId)
20 | {
21 | var s = new SessionKeyFormat("testapp");
22 | Assert.AreEqual(expected, s.GetBackupKey(sessionId));
23 | }
24 |
25 | [TestCase("bak:__AspSession_35c30248-2cdd-4a34-ac18-4749aeeb350b", "35c30248-2cdd-4a34-ac18-4749aeeb350b")]
26 | [TestCase("bak:__AspSession__abcd456", "__AspSession__abcd456")]
27 | [TestCase("bak:__AspSession_", "")]
28 | public void TestGetBackupKey2(string expected, string sessionId)
29 | {
30 | var s = new SessionKeyFormat(null);
31 | Assert.AreEqual(expected, s.GetBackupKey(sessionId));
32 | }
33 |
34 | [TestCase("__AspSession_testapp_abcd456", "bak:__AspSession_testapp_abcd456")]
35 | [TestCase("__AspSession_testapp_35c30248-2cdd-4a34-ac18-4749aeeb350b", "35c30248-2cdd-4a34-ac18-4749aeeb350b")]
36 | [TestCase("__AspSession_testapp_abcd456", "__AspSession_testapp_abcd456")]
37 | [TestCase("__AspSession_testapp_", "")]
38 | [TestCase("__AspSession_testapp_", "bak:__AspSession_testapp_")]
39 | public void TestGetPrimaryKey(string expected, string sessionId)
40 | {
41 | var s = new SessionKeyFormat("testapp");
42 | Assert.AreEqual(expected, s.GetPrimaryKey(sessionId));
43 | }
44 |
45 | [TestCase("__AspSession_testapp_abcd456", "bak:__AspSession_testapp_abcd456")]
46 | [TestCase("__AspSession_35c30248-2cdd-4a34-ac18-4749aeeb350b", "35c30248-2cdd-4a34-ac18-4749aeeb350b")]
47 | [TestCase("__AspSession_testapp_abcd456", "__AspSession_testapp_abcd456")]
48 | [TestCase("__AspSession_", "")]
49 | [TestCase("__AspSession__abcd456", "__AspSession__abcd456")]
50 | public void TestGetPrimaryKey2(string expected, string sessionId)
51 | {
52 | var s = new SessionKeyFormat("");
53 | Assert.AreEqual(expected, s.GetPrimaryKey(sessionId));
54 | }
55 |
56 | [TestCase("__AspSession_/LM/W3SVC/1/ROOT/ApexUI_02l2seqes0hrgaffs5hgfg55", "/LM/W3SVC/1/ROOT/ApexUI_02l2seqes0hrgaffs5hgfg55")]
57 | public void TestGetPrimaryKey3(string expected, string sessionId)
58 | {
59 | var s = new SessionKeyFormat(null);
60 | Assert.AreEqual(expected, s.GetPrimaryKey(sessionId));
61 | }
62 |
63 | [TestCase(false, "abcd456")]
64 | [TestCase(false, "")]
65 | [TestCase(true, "bak:__AspSession_testapp_abcd456")]
66 | [TestCase(true, "bak:__AspSession__abcd456")]
67 | [TestCase(true, "bak:__AspSession__")]
68 | [TestCase(false, "__AspSession_testapp_abcd456")]
69 | [TestCase(false, "__AspSession__abcd456")]
70 | [TestCase(false, "__AspSession__")]
71 | public void TestIsBackupKey(bool expected, string sessionId)
72 | {
73 | var s = new SessionKeyFormat(null);
74 | Assert.AreEqual(expected, s.IsBackupKey(sessionId));
75 | }
76 |
77 | [TestCase(false, "abcd456")]
78 | [TestCase(false, "")]
79 | [TestCase(false, "bak:__AspSession_testapp_abcd456")]
80 | [TestCase(false, "bak:__AspSession__abcd456")]
81 | [TestCase(false, "bak:__AspSession__")]
82 | [TestCase(true, "__AspSession_testapp_abcd456")]
83 | [TestCase(true, "__AspSession__abcd456")]
84 | [TestCase(true, "__AspSession__")]
85 | public void TestIsPrimaryKey(bool expected, string sessionId)
86 | {
87 | var s = new SessionKeyFormat("testapp");
88 | Assert.AreEqual(expected, s.IsPrimaryKey(sessionId));
89 | }
90 |
91 | [TestCase(true, "__AspSession_testapp_abcd456")]
92 | [TestCase(true, "__AspSession__abcd456")]
93 | [TestCase(true, "__AspSession__")]
94 | public void TestIsPrimaryKey2(bool expected, string sessionId)
95 | {
96 | var s = new SessionKeyFormat(null);
97 | Assert.AreEqual(expected, s.IsPrimaryKey(sessionId));
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider/SessionCacheWithBackup.cs:
--------------------------------------------------------------------------------
1 | #region [License]
2 | /* ************************************************************
3 | *
4 | * Copyright (c) 2014 Rohit Agarwal
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | * ************************************************************/
19 | #endregion
20 |
21 | using System;
22 | using System.Collections.Generic;
23 | using System.Configuration;
24 | using System.Reflection;
25 | using System.Threading;
26 | using Enyim.Caching;
27 | using Enyim.Caching.Configuration;
28 | using Enyim.Caching.Memcached;
29 |
30 | namespace MemcachedSessionProvider
31 | {
32 | internal sealed class SessionCacheWithBackup
33 | {
34 | private static readonly SessionCacheWithBackup _instance = new SessionCacheWithBackup();
35 | private volatile IMemcachedClient _client;
36 | private SessionKeyFormat _sessionKeyFormat;
37 | private const string DefaultConfigSection = "sessionManagement/memcached";
38 | private MemcachedClientSection _memcachedClientSection;
39 | private SessionNodeLocatorImpl _locatorImpl;
40 | private object _clientAccessLock = new object();
41 |
42 | private SessionCacheWithBackup()
43 | {
44 | _memcachedClientSection = ConfigurationManager.GetSection(DefaultConfigSection) as MemcachedClientSection;
45 | _locatorImpl = new SessionNodeLocatorImpl();
46 | _sessionKeyFormat = new SessionKeyFormat();
47 | }
48 |
49 | public static SessionCacheWithBackup Instance
50 | {
51 | get { return _instance; }
52 | }
53 |
54 | public void InitializeClient()
55 | {
56 | if (_client != null) return;
57 |
58 | lock (_clientAccessLock)
59 | {
60 | if (_client == null)
61 | _client = new MemcachedClient(_memcachedClientSection);
62 | }
63 | }
64 |
65 | public SessionData Get(string sessionId)
66 | {
67 | var cacheKey = _sessionKeyFormat.GetPrimaryKey(sessionId);
68 | var data = _client.Get(cacheKey);
69 |
70 | if (data != null)
71 | {
72 | // Check if a new primary was assigned
73 | data = CheckForNewer(sessionId, data);
74 | }
75 | else if (IsBackupEnabled()) // try backup
76 | {
77 | var backupKey = _sessionKeyFormat.GetBackupKey(sessionId);
78 | data = _client.Get(backupKey);
79 |
80 | if (data != null)
81 | {
82 | // Data found on backup node. This means primary is down.
83 | // Check on new primary
84 | data = CheckForNewer(sessionId, data);
85 | }
86 | }
87 |
88 | return data;
89 | }
90 |
91 | private SessionData CheckForNewer(string sessionId, SessionData data)
92 | {
93 | string cacheKey = _sessionKeyFormat.GetPrimaryKey(sessionId);
94 |
95 | // try if there is fresher copy
96 | // This happens when a node goes up or down
97 | if (AssignPrimaryBackupNodes(cacheKey))
98 | {
99 | var newData = _client.Get(cacheKey);
100 | if (newData == null || data.SavedAt > newData.SavedAt)
101 | {
102 | // If not found or older, that means this client hit the key first
103 | // so relocate session for next call
104 | Store(sessionId, data, TimeSpan.FromMinutes(data.Timeout));
105 | }
106 | else
107 | {
108 | // else found newer, that means some other client already updated this
109 | data = newData;
110 | }
111 | }
112 |
113 | return data;
114 | }
115 |
116 | public void Store(string sessionId, SessionData cacheItem, TimeSpan timeout)
117 | {
118 | var cacheKey = _sessionKeyFormat.GetPrimaryKey(sessionId);
119 | AssignPrimaryBackupNodes(cacheKey);
120 |
121 | cacheItem.SavedAt = DateTime.UtcNow.Ticks;
122 | _client.Store(StoreMode.Set, cacheKey, cacheItem, timeout);
123 |
124 | if (IsBackupEnabled()) // backup
125 | {
126 | var backupKey = _sessionKeyFormat.GetBackupKey(sessionId);
127 | _client.Store(StoreMode.Set, backupKey, cacheItem, timeout);
128 | }
129 | }
130 |
131 | public void Remove(string sessionId)
132 | {
133 | var cacheKey = _sessionKeyFormat.GetPrimaryKey(sessionId);
134 | _client.Remove(cacheKey);
135 |
136 | if (IsBackupEnabled())
137 | {
138 | _client.Remove(_sessionKeyFormat.GetBackupKey(sessionId));
139 | }
140 | }
141 |
142 | public void InitializeLocator(IList nodes)
143 | {
144 | _locatorImpl.Initialize(nodes);
145 | }
146 |
147 | public IMemcachedNode Locate(string key)
148 | {
149 | return _locatorImpl.Locate(key);
150 | }
151 |
152 | public IEnumerable GetWorkingNodes()
153 | {
154 | return _locatorImpl.GetWorkingNodes();
155 | }
156 |
157 | private bool IsBackupEnabled()
158 | {
159 | return _memcachedClientSection.Servers.Count > 1
160 | && IsUsingSessionNodeLocator();
161 | }
162 |
163 | private bool IsUsingSessionNodeLocator()
164 | {
165 | return _memcachedClientSection.NodeLocator.Type == typeof (SessionNodeLocator);
166 | }
167 |
168 | public bool AssignPrimaryBackupNodes(string cacheKey)
169 | {
170 | if (IsUsingSessionNodeLocator())
171 | {
172 | return _locatorImpl.AssignPrimaryBackupNodes(cacheKey);
173 | }
174 |
175 | return false;
176 | }
177 |
178 |
179 | internal void ResetMemcachedClient(string memcachedConfigSection)
180 | {
181 | if (_client != null)
182 | {
183 | _client.Dispose();
184 | _client = null;
185 | }
186 | _locatorImpl = new SessionNodeLocatorImpl();
187 | _memcachedClientSection = ConfigurationManager.GetSection(memcachedConfigSection) as MemcachedClientSection;
188 | InitializeClient();
189 | }
190 |
191 | internal void ResetMemcachedClient(IMemcachedClient newClient, SessionNodeLocatorImpl newLocator)
192 | {
193 | if (_client != null) _client.Dispose();
194 | _client = newClient;
195 | _locatorImpl = newLocator ?? _locatorImpl;
196 | }
197 |
198 | internal void ResetLocator()
199 | {
200 | _locatorImpl = new SessionNodeLocatorImpl();
201 | }
202 |
203 | internal SessionData GetByCacheKey(string key)
204 | {
205 | return _client.Get(key);
206 | }
207 |
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/.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 |
64 |
65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)
66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols
67 |
68 |
69 |
70 | RestorePackages;
71 | $(BuildDependsOn);
72 |
73 |
74 |
75 |
76 | $(BuildDependsOn);
77 | BuildPackage;
78 |
79 |
80 |
81 |
82 |
83 |
84 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
99 |
100 |
103 |
104 |
105 |
106 |
108 |
109 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
141 |
142 |
143 |
144 |
145 |
--------------------------------------------------------------------------------
/MemcachedSessionProvider.Tests/MockMemcachedClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Reflection;
6 | using System.Text;
7 | using Enyim.Caching;
8 | using Enyim.Caching.Configuration;
9 | using Enyim.Caching.Memcached;
10 |
11 | namespace MemcachedSessionProvider.Tests
12 | {
13 | public class MockMemcachedClient : IMemcachedClient
14 | {
15 | private SessionNodeLocator _locator;
16 |
17 | private Dictionary> _data;
18 |
19 | public MockMemcachedClient(IList nodes)
20 | {
21 | _data = new Dictionary>();
22 | foreach (var node in nodes)
23 | {
24 | _data.Add(node, new Dictionary());
25 | }
26 |
27 | _locator = new SessionNodeLocator();
28 | _locator.Initialize(nodes);
29 | }
30 |
31 | public T Get(string key)
32 | {
33 | var node = _locator.Locate(key);
34 | Console.WriteLine("Get => {0}:{1}", key, node == null? "null" : node.EndPoint.ToString());
35 |
36 | if (node == null
37 | || _data[node] == null
38 | || !_data[node].ContainsKey(key))
39 | return default(T);
40 |
41 | return (T)_data[node][key];
42 | }
43 |
44 | public bool Store(StoreMode mode, string key, object value, TimeSpan validFor)
45 | {
46 | var node = _locator.Locate(key);
47 | Console.WriteLine("Store => {0}:{1}", key, node == null ? "null" : node.EndPoint.ToString());
48 |
49 | _data[node][key] = value;
50 | return true;
51 | }
52 |
53 | public bool Remove(string key)
54 | {
55 | throw new NotImplementedException();
56 | }
57 |
58 | public void SetNodeDead(IMemcachedNode n1, List activeNodes)
59 | {
60 | Console.WriteLine("Down => {0}", n1.EndPoint);
61 |
62 | // Having to use reflection to set a private field
63 | var prop = n1.GetType().GetField("internalPoolImpl", BindingFlags.NonPublic | BindingFlags.Instance);
64 | var internalPoolImpl = prop.GetValue(n1);
65 | var prop2 = internalPoolImpl.GetType().GetField("isAlive", BindingFlags.NonPublic | BindingFlags.Instance);
66 | prop2.SetValue(internalPoolImpl, false);
67 |
68 | _data[n1] = null;
69 | _locator = new SessionNodeLocator();
70 | _locator.Initialize(activeNodes);
71 | }
72 |
73 | public void SetNodeAlive(IMemcachedNode n1, List activeNodes)
74 | {
75 | Console.WriteLine("Up => {0}", n1.EndPoint);
76 |
77 | // Having to use reflection to set a private field
78 | var prop = n1.GetType().GetField("internalPoolImpl", BindingFlags.NonPublic | BindingFlags.Instance);
79 | var internalPoolImpl = prop.GetValue(n1);
80 | var prop2 = internalPoolImpl.GetType().GetField("isAlive", BindingFlags.NonPublic | BindingFlags.Instance);
81 | prop2.SetValue(internalPoolImpl, true);
82 |
83 | _data[n1] = new Dictionary();
84 | _locator.Initialize(activeNodes);
85 | }
86 |
87 | public void Dispose()
88 | {
89 |
90 | }
91 |
92 | #region Not Implemented
93 |
94 |
95 | public object Get(string key)
96 | {
97 | throw new NotImplementedException();
98 | }
99 |
100 | public IDictionary Get(IEnumerable keys)
101 | {
102 | throw new NotImplementedException();
103 | }
104 |
105 | public bool TryGet(string key, out object value)
106 | {
107 | throw new NotImplementedException();
108 | }
109 |
110 | public bool TryGetWithCas(string key, out CasResult