I'm working with .NET 3.5 with a simple handler for http requests. Right now, on each http request my handler opens a tcp connection with 3 remote servers in order to receive some information from them. Then closes the sockets and writes the server status back to Context.Response.
However, I would prefer to have a separate object that every 5 minutes connects to the remote servers via tcp, gets the information and keeps it. So the HttpRequest, on each request would be much faster just asking this object for the information.
So my questions here are, how to keep a shared global object in memory all the time that can also "wake" an do those tcp connections even when no http requests are coming and have the object accesible to the http request handler.
A service may be overkill for this.
You can create a global object in your application start and have it create a background thread that does the query every 5 minutes. Take the response (or what you process from the response) and put it into a separate class, creating a new instance of that class with each response, and use System.Threading.Interlocked.Exchange to replace a static instance each time the response is retrieved. When you want to look the the response, simply copy a reference the static instance to a stack reference and you will have the most recent data.
Keep in mind, however, that ASP.NET will kill your application whenever there are no requests for a certain amount of time (idle time), so your application will stop and restart, causing your global object to get destroyed and recreated.
You may read elsewhere that you can't or shouldn't do background stuff in ASP.NET, but that's not true--you just have to understand the implications. I have similar code to the following example working on an ASP.NET site that handles over 1000 req/sec peak (across multiple servers).
For example, in global.asax.cs:
public class BackgroundResult
{
public string Response; // for simplicity, just use a public field for this example--for a real implementation, public fields are probably bad
}
class BackgroundQuery
{
private BackgroundResult _result; // interlocked
private readonly Thread _thread;
public BackgroundQuery()
{
_thread = new Thread(new ThreadStart(BackgroundThread));
_thread.IsBackground = true; // allow the application to shut down without errors even while this thread is still running
_thread.Name = "Background Query Thread";
_thread.Start();
// maybe you want to get the first result here immediately?? Otherwise, the first result may not be available for a bit
}
/// <summary>
/// Gets the latest result. Note that the result could change at any time, so do expect to reference this directly and get the same object back every time--for example, if you write code like: if (LatestResult.IsFoo) { LatestResult.Bar }, the object returned to check IsFoo could be different from the one used to get the Bar property.
/// </summary>
public BackgroundResult LatestResult { get { return _result; } }
private void BackgroundThread()
{
try
{
while (true)
{
try
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("http://example.com/samplepath?query=query");
request.Method = "GET";
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream(), System.Text.Encoding.UTF8))
{
// get what I need here (just the entire contents as a string for this example)
string result = reader.ReadToEnd();
// put it into the results
BackgroundResult backgroundResult = new BackgroundResult { Response = result };
System.Threading.Interlocked.Exchange(ref _result, backgroundResult);
}
}
}
catch (Exception ex)
{
// the request failed--cath here and notify us somehow, but keep looping
System.Diagnostics.Trace.WriteLine("Exception doing background web request:" + ex.ToString());
}
// wait for five minutes before we query again. Note that this is five minutes between the END of one request and the start of another--if you want 5 minutes between the START of each request, this will need to change a little.
System.Threading.Thread.Sleep(5 * 60 * 1000);
}
}
catch (Exception ex)
{
// we need to get notified of this error here somehow by logging it or something...
System.Diagnostics.Trace.WriteLine("Error in BackgroundQuery.BackgroundThread:" + ex.ToString());
}
}
}
private static BackgroundQuery _BackgroundQuerier; // set only during application startup
protected void Application_Start(object sender, EventArgs e)
{
// other initialization here...
_BackgroundQuerier = new BackgroundQuery();
// get the value here (it may or may not be set quite yet at this point)
BackgroundResult result = _BackgroundQuerier.LatestResult;
// other initialization here...
}
Related
I have a controller which returns a large json object. If this object does not exist, it will generate and return it afterwards. The generation takes about 5 seconds, and if the client sent the request multiple times, the object gets generated with x-times the children. So my question is: Is there a way to block the second request, until the first one finished, independent who sent the request?
Normally I would do it with a Singleton, but because I am having scoped services, singleton does not work here
Warning: this is very oppinionated and maybe not suitable for Stack Overflow, but here it is anyway
Although I'll provide no code... when things take a while to generate, you don't usually spend that time directly in controller code, but do something like "start a background task to generate the result, and provide a "task id", which can be queried on another different call).
So, my preferred course of action for this would be having two different controller actions:
Generate, which creates the background job, assigns it some id, and returns the id
GetResult, to which you pass the task id, and returns either different error codes for "job id doesn't exist", "job id isn't finished", or a 200 with the result.
This way, your clients will need to call both, however, in Generate, you can check if the job is already being created and return an existing job id.
This of course moves the need to "retry and check" to your client: in exchange, you don't leave the connection to the server opened during those 5 seconds (which could potentially be multiplied by a number of clients) and return fast.
Otherwise, if you don't care about having your clients wait for a response during those 5 seconds, you could do a simple:
if(resultDoesntExist) {
resultDoesntExist = false; // You can use locks for the boolean setters or Interlocked instead of just setting a member
resultIsBeingGenerated = true;
generateResult(); // <-- this is what takes 5 seconds
resultIsBeingGenerated = false;
}
while(resultIsBeingGenerated) { await Task.Delay(10); } // <-- other clients will wait here
var result = getResult(); // <-- this should be fast once the result is already created
return result;
note: those booleans and the actual loop could be on the controller, or on the service, or wherever you see fit: just be wary of making them thread-safe in however method you see appropriate
So you basically make other clients wait till the first one generates the result, with "almost" no CPU load on the server... however with a connection open and a thread from the threadpool used, so I just DO NOT recommend this :-)
PS: #Leaky solution above is also good, but it also shifts the responsability to retry to the client, and if you are going to do that, I'd probably go directly with a "background job id", instead of having the first (the one that generates the result) one take 5 seconds. IMO, if it can be avoided, no API action should ever take 5 seconds to return :-)
Do you have an example for Interlocked.CompareExchange?
Sure. I'm definitely not the most knowledgeable person when it comes to multi-threading stuff, but this is quite simple (as you might know, Interlocked has no support for bool, so it's customary to represent it with an integral type):
public class QueryStatus
{
private static int _flag;
// Returns false if the query has already started.
public bool TrySetStarted()
=> Interlocked.CompareExchange(ref _flag, 1, 0) == 0;
public void SetFinished()
=> Interlocked.Exchange(ref _flag, 0);
}
I think it's the safest if you use it like this, with a 'Try' method, which tries to set the value and tells you if it was already set, in an atomic way.
Besides simply adding this (I mean just the field and the methods) to your existing component, you can also use it as a separate component, injected from the IOC container as scoped. Or even injected as a singleton, and then you don't have to use a static field.
Storing state like this should be good for as long as the application is running, but if the hosted application is recycled due to inactivity, it's obviously lost. Though, that won't happen while a request is still processing, and definitely won't happen in 5 seconds.
(And if you wanted to synchronize between app service instances, you could 'quickly' save a flag to the database, in a transaction with proper isolation level set. Or use e.g. Azure Redis Cache.)
Example solution
As Kit noted, rightly so, I didn't provide a full solution above.
So, a crude implementation could go like this:
public class SomeQueryService : ISomeQueryService
{
private static int _hasStartedFlag;
private static bool TrySetStarted()
=> Interlocked.CompareExchange(ref _hasStartedFlag, 1, 0) == 0;
private static void SetFinished()
=> Interlocked.Exchange(ref _hasStartedFlag, 0);
public async Task<(bool couldExecute, object result)> TryExecute()
{
if (!TrySetStarted())
return (couldExecute: false, result: null);
// Safely execute long query.
SetFinished();
return (couldExecute: true, result: result);
}
}
// In the controller, obviously
[HttpGet()]
public async Task<IActionResult> DoLongQuery([FromServices] ISomeQueryService someQueryService)
{
var (couldExecute, result) = await someQueryService.TryExecute();
if (!couldExecute)
{
return new ObjectResult(new ProblemDetails
{
Status = StatusCodes.Status503ServiceUnavailable,
Title = "Another request has already started. Try again later.",
Type = "https://tools.ietf.org/html/rfc7231#section-6.6.4"
})
{ StatusCode = StatusCodes.Status503ServiceUnavailable };
}
return Ok(result);
}
Of course possibly you'd want to extract the 'blocking' logic from the controller action into somewhere else, for example an action filter. In that case the flag should also go into a separate component that could be shared between the query service and the filter.
General use action filter
I felt bad about my inelegant solution above, and I realized that this problem can be generalized into basically a connection number limiter on an endpoint.
I wrote this small action filter that can be applied to any endpoint (multiple endpoints), and it accepts the number of allowed connections:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ConcurrencyLimiterAttribute : ActionFilterAttribute
{
private readonly int _allowedConnections;
private static readonly ConcurrentDictionary<string, int> _connections = new ConcurrentDictionary<string, int>();
public ConcurrencyLimiterAttribute(int allowedConnections = 1)
=> _allowedConnections = allowedConnections;
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var key = context.HttpContext.Request.Path;
if (_connections.AddOrUpdate(key, 1, (k, v) => ++v) > _allowedConnections)
{
Close(withError: true);
return;
}
try
{
await next();
}
finally
{
Close();
}
void Close(bool withError = false)
{
if (withError)
{
context.Result = new ObjectResult(new ProblemDetails
{
Status = StatusCodes.Status503ServiceUnavailable,
Title = $"Maximum {_allowedConnections} simultaneous connections are allowed. Try again later.",
Type = "https://tools.ietf.org/html/rfc7231#section-6.6.4"
})
{ StatusCode = StatusCodes.Status503ServiceUnavailable };
}
_connections.AddOrUpdate(key, 0, (k, v) => --v);
}
}
}
In a nutshell, I need to notify a Web API service from SQL Server asynchronously as and when there are changes in a particular table.
To achieve the above, I have created a SQLCLR stored procedure which contains the asynchronous API call to notify the service. The SQLCLR stored procedure is called via a trigger as and when there is an insert into a table called Table1. The main challenge here is API has to read data from the same table (Table1).
If I use HttpWebRequest.GetResponse() which is the synchronous version, the entire operation is getting locked out due to the implicit lock of the insert trigger. To avoid this, I have used HttpWebRequest.GetResponseAsync() method which calls the API and doesn't wait for the response. So it fires the API request and the program control moves on so the trigger transaction doesn't hold any lock(s) on table1 and the API was able to read data from table1.
Now I have to implement an error notification mechanism as and when there are failures (like unable to connect to remote server) and I need to send an email to the admin team. I have wrote the mail composition logic inside the catch() block. If I proceed with the above HttpWebRequest.GetResponseAsync().Result method, the entire operation becomes synchronous and it locks the entire operation.
If I use the BeginGetResponse() and EndGetResponse() method implementation suggested in Microsoft documents and run the SQLCLR stored procedure, SQL Server hangs without any information, why? What am I doing wrong here? Why does the RespCallback() method not get executed?
Sharing the SQLCLR code snippets below.
public class RequestState
{
// This class stores the State of the request.
// const int BUFFER_SIZE = 1024;
// public StringBuilder requestData;
// public byte[] BufferRead;
public HttpWebRequest request;
public HttpWebResponse response;
// public Stream streamResponse;
public RequestState()
{
// BufferRead = new byte[BUFFER_SIZE];
// requestData = new StringBuilder("");
request = null;
// streamResponse = null;
}
}
public partial class StoredProcedures
{
private static SqlString _mailServer = null;
private static SqlString _port = null;
private static SqlString _fromAddress = null;
private static SqlString _toAddress = null;
private static SqlString _mailAcctUserName = null;
private static SqlString _decryptedPassword = null;
private static SqlString _subject = null;
private static string _mailContent = null;
private static int _portNo = 0;
public static ManualResetEvent allDone = new ManualResetEvent(false);
const int DefaultTimeout = 20000; // 50 seconds timeout
#region TimeOutCallBack
/// <summary>
/// Abort the request if the timer fires.
/// </summary>
/// <param name="state">request state</param>
/// <param name="timedOut">timeout status</param>
private static void TimeoutCallback(object state, bool timedOut)
{
if (timedOut)
{
HttpWebRequest request = state as HttpWebRequest;
if (request != null)
{
request.Abort();
SendNotifyErrorEmail(null, "The request got timedOut!,please check the API");
}
}
}
#endregion
#region APINotification
[SqlProcedure]
public static void Notify(SqlString weburl, SqlString username, SqlString password, SqlString connectionLimit, SqlString mailServer, SqlString port, SqlString fromAddress
, SqlString toAddress, SqlString mailAcctUserName, SqlString mailAcctPassword, SqlString subject)
{
_mailServer = mailServer;
_port = port;
_fromAddress = fromAddress;
_toAddress = toAddress;
_mailAcctUserName = mailAcctUserName;
_decryptedPassword = mailAcctPassword;
_subject = subject;
if (!(weburl.IsNull && username.IsNull && password.IsNull && connectionLimit.IsNull))
{
var url = Convert.ToString(weburl);
var uname = Convert.ToString(username);
var pass = Convert.ToString(password);
var connLimit = Convert.ToString(connectionLimit);
int conLimit = Convert.ToInt32(connLimit);
try
{
if (!(string.IsNullOrEmpty(url) && string.IsNullOrEmpty(uname) && string.IsNullOrEmpty(pass) && conLimit > 0))
{
SqlContext.Pipe.Send("Entered inside the notify method");
HttpWebRequest httpWebRequest = WebRequest.Create(url) as HttpWebRequest;
string encoded = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(uname + ":" + pass));
httpWebRequest.Headers.Add("Authorization", "Basic " + encoded);
httpWebRequest.Method = "POST";
httpWebRequest.ContentLength = 0;
httpWebRequest.ServicePoint.ConnectionLimit = conLimit;
// Create an instance of the RequestState and assign the previous myHttpWebRequest
// object to its request field.
RequestState requestState = new RequestState();
requestState.request = httpWebRequest;
SqlContext.Pipe.Send("before sending the notification");
//Start the asynchronous request.
IAsyncResult result =
(IAsyncResult)httpWebRequest.BeginGetResponse(new AsyncCallback(RespCallback), requestState);
SqlContext.Pipe.Send("after BeginGetResponse");
// this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), requestState, DefaultTimeout, true);
//SqlContext.Pipe.Send("after RegisterWaitForSingleObject");
// The response came in the allowed time. The work processing will happen in the
// callback function.
allDone.WaitOne();
//SqlContext.Pipe.Send("after allDone.WaitOne();");
// Release the HttpWebResponse resource.
requestState.response.Close();
SqlContext.Pipe.Send("after requestState.response.Close()");
}
}
catch (Exception exception)
{
SqlContext.Pipe.Send(" Main Exception");
SqlContext.Pipe.Send(exception.Message.ToString());
//TODO: log the details in a error table
SendNotifyErrorEmail(exception, null);
}
}
}
#endregion
#region ResposnseCallBack
/// <summary>
/// asynchronous Httpresponse callback
/// </summary>
/// <param name="asynchronousResult"></param>
private static void RespCallback(IAsyncResult asynchronousResult)
{
try
{
SqlContext.Pipe.Send("Entering the respcallback");
// State of request is asynchronous.
RequestState httpRequestState = (RequestState)asynchronousResult.AsyncState;
HttpWebRequest currentHttpWebRequest = httpRequestState.request;
httpRequestState.response = (HttpWebResponse)currentHttpWebRequest.EndGetResponse(asynchronousResult);
SqlContext.Pipe.Send("exiting the respcallBack");
}
catch (Exception ex)
{
SqlContext.Pipe.Send("exception in the respcallBack");
SendNotifyErrorEmail(ex, null);
}
allDone.Set();
}
#endregion
}
One alternative approach for above is using SQL Server Service Broker which has the queuing mechanism which will help us to implement asynchronous triggers. But do we have any solution for the above situation? Am I doing anything wrong in an approach perspective? Please guide me.
There are a couple of things that stand out as possible issues:
Wouldn't the call to allDone.WaitOne(); block until a response is received anyway, negating the need / use of all this async stuff?
Even if this does work, are you testing this in a single session? You have several static member (class-level) variables, such as public static ManualResetEvent allDone, that are shared across all sessions. SQLCLR uses a shared App Domain (App Domains are per Database / per Assembly owner). Hence multiple sessions will over-write each other's values of these shared static variables. That is very dangerous (hence why non-readonly static variables are only allowed in UNSAFE Assemblies). This model only works if you can guarantee a single caller at any given moment.
Beyond any SQLCLR technicalities, I am not sure that this is a good model even if you do manage to get past this particular issue.
A better, safer model would be to:
create a queue table to log these changes. you typically only need the key columns and a timestamp (DATETIME or DATETIME2, not the TIMESTAMP datatype).
have the trigger log the current time and rows modified into the queue table
create a stored procedure that takes items from the queue, starting with the oldest records, processes them (which can definitely be calling your SQLCLR stored procedure to do the web server call, but no need for it to be async, so remove that stuff and set the Assembly back to EXTERNAL_ACCESS since you don't need/want UNSAFE).
Do this in a transaction so that the records are not fully removed from the queue table if the "processing" fails. Sometimes using the OUTPUT clause with DELETE to reserve rows that you are working on into a local temp table helps.
Process multiple records at a time, even if calling the SQLCLR stored procedure needs to be done on a per-row basis.
create a SQL Server agent job to execute the stored procedure every minute (or less depending on need)
Minor issues:
the pattern of copying input parameters into static variables (e.g. _mailServer = mailServer;) is pointless at best, and error-prone regardless due to not being thread-safe. remember, static variables are shared across all sessions, so any concurrent sessions will be overwriting the previous values, hence ensure race conditions. Please remove all of the variables with names starting with an underscore.
the pattern of using Convert.To... is also unnecessary and a slight hit to performance. All Sql* types have a Value property that returns the expected .NET type. Hence, you only need: string url = weburl.Value;
there is no need to use incorrect datatypes that require conversion later on. Meaning, rather than using SqlString for connectionLimit, instead use SqlInt32 and then you can simply do int connLimit = connectionLimit.Value;
You probably don't need to do the security manually (i.e. httpWebRequest.Headers.Add("Authorization", "Basic " + encoded);). I am pretty sure you can just create a new NetworkCredential using uname and pass and assign that to the request.
Hi there (and Happy New Year) :)!
A couple of things:
If you can, stay away from triggers! They can cause a lot of unexpected side effects, especially if you use them for business logic. What I mean with that, is that they are good for auditing purposes, but apart from that I try and avoid them like the plague.
So, if you do not use triggers, how do you know when something is inserted? Well, I hope that your inserts happen through stored procedures, and are not direct inserts in the table(s). If you use stored procedures you can have a procedure which is doing your logic, and that procedure is called from the procedure which does the insert.
Back to your question. I don't really have an answer to the specifics, but I would stay away from using SQLCLR in this case (sidenote: I am a BIG proponent of SQLCLR, and I have implemented a lot of SQLCLR processes which are doing something similar to what you do, so I don't say this because I do not like SQLCLR).
In your case, I would look at either use Change Notifications, or as you mention in your post - Service Broker. Be aware that with SSB you can run into performance issues (latches, locks, etc.) if your system is highly volatile (+2,000 tx/sec). At least that is what we experienced.
I have an Asp.NET Web API 2. My client calls a particular action method but I need someway to set the timeout value.
I need the timeout value because I want the client to take appropriate action if the action method hasn't returned anything back in say 40 seconds. (Basically that's an arbitrary limit I've chosen...so if it hasn't completed it's job..i.e. hasn't returned back the valid JSON in 40 seconds, we're going to have to assume that something is taking way too long on Azure and we're going to perform a rollback).
Also, if the timeout has occurred I want someone way to Rollback the transaction.
If it helps, I'm using the UnitOfWorkActionFilter along with NHibernate.
The controller and action method are both asynchronous, and I'm explicitly catching the ExecuteAsync method along with the CancellationToken variable.
However, I'm unaware of a way to cancel this call OR use the CancellationToken variable.
I can post code if necessary.
I did read in a few places that since WebApi2 is asynchronous that I may not be able to cancel this!
Any recommendations on how to go about solving this?
I think setting a timeout on the request is the wrong approach as you will have no visibility of what is going on during the 40 seconds.
Rather make a ajax web request and then subsequent web requests to see if the process has completed.
For example,
Queue the request somehow with the initial request.
Write something to pick up and process the item from the queue. This also means if something goes wrong, you can just roll back at this point. You also need to store the status of the item somewhere.
Write a periodic poll in Javascript that makes another ajax request every 5 seconds to see if the request has been processed or not.
Depending on what kind of method is running on your WebApi service you could try the following:
Start a StopWatch at the start of your action
Check periodically if the elapsed time is greater than your arbitrary limit. When that happens throw an Exception (I called mine
CalculationTimeLimitExceededException)
Catch the exception and perform a rollback (assuming you want to do a rollback on the server)
Return a response (e.g. HTTP 500 with some useful information, e.g. server timeout)
Since the client gets a response within your time limit you can then handle the error on the client side.
Update (added code for PerformanceWatcher):
public class PerformanceWatcher : IDisposable
{
private System.Diagnostics.Stopwatch _sw;
private Timer _timer;
private int _maxSeconds;
public bool TimeLimitExceeded { get; private set; }
public PerformanceWatcher(int maxSeconds)
{
_maxSeconds = maxSeconds;
// start the StopWatch
_sw = System.Diagnostics.Stopwatch.StartNew();
// check every second
_timer = new Timer(1000);
_timer.AutoReset = true;
// set event-handler
_timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
_timer.Enabled = true;
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
// check if time limit was reached
if (this._sw.Elapsed.TotalSeconds > _maxSeconds)
{
this.TimeLimitExceeded = true;
}
}
public void Dispose()
{
this._timer.Dispose();
}
}
You can use this class in your action:
[HttpGet]
public HttpResponseMessage GetResultFromLongRunningMethod()
{
using (PerformanceWatcher watcher = new PerformanceWatcher(10))
{
try
{
// begin long-running operation
for (int i = 0; i < 20; i++)
{
if (watcher.TimeLimitExceeded)
{
throw new TimeLimitExceededException();
}
Thread.Sleep(1000);
}
// end long-running operation
} catch (TimeLimitExceededException e)
{
return this.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Time limit exceeded");
}
}
return this.Request.CreateResponse(HttpStatusCode.OK, "everything ok");
}
The above code isn't tested; I just copied some elements from my own classes.
Update 2: Fixed a bug in the code (Exception thrown from event handler wasn't caught)
I have an HTTP server written in C# based off the HttpListenerContext class. The server is for processing binary log files and converting them to text, and can take quite a long time to do the conversion. I would like to indicate progress back to the user, but I am unsure on the best way to do this. On the server side, in handling my HTTP request, I essentially have this function:
public async Task HandleRequest()
{
try
{
await ProcessRequest();
}
catch (HttpListenerException)
{
// Something happened to the http connection, don't try to send anything
}
catch (Exception e)
{
SendFailureResponse(500);
}
}
Currently, ProcessRequest() sends the HTML response when finished, but I would like to essentially add the IProgress interface to the function and somehow indicate that progress back to the Web client. What is the best way to do this?
One way of doing it would be to store progress on server side and periodically pull the information from client.
However, if you want the server to notify the client ( push ), then you will need to implement some kind of bi-directional communication between the server and client (I am currently using ASP.NET Web API and SignalR to achieve this at work).
Here is what I got I'll try to explain and I hope you notice its not FULL FULL complete, you'll have to understand the logic behind this and accept or not as a plausible option.
The Method: Set a custom object to store progress of your ongoing operations, make a global static list containing this metadata. Notice how I track them with Ids: I don't store that on DB, the natural act of instantiating the class will auto_increment their Id.
Then, you can add a new controller to respond the progress of a particular ongoing process.
Now that you have a controller to respond the progress of an ongoing process by Id, you can create a javascript timer to call it and update the DOM.
When creating your process, dont hold the htmlrequest until its over, open a background operation instead and just respond with the newly created ProgressTracker.Id, through that class/list you can keep track of the progress and reply accordingly.
As said in another answer, when an operation finishes you can send a push notification and the clientside javascript will interrupt the timers and proceed to the next view/result/page, or you can increment the looping timer to detect when its done and call the results from another controller. (this way you can avoid using push if needed.)
Here is the partial code:
public class ProgressTracker {
private static GlobalIdProvider = 0;
public int _id = ++GlobalIdProvider;
public int Id { get { return _id; } }
bool IsInProgress = false;
bool IsComplete = false;
float Progress;
public YourProgressObject Data;
}
public class GlobalStatic {
public static List<ProgressTracker> FooOperations = new List<ProgressTracker>();
}
public class SomeWebApiController {
[HttpGet]
[Authorize]
public HttpResponseMessage GetProgress(int Id) {
var query = (from a in GlobalStatic.FooOperations where a.Id==Id select a);
if(!query.Any()) {
return Request.CreateResponse(HttpStatusCode.NotFound, "No operation with this Id found.");
} else {
return Request.CreateResponse(HttpStatusCode.Ok, query.First());
}
}
}
// this is javascript
// ... Your code until it starts the process.
// You'll have to get the ProgressTracker Id from the server somehow.
var InProgress = true;
window.setTimeout(function(e) {
var xmlhttp = new XMLHttpRequest();
var url = "<myHostSomething>/SomeWebApiController/GetProgress?Id="+theId;
xmlhttp.setRequestHeader("Authentication","bearer "+localStorage.getItem("access_token"));
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var data = JSON.parse(xmlhttp.responseText);
updateProgressBar(data);
}
}
xmlhttp.open("GET", url, true);
xmlhttp.send();
function updateProgressBar(data) {
document.getElementById("myProgressText").innerHTML = data.Progress;
}
}, 3000);
Disclaimer: If my javascript is shitty, pardon me but I'm too used to using jQuery and all this fancy stuff x_x
I'm trying to create a web app which does many things but the one that I'm currently focused in is the inbox count. I want to use EWS StreamSubscription so that I can get notification for each event and returns the total count of items in the inbox. How can I use this in terms of MVC? I did find some code from Microsoft tutorial that I was gonna test, but I just couldn't figure how I could use it in MVC world i.e. What's the model going to be, if model is the count then how does it get notified every time an event occurs in Exchange Server, etc.
Here's the code I downloaded from Microsoft, but just couldn't understand how I can convert the count to json and push it to client as soon as a new change event occurs. NOTE: This code is unchanged, so it doesn't return count, yet.
using System;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.Exchange.WebServices.Data;
namespace StreamingNotificationsSample
{
internal class Program
{
private static AutoResetEvent _Signal;
private static ExchangeService _ExchangeService;
private static string _SynchronizationState;
private static Thread _BackroundSyncThread;
private static StreamingSubscriptionConnection CreateStreamingSubscription(ExchangeService service,
StreamingSubscription subscription)
{
var connection = new StreamingSubscriptionConnection(service, 30);
connection.AddSubscription(subscription);
connection.OnNotificationEvent += OnNotificationEvent;
connection.OnSubscriptionError += OnSubscriptionError;
connection.OnDisconnect += OnDisconnect;
connection.Open();
return connection;
}
private static void SynchronizeChangesPeriodically()
{
while (true)
{
try
{
// Get all changes from the server and process them according to the business
// rules.
SynchronizeChanges(new FolderId(WellKnownFolderName.Inbox));
}
catch (Exception ex)
{
Console.WriteLine("Failed to synchronize items. Error: {0}", ex);
}
// Since the SyncFolderItems operation is a
// rather expensive operation, only do this every 10 minutes
Thread.Sleep(TimeSpan.FromMinutes(10));
}
}
public static void SynchronizeChanges(FolderId folderId)
{
bool moreChangesAvailable;
do
{
Console.WriteLine("Synchronizing changes...");
// Get all changes since the last call. The synchronization cookie is stored in the _SynchronizationState field.
// Only the the ids are requested. Additional properties should be fetched via GetItem calls.
var changes = _ExchangeService.SyncFolderItems(folderId, PropertySet.IdOnly, null, 512,
SyncFolderItemsScope.NormalItems, _SynchronizationState);
// Update the synchronization cookie
_SynchronizationState = changes.SyncState;
// Process all changes
foreach (var itemChange in changes)
{
// This example just prints the ChangeType and ItemId to the console
// LOB application would apply business rules to each item.
Console.Out.WriteLine("ChangeType = {0}", itemChange.ChangeType);
Console.Out.WriteLine("ChangeType = {0}", itemChange.ItemId);
}
// If more changes are available, issue additional SyncFolderItems requests.
moreChangesAvailable = changes.MoreChangesAvailable;
} while (moreChangesAvailable);
}
public static void Main(string[] args)
{
// Create new exchange service binding
// Important point: Specify Exchange 2010 with SP1 as the requested version.
_ExchangeService = new ExchangeService(ExchangeVersion.Exchange2010_SP1)
{
Credentials = new NetworkCredential("user", "password"),
Url = new Uri("URL to the Exchange Web Services")
};
// Process all items in the folder on a background-thread.
// A real-world LOB application would retrieve the last synchronization state first
// and write it to the _SynchronizationState field.
_BackroundSyncThread = new Thread(SynchronizeChangesPeriodically);
_BackroundSyncThread.Start();
// Create a new subscription
var subscription = _ExchangeService.SubscribeToStreamingNotifications(new FolderId[] {WellKnownFolderName.Inbox},
EventType.NewMail);
// Create new streaming notification conection
var connection = CreateStreamingSubscription(_ExchangeService, subscription);
Console.Out.WriteLine("Subscription created.");
_Signal = new AutoResetEvent(false);
// Wait for the application to exit
_Signal.WaitOne();
// Finally, unsubscribe from the Exchange server
subscription.Unsubscribe();
// Close the connection
connection.Close();
}
private static void OnDisconnect(object sender, SubscriptionErrorEventArgs args)
{
// Cast the sender as a StreamingSubscriptionConnection object.
var connection = (StreamingSubscriptionConnection) sender;
// Ask the user if they want to reconnect or close the subscription.
Console.WriteLine("The connection has been aborted; probably because it timed out.");
Console.WriteLine("Do you want to reconnect to the subscription? Y/N");
while (true)
{
var keyInfo = Console.ReadKey(true);
{
switch (keyInfo.Key)
{
case ConsoleKey.Y:
// Reconnect the connection
connection.Open();
Console.WriteLine("Connection has been reopened.");
break;
case ConsoleKey.N:
// Signal the main thread to exit.
Console.WriteLine("Terminating.");
_Signal.Set();
break;
}
}
}
}
private static void OnNotificationEvent(object sender, NotificationEventArgs args)
{
// Extract the item ids for all NewMail Events in the list.
var newMails = from e in args.Events.OfType<ItemEvent>()
where e.EventType == EventType.NewMail
select e.ItemId;
// Note: For the sake of simplicity, error handling is ommited here.
// Just assume everything went fine
var response = _ExchangeService.BindToItems(newMails,
new PropertySet(BasePropertySet.IdOnly, ItemSchema.DateTimeReceived,
ItemSchema.Subject));
var items = response.Select(itemResponse => itemResponse.Item);
foreach (var item in items)
{
Console.Out.WriteLine("A new mail has been created. Received on {0}", item.DateTimeReceived);
Console.Out.WriteLine("Subject: {0}", item.Subject);
}
}
private static void OnSubscriptionError(object sender, SubscriptionErrorEventArgs args)
{
// Handle error conditions.
var e = args.Exception;
Console.Out.WriteLine("The following error occured:");
Console.Out.WriteLine(e.ToString());
Console.Out.WriteLine();
}
}
}
I just want to understand the basic concept as in what can be model, and where can I use other functions.
Your problem is that you are confusing a service (EWS) with your applications model. They are two different things. Your model is entirely in your control, and you can do whatever you want with it. EWS is outside of your control, and is merely a service you call to get data.
In your controller, you call the EWS service and get the count. Then you populate your model with that count, then in your view, you render that model property. It's really that simple.
A web page has no state. It doesn't get notified when things change. You just reload the page and get whatever the current state is (ie, whatever the current count is).
In more advanced applications, like Single Page Apps, with Ajax, you might periodically query the service in the background. Or, you might have a special notification service that uses something like SignalR to notify your SPA of a change, but these concepts are far more advanced than you currently are. You should probably develop your app as a simple stateless app first, then improve it to add ajax functionality or what not once you have a better grasp of things.
That's a very broad question without a clear-cut answer. Your model could certainly have a "Count" property that you could update. The sample code you found would likely be used by your controller.