@ViewBag.Title.
6 |
7 |
8 | Thank you for confirming your email. Please @Html.ActionLink("Click here to Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })
9 |
10 |
11 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Account/ExternalLoginConfirmation.cshtml:
--------------------------------------------------------------------------------
1 | @model SampleWebApplication.Models.ExternalLoginConfirmationViewModel
2 | @{
3 | ViewBag.Title = "Register";
4 | }
5 | @ViewBag.Title.
6 | Associate your @ViewBag.LoginProvider account.
7 |
8 | @using (Html.BeginForm("ExternalLoginConfirmation", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
9 | {
10 | @Html.AntiForgeryToken()
11 |
12 | Association Form
13 |
14 | @Html.ValidationSummary(true, "", new { @class = "text-danger" })
15 |
16 | You've successfully authenticated with @ViewBag.LoginProvider.
17 | Please enter a user name for this site below and click the Register button to finish
18 | logging in.
19 |
20 |
27 |
32 | }
33 |
34 | @section Scripts {
35 | @Scripts.Render("~/bundles/jqueryval")
36 | }
37 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Account/ExternalLoginFailure.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Login Failure";
3 | }
4 |
5 |
6 | @ViewBag.Title.
7 | Unsuccessful login with service.
8 |
9 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Account/ForgotPassword.cshtml:
--------------------------------------------------------------------------------
1 | @model SampleWebApplication.Models.ForgotPasswordViewModel
2 | @{
3 | ViewBag.Title = "Forgot your password?";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @using (Html.BeginForm("ForgotPassword", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
9 | {
10 | @Html.AntiForgeryToken()
11 | Enter your email.
12 |
13 | @Html.ValidationSummary("", new { @class = "text-danger" })
14 |
20 |
25 | }
26 |
27 | @section Scripts {
28 | @Scripts.Render("~/bundles/jqueryval")
29 | }
30 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Account/ForgotPasswordConfirmation.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Forgot Password Confirmation";
3 | }
4 |
5 |
6 | @ViewBag.Title.
7 |
8 |
9 |
10 | Please check your email to reset your password.
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Account/Login.cshtml:
--------------------------------------------------------------------------------
1 | @using SampleWebApplication.Models
2 | @model LoginViewModel
3 | @{
4 | ViewBag.Title = "Log in";
5 | }
6 |
7 | @ViewBag.Title.
8 |
60 |
61 | @section Scripts {
62 | @Scripts.Render("~/bundles/jqueryval")
63 | }
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Account/Register.cshtml:
--------------------------------------------------------------------------------
1 | @model SampleWebApplication.Models.RegisterViewModel
2 | @{
3 | ViewBag.Title = "Register";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
9 | {
10 | @Html.AntiForgeryToken()
11 | Create a new account.
12 |
13 | @Html.ValidationSummary("", new { @class = "text-danger" })
14 |
20 |
26 |
32 |
37 | }
38 |
39 | @section Scripts {
40 | @Scripts.Render("~/bundles/jqueryval")
41 | }
42 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Account/ResetPassword.cshtml:
--------------------------------------------------------------------------------
1 | @model SampleWebApplication.Models.ResetPasswordViewModel
2 | @{
3 | ViewBag.Title = "Reset password";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @using (Html.BeginForm("ResetPassword", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
9 | {
10 | @Html.AntiForgeryToken()
11 | Reset your password.
12 |
13 | @Html.ValidationSummary("", new { @class = "text-danger" })
14 | @Html.HiddenFor(model => model.Code)
15 |
21 |
27 |
33 |
38 | }
39 |
40 | @section Scripts {
41 | @Scripts.Render("~/bundles/jqueryval")
42 | }
43 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Account/ResetPasswordConfirmation.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Reset password confirmation";
3 | }
4 |
5 |
6 | @ViewBag.Title.
7 |
8 |
9 |
10 | Your password has been reset. Please @Html.ActionLink("click here to log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })
11 |
12 |
13 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Account/SendCode.cshtml:
--------------------------------------------------------------------------------
1 | @model SampleWebApplication.Models.SendCodeViewModel
2 | @{
3 | ViewBag.Title = "Send";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @using (Html.BeginForm("SendCode", "Account", new { ReturnUrl = Model.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) {
9 | @Html.AntiForgeryToken()
10 | @Html.Hidden("rememberMe", @Model.RememberMe)
11 | Send verification code
12 |
13 |
14 |
15 | Select Two-Factor Authentication Provider:
16 | @Html.DropDownListFor(model => model.SelectedProvider, Model.Providers)
17 |
18 |
19 |
20 | }
21 |
22 | @section Scripts {
23 | @Scripts.Render("~/bundles/jqueryval")
24 | }
25 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Account/VerifyCode.cshtml:
--------------------------------------------------------------------------------
1 | @model SampleWebApplication.Models.VerifyCodeViewModel
2 | @{
3 | ViewBag.Title = "Verify";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @using (Html.BeginForm("VerifyCode", "Account", new { ReturnUrl = Model.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) {
9 | @Html.AntiForgeryToken()
10 | @Html.Hidden("provider", @Model.Provider)
11 | @Html.Hidden("rememberMe", @Model.RememberMe)
12 | Enter verification code
13 |
14 | @Html.ValidationSummary("", new { @class = "text-danger" })
15 |
21 |
29 |
34 | }
35 |
36 | @section Scripts {
37 | @Scripts.Render("~/bundles/jqueryval")
38 | }
39 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Account/_ExternalLoginsListPartial.cshtml:
--------------------------------------------------------------------------------
1 | @model SampleWebApplication.Models.ExternalLoginListViewModel
2 | @using Microsoft.Owin.Security
3 |
4 | Use another service to log in.
5 |
6 | @{
7 | var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes();
8 | if (loginProviders.Count() == 0) {
9 |
10 |
11 | There are no external authentication services configured. See this article
12 | for details on setting up this ASP.NET application to support logging in via external services.
13 |
14 |
15 | }
16 | else {
17 | using (Html.BeginForm("ExternalLogin", "Account", new { ReturnUrl = Model.ReturnUrl })) {
18 | @Html.AntiForgeryToken()
19 |
20 |
21 | @foreach (AuthenticationDescription p in loginProviders) {
22 |
23 | }
24 |
25 |
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Home/About.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "About";
3 | }
4 | @ViewBag.Title.
5 | @ViewBag.Message
6 |
7 | Use this area to provide additional information.
8 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Home/Contact.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Contact";
3 | }
4 | @ViewBag.Title.
5 | @ViewBag.Message
6 |
7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P:
11 | 425.555.0100
12 |
13 |
14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com
17 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Home/Index.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Home Page";
3 | }
4 |
5 |
6 |
@ViewBag.Message
7 |
8 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Manage/AddPhoneNumber.cshtml:
--------------------------------------------------------------------------------
1 | @model SampleWebApplication.Models.AddPhoneNumberViewModel
2 | @{
3 | ViewBag.Title = "Phone Number";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @using (Html.BeginForm("AddPhoneNumber", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
9 | {
10 | @Html.AntiForgeryToken()
11 | Add a phone number
12 |
13 | @Html.ValidationSummary("", new { @class = "text-danger" })
14 |
20 |
25 | }
26 |
27 | @section Scripts {
28 | @Scripts.Render("~/bundles/jqueryval")
29 | }
30 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Manage/ChangePassword.cshtml:
--------------------------------------------------------------------------------
1 | @model SampleWebApplication.Models.ChangePasswordViewModel
2 | @{
3 | ViewBag.Title = "Change Password";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @using (Html.BeginForm("ChangePassword", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
9 | {
10 | @Html.AntiForgeryToken()
11 | Change Password Form
12 |
13 | @Html.ValidationSummary("", new { @class = "text-danger" })
14 |
20 |
26 |
32 |
37 | }
38 | @section Scripts {
39 | @Scripts.Render("~/bundles/jqueryval")
40 | }
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Manage/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model SampleWebApplication.Models.IndexViewModel
2 | @{
3 | ViewBag.Title = "Manage";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @ViewBag.StatusMessage
9 |
10 |
Change your account settings
11 |
12 |
13 | - Password:
14 | -
15 | [
16 | @if (Model.HasPassword)
17 | {
18 | @Html.ActionLink("Change your password", "ChangePassword")
19 | }
20 | else
21 | {
22 | @Html.ActionLink("Create", "SetPassword")
23 | }
24 | ]
25 |
26 | - External Logins:
27 | -
28 | @Model.Logins.Count [
29 | @Html.ActionLink("Manage", "ManageLogins") ]
30 |
31 | @*
32 | Phone Numbers can used as a second factor of verification in a two-factor authentication system.
33 |
34 | See this article
35 | for details on setting up this ASP.NET application to support two-factor authentication using SMS.
36 |
37 | Uncomment the following block after you have set up two-factor authentication
38 | *@
39 | @*
40 | - Phone Number:
41 | -
42 | @(Model.PhoneNumber ?? "None") [
43 | @if (Model.PhoneNumber != null)
44 | {
45 | @Html.ActionLink("Change", "AddPhoneNumber")
46 | @: |
47 | @Html.ActionLink("Remove", "RemovePhoneNumber")
48 | }
49 | else
50 | {
51 | @Html.ActionLink("Add", "AddPhoneNumber")
52 | }
53 | ]
54 |
55 | *@
56 | - Two-Factor Authentication:
57 | -
58 |
59 | There are no two-factor authentication providers configured. See this article
60 | for details on setting up this ASP.NET application to support two-factor authentication.
61 |
62 | @*@if (Model.TwoFactor)
63 | {
64 | using (Html.BeginForm("DisableTwoFactorAuthentication", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
65 | {
66 | @Html.AntiForgeryToken()
67 | Enabled
68 |
69 |
70 | }
71 | }
72 | else
73 | {
74 | using (Html.BeginForm("EnableTwoFactorAuthentication", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
75 | {
76 | @Html.AntiForgeryToken()
77 | Disabled
78 |
79 |
80 | }
81 | }*@
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Manage/ManageLogins.cshtml:
--------------------------------------------------------------------------------
1 | @model SampleWebApplication.Models.ManageLoginsViewModel
2 | @using Microsoft.Owin.Security
3 | @{
4 | ViewBag.Title = "Manage your external logins";
5 | }
6 |
7 | @ViewBag.Title.
8 |
9 | @ViewBag.StatusMessage
10 | @{
11 | var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes();
12 | if (loginProviders.Count() == 0) {
13 |
14 |
15 | There are no external authentication services configured. See this article
16 | for details on setting up this ASP.NET application to support logging in via external services.
17 |
18 |
19 | }
20 | else
21 | {
22 | if (Model.CurrentLogins.Count > 0)
23 | {
24 | Registered Logins
25 |
53 | }
54 | if (Model.OtherLogins.Count > 0)
55 | {
56 | using (Html.BeginForm("LinkLogin", "Manage"))
57 | {
58 | @Html.AntiForgeryToken()
59 |
60 |
61 | @foreach (AuthenticationDescription p in Model.OtherLogins)
62 | {
63 |
64 | }
65 |
66 |
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Manage/SetPassword.cshtml:
--------------------------------------------------------------------------------
1 | @model SampleWebApplication.Models.SetPasswordViewModel
2 | @{
3 | ViewBag.Title = "Create Password";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | You do not have a local username/password for this site. Add a local
9 | account so you can log in without an external login.
10 |
11 |
12 | @using (Html.BeginForm("SetPassword", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
13 | {
14 | @Html.AntiForgeryToken()
15 |
16 | Create Local Login
17 |
18 | @Html.ValidationSummary("", new { @class = "text-danger" })
19 |
25 |
31 |
36 | }
37 | @section Scripts {
38 | @Scripts.Render("~/bundles/jqueryval")
39 | }
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Manage/VerifyPhoneNumber.cshtml:
--------------------------------------------------------------------------------
1 | @model SampleWebApplication.Models.VerifyPhoneNumberViewModel
2 | @{
3 | ViewBag.Title = "Verify Phone Number";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @using (Html.BeginForm("VerifyPhoneNumber", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
9 | {
10 | @Html.AntiForgeryToken()
11 | @Html.Hidden("phoneNumber", @Model.PhoneNumber)
12 | Enter verification code
13 | @ViewBag.Status
14 |
15 | @Html.ValidationSummary("", new { @class = "text-danger" })
16 |
22 |
27 | }
28 |
29 | @section Scripts {
30 | @Scripts.Render("~/bundles/jqueryval")
31 | }
32 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Shared/Error.cshtml:
--------------------------------------------------------------------------------
1 | @model System.Web.Mvc.HandleErrorInfo
2 |
3 | @{
4 | ViewBag.Title = "Error";
5 | }
6 |
7 | Error.
8 | An error occurred while processing your request.
9 |
10 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Shared/Lockout.cshtml:
--------------------------------------------------------------------------------
1 | @model System.Web.Mvc.HandleErrorInfo
2 |
3 | @{
4 | ViewBag.Title = "Locked Out";
5 | }
6 |
7 |
8 | Locked out.
9 | This account has been locked out, please try again later.
10 |
11 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | @ViewBag.Title - My ASP.NET Application
7 | @Styles.Render("~/Content/css")
8 | @Scripts.Render("~/bundles/modernizr")
9 |
10 |
11 |
12 |
13 |
14 |
22 |
23 |
24 | - @Html.ActionLink("Home", "Index", "Home")
25 | - @Html.ActionLink("Invalidate", "Invalidate", "Home")
26 |
27 |
28 |
29 |
30 |
31 | @RenderBody()
32 |
33 |
36 |
37 |
38 | @Scripts.Render("~/bundles/jquery")
39 | @Scripts.Render("~/bundles/bootstrap")
40 | @RenderSection("scripts", required: false)
41 |
42 |
43 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Shared/_LoginPartial.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.AspNet.Identity
2 | @if (Request.IsAuthenticated)
3 | {
4 | using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
5 | {
6 | @Html.AntiForgeryToken()
7 |
8 |
9 | -
10 | @Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
11 |
12 | - Log off
13 |
14 | }
15 | }
16 | else
17 | {
18 |
19 | - @Html.ActionLink("Register", "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })
20 | - @Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/Web.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 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "~/Views/Shared/_Layout.cshtml";
3 | }
4 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Web.Debug.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
18 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Web.Release.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
18 |
19 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/Web.config:
--------------------------------------------------------------------------------
1 |
2 |
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 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/samples/SampleWebApplication/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cybermaxs/RedisMemoryCacheInvalidation/39f0bbb91de0b1e8fd0a42f0340e7d74c18e0178/samples/SampleWebApplication/favicon.ico
--------------------------------------------------------------------------------
/samples/SampleWebApplication/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cybermaxs/RedisMemoryCacheInvalidation/39f0bbb91de0b1e8fd0a42f0340e7d74c18e0178/samples/SampleWebApplication/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/samples/SampleWebApplication/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cybermaxs/RedisMemoryCacheInvalidation/39f0bbb91de0b1e8fd0a42f0340e7d74c18e0178/samples/SampleWebApplication/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/samples/SampleWebApplication/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cybermaxs/RedisMemoryCacheInvalidation/39f0bbb91de0b1e8fd0a42f0340e7d74c18e0178/samples/SampleWebApplication/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/samples/SampleWebApplication/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cybermaxs/RedisMemoryCacheInvalidation/39f0bbb91de0b1e8fd0a42f0340e7d74c18e0178/samples/SampleWebApplication/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/samples/SampleWebApplication/packages.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 |
--------------------------------------------------------------------------------
/scripts/pack.ps1:
--------------------------------------------------------------------------------
1 | $root = $env:APPVEYOR_BUILD_FOLDER
2 | $versionStr = $env:appveyor_build_version
3 |
4 | $content = (Get-Content $root\RedisMemoryCacheInvalidation.nuspec)
5 | $content = $content -replace '\$version\$',$versionStr
6 |
7 | $content | Out-File $root\RedisMemoryCacheInvalidation.compiled.nuspec
8 |
9 | & nuget pack $root\RedisMemoryCacheInvalidation.compiled.nuspec
--------------------------------------------------------------------------------
/scripts/run_tests.ps1:
--------------------------------------------------------------------------------
1 | .\packages\OpenCover.4.6.166\tools\OpenCover.Console.exe -register:user -target:.\packages\xunit.runner.console.2.0.0\tools\xunit.console.x86.exe -targetargs:"""tests\RedisMemoryCacheInvalidation.Tests\bin\Release\RedisMemoryCacheInvalidation.Tests.dll"" -noshadow -appveyor -notrait ""category=Integration""" -filter:"+[RedisMemoryCacheInvalidation]*" -output:opencoverCoverage.xml
2 |
3 | $coveralls = (Resolve-Path "./packages/coveralls.net.*/tools/csmacnz.coveralls.exe").ToString()
4 |
5 | & $coveralls --opencover -i opencoverCoverage.xml --repoToken $env:COVERALLS_REPO_TOKEN --commitId $env:APPVEYOR_REPO_COMMIT --commitBranch $env:APPVEYOR_REPO_BRANCH --commitAuthor $env:APPVEYOR_REPO_COMMIT_AUTHOR --commitEmail $env:APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL --commitMessage $env:APPVEYOR_REPO_COMMIT_MESSAGE --jobId $env:APPVEYOR_JOB_ID
--------------------------------------------------------------------------------
/scripts/run_tests_local.ps1:
--------------------------------------------------------------------------------
1 | Remove-Item ../build/coverage -rec -force
2 |
3 | ..\packages\OpenCover.4.6.166\tools\OpenCover.Console.exe -register:user -target:..\packages\xunit.runner.console.2.0.0\tools\xunit.console.x86.exe -targetargs:"""..\tests\RedisMemoryCacheInvalidation.Tests\bin\Release\RedisMemoryCacheInvalidation.Tests.dll"" -noshadow -appveyor -notrait ""category=Integration""" -filter:"+[RedisMemoryCacheInvalidation]*" -output:..\build\opencoverCoverage.xml
4 |
5 | ..\packages\ReportGenerator.2.2.0.0\tools\ReportGenerator.exe -reports:..\build\opencoverCoverage.xml -targetdir:../build/coverage
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Constants.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RedisMemoryCacheInvalidation
8 | {
9 | public static class Constants
10 | {
11 | public const string DEFAULT_INVALIDATION_CHANNEL = "invalidate";
12 | public const string DEFAULT_KEYSPACE_CHANNEL = "__keyevent*__:*";
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Core/Interfaces/INotificationManager.cs:
--------------------------------------------------------------------------------
1 | using RedisMemoryCacheInvalidation.Core.Interfaces;
2 | using System;
3 |
4 | namespace RedisMemoryCacheInvalidation.Core
5 | {
6 | ///
7 | /// Manage a list of subscription. Basically a custom IObservable to support topic-based subscriptions.
8 | ///
9 | ///
10 | public interface INotificationManager
11 | {
12 | IDisposable Subscribe(string topic, INotificationObserver observer);
13 |
14 | void Notify(string topicKey);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Core/Interfaces/INotificationObserver.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace RedisMemoryCacheInvalidation.Core.Interfaces
3 | {
4 | ///
5 | /// Provides a mechanism for receiving push-based notifications.
6 | ///
7 | ///
8 | public interface INotificationObserver
9 | {
10 | void Notify(T value);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Core/Interfaces/IRedisNotificationBus.cs:
--------------------------------------------------------------------------------
1 | using RedisMemoryCacheInvalidation.Core;
2 | using RedisMemoryCacheInvalidation.Redis;
3 | using System.Runtime.Caching;
4 | using System.Threading.Tasks;
5 |
6 | namespace RedisMemoryCacheInvalidation
7 | {
8 | public interface IRedisNotificationBus
9 | {
10 | IRedisConnection Connection { get; }
11 | InvalidationStrategyType InvalidationStrategy { get; }
12 | MemoryCache LocalCache { get; }
13 | INotificationManager Notifier { get; }
14 | Task NotifyAsync(string key);
15 | void Start();
16 | void Stop();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Core/NotificationManager.cs:
--------------------------------------------------------------------------------
1 | using RedisMemoryCacheInvalidation.Core.Interfaces;
2 | using RedisMemoryCacheInvalidation.Utils;
3 | using System;
4 | using System.Linq;
5 | using System.Collections.Concurrent;
6 |
7 | namespace RedisMemoryCacheInvalidation.Core
8 | {
9 | ///
10 | /// Manager subscriptions and notifications.
11 | ///
12 | internal class NotificationManager : INotificationManager
13 | {
14 | internal ConcurrentDictionary>> SubscriptionsByTopic { get; set; }
15 |
16 | public NotificationManager()
17 | {
18 | this.SubscriptionsByTopic = new ConcurrentDictionary>>();
19 | }
20 | public void Notify(string topicKey)
21 | {
22 | var subscriptions = SubscriptionsByTopic.GetOrAdd(topicKey, new SynchronizedCollection>());
23 |
24 | if (subscriptions.Count > 0)
25 | foreach (INotificationObserver observer in subscriptions.ToList()) //avoid collection modified
26 | {
27 | observer.Notify(topicKey);
28 | }
29 | }
30 |
31 | public IDisposable Subscribe(string topicKey, INotificationObserver observer)
32 | {
33 | var subscriptions = SubscriptionsByTopic.GetOrAdd(topicKey, new SynchronizedCollection>());
34 |
35 | if (!subscriptions.Contains(observer))
36 | subscriptions.Add(observer);
37 |
38 | return new Unsubscriber(subscriptions, observer);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Core/RedisNotificationBus.cs:
--------------------------------------------------------------------------------
1 | using RedisMemoryCacheInvalidation.Redis;
2 | using StackExchange.Redis;
3 | using System;
4 | using System.Runtime.Caching;
5 | using System.Threading.Tasks;
6 |
7 | namespace RedisMemoryCacheInvalidation.Core
8 | {
9 | ///
10 | /// Invalidation message bus.
11 | ///
12 | internal class RedisNotificationBus : IRedisNotificationBus
13 | {
14 | private readonly InvalidationSettings settings;
15 | public INotificationManager Notifier { get; private set; }
16 | public IRedisConnection Connection { get; internal set; }
17 |
18 | #region Props
19 | public InvalidationStrategyType InvalidationStrategy { get { return settings.InvalidationStrategy; } }
20 | public bool EnableKeySpaceNotifications { get { return settings.EnableKeySpaceNotifications; } }
21 | public MemoryCache LocalCache { get { return settings.TargetCache; } }
22 | public Action NotificationCallback { get { return settings.InvalidationCallback; } }
23 | #endregion
24 |
25 | #region Constructors
26 | private RedisNotificationBus(InvalidationSettings settings)
27 | {
28 | this.settings = settings;
29 |
30 | this.Notifier = new NotificationManager();
31 | }
32 |
33 | public RedisNotificationBus(string redisConfiguration, InvalidationSettings settings)
34 | : this(settings)
35 | {
36 | this.Connection = RedisConnectionFactory.New(redisConfiguration);
37 | }
38 |
39 | public RedisNotificationBus(ConnectionMultiplexer mux, InvalidationSettings settings)
40 | : this(settings)
41 | {
42 | this.Connection = RedisConnectionFactory.New(mux);
43 | }
44 | #endregion
45 |
46 | public void Start()
47 | {
48 | this.Connection.Connect();
49 | Connection.Subscribe(Constants.DEFAULT_INVALIDATION_CHANNEL, OnInvalidationMessage);
50 | if (this.EnableKeySpaceNotifications)
51 | Connection.Subscribe(Constants.DEFAULT_KEYSPACE_CHANNEL, OnKeySpaceNotificationMessage);
52 | }
53 |
54 | public void Stop()
55 | {
56 | this.Connection.Disconnect();
57 | }
58 |
59 | public Task NotifyAsync(string key)
60 | {
61 | return this.Connection.PublishAsync(Constants.DEFAULT_INVALIDATION_CHANNEL, key);
62 | }
63 |
64 | #region Notification Handlers
65 | private void OnInvalidationMessage(RedisChannel pattern, RedisValue data)
66 | {
67 | if (pattern == Constants.DEFAULT_INVALIDATION_CHANNEL)
68 | {
69 | this.ProcessInvalidationMessage(data.ToString());
70 | }
71 | }
72 |
73 | private void OnKeySpaceNotificationMessage(RedisChannel pattern, RedisValue data)
74 | {
75 | var prefix = pattern.ToString().Substring(0, 10);
76 | switch (prefix)
77 | {
78 | case "__keyevent":
79 | this.ProcessInvalidationMessage(data.ToString());
80 | break;
81 | default:
82 | //nop
83 | break;
84 | }
85 | }
86 | #endregion
87 |
88 | private void ProcessInvalidationMessage(string key)
89 | {
90 | if ((this.InvalidationStrategy & InvalidationStrategyType.ChangeMonitor) == InvalidationStrategyType.ChangeMonitor)
91 | Notifier.Notify(key);
92 |
93 | if ((this.InvalidationStrategy & InvalidationStrategyType.AutoCacheRemoval) == InvalidationStrategyType.AutoCacheRemoval)
94 | if (this.LocalCache != null)
95 | this.LocalCache.Remove(key);
96 |
97 | if ((this.InvalidationStrategy & InvalidationStrategyType.External) == InvalidationStrategyType.External)
98 | if (NotificationCallback != null)
99 | NotificationCallback?.Invoke(key);
100 |
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Core/Unsubscriber.cs:
--------------------------------------------------------------------------------
1 | using RedisMemoryCacheInvalidation.Core.Interfaces;
2 | using RedisMemoryCacheInvalidation.Utils;
3 | using System;
4 |
5 | namespace RedisMemoryCacheInvalidation.Core
6 | {
7 | internal class Unsubscriber : IDisposable
8 | {
9 | private SynchronizedCollection> observers;
10 | private INotificationObserver observer;
11 |
12 | public Unsubscriber(SynchronizedCollection> observers, INotificationObserver observer)
13 | {
14 | Guard.NotNull(observers, nameof(observers));
15 | Guard.NotNull(observer, nameof(observer));
16 |
17 | this.observers = observers;
18 | this.observer = observer;
19 | }
20 |
21 | public void Dispose()
22 | {
23 | if (observer != null && observers.Contains(observer))
24 | observers.Remove(observer);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/InvalidationManager.cs:
--------------------------------------------------------------------------------
1 | using RedisMemoryCacheInvalidation.Core;
2 | using RedisMemoryCacheInvalidation.Monitor;
3 | using RedisMemoryCacheInvalidation.Utils;
4 | using StackExchange.Redis;
5 | using System;
6 | using System.Runtime.Caching;
7 | using System.Threading.Tasks;
8 |
9 | namespace RedisMemoryCacheInvalidation
10 | {
11 | ///
12 | /// Libray's entry point.
13 | ///
14 | public static class InvalidationManager
15 | {
16 | internal static IRedisNotificationBus notificationBus;
17 |
18 | ///
19 | /// Redis connection state : connected or not.
20 | ///
21 | public static bool IsConnected
22 | {
23 | get {return notificationBus!=null && notificationBus.Connection.IsConnected;}
24 | }
25 |
26 | #region Setup
27 | ///
28 | /// Use to Configure Redis MemoryCache Invalidation.
29 | /// A new redis connection will be establish based upon parameter redisConfig.
30 | ///
31 | /// StackExchange configuration settings.
32 | /// InvalidationManager settings.(
33 | /// Task when connection is opened and subcribed to pubsub events.
34 | public static void Configure(string redisConfig, InvalidationSettings settings)
35 | {
36 | if (notificationBus == null)
37 | {
38 | notificationBus = new RedisNotificationBus(redisConfig, settings);
39 | notificationBus.Start();
40 | }
41 | }
42 |
43 | ///
44 | /// Use to Configure Redis MemoryCache Invalidation.
45 | ///
46 | /// Reusing an existing ConnectionMultiplexer.
47 | /// InvalidationManager settings.(
48 | /// Task when connection is opened and subcribed to pubsub events.
49 | public static void Configure(ConnectionMultiplexer mux, InvalidationSettings settings)
50 | {
51 | if (notificationBus == null)
52 | {
53 | notificationBus = new RedisNotificationBus(mux, settings);
54 | notificationBus.Start();
55 | }
56 | }
57 | #endregion
58 |
59 | #region CreateMonitor
60 | ///
61 | /// Allow to create a custom ChangeMonitor depending on the pubsub event (channel : invalidate, data:invalidationKey)
62 | ///
63 | /// invalidation key send by redis PUBLISH invalidate invalidatekey
64 | /// RedisChangeMonitor watching for notifications
65 | public static RedisChangeMonitor CreateChangeMonitor(string invalidationKey)
66 | {
67 | Guard.NotNullOrEmpty(invalidationKey, nameof(invalidationKey));
68 |
69 | EnsureConfiguration();
70 |
71 | if (notificationBus.InvalidationStrategy == InvalidationStrategyType.AutoCacheRemoval)
72 | throw new InvalidOperationException("Could not create a change monitor when InvalidationStrategy is DefaultMemoryCacheRemoval");
73 |
74 | return new RedisChangeMonitor(notificationBus.Notifier, invalidationKey);
75 | }
76 |
77 | ///
78 | /// Allow to create a custom ChangeMonitor depending on the pubsub event (channel : invalidate, data:item.Key)
79 | ///
80 | /// todo: describe item parameter on CreateChangeMonitor
81 | /// RedisChangeMonitor watching for notifications
82 | public static RedisChangeMonitor CreateChangeMonitor(CacheItem item)
83 | {
84 | Guard.NotNull(item, nameof(item));
85 |
86 | EnsureConfiguration();
87 |
88 | return new RedisChangeMonitor(notificationBus.Notifier, item.Key);
89 | }
90 | #endregion
91 |
92 | ///
93 | /// Used to send invalidation message for a key.
94 | /// Shortcut for PUBLISH invalidate key.
95 | ///
96 | ///
97 | /// Task with the number of subscribers
98 | public static Task InvalidateAsync(string key)
99 | {
100 | Guard.NotNullOrEmpty(key, nameof(key));
101 |
102 | EnsureConfiguration();
103 |
104 | return notificationBus.NotifyAsync(key);
105 | }
106 |
107 | private static void EnsureConfiguration()
108 | {
109 | if (notificationBus == null)
110 | throw new InvalidOperationException("Configure() was not called");
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/InvalidationSettings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.Caching;
3 |
4 | namespace RedisMemoryCacheInvalidation
5 | {
6 | public class InvalidationSettings
7 | {
8 | ///
9 | /// How to process invalidation message (remove from cache, invoke callback, notify change monitor, ...)
10 | ///
11 | public InvalidationStrategyType InvalidationStrategy { get; set; }
12 | ///
13 | /// Target MemoryCache when InvalidationStrategy=AutoCacheRemoval
14 | ///
15 | public MemoryCache TargetCache { get; set; }
16 | ///
17 | /// Subscribe to keyspace notification (if enabled on the redis DB)
18 | ///
19 | public bool EnableKeySpaceNotifications { get; set; }
20 | ///
21 | /// Invalidation callback invoked when InvalidationStrategy=External.
22 | ///
23 | public Action InvalidationCallback { get; set; }
24 |
25 | public InvalidationSettings()
26 | {
27 | this.InvalidationStrategy = InvalidationStrategyType.All;
28 | this.TargetCache = MemoryCache.Default;
29 | this.EnableKeySpaceNotifications = false;
30 | this.InvalidationCallback = null;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/InvalidationStrategyType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | namespace RedisMemoryCacheInvalidation
3 | {
4 | ///
5 | /// Cache Invalidation Strategy.
6 | ///
7 | [Flags]
8 | public enum InvalidationStrategyType
9 | {
10 | Undefined = 0,
11 | ///
12 | /// Use only change monitor to invalidate local items.
13 | ///
14 | ChangeMonitor=1,
15 | ///
16 | /// Auto remove items from default memory cache.
17 | ///
18 | AutoCacheRemoval=2,
19 | ///
20 | /// External. An event is emitted.
21 | ///
22 | External = 4,
23 | ///
24 | /// All.
25 | ///
26 | All = ~0
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Monitor/RedisChangeMonitor.cs:
--------------------------------------------------------------------------------
1 | using RedisMemoryCacheInvalidation.Core;
2 | using RedisMemoryCacheInvalidation.Core.Interfaces;
3 | using RedisMemoryCacheInvalidation.Utils;
4 | using System;
5 | using System.Globalization;
6 | using System.Runtime.Caching;
7 |
8 | namespace RedisMemoryCacheInvalidation.Monitor
9 | {
10 | public class RedisChangeMonitor : ChangeMonitor, INotificationObserver
11 | {
12 | private readonly string uniqueId;
13 | private readonly string key;
14 | private readonly IDisposable unsubscriber;
15 | ///
16 | /// Contructor.
17 | ///
18 | /// Registration handler
19 | /// invalidation Key
20 | public RedisChangeMonitor(INotificationManager notifier, string key)
21 | {
22 | Guard.NotNull(notifier, nameof(notifier));
23 | Guard.NotNullOrEmpty(key, nameof(key));
24 |
25 | var flag = true;
26 | try
27 | {
28 | this.unsubscriber = notifier.Subscribe(key, this);
29 | this.uniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
30 | this.key = key;
31 | flag = false;
32 | }
33 | catch (Exception)
34 | {
35 | //any error
36 | flag = true;
37 | }
38 | finally
39 | {
40 | base.InitializationComplete();
41 | if (flag)
42 | {
43 | base.Dispose();
44 | }
45 | }
46 | }
47 |
48 | protected override void Dispose(bool disposing)
49 | {
50 | // always Unsubscribe on dispose
51 | this.Unsubscribe();
52 | }
53 |
54 | public override string UniqueId
55 | {
56 | get { return this.uniqueId; }
57 | }
58 |
59 | #region INotification
60 | public void Notify(string value)
61 | {
62 | if (value == key)
63 | base.OnChanged(null);
64 | }
65 | #endregion
66 |
67 | private void Unsubscribe()
68 | {
69 | if (this.unsubscriber != null)
70 | this.unsubscriber.Dispose();
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/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("RedisMemoryCacheInvalidation")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Cybermaxs")]
12 | [assembly: AssemblyProduct("RedisMemoryCacheInvalidation")]
13 | [assembly: AssemblyCopyright("Copyright © 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("f5144c1c-3053-4d47-baf0-9af95792ab95")]
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.14474.1")]
36 | [assembly: AssemblyFileVersion("1.0.14474.1")]
37 |
38 | [assembly: InternalsVisibleTo("RedisMemoryCacheInvalidation.Tests")]
39 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Redis/ExistingRedisConnnection.cs:
--------------------------------------------------------------------------------
1 | using RedisMemoryCacheInvalidation.Utils;
2 | using StackExchange.Redis;
3 |
4 | namespace RedisMemoryCacheInvalidation.Redis
5 | {
6 | internal class ExistingRedisConnnection : RedisConnectionBase
7 | {
8 | public ExistingRedisConnnection(IConnectionMultiplexer mux)
9 | {
10 | multiplexer = mux;
11 | }
12 |
13 | public override bool Connect()
14 | {
15 | return this.IsConnected;
16 | }
17 |
18 | public override void Disconnect()
19 | {
20 | this.UnsubscribeAll();
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Redis/IRedisConnection.cs:
--------------------------------------------------------------------------------
1 | using StackExchange.Redis;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Threading.Tasks;
5 |
6 | namespace RedisMemoryCacheInvalidation.Redis
7 | {
8 | public interface IRedisConnection
9 | {
10 | bool IsConnected { get; }
11 | bool Connect();
12 | void Disconnect();
13 | Task[]> GetConfigAsync();
14 | void Subscribe(string channel, Action handler);
15 | void UnsubscribeAll();
16 | Task PublishAsync(string channel, string value);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Redis/RedisConnectionBase.cs:
--------------------------------------------------------------------------------
1 | using RedisMemoryCacheInvalidation.Utils;
2 | using StackExchange.Redis;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Threading.Tasks;
6 |
7 | namespace RedisMemoryCacheInvalidation.Redis
8 | {
9 | internal abstract class RedisConnectionBase : IRedisConnection
10 | {
11 | protected IConnectionMultiplexer multiplexer;
12 |
13 | #region IRedisConnection
14 | public bool IsConnected
15 | {
16 | get { return this.multiplexer != null && this.multiplexer.IsConnected; }
17 | }
18 |
19 | public void Subscribe(string channel, Action handler)
20 | {
21 | if (this.IsConnected)
22 | {
23 | var subscriber = this.multiplexer.GetSubscriber();
24 | subscriber.Subscribe(channel, handler);
25 | }
26 | }
27 |
28 | public void UnsubscribeAll()
29 | {
30 | if (this.IsConnected)
31 | this.multiplexer.GetSubscriber().UnsubscribeAll();
32 | }
33 |
34 | public Task PublishAsync(string channel, string value)
35 | {
36 | if (this.IsConnected)
37 | {
38 | return this.multiplexer.GetSubscriber().PublishAsync(channel, value);
39 | }
40 | else
41 | return TaskCache.FromResult(0L);
42 | }
43 | public Task[]> GetConfigAsync()
44 | {
45 | if (this.IsConnected)
46 | {
47 | var server = this.GetServer();
48 | return server.ConfigGetAsync();
49 | }
50 | else
51 | return TaskCache.FromResult(new KeyValuePair[] { });
52 | }
53 | #endregion
54 |
55 | #region privates
56 | protected IServer GetServer()
57 | {
58 | var endpoints = this.multiplexer.GetEndPoints();
59 | IServer result = null;
60 | foreach (var endpoint in endpoints)
61 | {
62 | var server = this.multiplexer.GetServer(endpoint);
63 | if (server.IsSlave || !server.IsConnected) continue;
64 | if (result != null) throw new InvalidOperationException("Requires exactly one master endpoint (found " + server.EndPoint + " and " + result.EndPoint + ")");
65 | result = server;
66 | }
67 | if (result == null) throw new InvalidOperationException("Requires exactly one master endpoint (found none)");
68 | return result;
69 | }
70 |
71 | public abstract bool Connect();
72 |
73 | public abstract void Disconnect();
74 | #endregion
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Redis/RedisConnectionFactory.cs:
--------------------------------------------------------------------------------
1 | using StackExchange.Redis;
2 |
3 | namespace RedisMemoryCacheInvalidation.Redis
4 | {
5 | internal class RedisConnectionFactory
6 | {
7 | public static IRedisConnection New(IConnectionMultiplexer mux)
8 | {
9 | return new ExistingRedisConnnection(mux);
10 | }
11 |
12 | public static IRedisConnection New(string options)
13 | {
14 | return new StandaloneRedisConnection(options);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Redis/StandaloneRedisConnection.cs:
--------------------------------------------------------------------------------
1 | using StackExchange.Redis;
2 | using System;
3 | using System.Reflection;
4 |
5 | namespace RedisMemoryCacheInvalidation.Redis
6 | {
7 | internal class StandaloneRedisConnection : RedisConnectionBase
8 | {
9 | private readonly ConfigurationOptions options;
10 | public StandaloneRedisConnection(string configurationOptions)
11 | {
12 | this.options = ConfigurationOptions.Parse(configurationOptions);
13 | }
14 |
15 | public override bool Connect()
16 | {
17 | if (this.multiplexer == null)
18 | {
19 | //myope overrides here
20 | this.options.ConnectTimeout = 5000;
21 | this.options.ConnectRetry = 3;
22 | this.options.DefaultVersion = new Version("2.8.0");
23 | this.options.KeepAlive = 90;
24 | this.options.AbortOnConnectFail = false;
25 | this.options.ClientName = "InvalidationClient_" + System.Environment.MachineName + "_" + Assembly.GetCallingAssembly().GetName().Version;
26 |
27 | this.multiplexer = ConnectionMultiplexer.Connect(options);
28 | }
29 |
30 | return this.multiplexer.IsConnected;
31 | }
32 |
33 | public override void Disconnect()
34 | {
35 | this.UnsubscribeAll();
36 | this.multiplexer.Close(false);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/RedisMemoryCacheInvalidation.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {13467724-727B-44CA-BE9D-19BFC4599B34}
8 | Library
9 | Properties
10 | RedisMemoryCacheInvalidation
11 | RedisMemoryCacheInvalidation
12 | v4.5
13 | 512
14 | ..\..\
15 | true
16 |
17 | 6483b00a
18 |
19 |
20 | true
21 | full
22 | false
23 | bin\Debug\
24 | DEBUG;TRACE
25 | prompt
26 | 4
27 | false
28 |
29 |
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 | false
37 |
38 |
39 |
40 | ..\..\packages\StackExchange.Redis.1.0.481\lib\net45\StackExchange.Redis.dll
41 | True
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
81 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Utils/Guard.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace RedisMemoryCacheInvalidation.Utils
5 | {
6 | internal static class Guard
7 | {
8 | ///
9 | /// Ensures the value of the given is not null.
10 | /// Throws otherwise.
11 | ///
12 | /// instance to test for null
13 | /// nameof the parameter (for ArgumentNullException)
14 | public static void NotNull(object parameter, string parameterName)
15 | {
16 | if (parameter == null)
17 | throw new ArgumentNullException(parameterName);
18 | }
19 |
20 | ///
21 | /// Ensures the value of the given is not null.
22 | /// Throws otherwise.
23 | ///
24 | /// instance to test for (Generic) Default value
25 | /// nameof the parameter (for ArgumentNullException)
26 | public static void NotDefault(T parameter, string parameterName)
27 | {
28 | if (EqualityComparer.Default.Equals(parameter, default(T)))
29 | throw new ArgumentNullException(parameterName);
30 | }
31 |
32 | ///
33 | /// Ensures the string value of the given is not null or empty.
34 | /// Throws in the first case, or
35 | /// in the latter.
36 | ///
37 | /// string to test for Null or empty
38 | /// nameof the parameter (for ArgumentNullException)
39 | public static void NotNullOrEmpty(string parameter, string parameterName)
40 | {
41 | if (string.IsNullOrEmpty(parameter))
42 | throw new ArgumentNullException(parameterName);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Utils/SynchronizedCollection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Diagnostics.CodeAnalysis;
5 | using System.Linq;
6 | using System.Text;
7 |
8 | namespace RedisMemoryCacheInvalidation.Utils
9 | {
10 | ///
11 | /// Inspired by System.ServiceModel.SynchronizedCollection
12 | /// https://github.com/Microsoft/referencesource/blob/master/System.ServiceModel/System/ServiceModel/SynchronizedCollection.cs
13 | ///
14 | ///
15 | [ExcludeFromCodeCoverage]
16 | public class SynchronizedCollection : ICollection
17 | {
18 | private List items;
19 | private object sync;
20 |
21 | public SynchronizedCollection()
22 | {
23 | this.items = new List();
24 | this.sync = new object();
25 | }
26 |
27 | public int Count
28 | {
29 | get { lock (this.sync) { return this.items.Count; } }
30 | }
31 |
32 | public void Add(T item)
33 | {
34 | lock (this.sync)
35 | {
36 | int index = this.items.Count;
37 | this.items.Insert(index, item);
38 | }
39 | }
40 |
41 | public void Clear()
42 | {
43 | lock (this.sync)
44 | {
45 | this.items.Clear();
46 | }
47 | }
48 |
49 | public void CopyTo(T[] array, int index)
50 | {
51 | lock (this.sync)
52 | {
53 | this.items.CopyTo(array, index);
54 | }
55 | }
56 |
57 | public bool Contains(T item)
58 | {
59 | lock (this.sync)
60 | {
61 | return this.items.Contains(item);
62 | }
63 | }
64 |
65 | public IEnumerator GetEnumerator()
66 | {
67 | lock (this.sync)
68 | {
69 | return this.items.GetEnumerator();
70 | }
71 | }
72 |
73 | int InternalIndexOf(T item)
74 | {
75 | int count = items.Count;
76 |
77 | for (int i = 0; i < count; i++)
78 | {
79 | if (object.Equals(items[i], item))
80 | {
81 | return i;
82 | }
83 | }
84 | return -1;
85 | }
86 |
87 | public bool Remove(T item)
88 | {
89 | lock (this.sync)
90 | {
91 | int index = this.InternalIndexOf(item);
92 | if (index < 0)
93 | return false;
94 |
95 | this.items.RemoveAt(index);
96 | return true;
97 | }
98 | }
99 |
100 | bool ICollection.IsReadOnly
101 | {
102 | get { return false; }
103 | }
104 |
105 | IEnumerator IEnumerable.GetEnumerator()
106 | {
107 | return ((IList)this.items).GetEnumerator();
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/RedisMemoryCacheInvalidation/Utils/TaskCache.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace RedisMemoryCacheInvalidation.Utils
4 | {
5 | internal class TaskCache
6 | {
7 | public static readonly Task AlwaysTrue = MakeTask(true);
8 | public static readonly Task AlwaysFalse = MakeTask(false);
9 | public static readonly Task