Have a WPF client that is using the latest CefSharp package to host web applications. Since we have multiple web apps we have multiple Views each with its own instance of a browser/BrowserSubProcess.
Say, for lack of a better example, I simply go into task manager and Kill one of the SubProcess.exe's. Is there an event we can tap into or otherwise be notified?
One thought would be to hook into the process by querying via some kind of pinvoke but that is a can of worms I would rather not open.
Thanks to #amaitland for pointing me in the right direction. Its a bit of a needle in a haystack but it is there.
For anyone interested, you have to implement IRequestHandler that is referenced in his comment above. You can either
do it from scratch,
use their fully implemented example at Example RequestHandler,
or do something in between using DefaultRequestHandler (DefaultRequestHandler Override Example).
So if we use DefaultRequestHandler we can do something like this for just the terminated event:
/// <summary>
/// Handle events related to browser requests.
/// </summary>
public class RequestHandler : DefaultRequestHandler
{
/// <summary>
/// Called when the render process terminates unexpectedly.
/// </summary>
/// <param name="browserControl">The ChromiumWebBrowser control</param>
/// <param name="browser">the browser object</param>
/// <param name="status">indicates how the process terminated.</param>
/// <remarks>
/// Remember that <see cref="browserControl"/> is likely on a different thread so care should be used
/// when accessing its properties.
/// </remarks>
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status)
{
switch (status)
{
case CefTerminationStatus.AbnormalTermination:
Log.Error("Browser terminated abnormally.");
break;
case CefTerminationStatus.ProcessWasKilled:
Log.Error("Browser was killed.");
break;
case CefTerminationStatus.ProcessCrashed:
Log.Error("Browser crashed while.");
break;
default:
Log.Error($"Browser terminated with unhandled status '{status}' while at address.");
break;
}
RenderProcessTerminated?.Invoke(browserControl, status);
}
/// <summary>
/// Fires when the render process terminates unexpectedly.
/// </summary>
public event EventHandler<CefTerminationStatus> RenderProcessTerminated;
}
If we have a browser object declared in the View like say:
<!--Bound to the ViewModel.Address property-->
<cef:ChromiumWebBrowser
x:Name="Browser"
Address="{Binding Address}">
</cef:ChromiumWebBrowser>
Then just wire in a new instance:
private readonly Dispatcher _mainDispatcher;
private readonly RequestHandler _requestHandler = new RequestHandler();
public MainWindow()
{
InitializeComponent();
_mainDispatcher = Dispatcher.CurrentDispatcher;
_requestHandler.RenderProcessTerminated += OnBrowserRenderProcessTerminated;
Browser.RequestHandler = _requestHandler;
}
private void OnBrowserRenderProcessTerminated(object sender, CefTerminationStatus e)
{
//Likely coming from a background thread
_mainDispatcher.InvokeAsync(() =>
Log.Error($"Browser crashed while at address: {Browser.Address}")
);
}
Related
I'll explain my use case, but I think it can be generalized as it is mainly a code design question.
So I use serilog for logging in my application. What I want is to check whenever the application actually log something that, if the level of the log is above a maximum one, it force exiting the application (in my case any Error or Fatal logging trigger this) :
public static void CheckForceExiting(LogEventLevel level, LogEventLevel maxLevel = MAX_LEVEL_BEFORE_EXIT)
{
//level is at most the maximum one, so keep the application running.
if (level <= maxLevel)
return;
//level is above maximum one, so Exit the App.
//Disable UI interaction
(Application.Current.MainWindow.DataContext as Main_VM).IsEnabled = false;
string msg = "An exception was caught !", title = "/!\\ERROR/!\\ ";
if (level > LogEventLevel.Error)
{
msg = "Unhandled exception occured !";
title = "/!\\FATAL ERROR/!\\ ";
}
msg += " Closing SDC now.\n\nYou can find the log in ./Logs folder if needed.";
title += HelperUtils.AssemblyName.Name + ", Ver: " + HelperUtils.AssemblyName.Version.ToString();
MessageBox.Show(msg, title, MessageBoxButton.OK, MessageBoxImage.Error);
OnCloseLogging();
Environment.Exit(1);
}
My first, working, attempt was to code a full wrapper of serilog.Log own static class, that, for each logging method on this class, add a call to my above method.
But it doesn't satisfy me to just somewhat copy/paste serilog.Log class to just add in any of their logging method a call to CheckForceExiting().
So, instead, I preferred to use my own custom serilog Sink class, that will just call CheckForceExiting() as its "logging" action :
/// <summary>
/// Custom <see cref="ILogEventSink"/> Sink class
/// that perform/call <see cref="LoggingUtils.CheckForceExiting(LogEventLevel, LogEventLevel)"/> check on each logging level
/// in order to force exiting the application if level is too high (default is Error or Fatal).
/// </summary>
class CheckLevelSink : ILogEventSink
{
/// <summary>
/// Default constructor
/// </summary>
public CheckLevelSink()
{
}
/// <summary>
/// Perform custom "logging".
/// Here we are just calling <see cref="LoggingUtils.CheckForceExiting(LogEventLevel, LogEventLevel)"/>
/// to force exiting the application if <paramref name="logEvent"/> level is above maximum allowed level
/// </summary>
/// <param name="logEvent">The logEvent to "log". Here onluy check its level to force exiting the application or not.</param>
public void Emit(LogEvent logEvent)
{
LoggingUtils.CheckForceExiting(logEvent.Level);
}
}
And add this sink to the "chained logging" in serilog configuration :
/// <summary>
/// Creating a generic host to allow the creation and use of a Serilog logger
/// </summary>
/// <returns>The generic host created</returns>
public static IHost CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
.UseSerilog((host, loggerConfig) =>
{
loggerConfig.WriteTo.File("Logs/log.txt", rollingInterval: RollingInterval.Day)
.Enrich.FromLogContext()
.MinimumLevel.Information();
loggerConfig.WriteTo.CheckLevel();
#if DEBUG
loggerConfig.WriteTo.Debug()
.MinimumLevel.Debug();
#endif
})
.ConfigureServices(services =>
{
})
.Build();
}
/// <summary>
/// Extension method pattern to allow an easy way to add the custom <see cref="CheckLevelSink"/> Sink in a <see cref="LoggerConfiguration"/>
/// </summary>
/// <param name="sinkConfiguration">The Sink configuration in which to "add" our custom <see cref="CheckLevelSink"/></param>
/// <param name="restrictedToMinimumLevel">The minimum <see cref="LogEventLevel"/> log level from which we allow logging in the <see cref="CheckLevelSink"/> Sink.</param>
/// <returns>The <see cref="LoggerConfiguration"/> configuration of the logger that have "added" the custom <see cref="CheckLevelSink"/> Sink.</returns>
public static LoggerConfiguration CheckLevel(
this LoggerSinkConfiguration sinkConfiguration,
LogEventLevel restrictedToMinimumLevel = LogEventLevel.Error)
{
return sinkConfiguration.Sink(new CheckLevelSink(), restrictedToMinimumLevel);
}
OK, I found it better, but still seems a little overkill no ?
So, in a more generalized way, whenever you call an external API method and you want to automatically add your custom logic on top (before and/or after) of the API own logic, what is the best practice ?
(Note: I envisaged to use inheritance to serilog File.Write method, but it is a sealed class. So I'm not sure inheritance is a generalized applicable solution).
Thanks.
I'm currently developing a Web API used by our mobile application. If an API-call is made that needs to send an email, the email is added to a queue in Azure Storage. For handling the queue (reading the queue mails and actually sending them) I thought the best solution would be creating a Hosted Service that will do this in the background.
For implementing this I followed the instructions from the following documentation: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1
I created a class that implements the abstract BackgroundService-class from .NET Core 2.1 for this. It looks like this:
namespace Api.BackgroundServices
{
/// <summary>
/// Mail queue service.
/// This handles the queued mails one by one.
/// </summary>
/// <seealso cref="Microsoft.Extensions.Hosting.BackgroundService" />
public class MailQueueService : BackgroundService
{
private readonly IServiceScopeFactory serviceScopeFactory;
/// <summary>
/// Initializes a new instance of the <see cref="MailQueueService"/> class.
/// </summary>
/// <param name="serviceScopeFactory">The service scope factory.</param>
public MailQueueService(IServiceScopeFactory serviceScopeFactory)
{
this.serviceScopeFactory = serviceScopeFactory;
}
/// <summary>
/// This method is called when the <see cref="T:Microsoft.Extensions.Hosting.IHostedService" /> starts. The implementation should return a task that represents
/// the lifetime of the long running operation(s) being performed.
/// </summary>
/// <param name="stoppingToken">Triggered when <see cref="M:Microsoft.Extensions.Hosting.IHostedService.StopAsync(System.Threading.CancellationToken)" /> is called.</param>
/// <returns>A <see cref="T:System.Threading.Tasks.Task" /> that represents the long running operations.</returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await HandleMailQueueAsync();
//await Task.Delay(3000, stoppingToken);
}
}
private async Task HandleMailQueueAsync()
{
using (IServiceScope serviceScope = serviceScopeFactory.CreateScope())
{
TelemetryClient telemetryClient = serviceScope.ServiceProvider.GetService<TelemetryClient>();
try
{
IMailHandler mailHandler = serviceScope.ServiceProvider.GetService<IMailHandler>();
await mailHandler.HandleMailQueueAsync();
}
catch (Exception exception)
{
telemetryClient.TrackException(exception);
}
}
}
}
}
After registering it by calling
services.AddHostedService<MailQueueService>();
in the Startup.cs, it will successfully handle the mail queue, but all other calls to the WebAPI take almost ten times as long. Only if I comment out the Task.Delay() part in my implementation of the BackgroundService, the performance goes back to an acceptable level.
However this seems more like a workaround than a real solution for my problem. Am I doing something else wrong that makes the performance tank like this?
I have the following class:
/// <summary>
/// Represents an implementation of the <see cref="IAspNetCoreLoggingConfigurationBuilder"/> to configure the ASP.NET Core Logging.
/// </summary>
public class AspNetCoreLoggingConfigurationBuilder : IAspNetCoreLoggingConfigurationBuilder
{
#region Properties
/// <summary>
/// Gets the <see cref="ILogSource"/> that's used to write log entries.
/// </summary>
public ILogSource LogSource{ get; private set; }
#endregion
#region IAspNetCoreLoggingConfigurationBuilder Members
/// <summary>
/// Sets the log source that should be used to save log entries.
/// </summary>
/// <param name="logSource">The source </param>
public void SetLogSource(ILogSource logSource)
{
LogSource = logSource;
}
#endregion
}
I also have a method in which I create an instance of this class:
/// <summary>
/// Adds logging to the <see cref="IApplicationBuilder"/> request execution pipeline.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/> to configure the application's request pipeline.</param>
/// <param name="configuration">Builder used to configure the ASP.NET Core Logging.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IApplicationBuilder UseAspNetCoreLogging(this IApplicationBuilder app, Action<IAspNetCoreLoggingConfigurationBuilder> configuration)
{
var aspNetLoggerConfiguration = new AspNetCoreLoggingConfigurationBuilder();
configuration(aspNetLoggerConfiguration);
// Add the registered ILogSource into the registered services.
_services.AddInstance(typeof (ILogSource), aspNetLoggerConfiguration.LogSource);
// The entire configuration for the middleware has been done, so return the middleware.
return app.UseMiddleware<AspNetCoreLoggingMiddleware>();
}
Notice the first line here, I'm creating an instance of the class.
However, when I inspect this variable in a watch, when my cursor is on line configuration(aspNetLoggerConfiguration); I do get that the variable does not exists in the current context.
Creating an instance of the variable does work when doing it directly in the watch window.
Anyone has a clue?
P.S. It's a DNX project which I'm testing in xUnit. The code is running in 'Debug' mode.
Thats no runtime and no compiling-error.
It's a problem of Visual Studio not beeing able to show the object in a debug-window as it is a runtime-object (something like that).
Another occurence of this problem is in a wcf-service client. Create a new serviceclient Client and try to show client.InnerChannel in the watch window. It won't work. You can however create a temp-object (bool, string, etc..) and write the desired value into it to see your value.
#if DEBUG
var tmpLog = aspNetLoggerConfiguration.LogSource;
#endif
You should see the LogSource in the tmpLog when your mouse is over it.
In my company there is a console app running on a windows environment serve/pc. My problem is when this Server shuts down or restarted by other people this app will be closed and have to restart the app manually for this I have to issue commands on it to start running.
and another problem is I would not know if the server state just restarted or shuts down.
I have this idea that i will build an app that would send me a sms message to my phone and alert me that this server is down or just restarted in .net vb/c#. honestly, I don't know where to start I tried to search it on the internet but found nothing. If you can help me where to start I'll appreciate it much and i will post here the development stage of this app.
thanks.
Sorry for the delay on an answer. Anyway, I have found out that there is no way to differentiate between a system shut down and a system restart. But in any case, I think your best approach is to the use the SystemEvents.SessionEnding and/or SystemEvents.SessionEnded events to capture the system/server's shutdown. The easiest way to do this would be to use a Windows Service and register these events, like so:
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
/* Choose one or both of these to register for */
SystemEvents.SessionEnding += OnSessionEnding; // Register with session ending event
SystemEvents.SessionEnded += OnSessionEnded; // Register with session ended event
}
protected override void OnStop()
{
/* Static events, so MUST deregister from them */
SystemEvents.SessionEnding -= OnSessionEnding;
SystemEvents.SessionEnded -= OnSessionEnded;
}
protected static void OnSessionEnding(Object sender, SessionEndingEventArgs e)
{
/* I suggest using SchwabenCode.EasySmtp as it is very easy to use and implements the IDisposable interface. If that is not an option, than simply use SmtpClient class */
if (e.Reason == SessionEndReasons.SystemShutdown)
{
// Send SMS message to yourself notifying shutdown is occurring on server
}
}
protected static void OnSessionEnded(Object sender, SessionEndedEventArgs e)
{
/* I suggest using SchwabenCode.EasySmtp as it is very easy to use and implements the IDisposable interface. If that is not an option, than simply use SmtpClient class */
if (e.Reason == SessionEndReasons.SystemShutdown)
{
// Send SMS message to yourself notifying shutdown is occurring on server
}
}
}
I hope that helps you get things started! Here is a enum and its extensions that I have used in the past for sending SMS messages:
/// <summary> Values that represent various carriers. </summary>
[Serializable]
public enum Carrier
{
None = 0,
Alltel = 1,
Att = 2,
BoostMobile = 3,
Sprint = 4,
Tmobile = 5,
UsCellular = 6,
Verizon = 7,
VirginMobile = 8
}
/// <summary> Carrier extensions. </summary>
public static class CarrierExtensions
{
/// <summary> Gets the email to SMS gateway for the specified carrier. </summary>
/// <param name="carrier"> The carrier to get the gateway for.</param>
/// <returns> The email to SMS gateway. </returns>
public static String GetGateway(this Carrier carrier)
{
switch (carrier)
{
case Carrier.Alltel:
return "#message.alltel.com";
case Carrier.Att:
return "#txt.att.net";
case Carrier.BoostMobile:
return "#myboostmobile.com";
case Carrier.Sprint:
return "#messaging.sprintpcs.com";
case Carrier.Tmobile:
return "#tmomail.net";
case Carrier.UsCellular:
return "#email.uscc.net";
case Carrier.Verizon:
return "#vtext.com";
case Carrier.VirginMobile:
return "#vmobl.com";
}
return String.Empty;
}
/// <summary> Formats the phone number with the appropriate email to SMS gateway. </summary>
/// <param name="carrier"> The carrier to get the gateway for.</param>
/// <param name="phoneNumber"> The phone number.</param>
/// <returns> The formatted phone number. </returns>
public static String FormatPhoneNumber(this Carrier carrier, String phoneNumber)
{
return String.Format("{0}{1}", phoneNumber, carrier.GetGateway());
}
}
Easiest would be to place the app in the startup folder:
for individual users: C:\Users[username]\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
for all users: C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup
But a better solution is to use the windows Task Scheduler and create a task to run the application on startup. Here is a link to an example using the scheduler.
I have a url: like this one: http://www.example/about/49.
I want it to be seen as http://www.example/about/, but I must have this parameters passed as QueryString parameters.
Is it possible ?
Be careful with session variables; it's easy to have multiple pages opened which are all using the same session and end up mixing the values.
It would be better to use TempData, which only allows the value to be used once (removed on first access). However, this implies the value will be used almost immediately.
You can also write a cookie with the desired value, intercept the request (ASP.Net provides a variety of ways of doing this, such as the BeginRequest event), and internally process the URL as though it contained the value.
Of course, you then must cleanup the cookie (which will have the same problem as a Session-based solution). Remember that a cookie is more vulnerable to tampering on the client.
Personally, I think any of these approaches are far more trouble than they are worth. "Hackable URLs" (such as those which contain a potentially meaningful ID) are usually a good thing.
My workaround for this (Which works REALLY well, thanks to the help of the SO Community)
Create a class called SiteSession.cs
Input the following code:
using System;
using System.Collections.Generic;
using System.Web;
/// <summary>
/// Summary description for SiteSession
/// </summary>
public class SiteSession
{
/// <summary>
/// The _site session
/// </summary>
private const string _siteSession = "__SiteSession__";
/// <summary>
/// Prevents a default instance of the <see cref="SiteSession" /> class from being created.
/// </summary>
private SiteSession()
{
}
/// <summary>
/// Gets the current Session
/// </summary>
/// <value>The current.</value>
public static SiteSession Current
{
get
{
SiteSession session = new SiteSession();
try
{
session = HttpContext.Current.Session[_siteSession] as SiteSession;
}
catch(NullReferenceException asp)
{
}
if (session == null)
{
session = new SiteSession();
HttpContext.Current.Session[_siteSession] = session;
}
return session;
}
}
//Session properties
public int PageNumber {get;set;}
}
You can put anything in the Session Properties, just make sure its public.
Then, set it by:
SiteSession.Current.PageNumber = 42
And call it with
int whatever = SiteSession.Current.PageNumber