Redis - Moving from IIS session state - c#

I am trying to understand how to implement redis by replacing all IIS session state values with the redis cache. I have redis working using a docker image. I am using a C# .Net Forms web app. I have included the StackExchange.Redis nuget package and set this up so far.
public class Redis
{
private static readonly Lazy<ConnectionMultiplexer> LazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
var redisConnectionString = ConfigurationManager.AppSettings["RedisConnectionString"];
var options = ConfigurationOptions.Parse(redisConnectionString);
options.AbortOnConnectFail = false;
return ConnectionMultiplexer.Connect(options);
});
public static ConnectionMultiplexer Connection => LazyConnection.Value;
}
But I am confused how to store the user context session key value pairs. By this I mean when I store a key for the user, say "UserId" can the key just be "UserId"? or do I needs to prefix it with a unique user specific context id. I cant find anything on how redis would work based on different users. How does it know the context of the user and hence how to get the correct key?
All I'm reading is that its a hashtable that stores values, which is fine for a single UserId, but I'm going to have lots of users with a UserId?
If anyone can help me understand this, that would be great, thanks you

Ok so after looking at the following link I was able to get it all working
https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-aspnet-session-state-provider
I installed a redis docker image as follows on the command line, replacing your your_long_password_here, you can also just specify port 6379:6379, but I wanted to see what happens if I used another port. The password is important as otherwise it will run in protected mode which means only localhost(loopback) calls can be made, which helps protect it more, as pre version 3.2 this was not the case!
docker run -p 8055:6379 --name redis --restart=always -d redis –-requirepass <your_long_password_here>
The run this command to make sure it was running, you should see that the port has the values you provided
docker container ls
Then in your .net project install the following nuget packagefrom the nuget package manager console or use the GUI package manager. You will need to use .net 4.6.2 for the current version.
Install-Package Microsoft.Web.RedisSessionStateProvider
Once installed comment out or remove this line in your Web.config
<sessionState mode="InProc" timeout="60" />
Then update or change the new provider that the nuget package will have added to your Web.config. This should be inside the system.web tag. Note that I used a connection string as this will not be hosted on azure as of yet, but there are lots of confusing extra options to configure it.
<sessionState mode="Custom" customProvider="MySessionStateStore">
<providers>
<add name="MySessionStateStore"
type="Microsoft.Web.Redis.RedisSessionStateProvider"
connectionString="localhost:8055,password=your_long_password_here"/>
</providers>
</sessionState>
You will need to make sure all the objects that you were storing in the iis session have the [Serializable] attribute as otherwise it wont work and will throw an error.
You should now see that you can run your website seamlessly, and you will now be using the redis cache and no longer the session state.

Related

Share Session Across Multiple Applications [duplicate]

I am trying to share sessions between two web applications, both hosted on the same server. One is a .net 2.0 web forms application the other is as .net 3.5 MVC2 application.
Both apps have their session set up like this:
<sessionState
mode="StateServer"
stateConnectionString="tcpip=127.0.0.1:42424"
/>
In the webform application I am posting the the session key to the MVC app:
protected void LinkButton1_Click(object sender, EventArgs e)
{
Session["myvariable"] = "dan";
string sessionKey = HttpContext.Current.Session.SessionID;
//Followed by some code that posts sessionKey to the other application
}
I then recieve it in the MVC application and try use the same session like this:
[HttpPost]
public void Recieve(string sessionKey )
{
var manager = new SessionIDManager();
bool redirected;
bool IsAdded;
manager.SaveSessionID(HttpContext.ApplicationInstance.Context, Id, out redirected, out IsAdded);
var myVar = Session["myvariable"];
}
The key is being posted but the session does not seem to get loaded in the MVC app, i.e. sessionKey is null. Can what I am trying to do be done?
I did it this way:
Basically the idea is both apps use native .net sessionState stored in sqlserver. By using the same machine key and making a small tweak to a stored procedure – both apps can share any session keys and/or forms authenication.
Both apps would do something like this in their web.config:
<sessionState mode="SQLServer" sqlConnectionString="Data Source=.\SQLEXPRESS;User Id=test;Password=test;Application Name=AppName" />
<machineKey
validationKey="SOMEKEY"
validation="SHA1" decryption="AES"
/>
Session state db would need to be set up on a database server, that both apps can see.
Docs for doing this:
http://msdn.microsoft.com/en-us/library/ms229862(VS.80).aspx
Command that would need to be run:
C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin>aspnet_regsql.exe -E -ssadd --sstype p -S .\SQLEXPRESS
Stored procedure (TempGetAppID) tweak to:
#appId int OUTPUT
AS
-- start change
-- Use the application name specified in the connection for the appname if specified
-- This allows us to share session between sites just by making sure they have the
-- the same application name in the connection string.
DECLARE #connStrAppName nvarchar(50)
SET #connStrAppName = APP_NAME()
-- .NET SQLClient Data Provider is the default application name for .NET apps
IF (#connStrAppName <> '.NET SQLClient Data Provider')
SET #appName = #connStrAppName
-- end change
SET #appName = LOWER(#appName)
The problem is that session keys are scoped to the applications, so two applications having the same session key in fact have separate sessions.
You can do one of two things:
Put both applications as a virtual directory under a common IIS Application. I don't think this is a good idea, but it will work.
Roll your own session data solution for the data you want to share. Possibly using the backend database as the common storage, if you have one that is.
Based on Justin's comment, just to clarify option 2 is not refering to the SQL state managemet for out of process sessions. I mean for you to actually manually manage the shared data for the two sessions, possibly using a database.
You can use a common Machine key to generate same Session ID inside both applications for a given user. Additionally, you should also plan on storing sessions of both applications in a common store such as ASP.NET State Service or a distributed cache.
You can use NCache distributed cache which takes provides session sharing feature between different applications. You specify same Application ID tag for both apps inside session state settings which allows you to share session object provided you have same Session ID generated for both applications.

Stackexchange.Redis configuration for .NET Core in ha mode

I have a redis 3 servers + 3 sentinels running locally in docker. Testing the setup I'm seeing that the sentinels are doing their job, if I crush the master a new one is elected.
What my test is
check docker-compose up logs
ssh into my master node and check the Info
write something on master
check the value on the slaves
crush the master
trail sentinels logs
check the new elected master
bring back the ex-master node
So this works all good
Now I'm confused in regarding with something. I want to build a dotnet core 6 app on top of this.
I'm using stackexchange redis, which I can see that on the multiplexer has ConnectSentinel method.
I'm confused, do I need to setup in .net something related to sentinel ? I am living under the impression that this is an infrastructure concern which my app should not be aware. My impression is that I have to only provide a connection to the master and slaves
"172.10.10.10:5660,172.10.10.10:5660,172.10.10.10:5660,password=pass"
, am I wrong and I have to set something for sentinel nodes ?
I can see that there is an option in ConfigurationOptions called ServiceName which states
/// <summary>
/// The service name used to resolve a service via sentinel.
/// </summary>
public string? ServiceName { get; set; }
Also checking this
https://stackexchange.github.io/StackExchange.Redis/Configuration.html
serviceName={string} ServiceName null Used for connecting to a sentinel primary service
I'm not sure what this means.
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="6.0.10" />
<PackageReference Include="StackExchange.Redis" Version="2.6.70" />

How to authenticate with a private repo on DockerHub through the Docker.Dotnet client?

I am currently writing a microservice in .NET Standard that writes a Dockerfile, builds the Docker image associated with that Dockerfile (which implicitly causes a pull image), and pushes the image to a private Docker registry. Those Docker operations are all performed using the Docker.Dotnet library that MSFT maintains. I believe that this is mostly just a wrapper around calls to the Docker Remote API. The execution context of this microservice is in a K8s cluster hosted on AWS, or internally on bare-metal, depending on the deployment.
Previously our Docker registry has just been a private registry hosted on Artifactory internally, but we are migrating to a private DockerHub registry/repository/. With this migration have come some authentication problems.
We authenticate all of the pull and push operations with an AuthConfig that consists of the username and password for the account associated with the registry. The AuthConfig is either added to a Parameters object and then passed to the call:
imageBuildParameters.AuthConfigs = new Dictionary<string,
AuthConfig>() { { DockerEnvVariables.DockerRegistry, authConfig } };
…
using (var responseStream = _dockerClient.Images.BuildImageFromDockerfileAsync(tarball, imageBuildParameters).GetAwaiter().GetResult())
Or it’s (strangely, to me) both passed in a parameter and separately to the call:
ImagePushParameters imagePushParameters = new ImagePushParameters() { ImageID = image.Id, RegistryAuth = authConfig, Tag = "latest" };
_dockerClient.Images.PushImageAsync(RepoImage(image.Id), imagePushParameters, authConfig, this).GetAwaiter().GetResult();
We are currently getting auth errors for any of the various Docker registries I’ve tried for DockerHub, as such (where I’ve redacted the organzation/namespace and image:
{“message”:“pull access denied for registry-1.docker.io//, repository does not exist or may require ‘docker login’: denied: requested access to the resource is denied”},“error”:“pull access denied for registry-1.docker.io//, repository does not exist or may require ‘docker login’: denied: requested access to the resource is denied”
The list of DockerHub urls that I’ve tried is following, all with either the error above or a different “Invalid reference format” error:
Hub.docker.io
Hub.docker.io/v1/
Docker.io
Docker.io/v1/
Index.docker.io
Index.docker.io/v1/
Registry.hub.docker.com
registry-1.docker.io
Strangely enough, if I run it locally on my Windows system, the bolded URL’s actually work. However, they all fail when deployed to the cluster (different method of interacting with the Docker socket/npipe?).
Does anyone know the correct URL I need to set to properly authenticate and interact with DockerHub? Or if my current implementation and usage of the Docker.Dotnet library is incorrect in some way? Again, it works just fine with a private Docker registry on Artifactory, and also with DockerHub when run on my local Windows system. Any help would be greatly appreciated. I can provide any more information that is necessary, be it code or configuration.

Maintaining both on-line (Azure) and off-line version of a website

Note: Not sure if the question is asked the right way. This is how I perceive the issue but it's fully possible that the problem is addressable form a totally different angle, which I'm unaware of due to the ignorance.
Question
Is there a built-in database available for an out-of-the-box, MVC solution? If so, how do I find out its connection string?
Current string is for Azure and looks like this (frankly, it scares living excrement out me because I don't understand most of it).
<add name="DefaultConnection" connectionString="
Data Source=(LocalDb)\v11.0;
Initial Catalog=aspnet-Plong-20141107210818;
Integrated Security=SSPI;
AttachDBFilename=|DataDirectory|\aspnet-Plong-20141107210818.mdf"
providerName="System.Data.SqlClient" />
Background info
I'm developing a site and publish it to the Azure. It has some connections to the database and I'm using Code First and Entity Framework. Everything works great (maybe except the fact that it takes a few seconds to upload and initialize the page prior showing, which is annoying if I've only made changes to the markup of Razor).
In fact, all's been set up pretty much automagically, and I didn't have to configure much at all. Now, it's biting my in the sitting device because of the following.
I need to be able to run my site on the local host (using F5, if you will) because I'll be going off-line (or at the very least under a very lousy connection). I can do that right now, except for the page that contacts the database reference, where I'm getting the error below.
I get what the problem is - no local DB set up using code first. I wonder if there's a lazy man's solution to it (using some built in DB and code-first-able). If so, where do I set it up? I prefer to keep the reference to default connection string as intact as possible but if I need to edit it (or, most likely, add a new one and reference it), how do I learn the correct connection string?! (Yes, I know, this is the price I pay for taking an easy way out letting Azure configure everything for me. Head down in shame.)
{"Model compatibility cannot be checked because the database does not contain model metadata. Model compatibility can only be checked for databases created using Code First or Code First Migrations."}
As far as I know that's not a connection string to SQL Azure, but for a local (development) database. It will create a .mdf file in your App_Data folder. You can find connection strings to SQL Azure somewhere in the Azure dashboard. I switch between dev en production using for example:
public MyContext : DbContext
{
public MyContext()
#if DEBUG
: base("development")
#else
: base("production")
#endif
{
}
}
However, you could also use XML transformation of the web.config, web.debug.config and web.release.config. Note that web.debug.config is not really used when you run your application locally, so put your development connection strings in web.config and publish your application in release mode so the XML transformation of web.release.config takes place.
Edit: Get the SQL Azure connection string
Got to the management portal and click on SQL Databases. Click on the database and go to Dashboard. On the right side you see "Show connection strings". It looks something like this:
Server=tcp:xxx.database.windows.net,1433;Database={your_db_name};User ID={your_user_id};Password={your_password_here};Trusted_Connection=False;Encrypt=True;Connection Timeout=30;

Azure Mobile Services - Connection string not found after publishing

After I deployed my mobile service to Azure, calls to the service fails because of this error:
No connection string named 'ApplicationEntities' could be found in the application config file.
The error only occurs on Azure side. When I test the service locally, the connection to the remote database works without a problem.
I separated my solution into several projects:
Web Api
Business Logic
Data Access (contains the DbContext, database first)
Common (contains the entities generated by EF)
As I always do, I copied the connection string generated in my app.config of the DataAccess Assembly into the connectionStrings-Element of my web.config (Web Api project).
<connectionStrings>
<add name="ApplicationEntities" connectionString="<the connection string>" providerName="System.Data.EntityClient" />
</connectionStrings>
In the web deploy settings, I selected the connection string for "ApplicationEntities". I tested it with and without the option "Use this connection string at runtime (update destination web.config). I always get the same error.
Then I got curious and logged the connection strings available via the ConfigurationManager with something like this:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < ConfigurationManager.ConnectionStrings.Count; i++)
{
sb.AppendLine(ConfigurationManager.ConnectionStrings[i].ConnectionString);
}
trace.Info(sb.ToString());
I got two connection strings:
data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true
Data Source=theserver.database.windows.net;Initial Catalog=thedb_db;User ID=theuser;Password=thepassword;Asynchronous Processing=True;TrustServerCertificate=False;
The username and password is strangely different from the username and password stated in the management portal.
Also the EF-Metadata information get lost.
I get this connection strings when testing locally:
data source=.\\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true
metadata=res://*/DataContext.ApplicationEntities.csdl|res://*/DataContext.ApplicationEntities.ssdl|res://*/DataContext.ApplicationEntities.msl;provider=System.Data.SqlClient;provider connection string=\"data source=theserveraddress,1433;initial catalog=thedb_db;persist security info=True;user id=theusername;password=thepassword;MultipleActiveResultSets=True;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;App=EntityFramework\"
I expect to get the same connection strings when running on Azure. Any idea what's going wrong?
Update:
I just went for remote debugging the service (see here). As I opened the downloaded publish-profile in my text editor, I discovered that there is an attribute SQLServerDBConnectionString - containing the connection string that always gets forced when deployed - with the same username and password I'm not aware of that it exists.
I tried to overrwite this connection string but it did not help. That connection remains the active.
Update 2 / May 29th 2014:
Seems that in the meantime the error has been fixed by the azure mobile team. Still running the same code using the database first approach and it's working now. Thanks a lot!
I have myself tried using DB first approach with dot net backend mobile services & ran into same sort of problems as you. Digging around further, following is my observation :
The azure mobile service with dot net backend must use code first approach only. This generates a specialised connection string with metadata (starting with res:)
The name of the connection string has to be the same as it is when you download the default sample todo app I.e. Ms_TableConnectionString.
There is an option in azure websites to select custom SQL provider to help make a custom connection string, this option, however, is not available for dot net backend mobile service in Azure Management portal.
P.s. Posting as answer because I don't have a enough points to comment..
Just wanted to give an update that I have been looking at this for some time and think I for the first time see it too. I don't know what is going on but wanted to let you know that we are indeed looking at it.
Henrik
(I don't have enough points to comment so having to do this as an answer)
Henrik, I have this same problem. If I ftp on to the box I can see that the web.config has the correct connection string but it fails as trying to use the username in the SQLServerDBConnectionString property (OoWUqr****Login). Is it possible you could let me know in what order it is looking for connection strings and where?
And if it can't stop it using the other user is there a way I can permission them for the correct database through mobile services?
Thanks
F

Categories