In my ASP.NET MVC 5 application, I'm performing a GET request on a method inside a controller that needs to read a value stored in session. To avoid session state locking issue, I've set SessionStateBehavior to ReadOnly on the class level.
[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
public class TestController: Controller
{
var test = Session["KEY"];
...
However, very occasionally, I need to overwrite the Session variable to something else inside that same method. ASP.NET MVC does not allow me to do this with SessionStateBehavior set to ReadOnly. I can't set it to Required because then I run into the issue of session state locking issue again, preventing concurrent AJAX requests.
What's a good solution for this?
Edit: We're using SQL server for session state management.
If you target .NET Framework 4.6.2 and you're on SQL Server, you could leverage the new async SessionState module to access the session storage providers asynchronously.
Download and install the SessionStateModule and the SqlSessionState NuGet packages.
Consider also that SQLServer mode stores session state in the SQL Server database.
Note (from the comments)
While the session in ASP.NET Core is non-locking, only next release of SqlSessionStateProviderAsync should introduce this feature, according to this msdn blog.
Alternative provider
Another, different option would be to use StackExchange.Redis: e.g. for a web app in Azure App Service, follow these configuration steps.
More generally, in a Redis server or servers, RedisSessionProvider never locks the Session
create a customControllerFactory which inherit with DefaultControllerFactory, and override the GetControllerSessionBehavior(). then use ControllerBuilder.Current.SetControllerFactory() to register the factory at Application_Start
see http://www.c-sharpcorner.com/UploadFile/ff2f08/session-state-behavior-per-action-in-Asp-Net-mvc/
Because you're using a SQL Server session backend, I think you can achieve what you want by manually interacting with the session database and bending it to your will.
Familiarize yourself with the workings of a session provider, in particular the SqlSessionStateStore class. Under the hood, all it's doing is (de)serializing your data and calling the stored procedures in the session state database (specifically, check out SetAndReleaseItemExclusive).
If you can figure out how to construct a SessionStateStoreData object, or more specifically the ISessionStateItemCollection needed to construct it, from the session collection you have access to via the HttpContext plus whatever changes you want to make, you can then use reflection to call the internal static SessionStateUtility.SerializeStoreData method. After that, it should be trivial to call the correct stored procedure (based on the updated session's size).
You can dynamically set the session state behavior for every request. Based on some condition you can switch to SessionStateBehavior.Required and in any other case just use the default behavior.
You will need a custom MvcRouteHandler:
public class MyMvcRouteHandler : MvcRouteHandler
{
protected override SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext)
{
if (someCondition)
{
return SessionStateBehavior.Required;
}
// fallback to default behavior
return base.GetSessionStateBehavior(requestContext);
}
}
In the requestContext you will find HttpContext and based on that you can decide what to do.
You will need to set this new RouteHandler for every necessary route. You can do this in the RouteConfig file, put this after route registrations:
// ...
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
// ...
// set MyMvcRouteHandler for every route
foreach (var route in routes.OfType<Route>())
{
route.RouteHandler = new MyMvcRouteHandler();
}
The issue is a logic issue and two opposite business requirements which are: the readOnly and the need to modify that given some conditions.
So, what i would do is to go back to your logic and cover the conditions.
I would use the Set method SetSessionStateBehavior() to sort of toggle.
For example, add a simple if statement as follows (sudo code)
If a condition is met that requires non readOnly then
SetSessionStateBehavior(
SessionStateBehavior required
)
// here do what you need to do then set it back
You can try Microsoft.AspNet.SessionState.SqlSessionStateProviderAsync 1.1.0 version which supports concurrent requests with same sessionid. Here are the steps to use that nupkg. (you can find more details about the package on this blog)
install SqlSessionStateProviderAsync 1.1.0
add appsetting to web.config
Related
I'm currently working on a webserver in asp.net core.
I want the server to process the users input and data and am looking for a good solution to save complex Objects for the runtime.
So my first approach was to use Sessions. In Asp.net, sessions used to work like Session["key"] = new ValueObject()
In asp.net core however you can only use the methods SetString, SetInt32 and Set for byte arrays. I found a lot of solutions which basically converted the objects into Json strings. However in my case this isn't possible due to the objects containing other object references and more.
My second idea was to create a list of objects with the SessionId as identifier. Problem with this is that every time I would make request to the server, it needs to go through all existing Sessions to find the matching one, so this would probably drastically increase the time for the request.
So my question is what would be the best way to save user related objects?
Is using Sessions even the best way for solving this problem or am I missing something?
Note: Request are handled by JQuery AJAX, so reloading the page for accessing data is not an option.
You could try using the MemoryCache that can hold any .net type. It is not a problem but given it is a shared structure, it will be shared to all users, so, you have to carefull manage it. To do it, you could use HttpContext.Session.Id to define the keys on the memory cache instance. For sample (pseudo-code I didn't test):
public class HomeController : Controller
{
private IMemoryCache _cache;
public HomeController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
public async Task<IActionResult> CacheGetOrCreateAsynchronous()
{
string cacheKey = $"{HttpContext.Session.Id}_data";
var cacheEntry = await
_cache.GetOrCreateAsync(cacheKey , entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3);
return Task.FromResult(DateTime.Now);
});
return View("Cache", cacheEntry);
}
}
Im new to asp.net core and I was trying to develop an online SQL database manager, that will work on any SQL database, after passing: ServerAddress, Login, Password and DatabaseType (my ConnectionInformation model).
Something like SSMS but online.
I want to pass my ConnectionInformation model from Login controller to Database controller.
Redirecting to action uses query string which exposes all of my data.
TempData only accepts strings and converting my model to json isn't the most elegant way to solve this problem.
Login Controller:
public class LoginController : Controller
{
private readonly ILoginLogic _loginLogic;
public LoginController(ILoginLogic loginLogic)
{
_loginLogic = loginLogic;
}
[HttpGet]
public IActionResult Index()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(ConnectionInformationViewModel connectionViewModel)
{
if (!ModelState.IsValid)
return View();
ConnectionInformation connection = Mapper.Mapper.ConnectionInformationMapper(connectionViewModel);
var connectionSuccess = _loginLogic.ConnectToDatabase(connection);
if (connectionSuccess)
return RedirectToAction("Index", "Database", connection);
else
return View(); // TODO: Return view with error or handle it in js
}
}
Database Controller:
public class DatabaseController : Controller
{
private readonly IDatabaseLogic _databaseLogic;
public DatabaseController(IDatabaseLogic databaseLogic)
{
_databaseLogic = databaseLogic;
}
public IActionResult Index(ConnectionInformation connection)
{
var databases = _databaseLogic.GetDatabases(connection);
return View(databases);
}
}
Prehaps my approach is totally wrong. My main goal is to check if I can connect to database, and if I can I want to perform query operations on that Database untill user logs out.
Well, simply, you have to persist the information some way. In that regard, there's a number of options. You could persist it in local storage on the client and actually pass it back with each further request. That works better for SPA-style apps, where you're pretty much doing everything via AJAX, though. Another technically client-side storage mechanism would be setting a session cookie with the posted connection information. Here I'm talking about explicitly setting and reading from a cookie with a "session" lifetime, not using sessions.
Or you can can actually use a true session, i.e. Session. TempData is basically just Session anyways, but here it would be inappropriate as you'd then need to ensure that the TempData is kept every time it's accessed or it won't survive the next request. If you're doing that, then you might as well just use Session and not have to worry about it.
Serialization is pretty much required no matter what you do. There's no way to persist an actual C# object instance, so you're either going to have to write it to a relational store like a database or serialize it to JSON.
One alternate solution, which avoids having to persist the actual connection information is to basically create your own connection pool. This will require a singleton-scoped class with a ConcurrentDictionary ivar and likely the use of SemaphoreSlim to lock during reads and writes of that dictionary so that you don't create and orphan connections. Then, you'd just need to assign the key to their particular connection in the dictionary client somehow, such as via Session or a cookie. This is actually a little more secure as well, as you're not persisting the database connection info past the initial post, but you might end up exhausting the available server connections if there's too many simultaneous users. Of course, that could potentially be an issue regardless. You'll also need some policy for eviction of connections. It's not the easiest setup.
Personally, I'd just stick with using Session on this one. It's secure enough, as long as you take the standard session hijacking prevention measures, and it's simple to implement. If you are building a SPA-style app, then I'd stick with local storage, as that's going to be imminently better keeping the info client-side to begin with, but it requires a bit more plumbing that way.
I am new to MVC and I have very simple problem.
When user login to my application I need to create a specific object (model) for the user for eg UserObject.
This object is unique to current logged in user and should only be disposed when user click on logout.
I don’t know how to maintain the lifetime of the object. As if I create object in Action method of controller class then as soon as the request is finished I lose the reference of the object.
How this should have been done?
The lifetime of your models are only going to be as long as the request. So each time the user goes to another page or refreshes, the MVC framework is going to instantiate a new controller (and model within). Otherwise your server would have a ton of static objects floating around in memory which would use up a lot of resources and wouldn't scale.
In order to manage state, you are going to need to use other methods such as sessions/cookies and a database.
So let's say the user logs in via /User/Login. This routes the request to an action named UserController.Login().
Inside this action, it instantiates a UserModel.
public ActionResult Login(string username, string password) {
var userModel = new UserModel();
if (userModel.Authenticate(username, password)) {
// Setup your session to maintain state
Session["username"] = username;
} else {
return View("Login");
}
return View("LoginComplete");
}
You might want the user model to actually create the session, but I've shown it here for clarity.
The user model authenticates the user, and then you create a session just like you would in a traditional non-MVC site.
Then in subsequent requests, you will want to authorize the user, and use any session data you have to retrieve state information.
public ActionResult SuperSecretPlace() {
var userModel = new UserModel();
string username = Session["username"]
var user = userModel.GetUserByUsername(username);
if (user == null) throw new HttpException(401, "User is not authorized.");
return View("SuperSecretPlace", user);
}
In the action above, the UserModel might do something like query a database to retrieve the user's data so you can pass it in to the corresponding view.
If you want to make life easier, you might want to just use .NET's built in forms authentication:
http://www.codeproject.com/Articles/578374/AplusBeginner-splusTutorialplusonplusCustomplusF
For more info about the lifecycle of MVC:
http://www.dotnet-tricks.com/Tutorial/mvc/TbR0041112-Asp.net-MVC-Request-Life-Cycle.html
http://www.asp.net/mvc/overview/getting-started/lifecycle-of-an-aspnet-mvc-5-application
Actually what you are trying to achieve is passing model from controller to controller which is not possible. When an action is executed the context of the model object is disposed at the view and it can cannot be passed from controller to controller. You have to create a new object repopulate it and use it to achieve the goal in different controller.If you need the data to be persisted you can use sessions but still you need to create an object of the model in every controller.
The following image is for your reference as to see what to use when passing data between model-view-controller. Please feel free to ask if you need more information on this.
As opposed to the other aswers I would not use session as it has quite some disadvantages (scalability, pessimistic concurrency which blocks concurrent calls, app pool recycling...). Why you should not use session is documented in a lot of places like here or here.
Instead, I would store it in a cookie.
However, be sure to not store confidential or sensitive data. Whatever you use (cookies or session), it can be tampered with or stolen. If you are dealing with sensitive information, you need other solutions. Read also more about secure cookie solution here.
I am working with two controllers, they both save a value to Session but only one of the Controller manages to maintain it's value.
The line of code that saves the value is
Session["LoginDate"] = <dateTimeObject>;
and this is the same in both Controllers. The Second controller gets called from the First Controller and while in the second controller, if I set the value of Session then we're ok until I get back in the calling controller. If I call the First controller only, the value can get set and be sent back to the client.
I have tried modifying the second config file to include
<sessionState mode="InProc" timeout="30" />
and have made sure they are at the same version of .NET, MVC, etc...
Any ideas as to how to debug this? What else should I check?
UPDATE
Is there a way to pass the session state from different servers or would usign cookies be better since the cookie will be on the client browser? The new discovery is that the second controller does an
Redirect("serverOfController_1");
The controller gets initialised by the MVC core, so that it has the correct references to the context of the current request. When you create an instance of a controller yourself, that won't have any context at all, so it can't use anything from the controller interface.
For a method in that controller to work in that context, it can't rely on anything in the controller interface. If you want to set a session variable from that method, you have to get the current context and access the Session object from that:
System.Web.HttpContext.Current.Session["LoginDate"] = <dateTimeObject>;
You can also copy the controller context from the current controller after you have created the instance. That way the controller that you created will have the same context as the current controller. Example:
SecondController second = new SecondController();
second.ControllerContext = ControllerContect;
second.SomeMethod();
I want to log visits only for some controllers (or routes) as it was possible with classic ASP.NET pages by checking/unchecking the 'log visits' checkbox in IIS.
Does anyone know if this is possible somehow? A solution without a custom logging component would be fantastic! Please share your knowledge, if you know how ;)
Thanks in advance
Create a BaseController which the controllers that you want to record data for inherit from. Then create an ActionFilter which overrides the OnActionExecuted method and apply it to the base controller. Something like this..
public class ActionExecutedFilter : System.Web.Mvc.ActionFilterAttribute
{
UnitOfWork unitOfWork= new UnitOfWork();
public override void OnActionExecuted(ActionExecutedContext filter)
{
Transaction tran = new Transaction();
tran.Controller = filter.ActionDescriptor.ControllerDescriptor.ControllerName;
tran.ActionName = filter.ActionDescriptor.ActionName;
tran.User = HttpContext.Current.User.Identity.Name;
tran.Date = DateTime.Now;
unitOfWork.TransactionRepository.Insert(tran);
unitOfWork.Save();
}
}
This will save to a database table called Transactions information for every time a action method is called on that controller, recording the user, controller and action method name. Obviously I just typed in the UnitOfWork method of saving to the database, you can just plug in whichever method you like. I usually keep these methods in a filters folder, add a using statement then add it to the controller like so;
[ActionExecutedFilter]
public class BaseController : Controller
{
}
Again, just make all the controller you wish to record data from inherit the BaseController.
You could also use something like Log4Net, but I find just doing it this way gets what I need. Whatever you think yourself.
http://dotnetdarren.wordpress.com/2010/07/29/logging-in-mvc-part-4-log4net/
A solution without a custom logging component would be fantastic!
Apart from basic IIS diagnostic logs, I cannot think of any ASP.Net MVC specific Logging features in IIS.
One simple solution what I want to offer is to write a HttpModule. In the HttpModule you can log the requests. Also if you want to have control on what to log and what not to, then based on Routes you can make that happen in HttpModule. Advantage with HttpModule is it is easy to plug in and also easy to remove.
Make HttpModule work only on certain routes
When it comes to logging itself, you can have your own custom logic to database or to log file. But you can opt for ELMAH or Log4Net or Enterprise Logging Application Block