I have written a chat app using signalR. It's a ASCX control containing the markup and the javascript that runs the chat. The page that holds the user control has a updatepanel that renders asynchronous and allows the user to refresh some content specific to a entered code. The problem is, I can click as many times the refresh button and the page behaves without any problem. When I click to connect the chat (which is all build in JavaScript) and I click a couple of times the refresh button it appears to behave fine but suddenly the page brakes and some viewstate errors are logged saying : The state information is invalid for this page and might be corrupted. Invalid view state.. blah blah... it's an ugly error.
This only happens when I connect to the hub. If I don't initiate the connection this never happens.
One thing to mention though, the code behind of the control stores some value in a property that refers to the viewstate (without storing it in the viewstate the page brakes on every postback) that later is written in the markup so the scripts that initiate the chat know who should be part of the conversations.
Please help.
Some code here:
StandAlonePanel.aspx -> contains the updatepanel with the refresh button.
ChatControl.ascx ->
public int userId{
get
{
if (ViewState["UserID"] == null)
ViewState["UserID"] = 0;
return Convert.ToInt32(ViewState["UserID"]);
}
set
{
ViewState["UserID"] = value;
}
}
//--- Same for userName
if (userId == 0 && CurrentUserSession.User != null)
{
this.userId = CurrentUserSession.User.Id;
this.userName = CurrentUserSession.User.Name;
}
in the markup
var userInfo = {
userId : "<%= userId %>",
userName : "<%= userName %>",
IsAnonymized: "<%= IsAnonymized %>",
enableLogging: "<%= enableLogging %>"
}
if (chat != null) {
chat.Disconnect();
}
chat = new Chat(userInfo);
chat.Connect();
//-- The script conn
var hubConn = $.hubConnection(url);
hubConn.logging = self.enableLogging
hubConn.qs = { 'u': userInfo.userId, 'tc': '0', 'oo': userInfo.showOnlineOnly, 'ach': self.IsAnonymized, 'lgch': self.enableLogging }; /* Initiating queryString */
hubConn.error(function (error) {
logMe(error);
});
var hubProxy = new ChatProxy(hubConn);
/* hook up callbacks to hubProxy */
self.Connect = function () {
if (self.longpolling == true) {
hubConn.start({ transport: 'longPolling' }).done(function (result) {
///--
}).fail(function (error) {
///--
alert("error" + error);
});
}
else {
hubConn.start().done(function (result) {
///--
}).fail(function (error) {
///--
alert("error" + error);
});
}
}
I guess I'm closing this question because it only happens in my asp dev env. when I deploy the app to iis it does not present the same problem
Related
I'm using service worker for push notifications, following this article. Everything is working with Chrome but with Firefox (v.44.0.2) I have a weird issue.
On successful login to my app, I register the service worker which does nothing but waiting for push events; I see that is correctly registered (from some logging and from about:serviceworkers). Now, if I refresh the page (CTRL+R) all my POST have CORS issues (missing Access-Control-Allow-Origin header) due to this service worker and the user is redirected to login page; from here on all POSTs do not work for the same reason.
Conversely, if I login, unregister the service worker and then refresh, there are no problems at all. Any idea of what's going on? Again my service worker just handles push events, no caching no other processing done and it perfectly works on Chrome.
Here's my service worker code ( SOME_API_URL points to a real API which is not needed for testing purpose cause the issue happens after the service worker registers, no push events needed)
self.addEventListener('push', function(event) {
// Since there is no payload data with the first version
// of push messages, we'll grab some data from
// an API and use it to populate a notification
event.waitUntil(
fetch(SOME_API_URL).then(function(response) {
if (response.status !== 200) {
// Either show a message to the user explaining the error
// or enter a generic message and handle the
// onnotificationclick event to direct the user to a web page
console.log('Looks like there was a problem. Status Code: ' + response.status);
throw new Error();
}
// Examine the text in the response
return response.json().then(function(data) {
if (data.error || !data.notification) {
console.error('The API returned an error.', data.error);
throw new Error();
}
var title = data.notification.title;
var message = data.notification.message;
var icon = data.notification.icon;
var notificationTag = data.notification.tag;
return self.registration.showNotification(title, {
body: message,
icon: icon,
tag: notificationTag
});
});
}).catch(function(err) {
console.error('Unable to retrieve data', err);
var title = 'An error occurred';
var message = 'We were unable to get the information for this push message';
var notificationTag = 'notification-error';
return self.registration.showNotification(title, {
body: message,
tag: notificationTag
});
})
);
});
self.addEventListener('notificationclick', function(event) {
console.log('On notification click: ', event.notification.tag);
// Android doesn't close the notification when you click on it
// See: http://crbug.com/463146
event.notification.close();
// This looks to see if the current is already open and
// focuses if it is
event.waitUntil(
clients.matchAll({
type: 'window'
})
.then(function(clientList) {
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
if (client.url == '/' && 'focus' in client)
return client.focus();
}
if (clients.openWindow) {
return clients.openWindow('/');
}
})
);
});
Firefox 44 has bug 1243453, which causes the Origin header of cross-origin requests to get dropped if the service worker doesn't listen for fetch events.
The bug has been fixed in Firefox 45, which will be released the week of March 8, 2016 (next week, as of the time of this writing). In the meantime, and for users who don't immediately upgrade to the latest Firefox release, you can work around the problem by adding this code to the service worker:
addEventListener('fetch', function(evt) {
evt.respondWith(fetch(evt.request));
});
I'm using cookie authentication in MVC5. My web pages rely heavily on authenticated as well as unauthenticated Ajax calls every 1-5 seconds to keep data updated. Consequently, my users never log out of the site.
My ideal scenario: If a user is actively browsing or conducting actions on my site, keep the session alive. If they have left a page open after 10 minutes, I'd like their session to timeout and I’'ll use the failing Ajax calls to redirect to a login page. I think this would best be accomplished at the controller or action level.
I tried controlling the session state behavior as suggested below but the session still did not time out. After 65 seconds of hitting ReadOnly/Public once per second, I call ReadOnly/Authorized and successfully retrieve data from it.
Here is my CookieAuthentication configuration.
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = TimeSpan.FromMinutes(1),
});
}
My test page:
<div id="public"></div>
<div id="authorized"></div>
#section scripts{
<script>
function poll(times) {
var url = '/ReadOnly/Public';
$.ajax({
url: url,
dataType: 'json',
data: null,
cache: false,
success: function (data) {
$('#public').html(times + ' ' + data.test);
},
error: function (data) {
$('#public').html(times + ' ' + 'failed');
}
});
};
function checkAuth(times) {
var url = '/ReadOnly/Authorized';
$.ajax({
url: url,
dataType: 'json',
data: null,
cache: false,
success: function (data) {
$('#authorized').html(times + ' ' + data.test);
},
error: function (data) {
$('#authorized').html(times + ' ' + 'failed');
}
});
};
$(function () {
var times = 1;
setInterval(function () {
poll(times);
times++;
}, 1000);
setInterval(function () {
checkAuth(times);
}, 65000);
});
</script>
}
and test controller code (tried this with both the disabled and readonly options)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.SessionState;
namespace SessionTest.Controllers
{
[SessionState(SessionStateBehavior.ReadOnly)]
public class ReadOnlyController : Controller
{
[Authorize]
public ActionResult Authorized()
{
return Json(new
{
test = "ReadOnly and Authorized"
}, JsonRequestBehavior.AllowGet);
}
public ActionResult Public()
{
return Json(new
{
test = "ReadOnly and Public"
}, JsonRequestBehavior.AllowGet);
}
}
}
Maybe you need to have 2 separate web apps. One is for serving authenticated requests. Another one is for all public requests.
That's similar to how the Google Analytics script creates and maintains its own Session on Google side about your site without impacting your web application's internal session management. Otherwise, you will get stuck with the default behavior of ASP .NET the way it is handling cookies and keeps session alive.
Good luck.
I wouldn't implement a timeout in this situation. In fact I try to avoid them unless there is a fundamental and key reason why they are necessary, otherwise they just become an annoyance.
However if you do feel you need one, I would implement it in this case, by creating a separate javascript function which has a timer, and that is reset with user input. If the timer completes an ajax call is performed that executes a manual session invalidation on server side.
I would configure the listener method or class to not use session which will prevent it from being extended.
There are attributes available for both methods and controllers that provides different session modes.
More info here:
http://www.dotnet-tricks.com/Tutorial/mvc/906b060113-Controlling-Session-Behavior-in-Asp.Net-MVC4.html
Ajax calls will keep the session alive.
One approach will be to set a timeout on client side to delete cookie after some time.
I'm not sure you have anymore options.
If the calls every 5 sec are only to non-authenticated request, just keep the cookie out of the ajax request.
I think the sliding expiration is set to true by default.
I think perhaps when the call that is made to the action Public, it's made with cookie and thus extending the timeout.
public ActionResult Public()
{
return Json(new
{
test = "ReadOnly and Public"
}, JsonRequestBehavior.AllowGet);
}
If I set this below: (SlidingExpiration = false). I get the failed message.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = TimeSpan.FromMinutes(1.0),
SlidingExpiration = false
//Provider = new CookieAuthenticationProvider
//{
// OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
// validateInterval: TimeSpan.FromMinutes(30),
// regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
//}
});
I'm using a Backbone Router with Backbone.history.start({pushState: true, root: "/"}); and I'm loading sections of pages through AJAX and use Backbone.history.navigate(url, true); to make sure the url shown by the browser points to the corresponding section.
For instance, I load a page with the relative url "/username/profile", then load a section on that page ('Favorite Books') by clicking on a tab and triggering an AJAX request, and then change the url to "/username/favorite_book".
The problem is that if I go back (with the Back button) to a previous section from the one loaded through ajax, the page content does not change even though the url changes.
I have seen previous posts talking about Ajax Browser History, but I would like to know what should I do in the context of Backbone? I could not find a clear explanation of the issue and how to solve it.
To be precise, what should I add to the function I trigger when clicking on the tab of a section to be loaded with ajax? My aim is to change the URL and the page (go back to state before AJAX request) when using the Back button. I'm currently doing as follows:
RenderSection: function(event) {
var data = '';
var url = $(event.currentTarget).attr("href");
$.post(url, data, function(data){
$(".ajax_section").html(data);
var protocol = this.protocol + '//';
// Ensure the protocol is not part of URL, meaning its relative.
if (url && url.slice(protocol.length) !== protocol) {
Backbone.history.navigate(url, true);
}
});
return false;
},
Turns out I was not using Backbone correctly. Here is what I ended up using and it works great!
In my Backbone View, I created 2 methods: the first is triggered when a link (an anchor) with class="ajax_enabled" is clicked, while the second is triggered by a Backbone Events trigger included in the Router's action. The Backbone View methods look as follows:
events: {
'click a.ajax_enabled': 'NavigateToUrl'
}
initialize: function() {
EventAggregator.on("render:route", this.RenderAjax, this);
},
NavigateToUrl: function(event) {
var url = $(event.currentTarget).attr("href");
var protocol = this.protocol + '//';
// Ensure the protocol is not part of URL, meaning its relative.
if (url && url.slice(protocol.length) !== protocol) {
Backbone.history.navigate(url, true);
}
return false;
},
RenderAjax: function(route) {
var data = '';
var url = window.location.pathname + window.location.search;
$.post(url, data, function(data){
$(".ajax_section").html(data);
});
}
My Backbone Router handles the call from Backbone.history.navigate(url, true); and triggers the event to update the view through the default action, as follows:
window.EventAggregator = _.extend({}, Backbone.Events);
var Router = Backbone.Router.extend({
initialize: function(options) {
var router = this;
Backbone.history.start({pushState: true, root: "/", silent: true});
},
routes: {
'' : 'defaultAction',
'*route': 'defaultAction'
},
defaultAction: function(route) {
if(typeof(route)==='undefined') {
route = '';
}
EventAggregator.trigger("render:route", route);
}
});
return Router;
For reference, I found this other answer helpful, about using events to trigger methods in the View from the Router.
I can't seem to get the AJAX call correct. There have been other QA that deal with the $.ajax() function but I'm trying to solve this with $.post().
When the form button is clicked the javascript at the head is executed, which includes a $.post(). The url /login is routed through and passed to loginPost function. There a response is determined and sent back to the javascript (right?). Instead, webpage renders the response (pass || fail).
Why isn't the response from the AJAX call being sent back to get processed?
This is a simple example that I am working with to get me better acquainted to how AJAX in expressJS and jQuery work. Any Help is greatly appreciated!
--views/login.jade
script(src='/_js/jquery-1.8.2.min.js')
script
$(document).ready(function(req, res) {
$('#login').submit(function() {
var formData = $(this).serialize();
console.log(formData);
$.post('/login', formdata, processData).error('ouch');
function processData(data, status) {
console.log(status);
console.log(data);
if (data == 'pass') {
$('#content').html('<p>You have successfully loggin in!</p>');
} else {
if (! $('#fail').length) {
$('#formFrame').prepend('<p id="fail">Incorrect login information. Please try again)</p>');
}
}
} //end processData
}); //end submit
}); //end ready
div.main
h1= title
div#formFrame
form(id='login', action='/login', method='POST')
p
label(for='username') Username:
input(id='username', type='text', name='username')
p
label(for='password') Password:
input(id='password', type='password', name='password')
p
input(id='button', type='submit', name='button', value='Submit')
--routes/index.js
app.post('/login', loginPost);
--routes/loginPost
module.exports.loginPost = function(req, res) {
var password = 'admin'
, username = 'user'
, data = req.body;
if (data.username == username && data.password == password) {
res.send('pass');
} else {
res.send('fail');
}
};
You still have to stop the <form> from submitting via its default action, which can be done with event.preventDefault():
$('#login').submit(function(evt) {
evt.preventDefault();
// ...
});
Otherwise, the <form> will redirect the page to its action (or back to the current address if no action was given), interrupting the $.post request.
I've got a piece of code that look like this.
app.get('/auth/facebook', function( request, response ) {
if( request.session.user ){
response.render( 'index.jade' );
} else {
request.authenticate(['facebook'], function(error, authenticated) {
if( authenticated ) {
request.session.user = request.getAuthDetails().user;
response.writeHead(303, { 'Location': "/auth/facebook" });
}
});
}
});
If there is a user in session it will render the page, if not it will get a user from Facebook and store that in a session variable and reload the page... and render it. It works perfectly fine. But I want to trigger that piece of code via AJAX and do something like this:
app.get('/auth/facebook', function( request, response ) {
response.contentType('application/json');
if( request.session.user ){
response.send(JSON.stringify({'authenticated':true}));
} else {
request.authenticate(['facebook'], function(error, authenticated) {
if( authenticated ) {
request.session.user = request.getAuthDetails().user;
response.writeHead(303, { 'Location': "/auth/facebook" });
} else {
response.send(JSON.stringify({'authenticated':false}));
}
});
}
});
But that doesn't work. It says "Can't use mutable header APIs after sent" and puts it self in an endless loop saying "Can't render headers after they are sent to the client." over and over again.
Am I going about this the wrong way? I want my server code to connect with Facebook without the need of a page reload.