FileSystemWatcher in an AspNetCore 2.1 BackgroundService\IHostedService - asp.net-core

I am trying to use an implementation of the BackgroundService in an AspNet Core 2.1 application. I create a FileSystemWatcher in ExecuteAsync and link the associated events,however, the fsw events are either never fired (unreachable? already disposed?) or its some thing I am doing wrong with this being async or the scope is messed up. I can't seem to figure it out. Following is the relevant code.
public class FSWImpl : BackgroundService
{
private readonly IHostingEnvironment _env;
private readonly ILogger<LiftAndShift> _logger;
private FileSystemWatcher _fsw;
public LiftAndShift(IHostingEnvironment env, ILogger<FSWImpl> logger)
{
_env = env;
_logger = logger;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Creating new FSW");
var path = Path.Combine(_env.ContentRootPath, "WebData");
_fsw = new FileSystemWatcher(path,"*.json");
_fsw.Created += _fsw_Created;
_fsw.Changed += _fsw_Changed;
_fsw.Renamed += _fsw_Renamed;
_fsw.Error += _fsw_Error;
return Task.CompletedTask;
}
private void _fsw_Error(object sender, ErrorEventArgs e) => _logger.LogInformation("File error");
private void _fsw_Renamed(object sender, RenamedEventArgs e) => _logger.LogInformation("File Renamed");
private void _fsw_Changed(object sender, FileSystemEventArgs e) => _logger.LogInformation("File changed");
private void _fsw_Created(object sender, FileSystemEventArgs e) => _logger.LogInformation("File created");
}
I register this service in startup as services.AddHostedService<FSWImpl>();

For enabling FileSystemWatcher, you need to set EnableRaisingEvents as True.
Demo Code:
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Creating new FSW");
var path = Path.Combine(_env.ContentRootPath, "WebData");
_fsw = new FileSystemWatcher(path, "*.json");
_fsw.Created += _fsw_Created;
_fsw.Changed += _fsw_Changed;
_fsw.Renamed += _fsw_Renamed;
_fsw.Error += _fsw_Error;
_fsw.EnableRaisingEvents = true;
return Task.CompletedTask;
}
FileSystemWatcher.cs
//
// Summary:
// Gets or sets a value indicating whether the component is enabled.
//
// Returns:
// true if the component is enabled; otherwise, false. The default is false. If
// you are using the component on a designer in Visual Studio 2005, the default
// is true.
//
// Exceptions:
// T:System.ObjectDisposedException:
// The System.IO.FileSystemWatcher object has been disposed.
//
// T:System.PlatformNotSupportedException:
// The current operating system is not Microsoft Windows NT or later.
//
// T:System.IO.FileNotFoundException:
// The directory specified in System.IO.FileSystemWatcher.Path could not be found.
//
// T:System.ArgumentException:
// System.IO.FileSystemWatcher.Path has not been set or is invalid.
public bool EnableRaisingEvents { get; set; }

Related

ASP.net Core - Handle Client Info Server-Side After Signalr Disconnect

I'm basically trying to do some database work if the user has been disconnected from signalR for more than 10 minutes. I put a timer in the Hub's OnDisconnectedAsync(). The issue I'm running into is ObjectDisposedException on things that I need to access the database. I get the same problems when I bypass the timer and try to make the calls directly in OnDisconnectedAsync(). I guess everything has been destroyed by the time I make it into OnDisconnectedAsync().
I've been trying everything I can find for days, but nothing seems to work. What is the correct way to handle this kind of problem?
public class RangeHub : Hub
{
private readonly IHubContext<RangeHub> _hubContext;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IDashboardService _service;
private readonly ApplicationDbContext _context;
private readonly ISubscriptionService _subscription;
private readonly HOptions _hOptions;
//private Timer disconnectTimer = new Timer();
private CustomTimer disconnectTimer;
private string UserId
{
get
{
//return User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier).Value;
return Context.UserIdentifier;
}
}
public RangeHub(IHubContext<RangeHub> hubContext, UserManager<ApplicationUser> userManager, IDashboardService service, ApplicationDbContext context,
ISubscriptionService subscription, IOptionsMonitor<HOptions> hOptions)
{
_hubContext = hubContext;
_context = context;
_userManager = userManager;
_service = service;
_subscription = subscription;
_hOptions = hOptions.CurrentValue;
disconnectTimer = new CustomTimer
{
Interval = 30000,
AutoReset = false
};
disconnectTimer.Elapsed += DisconnectTimedEvent;
//disconnectTimer.Elapsed += (sender, e) => DisconnectTimedEvent(sender, e, UserId);
//disconnectTimer.Interval = 30000;//120000;
//disconnectTimer.AutoReset = false;
}
public override Task OnConnectedAsync()
{
Log.Information("OnConnectedAsync: CONNECT TO CLIENT");
System.Diagnostics.Debug.WriteLine("OnConnectedAsync: CONNECT TO CLIENT");
//If timer was running, stop timer/reset
if(disconnectTimer.Enabled)
{
//reset timer
disconnectTimer.Stop();
}
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
//TODO: This is firing off when the dashboard is left, need to fix
Log.Information("OnDisconnectedAsync: DISCONNECTED FROM CLIENT");
System.Diagnostics.Debug.WriteLine("OnDisconnectedAsync: DISCONNECTED FROM CLIENT");
_service.StopActiveEnvironment(UserId);
//_hubContext.Clients.
//TODO: place logic here to stop environment and stop user's time
disconnectTimer.userId = UserId;
disconnectTimer.dashService = _service;
disconnectTimer.dBContext = _context;
//disconnectTimer.Start();
//*is this for a single user? will this effect all users at once?
return base.OnDisconnectedAsync(exception);
}
public void DisconnectTimedEvent(object sender, ElapsedEventArgs e)//, IDashboardService dashService)
{
//Shut down the range
Log.Information("DEBUG Timer");
Log.Information("UserId: " + ((CustomTimer)sender).userId);
Log.Information("Shutting down environment due to signalR disconnection timer.");
//Van - 9-28-2019: Not using await here. This should be the last thing done before the instance of this class is destroyed.
// Using await takes too long and the Hub object is already destroyed before its finished.
//_service.StopActiveEnvironment(((CustomTimer)sender).userId);
//Task.Run(async () => await _service.StopActiveEnvironment("051735c4-fa6d-4d90-9f76-b540aaa110bc"));
//Task.Run(async () => await _service.StopActiveEnvironment("051735c4-fa6d-4d90-9f76-b540aaa110bc")).WaitAndUnwrapException();
try
{
var func = ((CustomTimer)sender).dashService.StopActiveEnvironment(((CustomTimer)sender).userId);
func.WaitAndUnwrapException();
}
catch (Exception ex)
{
Log.Error(ex.ToString());
}
}
}
class CustomTimer : Timer
{
public string userId;
public IDashboardService dashService;
public ApplicationDbContext dBContext;
}

Revit Synchronization event

Starting with this...
https://github.com/jeremytammik/RevitLookup/blob/master/CS/EventTrack/Events/ApplicationEvents.cs
I'm trying to add an event listener for a synchronization event. the code below throws an error stating that the m_app is null. Can i not subscribe to this event while Revit is starting up?
I was able to do this before with application.ViewActivated += ..... Im wondering if this has something to do with DB vs UI driven events and when they are allowed to be subscribed to? I just don't know.
namespace RevitModelHealth
{
public class checkHealth : IExternalApplication
{
// Document doc;
static public Autodesk.Revit.ApplicationServices.Application m_app = null;
public Result OnShutdown(UIControlledApplication application)
{
return Result.Succeeded;
}
public Result OnStartup(UIControlledApplication application)
{
m_app.DocumentSynchronizingWithCentral += new EventHandler<DocumentSynchronizingWithCentralEventArgs>(m_app_DocumentSavingToCentral);
return Result.Succeeded;
}
void m_app_DocumentSavingToCentral(object sender, Autodesk.Revit.DB.Events.DocumentSynchronizingWithCentralEventArgs e)
{
MessageBox.Show("asd","asd");
}
}
}
Here is updated code reflecting my response to the first answer. The message box opens when the document is loaded. No errors are thrown when I try to initialize the synchronization event handlers, however, neither of the message boxes open before or after a synchronization event.
public class checkHealth : IExternalApplication
{
// Document doc;
static public Autodesk.Revit.ApplicationServices.Application m_app;
public Result OnShutdown(UIControlledApplication application)
{
return Result.Succeeded;
}
public Result OnStartup(UIControlledApplication application)
{
application.ControlledApplication.DocumentOpened += new EventHandler<DocumentOpenedEventArgs>(app_DocOpened);
return Result.Succeeded;
}
public void app_DocOpened(object sender, DocumentOpenedEventArgs args)
{
MessageBox.Show("asd","asd");
m_app.DocumentSynchronizingWithCentral += new EventHandler<DocumentSynchronizingWithCentralEventArgs>(m_app_DocumentSavingToCentral);
m_app.DocumentSynchronizedWithCentral += new EventHandler<Autodesk.Revit.DB.Events.DocumentSynchronizedWithCentralEventArgs>(m_app_DocumentSavedToCentral);
}
void m_app_DocumentSavingToCentral(object sender, Autodesk.Revit.DB.Events.DocumentSynchronizingWithCentralEventArgs e)
{
MessageBox.Show("sync", "sync");
}
void m_app_DocumentSavedToCentral(object sender, Autodesk.Revit.DB.Events.DocumentSynchronizedWithCentralEventArgs e)
{
MessageBox.Show("Doone", "Done");
}
}
this worked.... Thanks largely in part to the SDK sample project EventsMonitor
namespace RevitModelHealth
{
public class checkHealth : IExternalApplication
{
public Result OnShutdown(UIControlledApplication application)
{
return Result.Succeeded;
}
public Result OnStartup(UIControlledApplication application)
{
application.ControlledApplication.DocumentSynchronizingWithCentral += new EventHandler<DocumentSynchronizingWithCentralEventArgs>(app_syncStart);
application.ControlledApplication.DocumentSynchronizedWithCentral += new EventHandler<DocumentSynchronizedWithCentralEventArgs>(app_syncOver);
return Result.Succeeded;
}
public void app_syncStart(object o ,DocumentSynchronizingWithCentralEventArgs args)
{
MessageBox.Show("","Stasrting");
}
public void app_syncOver(object o,DocumentSynchronizedWithCentralEventArgs args)
{
MessageBox.Show("", "Over");
}
}
}
Try
application.ControlledApplication.DocumentSynchronizingWithCentral += new EventHandler<DocumentSynchronizingWithCentralEventArgs>(m_app_DocumentSavingToCentral)
in your OnStartup() method.
The call is failing because instance member m_app is initialized to null.
The UIApplication.ControlledApplication object that raises the DocumentSynchronizingWithCentralEventArgs is being accessible from the parameter to OnStartup.
You can try this:
public void app_DocOpened(object sender, DocumentOpenedEventArgs args)
{
MessageBox.Show("asd","asd");
Autodesk.Revit.ApplicationServices.Application m_app = args.Document.Application;
m_app.DocumentSynchronizingWithCentral += new EventHandler<DocumentSynchronizingWithCentralEventArgs>(m_app_DocumentSavingToCentral);
m_app.DocumentSynchronizedWithCentral += new EventHandler<Autodesk.Revit.DB.Events.DocumentSynchronizedWithCentralEventArgs>(m_app_DocumentSavedToCentral);
}

AsyncAutoResetEvent not working in WCF async method

I created a WCF service using Visual Studio 2017 Community version (employing TAP). I used the AsyncAutoResetEvent from the Microsoft.VisualStudio.Threading reference but it seems that this waithandle is not getting signalled after calling the Set function. The service is hosted in a console application. The traces generated by the NonBlockingConsole.WriteLine display properly however.
server:
AsyncAutoResetEvent aare = new AsyncAutoResetEvent(false);
public async Task<string> TestfuncAsync()
{
string strRet = "finished";
NonBlockingConsole.WriteLine("before autoresetevent");
await aare.WaitAsync();
NonBlockingConsole.WriteLine("after autoresetevent"); //is not traced even if asyncautoresetevent is set
return strRet;
}
void SetEvent()
{
aare.Set();
NonBlockingConsole.WriteLine("auto reset event set");
}
client UI:
private async void button1_Click(object sender, EventArgs e)
{
string value = await client.TestfuncAsync();
...
}
private void button2_Click(object sender, EventArgs e)
{
client.SetEvent();
}
NonBlockingConsole class: (reused from Does Console.WriteLine block?)
public static class NonBlockingConsole
{
private static BlockingCollection<string> m_Queue = new BlockingCollection<string>();
static NonBlockingConsole()
{
var thread = new Thread(
() =>
{
while (true) Console.WriteLine(m_Queue.Take());
}
);
thread.IsBackground = true;
thread.Start();
}
public static void WriteLine(string value)
{
value = DateTime.Now.ToString("<HH:mm:ss.fff>") + " " + value + " <ThreadID>: " + Thread.CurrentThread.ManagedThreadId.ToString();
m_Queue.Add(value);
}
}

Migrating to core, but stuck with old "Module" (HttpApplication)

I've done some parts for the Middleware in startup etc..
So this is my class
public class AcceptQueryMiddleware
{
private const string Realm = "Basic realm=My Sales System";
private readonly RequestDelegate _next;
private static readonly IDictionary<Regex, string> FormatAcceptMap = new Dictionary<Regex, string>();
public AcceptQueryMiddleware(RequestDelegate next)
{
_next = next;
}
public void AcceptQueryHttpModule()
{
RegisterFormatAcceptQuery("csv", "text/csv");
RegisterFormatAcceptQuery("excel", "application/vnd.ms-excel");
RegisterFormatAcceptQuery("nav", "application/navision");
}
private static void RegisterFormatAcceptQuery(string format, string acceptHeader)
{
var regex = new Regex(#"([?]|[&])format=" + format);
FormatAcceptMap.Add(regex, acceptHeader);
}
private static void OnApplicationBeginRequest(object sender, EventArgs eventArgs)
{
var app = sender as HttpApplication;
if (app == null) { return; }
var url = app.Request.RawUrl;
foreach (var format in FormatAcceptMap)
{
if (format.Key.Match(url).Success)
{
app.Request.Headers["Accept"] = format.Value;
break;
}
}
}
public void Dispose()
{
}
}
How do I convert it to Core? Way around it?
Specifically the HttpApplication that is not supported in .NET Core..
Or do you have any links or tips I can follow?
You want to migrate your HTTP module to a middleware. See the documentation to learn how.
In your case, you're using the HttpApplication to access request-related data. You're getting the raw URL and adding some headers. That's something you can easily do in a middleware. Here's an example:
public class AcceptQueryMiddleware
{
private readonly RequestDelegate _next;
public AcceptQueryMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
// Do your "before" stuff here (use the HttpContext)
await _next.Invoke(context);
// Do your "after" stuff here
}
}
and call app.UseMiddleware<AcceptQueryMiddleware>(); in your Startup class (in the Configure method).

Retrying Event For SqlAzureExecutionStrategy

When working with SQL Azure, if I create my own RetryPolicy, e.g.:
var retryStrategy = new FixedInterval(3, TimeSpan.FromSeconds(10));
var retryPolicySQL = new RetryPolicy<SqlAzureTransientErrorDetectionStrategy>(retryStrategy);
retryPolicySQL.Retrying += ....;
I am able to get notified when a retry is happening which is useful to log.
However, if I use what seems to be the new recommended strategy with EF6 and Azure - i.e. a custom DbConfiguration class something like this:
public class MyConfiguration : DbConfiguration
{
public MyConfiguration()
{
this.SetExecutionStrategy("System.Data.SqlClient", () =>
{
var strat = new SqlAzureExecutionStrategy();
// strat. No events
return strat;
});
}
}
I can't seem to find a way to hook into the retrying process. Is there a way to do this?
Implement the protected method called ShouldRetryOn by subclassing from SqlAzureExecutionStrategy. In that method you could put your logic to log or hook it into a handler as I show below.
public delegate void ChangedEventHandler(object sender, EventArgs e);
public class MyStrategy : SqlAzureExecutionStrategy
{
public event ChangedEventHandler Changed;
protected override bool ShouldRetryOn(Exception exception)
{
OnChanged(EventArgs.Empty);
return base.ShouldRetryOn(exception);
}
protected virtual void OnChanged(EventArgs e)
{
if (Changed != null)
Changed(this, e);
}
}
If you would perfer to just log the exception or the retry, you can do as follows:
public class LoggedSqlAzureExecutionStrategy : SqlAzureExecutionStrategy
{
protected override bool ShouldRetryOn(Exception exception)
{
var shouldRetry = base.ShouldRetryOn(exception);
if (shouldRetry)
{
// log retry
}
return shouldRetry;
}
}