I have a small MVC website which is for a friends Hair Salon. On this page I have a div which is used to display a number which it takes from a database record. This number is the current number of people sitting in the queue waiting for a haircut.
What I have currently is the ability to logon to an "admin" page and update this number using a form, from say "2" to "5", then change "5" to "6" dependant on how many people are sitting in this queue.
This is a manual operation as it currently stands. Code is below:
=============================
Controller
[HttpPost]
public ActionResult Update(Data data)
{
if (ModelState.IsValid)
{
data.ID = 1; //EF need to know which row to update in the database.
db.Entry(data).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(data);
}
====================================
Model code
{
public class Data
{
public int ID { get; set; }
public string Queue_Number { get; set; }
}
public class DataDBContext : DbContext
{
public DbSet<Data>Queue { get; set; }
}
}
What I would really like to happen is that once you have manually updated the Queue Number from the form on the "admin" page I'd like an automatic count down of 20 minutes (the rough time it takes for the haircut) and then have the Queue Number auto-adjust down by one till it gets to "0".
e.g. We have 5 people in the queue, 20 minutes later it is auto adjusted to 4 people and the web page will auto update / refresh, then 2 more people walk in so we manually adjust it to 6 people in the queue and the timer starts again, each 20 min passes the queue is adjusted by -1 till it gets down to "0". Once it gets to "0" it stays there until we manually add more people to the queue.
I'm afraid I have no idea how to even begin with such a request, or even if it is possible?
I'd be really thankful for any help from the experts here that might be able to "babystep" it for me. Any information I've not provided I'll endeavour to add - I realise I'm not the best at explaining myself :-(
Have you considered Ajax? are you storing the last updated time on manually setting the flag? You can use Ajax request to simultaneously run using jquery Set interval. which will trigger the ajax request every 2 minutes. Find the last time it was updated, if that is passed 20 minutes then remove one from the database, your return would be the new number and jquery can update that number for you.
Quite a simple process actually but need more detail on the underlying data.
Here is how I can see it working from your question
In Controller
public ActionResult ajaxUpdate()
{
//open connection
dbcontext db = new dbcontext();
db.Connection.Open();
// get the last updated record in the database.
var entry = db.Entry.OrderByDecending(m=> m.LastUpdatedDate).FirstOrDefault();
//clean up
db.Connection.Close();
db.Dispose();
//return -1 as error
if(entry == null){
return Json(-1,JsonRequestBehavior.AllowGet);
}
// get current number of people in queue
Int32 numberOfPeople = entry.QueueNumber;
TimeSpan span = DateTime.Now.Subtract(entry.LastUpdatedDate);
if(span.Minutes >= 20){
// if 20 mins have passed assume a person has been completed since manual update
numberOfPeople--;
}
//this returns a number, alternatively you can return a Partial
return Json(numberOfPeople, JsonRequestBehavior.AllowGet);
}
Jquery and Ajax
$(document).ready(function () {
// run function every x minutes
setInterval(function () {
UpdateQueue();
}, 100000);
});
function UpdateQueue() {
$.ajax({
cache: true,
type: 'POST',
url: "/ControllerName/ajaxUpdate",
async: false,
dataType: "json",
success: function (result) {
// on success result will be the number returned
// -1 is error
if (result == -1) {
return;
}
// check the -- didn't return a negative
if (result < 0) {
result = 0;
}
//find your element in the HTML to update
$('#NumberElement').text().replaceWith(result);
}
});
}
You must ensure you include your jquery libraries before you include this code or you will have Jquery not defined.
I have made up for you server side solution with a little bit threading. Hope I am correct on critical sections locks.
It has an advantage that admin of your application does not have to hang on the page to get number of current customers downcounted (like he should with ajax requests).
How it works
On 'number of customers' update it is starting (if necessary) new counting-down thread, which waits (sleeps) for predefined interval and then decreases the number.
public class CustomerAdminService
{
// time in milliseconds it will take to decrease number of waiting customers
const int sleepTime = 10000;
// current number of customers (just for simplicity - you can have it in db or somewhere else)
static int numberOfCustomers;
static Thread updaterThread;
// object lock
static readonly object locker = new Object();
public int GetNumberOfCustomers()
{
return numberOfCustomers;
}
public void UpdateCustomers(int value)
{
lock (locker)
{
if (updaterThread == null)
{
//start new downcounting thread
updaterThread = new Thread(new ThreadStart(UpdateWorker));
updaterThread.Start();
}
SetNumberOfWaitingCustomers(value);
}
}
private void SetNumberOfWaitingCustomers(int value)
{
numberOfCustomers = value;
}
// downcounting thread method
private void UpdateWorker()
{
while (true)
{
// sleep for predefined time
Thread.Sleep(sleepTime);
lock (locker)
{
var number = GetNumberOfCustomers();
if (number <= 1)
{
// if number of currents customers is now zero - end the downcounting thread
SetNumberOfWaitingCustomers(0);
updaterThread = null;
return;
}
SetNumberOfWaitingCustomers(number - 1);
}
}
}
}
Comment: You can consider using jQuery for some timer down-counting script. Showing something like: You can be served in 40 minutes ;-)
Yes Ajax is the key. It can be used by your website to communicate with your server unnoticeably.
An alternative approach would be to not update the count in the database but simply use a query to determine the number of customers within a certain time period. You can do this by modifying the model so that instead of QueueNumber it uses an arrival time and changing the controller so that it inserts a new Data record.
{
public class Data
{
public int ID { get; set; }
public DateTime Arrival_Time { get; set; }
}
public class DataDBContext : DbContext
{
public DbSet<Data> Queue { get; set; }
}
}
This way, as others have suggested you can use AJAX to poll for the number of people in the queue with a controller action that might look something like this:
[HttpGet]
public ActionResult NumberOfPeopleInQueue()
{
var result = db.NumberOfCustomersSince(DateTime.Now.AddMinutes(-20));
return Json(result);
}
The nice thing about this approach is that should haircuts start to take longer (say 30 minutes) you can simply change the query and the application continues to work.
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);
}
}
}
I have two methods as below
private void MethodB_GetId()
{
//Calling Method A constinuosly in different thread
//Let's say its calling for Id = 1 to 100
}
private void MethodA_GetAll()
{
List<string> lst;
lock(_locker)
{
lst = SomeService.Get(); //This get return all 100 ids in one shot.
//Some other processing and then return result.
}
}
Now client is calling MethodB_GetById continuously for fetching data for id: 1 to 100 randomly. (It require some of data from these 100 Ids, not all data)
MethodA_GetAll get all data from network may be cache or database in one shot. and return whole collection to method B, then method B extract record in which it is interested.
Now if MethodA_GetAll() makes GetALL() times multiple times and fetching same records will be useless. so i can put a lock around it one thread is fetching record then other will be blocked.
Let's When MethodA_GetAll called by Id = 1 acquire lock and all others are waiting for lock to be released.
What i want is one data is available by any one thread just don't make call again.
Solution option:
1. Make List global to that class and thread safe. (I don't have that option)
I require some how thread 1 tell all other threads that i have record don't go fetching record again.
something like
lock(_locker && Lst!=null) //Not here lst is local to every thread
{
//If this satisfy then only fetch records
}
Please excuse me for poorly framing question. I have posted this in little hurry.
It sounds like you want to create a threadsafe cache. One way to do this is to use Lazy<t>.
Here's an example for a cache of type List<string>:
public sealed class DataProvider
{
public DataProvider()
{
_cache = new Lazy<List<string>>(createCache);
}
public void DoSomethingThatNeedsCachedList()
{
var list = _cache.Value;
// Do something with list.
Console.WriteLine(list[10]);
}
readonly Lazy<List<string>> _cache;
List<string> createCache()
{
// Dummy implementation.
return Enumerable.Range(1, 100).Select(x => x.ToString()).ToList();
}
}
When you need to access the cached value, you just access _cache.Value. If it hasn't yet been created, then the method you passed to the Lazy<T>'s constructor will be called to initialise it. In the example above, this is the createCache() method.
This is done in a threadsafe manner, so that if two threads try to access the cached value simultaneously when it hasn't been created yet, one of the threads will actually end up calling createCache() and the other thread will be blocked until the cached value has been initialised.
You can try double-check-locking lst:
private List<string> lst;
private void MethodA_GetAll()
{
if (lst == null)
{
lock (_locker)
{
if (lst == null)
{
// do your thing
}
}
}
}
I am using mvc with Entity framework.
I have one method which is called on button click. method used to get the some value from db. And I am doing some calculation and subtracting the value based on my requirement. At the end I am updating this entity with latest changes.
If I don't have enough value in db for subtraction I want to show the error message to user "Enough value in db". its working fine for single user.
But if that method is called by different user at same time from different-different browser, then its not working.
I have tried with lock the Object or async await but not able to handle this situation. lock is not working on event which is fired by different-2 browser at same time.
Code:
public async Task SaveContainerRoutes(List<ContainerRouteVM> lstCRoute, int cid)
{
//my code
}
Lock code:
public ActionResult SaveContainerRoutes(List<ContainerRouteVM> lstCRoute, int cid)
{
try
{
ContainerRouteBL bl = new ContainerRouteBL();
lock (bl)
{
string note = bl.SaveContainerRoutes(lstCRoute, cid);
}
}
catch (Exception ex)
{
return Json(new { success = false, message = ex.Message });
}
}
Please help. Thanks in advance.
Declare this line in class level
private static Object thisLock = new Object();
use thislock in method
public async Task SaveContainerRoutes(List<ContainerRouteVM> lstCRoute, int cid)
{
lock(thisLock)
{
//place use code
}
}
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
So the problem is as follows: I'm using a third party client class to issue commands to an external system to retrieve data (fairly standard). The problem is that when I issue commands via an instance of this class, it uses a callback reference based on the function name in my code and the line number within that function from which it was called, it then serializes this and other information into JSON and transmits to the external system for processing, with the data return being identified by the reference. This works "well" until we get to iteration, then the callback reference remains the same and I only receive data for one iteration. The third party isn't going to alter their code so I need a way of generating unique references in my code, but I'm unsure of how I can do this within C#. I can't edit their class as it is provided as a DLL, I can only access this system by using it (it is part of their SDK).
Any ideas greatly appreciated!
Example code:
[Note: actual code is part of a Windows Form Application]
The last part client.IsUserInGroup is the problem.
using thirdParty.Client;
class TestProgram
{
static void Main(string[] args)
{
//Area and user objects defined within third party class
List<Area> Areas = new List<Area>();
List<User> myUsers = new List<User>();
int publicAreaID = 0;
bool isConnected=false;
client.Connect("user", "pass",
(connstatus) =>
{
switch (connstatus)
{
case ConnectionStatus.Success:
isConnected = true;
Console.WriteLine("Connected");
break;
case ConnectionStatus.InvalidCredentials:
Console.WriteLine("InvalidCredentials");
break;
case ConnectionStatus.Timeout:
Console.WriteLine("Timeout");
break;
}
});
if (isConnected)
{
client.GetAreas(
(result) =>
{
Areas = result;
});
//Get ID of public area
foreach (Area myArea in Areas)
{
if (myArea.Name.Equals("Public"))
{
publicAreaID = myArea.ID;
}
}
//Get all keyholders in Public area and store in list
client.GetUsersInArea(publicAreaID,
(result) =>
{
myUsers = result;
});
//Iterate over all users in list and verify they are in the everyone group
foreach (User myUser in myUsers)
{
User tempUser = myUser;
client.IsUserInGroup(tempUser.ID, 0,
(result) =>
{
if (result) //this is a bool
{
//This only returns one result..
Console.WriteLine(String.Format("{0} is in Everyone Group and Public Area", tempUser.Name));
}
});
}
client.Disconnect();
}
}
}
UPDATE
I've been doing more testing by removing the foreach loop and just calling client.IsUserInGroup twice to generate alternative callback references; the results are interesting. As expected there are unique references, but there is still only one result displayed, the only way to get both is to create two User objects rather than reuse one. As mentioned above, the "real" code is used in a Windows forms app, could this be something to do with object referencing? Example code below:
new User tempUser1 = myUsers[0];
client.IsUserInGroup(tempUser1.ID, 0,
(result) =>
{
if (result) //this is a bool
{
Console.WriteLine(String.Format("{0} is in Everyone Group and Public Area", tempUser1.Name));
}
});
new User tempUser2 = myUsers[1];
client.IsUserInGroup(tempUser2.ID, 0,
(result) =>
{
if (result) //this is a bool
{
Console.WriteLine(String.Format("{0} is in Everyone Group and Public Area", tempUser2.Name));
}
});
Answer moved from OP's original question:
Ok so I was playing with this a lot over the last few hours and kind of made it work by keeping the iterative loop but doing two things; firstly I assumed that the third party class would synchronize information requests and not allow my code to continue until it had a result returned - this seemingly is not the case as the output from recursion with an extra Console.WriteLine(iterationCount) in it shows the count increasing with no data returned; therefore I am forced to slow down the code by Thread.Sleep (I'm investigating better ways of doing this). Secondly any code within the lambda that could be moved outside, was. Instead a temp bool outside of the lambda was assigned the value of the result bool. The code looks like:
//Iterate over all users in list and verify they are in the everyone group
foreach (User myUser in myUsers)
{
User tempUser = myUser;
bool tempRes = false;
client.IsUserInGroup(tempUser.ID, 0,
(result) =>
{
tempRes = result;
});
if (tempRes)
{
Console.WriteLine(String.Format("{0} is in Everyone Group and Public Area", tempUser.Name));
}
System.Threading.Thread.Sleep(75); //Not a good way of enforcing sync!
}