Session variable does not maintain value across different controllers - c#

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();

Related

ASP.NET Core UrlHelper and how it works

I'm rather new to ASP.NET Core, and right now I am trying to get a grasp on how UrlHelper works in general.
In my controller, I want to create an absolute URL to another action in the same controller, e.g. http://localhost:PORT/api/controller/action. The question is now, how do I do it?
I have tried with the following:
var urlHelper = new UrlHelper(new ActionContext());
var url = urlHelper.Action("ACTION", "CONTROLLER");
Furthermore, what are those different contexts like ActionContext?
You really shouldn’t create a UrlHelper yourself. It’s likely that whatever context you are currently in, there is already an IUrlHelper instance available:
ControllerBase.Url inside of controllers.
PageModel.Url inside a Razor view.
ViewComponent.Url inside a view component.
So chances are, that you can just access this.Url to get an URL helper.
If you find yourself in a situation where that does not exist, for example when implementing your own service, then you can always inject a IUrlHelperFactory together with the IActionContextAccessor to first retrieve the current action context and then create an URL helper for it.
As for what that ActionContext is, it is basically an object that contains various values that identify the current MVC action context in which the current request is being handled. So it contains information about the actual request, the resolved controller and action, or the model state about the bound model object. It is basically an extension to the HttpContext, also containing MVC-specific information.
If you are running ASP.NET Core 2.2 or later, you can also use the LinkGenerator instead of the IUrlHelper inside your services which gives you an easier way to generate URLs compared to having to construct the helper through the IUrlHelperFactory.

Writing to a session variable with SessionStateBehavior.ReadOnly

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

variable set null after method

I'm building a web site, and I need to show a list of customers. When a customer is selected, to show his items, I send the id of the customer from the view with this:
<th>
#using (Html.BeginForm("SetID", "Client", FormMethod.Post, new
{ id = item.id.ToString() }))
{
#Html.Hidden("id", item.id.ToString());
<input type="submit" value="see items" />
}
</th>
I receive the id in the controller and save it to make the query and show the values this way.
private string customer_id
[HttpPost]
public ActionResult SetCustomer(string id) {
Save(id);
return RedirectToAction("Index");
}
private void Save(string id) {
this.customer_id = id;
}
But when I get redirected to the Index view, the variable "customer_id", is null. Is there something I'm missing?
Because you're not persisting the value anywhere.
HTTP is stateless. What this means in ASP.NET is that each HTTP requests results in a new instance of the controller being requested. So the following sequence of events is happening here:
User makes a request to SetCustomer, creating a new instance of the class
Value is stored in a class-level variable
Request is responded to and completed, the instance of the class is destroyed
User makes a request to Index, creating a new instance of the class
Since it's a new instance, no value is set. There are a variety of places you can store data, it just has to be in a context that both requests can access. Examples, in no particular order, include:
Database
Session
URL query string
Cookie
etc.
Basically, you have to write the value to some location which persists between requests. In-memory variables don't do that in web applications.
A simple example here could be to include the value on the query string in the redirect. In ASP.NET MVC, that might look something like this:
return RedirectToAction("Index", new { customer_id = id });
What this would do is include on a URL parameter a customer_id value when the user is redirected. So your Index action you could accept that parameter:
ActionResult Index(int? customer_id = null)
{
// use the customer id if one is provided
}
In this case I assumed that the value should be nullable in case the Index is ever requested without a value. But what you prefer to do for that is up to you. Basically this action now has an optional parameter, which you would use however you're currently trying to use it.
The benefit of this is that it maintains the intended statelessness of web applications. You're storing the state (the customer_id value) in the request/response itself, as opposed to some other medium (session, database, etc.) where you become responsible to maintaining it.
ASP.NET MVC controllers are instantiated to serve every request. Your local variable this.customer_id is not supposed to survive a call to the server. If you want to save something, it should go in a database table, or a file or somewhere persistent.
You could also store it in memory like in a cache, but that would need to be in a reference to something that lives longer than the controller (which will be thrown away after the server responds back to the browser).
If you want something like I described to be long lived, you might need to use a service to inject into your controllers. Many people would use a DI or IOC framework and life cycle configurations to accomplish this.

Maintain the model lifetime in MVC application

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.

skip all Umbraco magic for single method

I was just assigned to implement one functionality in project that uses Umbraco. My job is to basically generate specific XML and return it to user. However i cannot get it to work, because when i create new controller (i've tried creating
Controller, RenderMvcController and SurfaceController
) and method in it (also if i just create new method in existing controller), i get error 404 after typing url into browser. Example: I create TestController and method Index in it. I've tried combinations where TestController was derived from RenderMvcController or SurfaceController or just Controller. After compiling, etc. when i run
http://my_address/Test
or
http://my_address/Test/Index
i get 404 error from umbraco. I looked at another pages in umbraco that were already in project and they all are also configured somehow in umbraco web panel:
http://my_address/umbraco
I aslo tried adding new methods to existings controllers, but no luck (again 404 errors). I've never worked with umbraco and i don't know how to configure it. I just want to know if there is any way to create method which will be accessible at:
http://my_address/MyMethod
or
http://my_address/MyController/MyMethod
and would return just exactly what i will program it to (without any Views, Partial Views, etc. - i can set Headers and ContentType manually and my content is pure text) in an existing Umbraco project without having to deal with umbraco admin panel?
Thanks for any help :)
//Edit
My mind is officially blown... My response is culture dependent (i mean i pull different data from db depending on country), but it's not as simple as
CurrentCulture.CultureInfo
Umbraco is configured to return different culture based on domain extension (Germany for .de, Great Britain for .co.uk, and Dennmark for .dk - it's just a manual configuration in umbraco admin panel assigning different culture info and views to different hostnames). Regular controllers get this modified culture from
RenderModel.CurrentCulture
passed as argument to controller's method. Is there a way to create umbraco controller/method/anthing that will not have layout/model assigned to it (so i can display pure XML data i receive from external service) and still have access to umbraco's RenderModel's culture? What i am trying to create is if user types url:
http://my_address.de/myController/myMethod
my controller will get current culture, call external service passing culture as parameter and display received data without wrapping it in any views. Example:
public class myController : SomeBaseUmbracoControllerOrsomething
{
public string/XmlDocument/ActionResult myMethod(RenderModel model)
{
int countryId = myFunctionToTranslateCultureToCountryId(model.CurrentCulture);
return MethodThatCallsExternalServiceAndReturnsXml(countryId);
}
}
Sorry for confusion, but i've learned about this whole mess with countries just now...
You don't want to use
controller, because this is not picked up by umbraco routing process
you don't want to use RenderMvcController, because this is overkill
you don't want to use Surfacecontroller because you are not using a Child action or form.
What you need is a UmbracoApiController (http://our.umbraco.org/documentation/Reference/WebApi/) or is your umbraco version is PRE 6.1 then use /Base extention (http://our.umbraco.org/documentation/Reference/Api/Base/Index)
Or if you really want to skip ALL umbraco magic for a certain route, add the path to the web.config/AppSettings/umbracoReservedUrls.

Categories

Resources