Using sessions in .net core 1 - c#

I'm trying to enable sessions in .net core webapp. I have tried following the documentation from here. But issue is the sessions are not getting persisted. With every new request new session id is generated even if previous request has stored something in session. Also I don't see any cookie in dev tools.
Referenced dlls
"Microsoft.AspNetCore.Session": "1.1.1",
"Microsoft.Extensions.Caching.Memory": "1.1.1"
My startup file looks something like this
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc(options => { options.Filters.Add(new RequireHttpsAttribute()); });
// Add services needed for sessions
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(10);
});
// Add in-memory distributed cache
services.AddDistributedMemoryCache();
// initialising other services, authentication and authorization policies
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// enable session before uisng it in pipeline
app.UseSession();
// setting custom user middleware
app.UseUserMiddleware();
// set up the mvc default route
app.UseMvc(routes => { routes.MapRoute("default", "myDefaultRoute"); });
// adding few other middlewares
}
And I set and access the session values in my controller something like this
public class MyController : Controller
{
private const string Key = "someKey";
public async Task<ResponseModel> Get()
{
var id = HttpContext.Session.GetInt32(Key);
return new ResponseModel(await _myService.GetAsync(id));
}
public async Task Set([FromBody] RequestModel request)
{
var id = await _myService.GetAsync(request.id);
HttpContext.Session.SetInt32(Key, id);
}
}

In ASP.NET Core, session state is stored in distributed cache, which you've configured to be in-memory. This is basically the same as In Proc session storage in ASP.NET. Since everything stored in memory is tied to the process, whenever the process changes, your session store is wiped.
Now, it should still persist request to request, as long as you keep the application running, but particularly if you stop/start debugging in Visual Studio, you're killing and restarting the process and therefore, wiping the session.
Long and short, if you need your sessions to be persistent, you need to use a persistent store, like SQL Server or Redis. Either can be used in development just as well as in production, if you so desire. Refer to the documentation for details on how to set up a persistent store.

Related

ASP.Net Core Web Api - Use production and test database connection strings

I am building a web api with asp.net core and I wanted to ask how you would handle production and test environments inside the api. The goal would be to publish the web api on the IIS and then for it to use the production db connection string. When I start my api locally I would like my api to connect to the test database without me changing a lot of stuff in the code.
In my Startup.cs I use Entity Framework like this:
services.AddDbContextFactory<ProdContext>(options =>
{
string decrypted = ConnStringSecurity.Decrypt(Configuration.GetConnectionString("ProdDBConnection"));
options.UseSqlServer(decrypted,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(5),
errorNumbersToAdd: null
);
});
});
services.AddDbContextFactory<TestContext>(options =>
{
string decrypted = ConnStringSecurity.Decrypt(Configuration.GetConnectionString("TestDBConnection"));
options.UseSqlServer(decrypted,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(5),
errorNumbersToAdd: null
);
});
});
In the Configure method I see that you can differentiate between development and production but I can't quite imagine how the DbContexts can be integrated in the if statement:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
//Use Test Db globally
app.UseDeveloperExceptionPage();
}
else if(env.IsProduction())
{
//Use Prod Db globally
}
}
Also in every Controller I inject the Context that I need so to make this happen I would have to check on every endpoint if I am currently in development or production. Is there a more efficient way to do this? The 'lazy' approach would be to publish two instances of my api, one for prod and one for test where the code is changed accordingly.
You can inject IWebHostEnvironment to the Startup class by its constructor, which is the same one as you have among the parameters of Startup.Configure method. Then, you can use env.IsDevelopment() in the Startup.ConfigureServices method, where you set up DB context.
I wonder if you really want to different DB contexts in the development and production environment.
So, having a single context MyDbContext for both environments, Startup class becomes like below. Then, your controllers will get MyDbContext instance injected which connects to a different DB by the environment.
public class Startup
{
public IConfiguration Configuration { get; }
private readonly IWebHostEnvironment _env;
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
_env = env;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options =>
{
string decrypted = ConnStringSecurity.Decrypt(Configuration.GetConnectionString(
_env.IsDevelopment() ? "TestDBConnection" : "ProdDBConnection"));
options.UseSqlServer(decrypted,
sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(5),
errorNumbersToAdd: null
);
});
});
The Microsoft docs article Use multiple environments in ASP.NET Core contains an example:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
EDIT
I did not notice you have a DbContexts for each environment so I didn't take that into consideration in my answer. You should really just stick with one single DbContext instead of one for each environment unless you have a very good reason not to.
So, if you get rid of the "one DbContext per environment" idea you can just:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContextFactory<MyDbContext>(options =>
{
var cs = builder.Environment.IsDevelopment() ? "TestDBConnection" : "ProdDBConnection";
string decrypted = ConnStringSecurity.Decrypt(Configuration.GetConnectionString(cs));
options.UseSqlServer(decrypted,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(5),
errorNumbersToAdd: null
);
});
});
And finallly in your controller you can just take a dependency on a single DbContext
class MyController
{
public MyController(MyDbContext dbContext) { ... }
}
EDIT 2
My solution is dotnet core 6.0 (net6.0) and above. For net5 go with yas-ikeda's answer instead.
firstly, its is not a good practice to store you database connection strings in configuration files where you push them into source code management (ex: github, devops, etc).
when you are developing and running your app locally, then you can make use of the "Secret.json" file to store sensitive data like db connection strings which will not be pushed to repository and will not even appear in git changes.
you can create this file by right clicking on the API project -> select "Manage user secrets". this will create a file in your pc. this is similar to appsetting.json but will be only available on your pc while developing locally.
you can set the db connection string like same as in appsetting.json as show below.
you can add all your sensitive key value pairs on the secrets.json when developing. the same keys in the appsetting.json will be overridden by secrets.json if they have the same key.
for production you can create application level variables by the same key value pair or use key-vault, or use app configuration if you are hosting in azure web app service.

How to restrict multi-tenant login to specific tenants via code?

I have built a very simple Razor Pages app that is hosted using the Azure free plan and which uses multi-tenant authentication.
It's a simple application, not an Enterprise Application.
It seems like tenant restrictions are what I'd need, but those seem to require a premium plan.
Is there some way to specify in code that only certain tenants are allowed? Basically, if a user signed in using a different domain account than what I've allowed, I want to send a 403 error (doesn't have to look pretty, as usually, only users from one or two specific tenants would sign in anyway, it's not a public website).
Like this, for all pages (pseudocode):
if(!User.Identity.Name.EndsWith("#alloweddomain.com"))
{
throw new NotAuthorizedException(); // Or HTTP 403
}
It would be even better if I could configure that somehow in the authentication configuration.
This is what I have so far (autogenerated by Visual Studio):
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"));
;
services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy
options.FallbackPolicy = options.DefaultPolicy;
});
services.AddRazorPages()
.AddMvcOptions(options => { })
.AddMicrosoftIdentityUI();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
});
}
}
I tried to create an environment with multi-tenant login and restrict user .
Here are the steps i followed :
Created a Azure active directory tenant
In visual studio select Authentication account and provide my domain as below;
Here i have my client id which was created (App registration)
or we can create manually by going to Azure portal and we can select following to which account we want provide authentication
After mentioning client id and callbackurl as above now it allow users to login
Now, to restrict user to not allow to the webapp ,in your startup.cs change the default setting as below instead of validateissuer = false make it as true and .
provide the client id which mention in app settings.json
Here also we can provide multiple usser whom we want to allow by providing the client id
ValidateIssuer = true,
valiadIssuer = new List<string>()
{"https://sts.windows.net/clientid***/" }
Please refer this Microsoft documentation for more information : Build a multi-tenant daemon with the Microsoft identity platform endpoint & to Restrict who can sign in to your application .

User registration using ASP.NET Identity v3 - users and database are not created

I am trying to build a simple login system from the ground up, using ASP.NET MVC v5, Entity Framework v7, and Identity v3.
All I want is for the user to create an account, and for that account to be saved in a database. I was able to get it working with Identity v2, and the scaffolded MVC app uses Identity v3 to create a database and save users just fine, so I'm trying to connect the code from both working apps to make a new one.
Here's the code I have so far:
Startup.cs:
public class Startup
{
public IConfigurationRoot Configuration { get; set; }
public Startup()
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear());
app.UseStaticFiles();
app.UseIdentity();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
appsettings.json contains this code for the connection to the database:
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=SimpleAuthenticationApp;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Here's the code for the Register POST action in Controllers/AccountController.cs:
[HttpPost]
public async Task<IActionResult> Register (RegisterViewModel model)
{
try
{
var user = new ApplicationUser { UserName = model.Email };
IdentityResult result = await _userManager.CreateAsync(user, model.Password);
Console.WriteLine(result);
return View("Home", "Index");
}
catch (Exception ex)
{
Console.WriteLine(ex);
return View();
}
}
Here, I set a break point at the try block, and when it enters the catch block, the exceptions reads "Count=1", or "One or more errors occurred", and I'm not sure how to see what those errors are.
In this code, RegisterViewModel is just a ViewModel with fields for Email, Password, and ConfirmPassword.
The Account/Register view is just a form that asks for these fields. ApplicationUser is a class that extends from IdentityUser.
I'm stuck here, and I'd like to figure out how to get the app to create a database and save user profiles in it using Identity. Any ideas for the code, or how to see what the error messages are?
I'd like to figure out how to get the app to create a database and save user profiles in it using Identity.
As far I know database is not created automatically during user registration process, so you need have updated db before.
You can create db yourself or you can use DB initializers.
Remember - if you use Code-First approach - that you need apply all migrations before lunching app
Console command in VS2015 RC1: dnx ef database update
All I want is for the user to create an account, and for that account to be saved in a database. I was able to get it working with Identity v2, and the scaffolded MVC app uses Identity v3 to create a database and save users just fine, so I'm trying to connect the code from both working apps to make a new one.
Maybe starting form latest code will be better way then connecting old and new ? Try create new Project with default implementation - it's works fine for me, and then you can extend default solution as you want :)
More about Authentication and Authorization.
Here, I set a break point at the try block, and when it enters the catch block, the exceptions reads "Count=1", or "One or more errors occurred", and I'm not sure how to see what those errors are.
Use ex.ToString() method - this give you full stack information about exception and all inner exceptions.

Using Tempdata is crashing my application

I'm very new to ASP.NET and am attempting to pass an object between two controllers in a web application I'm making in Visual Studio 2015. The web application is using an ASP.Net 5 Preview Template Web application (if it helps, I think I'm using beta code 7 and I'm not building for DNX Core 5).
The problem I'm having is whenever I try to put anything into the TempData variable, the program seems to crash. For example, in a "Create" method I have:
[HttpPost]
public ActionResult Create(Query query)
{
switch (query.QueryTypeID)
{
case 1:
TempData["Test"] = "Test";
return RedirectToAction("Index", "EventResults");
case 2:
break;
default:
break;
}
return View();
}
In that method, I attempt to add a simple test string under the key "test". When I run the application with that TempData statement in there, I receive an error message stating
An unhandled exception occurred while processing the request.
InvalidOperationException: Session has not been configured for this application >or request.
Microsoft.AspNet.Http.Internal.DefaultHttpContext.get_Session()
I have tried going to the Web.config located in the wwwroot element of the project and adding a "sessionState" object into a "system.web" element, but this had no effect on the error.
Any help would be very much so appreciated as I've been looking for solutions for this everywhere. I'm hoping it's something stupid/blindingly obvious that I somehow missed.
In order to use middleware, such as Session, Cache, etc in ASP.NET 5, you have to enable them explicitly.
Enabling session is done by adding the appropriate nuget package in your project.json file's dependencies section (make sure that the package version matches the versions of the other dependencies you have added):
"Microsoft.AspNet.Session": "1.0.0-*"
and the appropriate session (cache) storage package as well (like the example below; in memory):
"Microsoft.Extensions.Caching.Memory": "1.0.0-*"
and adding the middleware to dependency resolution in the Startup.cs Service configuration:
public void ConfigureServices(IServiceCollection services)
{
services.AddCaching();
services.AddSession(/* options go here */);
}
and adding the middleware to OWIN in the Startup.cs OWIN configuration:
public void Configure(IApplicationBuilder app)
{
app.UseSession();
//...
Make sure that the UseSession comes before the MVC configuration.
For Asp.Net Core, make sure Asp.NetCore.Session is added.
You can configure session in StartUp.cs like below.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
// Adds a default in-memory implementation of IDistributedCache.
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.CookieHttpOnly = true;
});
}
public void Configure(IApplicationBuilder app)
{
app.UseSession();
app.UseMvcWithDefaultRoute();
}

How to store session data using Owin hosting?

I have problem with creating a session in application hosted using Owin. I have tried using RedisSession, but I didn't know how to configure it so it gave me an error.
I was looking for a solution for some time, tried different things and finally decided to ask here for help.
Scenario:
I'm logging in the application using HTTP POST request,
User login and password should be stored in session,
For each next GET/POST request which need previous login session is
empty (login and password are null).
Object HTTPContext is empty.
I'm using Ninject for dependency injections.
I tried something like that: Can OWIN middleware use the http session?
Does anybody have an idea how to store login data in Owin session?
Below is Owin configuration file, included in it things are from link posted above.
[assembly: OwinStartup(typeof(Service.Startup))]
namespace Service
{
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{action}/{id}", new { id = RouteParameter.Optional }
);
appBuilder.RequireAspNetSession();
appBuilder.UseNinjectMiddleware(CreateKernel).UseNinjectWebApi(config);
}
public static StandardKernel CreateKernel()
{
var kernel = new StandardKernel(new Module());
return kernel;
}
}
public static class AspNetSessionExtensions
{
public static IAppBuilder RequireAspNetSession(this IAppBuilder app)
{
app.Use((context, next) =>
{
// Depending on the handler the request gets mapped to, session might not be enabled. Force it on.
HttpContextBase httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);
return next();
});
// SetSessionStateBehavior must be called before AcquireState
app.UseStageMarker(PipelineStage.MapHandler);
return app;
}
}
}
I've had some struggling with sessions as well.
Here is the solution, which works for me:
1) Add NuGet Microsoft.AspNetCore.Session
2) Call .AddSession on your IServiceCollection. Note, it might need configuration.
In my case it is:
3) Use your session. Keep in mind, that if there are no values set to a session, on each request SessionID is different.
So you'd have to add some value to a session. This is how it would stay the same across multiple requests.
And here is my session pinning middleware:
Hope it helps.
For anyone wanting to do this with ASP.net Framework, instead of Core, read on.
First, make sure Owin.Extensions is installed.
Next, add this code to your Owin Startup.cs before the middleware that you want to use session.
app.Use((context, next) =>
{
var httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);
return next();
}).UseStageMarker(PipelineStage.MapHandler);
By default, OWIN Middleware run at the last event (PipelineStage.PreHandlerExecute) which is too late for accessing session state.
.UseStageMarker tells OWIN where in the execution pipeline to execute the function passed to .Use().
To use sessions, you need to execute middleware that runs after the session has been initialized by the ASP.net runtime. This middleware must be run in the PostAquireState phase, like so:
.Use((context, next) =>
{
HttpContext.Current.Session["mykey"] = "myvalue";
return next();
}).UseStageMarker(PipelineStage.PostAcquireState);

Categories