pattern for sending server side events to a webpage - c#

I am wondering if there are any design patterns that allow server side events to be passed to a website?
The best I can think of is having some state on the server that is updated when the event is triggered, then maybe call back to the server via ajax every so often to check the state, and return what I need. Is this suitable? Is there a better way of doing this?
My application is written in ASP.NET MVC4.

There are a couple of ways of handling this depending on how you would like to tackle it and what technologies you would like to introduce to your stack.
Firstly, a periodic ajax call to a controller method on your server that will report the current state of the server is perfectly fine, for example: If a long running task is started on your server, returning some kind of indication of its progress via a controller method and polling that method periodically is perfectly reasonable.
The other method is to use a technology called SignalR, which essentially allows your server to call javascript functions on your client, I would certainly recommend looking into it.
Small example:
Server:
public class Chat : Hub
{
public void Send(string message)
{
// Call the addMessage method on all clients
Clients.All.addMessage(message);
}
}
Client:
<script src="http://code.jquery.com/jquery-1.8.2.min.js" type="text/javascript"></script>
<script src="Scripts/jquery.signalR-1.0.0-alpha2.min.js" type="text/javascript"></script>
<!-- If this is an MVC project then use the following -->
<!-- <script src="~/signalr/hubs" type="text/javascript"></script> -->
<script src="/signalr/hubs" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
// Proxy created on the fly
var chat = $.connection.chat;
// Declare a function on the chat hub so the server can invoke it
chat.client.addMessage = function (message) {
$('#messages').append('<li>' + message + '</li>');
};
$("#broadcast").click(function () {
// Call the chat method on the server
chat.server.send($('#msg').val());
});
// Start the connection
$.connection.hub.start();
});
</script>
<div>
<input type="text" id="msg" />
<input type="button" id="broadcast" value="broadcast" />
<ul id="messages">
</ul>
</div>
The above example was taken from here:
https://github.com/SignalR/SignalR/wiki/QuickStart-Hubs
The SignalR website can be found here:
http://signalr.net/
I really love SignalR... it will probably provide the best experience for both you the programmer and the user too.
Hope this helps.

Sounds like you should be looking at the SignalR Project. This allows bi directional server communication.

You can wait for server event using long polling/comet it mean that you should send ajax request to server and ajax request not executed at once, request will wait for server event until timeout expired.
Alternatively you can to listening server's events using ajax call server events like this, without signalR framework, for example:
[SessionState(SessionStateBehavior.ReadOnly)]
public class HomeController : Controller
{
static readonly ManualResetEvent Starter = new ManualResetEvent(false);
public ActionResult Index()
{
return View();
}
public String RiseServerEvent()
{
Starter.Set();
return "";
}
public async Task<String> WaitForServerEvent()
{
string contents = null;
var result = Task.Factory.StartNew(() =>
{
var reg = ThreadPool.RegisterWaitForSingleObject
(Starter, (state, #out) =>
{
contents = DateTime.Now.TimeOfDay.ToString();
}, "Some Data", -1, true);
Starter.WaitOne(10000);
contents = DateTime.Now.TimeOfDay.ToString();
});
await result;
return contents;
}
}

Related

Async MVC.NET action method blocks any other HTTP requests

(I apologize for changing the question)
The following snippet is from a MVC.NET controller (.NET: v4.5; AspNet.MVC: v5.2.3) .
After LongOperation is called, it:
Spawn a process
Waits for its completion
Monitors a few LOG files
Uses SignalR to notify browser of the progress from the LOG files
(I have omitted the code for simplicity)
All this works, only while LongOperation is running, no other HTTP requests are handled by the controllers.
They get handled after the LongOperation completes and the action method returns result to the AJAX call.
What am I messing up?
Thank you in advance.
Update (for #angelsix comment):
Here is a simplified setup:
I have removed async/await as advised
Added breakpoints as advised
Verified they are hit as explained in the above
Basically: same result, see the console.log-ed text and timestamps
Will appreciate any help from the community.
Thank you in advance!
GUI and log
Action methods in the Controller
[AjaxOnly]
public ActionResult _RunLongOperation(string hubId)
{
try
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
ProgressNotifierHub.Notify(hubId, string.Format("Notification from _RunLongOperation {0}", i));
}
return new HttpStatusCodeResult(HttpStatusCode.OK, "_RunLongOperation : OK");
}
catch (Exception)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "_RunLongOperation : NOK");
}
}
[AjaxOnly]
public ActionResult _RunAnotherOperation(string hubId)
{
return new HttpStatusCodeResult(HttpStatusCode.OK, "_RunAnotherOperation : OK");
}
Razor View (partial) and javascript with SignalR hub setup Ajax calls
<script src="~/signalr/hubs"></script>
#{
Layout = null;
}
<button id="longOperationBtn" type="button" class="t-button" style='width: 155px'>Long Operation</button>
<button id="anotherOperationBtn" type="button" class="t-button" style='width: 155px'>Another Operation</button>
<script type="text/javascript">
$(function () {
setupEventHandlers();
setupProgressNorificator();
});
function setupEventHandlers() {
$('#longOperationBtn').click(function (event) {
requestOperation('_RunLongOperation')
});
$('#anotherOperationBtn').click(function (event) {
requestOperation('_RunAnotherOperation')
});
}
function requestOperation(method) {
trace(method + ' requested');
$.ajax({
url: '/Profiles/Validate/' + method,
type: 'GET',
data: { hubId: $.connection.hub.id },
contentType: 'application/json; charset=utf-8',
success: function () {
trace(method + ' completed');
},
error: function () {
trace(method + ' failed');
}
});
}
function setupProgressNorificator(profileId) {
var hub = $.connection.progressNotifierHub;
hub.client.notify = function (notification) {
console.log(notification);
};
$.connection.hub.start();
}
function trace(s) {
console.log('[' + new Date().toUTCString() + '] ' + s);
}
</script>
It looks like you are running the client "test" on Chrome. I'm not sure what the 2018 limitation is (it seems to change from release to release), but browsers do limit the number of concurrent connections allowed to the same host. I believe Chrome's limit is 6 or 8.
The log you posted appears to be from the client side. If it is, the behavior you are seeing could actually be the client waiting for a free connection to connect to the server - the problem may not be with ASP.NET at all, but rather with how you are testing.
It would be easy to validate - write another test that you can run concurrently with your current test in a separate browser window that just calls the "short" operation. If it has latency, I'm wrong. If it doesn't, well hopefully I've helped out!
The issue
Your response does not mention anything about caching. So I suspect the browser is caching the response and using that.
Verify
To verify press F12 in the browser to open developer tools and then see if the second response shows a status of 304 or states Cached
Solve
To prevent caching on the action, inside the action you want to do this
Prevent Caching in ASP.NET MVC for specific actions using an attribute
According to this:
Some ... protocols like HTTP Polling (XHR) use up to two simultaneous connections per ... client. It is important to understand that the maximum number of connections is per browser and not per browser tab. Attempting to run multiple clients within the same browser might cause this limit to be reached.
In my case, SignalR client was using long polling. A fried reported that when Web sockets are used, there was no blocking issue. Closing the subject.
Thanks for the help Joe.
You need to set your controller to have read-only session state behavior. Then you can do ajax requests to the controller during running a long controller method. In other case (as the one that you are complaining about) the requests will be queued and call all at once after finishing the controller action call. Just put this before controller class definition
[SessionState(SessionStateBehavior.ReadOnly)]

Session does not stay alive

I am using the following article to keep my session alive in my asp.net application.
How to Keep Session Alive
But when I checked the session variable value using alert in javascript I didn't find anything. alert('<%=Session["Heartbeat"]%>');
Actually In my webpage there is no postback. All the work I am doing from Jquery Ajax. When there is any postback only then the solution of that article works.
But I want to keep my session alive without any postback.
Please help guys.....
alert('<%=Session["Heartbeat"]%>'); will not work without fully postback, because <%=Session["Heartbeat"]%> is a server-side code.
In order for Session to keep alive, all you need is just a simple Ajax call to server.
Easiest way to implement ASHX generic handler, and call it via Ajax. For example,
Ping.ashx
<%# WebHandler Language="C#" CodeBehind="Ping.ashx.cs" Class="PROJECT_NAMESPACE.Ping" %>
Ping.ashx.cs
public class Ping : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
context.Response.Write("Ping");
}
public bool IsReusable { get { return false; }}
}
Please try this example :-
<script language="javascript" type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script language="javascript" type="text/javascript">
function KeepSession() {
// A request to server
$.post("http://servername:port/appName/SessionCheck.aspx");
//now schedule this process to happen in some time interval, in this example its 1 min
setInterval(KeepSession, 60000);
}
// First time call of function
KeepSession();
</script>

Timer with javascript and C#

I need to create a timer in an ASP.NET web page.
I have tested some JavaScript code like:
window.onload = function WindowLoad(event) {
setTimeout(function () { alert("DoIT") }, 60000);
}
and it works like expected but when I replace the alert with a call to a C# function like:
window.onload = function WindowLoad(event) {
setTimeout(function () { <% doIt(); %> }, 60000);
}
the function works on the load of the page and not after the specified period.
<% doIt() %> runs during the server side process, not on client. If you want to do something on the server side you should create a webmethod and make a post to it.
Try this blog post, this should be what you are looking for: http://deebujacob.blogspot.com/2012/01/aspnet-ajax-web-method-call-using.html
It's like other people sad you can not call server side functions directly from javascript you have to make a request, that's is way good God gave as ajax :)

Load .NET Resources Client-Side on each request

I have the following JavaScript snippet:
<script type="text/javascript">
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_beginRequest(ajaxBeginRequest);
prm.add_endRequest(ajaxEndRequest);
function ajaxBeginRequest(sender, args) {
$.blockUI({ message: '<h1><img src="/images/ajax_loading.gif" /> <%= MyNameSpace.SomeWhereElse.Shared.RandomLoadingMessage() %></h1>' });
}
function ajaxEndRequest(sender, args) {
$.unblockUI();
}
</script>
Right now the C# line runs at page load and gives me one random message, but it is constant through the page execution until the page is refreshed. What I'd like it to do is give me a different message on each ajax Request
Any suggestions for this?
You can make a ajax call to a web service (called script service) to get the message on each invocation. See this article where it explains how to call script services using ASP.NET generated proxies as well as using jquery methods. You can use either method.

jQuery "Microsoft JScript runtime error: Object expected"

I have the below code that does not seem to work at all :( I keep getting:
Microsoft JScript runtime error: Object expected
The error seems to occur when the timeout is done. So if I raise the timeout with 10 seconds the error holds for another 10 seconds.
I want to be able to update the number of friends online async. The number is shown with the following html:
(?)</strong>
The friends part is set at the first run, but when the timeout calls back it does not fire again. Also, I cannot see on which line the error occurs because if I want to break on the error it just shows "no source code" etc.
The code below is the code I'm using. Thanks!
<script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.3.2.js" type="text/javascript"></script>
<script src='/Scripts/MicrosoftAjax.js' type="text/javascript"></script>
<script src='/Scripts/MicrosoftMvcAjax.js' type="text/javascript"></script>
<script src='/Scripts/jquery.autocomplete.js' type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
UpdateFriendsOnline();
function UpdateFriendsOnline() {
window.setTimeout("UpdateFriendsOnline()", 1000);
$.get("/Account/GetFriendsOnline", function(data) {
$("#friendsOnline").html("(" + data + ")");
});
}
});
</script>
Change your setTimeout() like this:
window.setTimeout(UpdateFriendsOnline, 1000);
Currently your function isn't available outside the document.ready, so it's not accessible as a global function, which passing it as a string is trying to access it as. As a general rule, never ever pass setTimeout() a string if you can avoid it...it can cause problems like this case, and I can't think of an example (that if avoidable) is made better by it being a string.
Also, I suggest firing it when you get the response back, otherwise you'll start queuing up overlapping ajax requests, you can do that by adjusting your function to this:
function UpdateFriendsOnline() {
$.get("/Account/GetFriendsOnline", function(data) {
$("#friendsOnline").html("(" + data + ")");
window.setTimeout(UpdateFriendsOnline, 1000);
});
}
Try this:
window.setTimeout(UpdateFriendsOnline, 1000);
The version you had would have worked if the function was defined in the global namespace.
This way, you're passing in a local reference to the function, which will get called every second.
EDIT:
If you need to cancel the previous request before the new one starts, you can do something like this:
<script type="text/javascript">
$(document).ready(function() {
var request; // Stores XMLHTTPRequest object
UpdateFriendsOnline();
function UpdateFriendsOnline() {
if(request) request.abort(); // Abort current request if there is one
window.setTimeout(UpdateFriendsOnline, 1000);
// Store new XMLHTTPRequest object
request = $.get("/Account/GetFriendsOnline", function(data) {
request = null; // Clear request object upon success
$("#friendsOnline").html("(" + data + ")");
});
}
});
</script>

Categories