In one of my web application I need to popup alert when session timeout in 5 minutes. Users have option either continue to extend the session or log out immediately.
In Web.config set session timeout to 30 min:
<sessionState mode="InProc" timeout="30">
Since ASP.NET MVC do not give a way to check the remaining session timeout, I come up a solution as follow:
In Global.asax it tracks the current session last access time as a session variable. If the coming in request is not session state readonly (see below), the last access time session variable is updated with current time. Otherwise session variable's value is set with current time.
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
var context = HttpContext.Current;
if (context != null && context.Session != null && !context.Session.IsReadOnly)
{
context.Session["_LastAccessTime"] = DateTime.Now;
}
}
In my session controller, I set the session state behavior to read only. Requests to this controller neither reset session nor do it refresh my last access time session variable.
[SessionState(SessionStateBehavior.ReadOnly)]
public class SessionController : BaseController
{
[AjaxOnly]
public ActionResult GetRemainingSessionTimeout()
{
if (!Request.IsAjaxRequest())
{
return Content("Not an Ajax call.");
}
var remainingSessionTimeout = 0;
if (Session["_LastAccessTime"] != null)
{
var lastAccessTime = (DateTime) Session["_LastAccessTime"];
var timeSpan = DateTime.Now.Subtract(lastAccessTime);
remainingSessionTimeout = Session.Timeout - timeSpan.Minutes;
}
return Json(new {RemainingSessionTimeout = remainingSessionTimeout}, JsonRequestBehavior.AllowGet);
}
}
In a view, Ajax request checks the remaining session timeout silently. If less than 5 min, then popup alert:
$(document).ready(function () {
$("#remainingSessionTimeout").click(function (e) {
var url = '#Url.Action("GetRemainingSessionTimeout", "Session", new { area = "" })';
$.ajax({
type: "POST",
url: url,
data: {},
success: function (result) {
$("#timeoutLeft").html('Your session expries in ' + result.Timeout * 60 + ' seconds.');
},
error: function (result) {
$("#timeoutLeft").html('error in getting session data.');
}
});
});
});
Any drawback or flaw of this solution? Thanks.
Related
I have instrumented my web application to have telemetry events sent to azure portails with the authenticated user id when available.
It works for TrackEvent coming from backend : for that once my user is authenticated , I add some information in the HttpContext.Current.Session that I am using in the TelemetryInitialiser.
public class MyTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
if (HttpContext.Current?.Session?["user"] != null)
{
// Set the user id on the Application Insights telemetry item.
telemetry.Context.User.AuthenticatedUserId = user;
telemetry.Context.User.UserAgent = HttpContext.Current.Request.UserAgent;
// Set the session id on the Application Insights telemetry item.
telemetry.Context.Session.Id = HttpContext.Current.Session.SessionID;
}
}
}
and in Global.asax.cs
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
BundleTable.EnableOptimizations = false;
AutoMapperConfig.RegisterMappings();
Bootstrapper.Initialise();
TelemetryConfiguration.Active.TelemetryInitializers.Add(new Common.MyTelemetryInitializer());
}
}
This makes already all events coming from back-end having the authenticated id
field set ( namely : Trace, Custom Event, Exception , Dependency (call to DB ).
After that I also customized the automatically created Javascript code in the Front End so that "Page View" have as well the authenticated id :
<script type='text/javascript'>
var appInsights=window.appInsights||function(config)
{
function r(config){ t[config] = function(){ var i = arguments; t.queue.push(function(){ t[config].apply(t, i)})} }
var t = { config:config},u=document,e=window,o='script',s=u.createElement(o),i,f;for(s.src=config.url||'//az416426.vo.msecnd.net/scripts/a/ai.0.js',u.getElementsByTagName(o)[0].parentNode.appendChild(s),t.cookie=u.cookie,t.queue=[],i=['Event','Exception','Metric','PageView','Trace','Ajax'];i.length;)r('track'+i.pop());return r('setAuthenticatedUserContext'),r('clearAuthenticatedUserContext'),config.disableExceptionTracking||(i='onerror',r('_'+i),f=e[i],e[i]=function(config, r, u, e, o) { var s = f && f(config, r, u, e, o); return s !== !0 && t['_' + i](config, r, u, e, o),s}),t
}({
instrumentationKey:'XXXXXXXXXXXXXXXXXX'
});
window.appInsights=appInsights;
</script>
#if (Request.IsAuthenticated)
{
<script>
var user = "#User.Identity.Name.Replace("\\", "\\\\")";
appInsights.setAuthenticatedUserContext(user.replace(/[,;=| ]+/g, "_"));
</script>
}
<script>
appInsights.trackPageView();
</script>
For that to work it supposes that I set a Response Cookie before when authentified in the back end.
This is also working and I have my Page View events with the authenticated user id.
Nevertheless, I still have a gap : Request event does no have the authenticated user id field set.
If I examine the Requests without the field, it is both Direct Requests and all the AJAX requests that can be triggered when browsing a page. (some Going to Application defined pages, others part of the framework)
This time I have no idea of how can I make them contained the authenticated user. Any help appreciated.
I need to implement chat on my web project. How to implement it on one page - there are many articles about it. But I need to have ability :
1. Notify other users, that somebody logged to site (on any page, not only on chat page)
2. Notify other users, that somebody logout
So, I have the following code of hub:
public void Connect()
{
try
{
var id = Context.ConnectionId;
string username = Context.User.Identity.Name;
var currentUser = connectedUsers.Where(p => p.Username == username).FirstOrDefault();
if (currentUser == null)
{
AddNewUserToCollection();
}
else
{
// update ConnectionId for sure (connection id is changed sometimes (probably if user is logged out and login again))
if (currentUser.ConnectionId != id)
{
var companyId = _chatRepository.GetCompanyIdOfUser(username); // throws exception if companyId is null
Groups.Remove(currentUser.ConnectionId, companyId.ToString());
Groups.Add(id, companyId.ToString());
currentUser.ConnectionId = id;
//Clients.Group(companyId.ToString()).onNewUserConnected(username);
}
}
}
catch(InvalidCompanyException c_ex)
{
Clients.Client(Context.ConnectionId).onErrorMessage($"User '{c_ex.Username}' does not exist");
}
}
public void Disconnect()
{
string username = Context.User.Identity.Name;
var item = connectedUsers.Where(p => p.Username == username).FirstOrDefault();
if (item != null)
{
connectedUsers.Remove(item);
Groups.Remove(item.ConnectionId, item.CompanyID.ToString());
Clients.Group(item.CompanyID.ToString()).onUserDisconnected(item.Username);
}
}
public override Task OnDisconnected(bool stopCalled)
{
var item = connectedUsers.Where(p => p.ConnectionId == Context.ConnectionId).FirstOrDefault();
if (item != null)
{
connectedUsers.Remove(item);
Groups.Remove(item.ConnectionId, item.CompanyID.ToString());
Clients.Group(item.CompanyID.ToString()).onUserDisconnected(item.Username);
}
return base.OnDisconnected(stopCalled);
}
and I added the following code to _layout.cshtml:
<script>
$(document).ready(function () {
var chat = $.connection.chatHub;
$.connection.hub.start().done(function () {
chat.server.connect();
});
});
</script>
to notify other users, that the current user is logged. But debugger says, that pair OnDisconnected/Connect is called every time, when user reload page (go thru pages) with different connectionId. When I remove this client code - that pair is not called. How to implement it correctly, to notify other users, that somebody is online, but without reconnect each time?
Since you have the connect() call in a razor (_layout.cshtml) page, you will effectively be disconnecting/connecting on every post back (page load).
The scenario you're trying to use is best in a SPA (or AJAX) scenario where navigation is handled asynchronously by client side JavaScript. Your current setup is refreshing the screen, reloading the JavaScript, re-running the document ready() function with each server rendered navigation.
Another alternative is to use the client's actual user Id, and pass that to the server.connect(id) method. Then use this user Id to track the user activity instead of the hub ConnectionId.
I am working on a ASP.net application. I have some code in the page init event and using some session variables.
protected override void OnInit(EventArgs e)
{
if (HttpContext.Current.Session["ApplicationUser"] == null)
// If Session Expires then reload the user roles.
{
HttpContext.Current.Session.Remove("ApplicationUser");
var userService = new UserService();
var userRoles = userService.GetUserRoles(userName);
if (userRoles.Count() == 0)
{
HttpContext.Current.Response.Redirect("~/UnAuthorized.aspx", true);
}
else
{
isAuthunticated = true;
roles = userRoles;
HttpContext.Current.Session["ApplicationUser"] = userRoles;
}
}
}
Note: I am not using any session variables apart from this two places.
The default session timeout value is 20 mins.
I opened a page and left the system idle for 30 mins. After 30 mins, I filled some fields in the page and hits save button(now page is posting and the session is expired)
In this scenario, system will accept the post request if the session is expired?
I'm using multiple updatepanels in my SharePoint Visual Web Parts. Everything is working fine until I leave the page to idle for a while.
For example if I change a few drop downs and leave the page to idle for about 5 minutes. Coming back to the page and changing a drop down will cause a full postback.
Another example is using a gridview with pagination. Leaving the grid view on page 5. Idle for 5 minutes and come back to the page. Clicking on page 8 for example will make the gridview go to page 1.
I am new to using Updatepanels and would really appreciate some advice.
I have solver this anonymous problem by adding this javascript. I have found that after some ideal time(~30sec) for each request it page goes for authenticate user. and if user is not authentication then it reload the whole page and re authenticate user that's why page is reload.
Add this java script code in your page which will solve your problem.
<script type="text/javascript">
var isNtlmActive = false;
var updatePannelsToUpdate = [];
var eventTarget = '';
var eventArgument = '';
var causesValidation = false;
var validationGroup = '';
var requestBody = '';
function initializeRequestHandler(sender, args) {
var onSuccess = function () {
//At this point the NTLM connection is re-established
var pageRequestManagerInstance;
isNtlmActive = true;
pageRequestManagerInstance = Sys.WebForms.PageRequestManager.getInstance();
// re-issuing the 'original' request
pageRequestManagerInstance.beginAsyncPostBack(updatePannelsToUpdate, eventTarget, eventArgument, causesValidation, validationGroup);
};
var onError = function () {
// do something here if error occurred
}
if (!isNtlmActive) {
// capturing and preserving the body as well as some other meta data about the original request
requestBody = args.get_request().get_body();
updatePannelsToUpdate = sender._postBackSettings.panelsToUpdate;
eventTarget = sender._postBackSettings.asyncTarget;
eventArgument = '';
causesValidation = false;
validationGroup = '';
// NOTE: the variable '_spFormOnSubmitCalled' is a global variable that gets injected by the logic iplemented in the 'init.js' file.
// Based on our observation of the logic in 'init.js' the varialbe '_spFormOnSubmitCalled' is set to true when HTML form's
// 'onsubmit' function is called and it is never set back to false (after we cancel the postback)
// As the result, any subsequent attempts to submit the form do not work.
// Thus, we excplicetely set the value back to false before we cancel the original post back request.
//
//'init.js'is autoatically referenced by SharePoint and included on to the 'master' page.
// The HTML form as well as the functionality to handle submit is also provided by SharePoint.
if (typeof _spFormOnSubmitCalled === "boolean") {
_spFormOnSubmitCalled = false;
}
args.set_cancel(true);
callServerSideServiceToReviveNtlmSession(onSuccess, onError);
}
else {
// resetting the body of the request with the value captured from the original request
args.get_request().set_body(requestBody);
isNtlmActive = false;
updatePannelsToUpdate = [];
eventTarget = '';
eventArgument = '';
causesValidation = false;
validationGroup = '';
}
}
function getCurrentSiteCollectionUrl() {
var url;
url = window.location.protocol + "//" + window.location.host + _spPageContextInfo.siteServerRelativeUrl;
return url;
}
function callServerSideServiceToReviveNtlmSession(successHandler, errorHandler) {
var siteCollectionUrl;
var testServiceUrl;
var spRequestExecutor;
var request;
siteCollectionUrl = getCurrentSiteCollectionUrl();
testServiceUrl = siteCollectionUrl + "/_api/web/title";
spRequestExecutor = new SP.RequestExecutor(siteCollectionUrl);
request = {
url: testServiceUrl,
method: "GET",
headers: { "Accept": "application/json; odata=verbose" },
success: successHandler,
error: errorHandler
};
spRequestExecutor.executeAsync(request);
}
try {
$(document).ready(function () {
try {
var pageRequestManagerInstance = null;
//Note: Sys.WebForms.PageRequestManager gets injected into your page the minute you use ScriptManager (and UpdatePanel)
pageRequestManagerInstance = Sys.WebForms.PageRequestManager.getInstance();
pageRequestManagerInstance.add_initializeRequest(initializeRequestHandler);
}
catch (ex) {
//alert('INNER EXCEPTION: document ready - ' + ex.message);
}
});
}
catch (ex) {
//alert('EXCEPTION: document ready - ' + ex.message);
}
</script>
Mark as answer if this answer found helpful...
Thanks..!!
Enable or disable session state across the entire farm
On the taskbar, click Start, point to Administrative Tools, and then click SharePoint 3.0 Central Administration.
In the top navigation bar, click the Application Management tab.
On the Application Management page, in the Office SharePoint Servers Shared Services section, click Configure session state.
On the Configure Session State page, in the Enable Session State section, select the Enable Session State check box to enable session state for the farm.
To specify the duration of sessions, in the Timeout section, enter a number (in minutes) in the Session should be timed out after (minutes) box. The default is 60 minutes.
Click OK to save the session state configuration.
This one will give you more guidance
http://technet.microsoft.com/en-us/library/cc263527.aspx
if any confusion ask me,
I have the following code which re-uses a CookieContainer which logs in on the first request, but just uses the cookie container for requests after.
After a period of time if idle the site will give a Session Timeout, I will need to perform the login again.
Q: Can I determine (with the cookie container object) if the timeout has happened or is it best to determine if it has happened from the HttpWebResponse which happens to contains text like 'session timeout'. What is the best way to do this?
private static CookieContainer _cookieContainer;
private static CookieContainer CurrentCookieContainer
{
get
{
if (_cookieContainer == null || _cookieContainer.Count == 0)
{
lock (_lock)
{
if (_cookieContainer == null || _cookieContainer.Count == 0)
{
//_cookieContainer.GetCookies(
_cookieContainer = DoLogin();
}
}
}
return _cookieContainer;
}
set
{
_cookieContainer = value;
}
}
And then this method calls out to the container:
public static string SomeMethod(SomeParams p)
{
HttpWebRequest request_thirdPartyEnquiryDetails = (HttpWebRequest)WebRequest.Create(thirdPartyEnquiryDetails);
CookieContainer cookieContainer = CurrentCookieContainer;
request_thirdPartyEnquiryDetails.CookieContainer = cookieContainer;
//... and it goes on to submit a search and return the response
}
Well, since the timeout is 30 mins, I have set the login to repeat after 25 mins.
private static DateTime? lastLoggedIn;
private static CookieContainer _cookieContainer;
private static CookieContainer CurrentCookieContainer
{
get
{
if (_cookieContainer == null || _cookieContainer.Count == 0 || !lastLoggedIn.HasValue || lastLoggedIn.Value.AddMinutes(25) < DateTime.Now)
{
lock (_lock)
{
if (_cookieContainer == null || _cookieContainer.Count == 0 || !lastLoggedIn.HasValue || lastLoggedIn.Value.AddMinutes(25) < DateTime.Now)
{
_cookieContainer = DoLogin();
lastLoggedIn = DateTime.Now;
}
}
}
return _cookieContainer;
}
set
{
_cookieContainer = value;
}
}
As an extra precaution, I check the HttpResponse for the text which returns when the page session times out (although its now expected this won't be seen). If this happens I set the lastLoggedIn date to null and run the search method again.
You can extract all cookies for a domain using the CookieContainer.GetCookies(string uri) method. Using this CookieCollection you can get the cookie you are interested in and check its Expired property to see if it has expired.
There is one thing you should note: Your session may end even if your cookie is valid. IIS may restart the app domain the web application runs in and in that case all authenticated users may loose their session data. So checking the cookie is generally not enough to ensure that you stay logged in.
I am not sure what you want to achieve, but you should notice that CookieContainer has a bug on .Add(Cookie) and .GetCookies(uri) method.
See the details and fix here:
http://dot-net-expertise.blogspot.com/2009/10/cookiecontainer-domain-handling-bug-fix.html