Hosting an ASP Web Api inside a WPF app wont stop gracefully - c#

I have an Asp Core 3.1 app hosting a rest API. It can run standalone, but I have a WPF app that I want to be able to also host the rest API while it is running.
I have gotten this working, but my problem is that when I close my last WPF window, I tell ASP's IHost to shutdown and it leaves the process open. I have recreated the problem with brand new projects with just a couple modifications:
In the WPF project, I have removed the StartupURI and use the Startup and Exit events:
private void Application_Startup(object sender, StartupEventArgs e)
{
RestApiProgram.StartAsync(CancellationToken.None).GetAwaiter().GetResult();
MainWindow window = new MainWindow();
window.Show();
}
private void Application_Exit(object sender, ExitEventArgs e)
{
RestApiProgram.StopAsync(CancellationToken.None).GetAwaiter().GetResult();
}
In the Asp project, I have modified the Program class so I can call start and stop on the IHost:
private static IHost _host;
//Main is called when running the ASP project by itself
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
//StartAsync is called by the WPF app
public static Task StartAsync(CancellationToken token)
{
_host = CreateHostBuilder(Array.Empty<string>()).Build();
return _host.StartAsync(token);
}
public static async Task StopAsync(CancellationToken token)
{
using (_host)
{
await _host.StopAsync(token);
}
}
When I close the main window, my WPF app calls StopAsync, I see the message "Microsoft.Hosting.Lifetime: Information: Application is shutting down..." in the output, but the process does not shut down. When I pause, it is stuck waiting for the StopAsync to complete. Any ideas why?

You should await the Task returned by StopAsync.
To prevent the WPF app from shutting down before the task has completed, you could handle the OnClosing event of the window:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Closing += WhenClosing;
}
...
private async void WhenClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
await RestApiProgram.StopAsync(CancellationToken.None);
this.Closing -= WhenClosing;
Close();
}
}
The same goes for StartAsync:
private async void Application_Startup(object sender, StartupEventArgs e)
{
await RestApiProgram.StartAsync(CancellationToken.None);
MainWindow window = new MainWindow();
window.Show();
}
Calling .GetAwaiter().GetResult() is almost always a bad idea as it might cause a deadlock.

Related

Software freeze after Puppeteer launch

I have created a small application which should get some data from internet trought Puppeteer Sharp, the problem's that after I instantiate the browser the software freeze without no error.
public partial class MainWindow : Window
{
public Handler Handler { get; } = new Handler();
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Handler.Init(Handler).Wait();
}
}
as you can see I have Handler which contains all the properties of the software:
public class Handler
{
private static Url URL = new Url("https://www.diretta.it/");
public static Browser Browser { get; set; }
public async Task<bool> Init(Handler prop)
{
DotNetEnv.Env.Load("./config.env");
// The problem's here
Browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
ExecutablePath = Environment.GetEnvironmentVariable("CHROME_PATH"),
});
return true;
}
}
where CHROME_PATH is this: CHROME_PATH="C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"
what I did wrong? I have the latest version of Chrome and PuppeteerSharp too.
Change your Window_Loaded event method to async and do an await on the Init method, Event Handler methods are an exception. using async void instead of async Task is ok in this scenario. - Reference (Should I avoid 'async void' event handlers?):
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
await Handler.Init(Handler);
}

Animated Splashscreen using Caliburn.Micro

I'm creating an application using Caliburn.Micro. The application communicates with an api during startup which is why I need to show a Splashscreen to the users. I've created my own animated Splashscreen as a Window which is activated from bootstrapper in the OnStartup method.
The startup process is managed by the splashscreens viewmodel.
When all startup related processes are finished how do I tell the bootstrapper to close the splashscreen and activate another window?
I thought about raising an event but I cannot subscribe the bootstrapper to the IEventaggregator.
I tried displaying the Splashscreen inside of a contentcontrol in the ShellView and just switch to a different vm after the loading is done. The problem here is that the splash should be displayed on a transparent, borderless window which cannot be changed after the window is created.
public class Bootstrapper : BootstrapperBase
{
private SimpleContainer _container = new SimpleContainer();
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
_container
.Singleton<IWindowManager, WindowManager>()
.Singleton<IEventAggregator, EventAggregator>();
GetType().Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.Name.EndsWith("ViewModel"))
.ToList()
.ForEach(viewModelType => _container.RegisterPerRequest(
viewModelType, viewModelType.ToString(), viewModelType));
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(
XmlLanguage.GetLanguage(
CultureInfo.CurrentCulture.IetfLanguageTag
)
)
);
DisplayRootViewFor<AnimatedSplashViewModel>();
//DisplayRootViewFor<ShellViewModel>();
}
protected override object GetInstance(Type service, string key)
{
return _container.GetInstance(service, key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
}
public class AnimatedSplashViewModel : Screen
{
private IEventAggregator _events;
private string _splashMessage;
public string SplashMessage
{
get { return _splashMessage; }
set
{
_splashMessage = value;
NotifyOfPropertyChange(() => SplashMessage);
}
}
public AnimatedSplashViewModel(IEventAggregator events)
{
_events = events;
SplashMessage = "Please wait";
// Simulation of long tasks
var worker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_events.PublishOnUIThread(new SplashFinishedEvent());
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(10000);
}
}
You should either use a ShellViewModel for the root view and replace the splash screen view with a "main" view, or you could just wait to display the root view until the splash screen has been closed:
protected override void OnStartup(object sender, StartupEventArgs e)
{
Application.ShutdownMode = ShutdownMode.OnExplicitShutdown;
var windowManager = IoC.Get<IWindowManager>();
var eventAggregator = IoC.Get<IEventAggregator>();
windowManager.ShowDialog(new AnimatedSplashViewModel(eventAggregator));
DisplayRootViewFor(typeof(ShellViewModel));
}
...
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
TryClose();
}
I case anyone wondering about the final solution:
First I used the WindowManager to create a Dialog of the Splashscreen and let the SplashscreenViewModel do all the work.
Turned out this approach takes ages to load. So when I tried to execute it took around 8 seconds for the Dailog to show up. This is far too long for my impatient users.
I think this was because I used IoC to inject alot of dependecies into the SplashscreenViewModel.
windowManager.ShowDialog(new AnimatedSplashViewModel(locationEndpoint, userEndpoint, applicationEndpoint, adUser, clientInfo, locationInfo, loggedInUser));
Second approach was to create the Splashscreen as a dialog and use a BackgroundWorker for all the computing and api stuff inside the Bootstrapper.
While this worked quite fast I felt that there must be a better approach.
Third and final solution:
The Bootstrapper calls the ShellViewModel.
public Bootstrapper()
{
Initialize();
DisplayRootViewFor<ShellViewModel>();
}
In the OnInitialize method I've created a BackgroundWorker which executes all the long running tasks while displaying the SplashScreen as a Dialog using the WindowManager.
protected override void OnInitialize()
{
var windowManager = new WindowManager();
using (BackgroundWorker bw = new BackgroundWorker())
{
bw.DoWork += InitializeApplication;
bw.RunWorkerCompleted += InitializationCompleted;
bw.RunWorkerAsync();
windowManager.ShowDialog(new AnimatedSplashViewModel(_events));
}
}
The AnimatedSplashscreenViewModel now only requires one dependency which is the EventAggregator. I let it handle a custom Event named SplashMessageChangedEvent.
public class SplashMessageChangedEvent
{
public string Content { get; set; }
public bool CloseDialog { get; set; } = false;
public SplashMessageChangedEvent(string content)
{
Content = content;
}
public SplashMessageChangedEvent(bool closeDialog)
{
CloseDialog = closeDialog;
}
}
In the InitializationCompleted Event in the ShellViewModel I publish the following event to close the Dialog:
private void InitializationCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_events.PublishOnUIThread(new SplashMessageChangedEvent(true));
}
Now this final approach is much faster than the other two.
The Splashscreen is shown instantly after starting the executable.

How to close or dipose a self host service?

I have a WPF application that host a service, the code behind is this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
_host = new ServiceHost(typeof(GestorAplicacionesService));
_host.Open();
}
private ServiceHost _host;
}
I have read that it is good practice to close the service, but I don't know how to do it in this case? Because I have the main window, that if I close the application, I could close the service in the closing event. How ever, if there are some exception that could break the application that doesn't fire the closing event, then the service wouldn't be close.
So I was wondering how it would be the best way to close the service when it is hosted in a wpf application.
Thanks.
Handle the Closing event and close it there. You may also want to implement the IDisposable interface to cope with best practises for disposable fields:
public sealed partial class MainWindow : Window, IDisposable
{
private readonly ServiceHost _host = new ServiceHost(typeof(GestorAplicacionesService));
public MainWindow()
{
InitializeComponent();
_host.Open();
Closing += MainWindow_Closing;
}
private void MainWindow_Closing(object sender, CancelEventArgs e)
{
Dispose();
}
public void Dispose()
{
_host.Close();
_host.Dispose();
}
}
This is the best you can do. If the entire process gets shut down unexpectedly, there is not much you can do about it in your WPF application. The memory will still be returned to the operating system.
you can check that all in app.xaml
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
this.DispatcherUnhandledException += App_DispatcherUnhandledException;
base.OnStartup(e);
}
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
((MainWindow)Application.Current.MainWindow).host.Close();
}
protected override void OnExit(ExitEventArgs e)
{
if (((MainWindow)Application.Current.MainWindow).host.State == System.ServiceModel.CommunicationState.Opened)
((MainWindow)Application.Current.MainWindow).host.Close();
base.OnExit(e);
}
and for threading issues please follow this link : https://soumya.wordpress.com/2010/05/26/wcf-simplified-part-7-hosting-a-wcf-service-using-wpf/

GUI and server application

I have a question regarding WPF and server application. I was given a task to write a simple TCP server with a GUI. I'm new to C# (and GUIs in general), hence I have a question.
I have 2 classes:
App.xaml.cs
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
MainWindow window = new MainWindow();
if (e.Args.Length != 1)
{
MessageBox.Show("Wrong number of arguments!", "An error has occured", MessageBoxButton.OK, MessageBoxImage.Error);
Environment.Exit(1);
}
window.Show();
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ShowConnectionsButton_Click(object sender, RoutedEventArgs e)
{
LogsTextBox.Text += "text\n";
}
}
that are both initially generated by Visual Studio. I assume that MainWindow.xaml is for handling GUI-related stuff and App.xaml is for application's logic. So, my (simple) question is, how should I start the server part? Should it be
server = new Server();
server.start();
window.Show();
or maybe
window.Show();
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
server = new Server();
server.start();
}).Start();
or maybe use BackgroundWorker?
Where you put the server logic will depend on how you want your UI to behave.
Usually, you will want the window to load separately with loading/retrieving data.
You could put your server call in the Loaded event. For example:
public void OnLoad(object sender, RoutedEventArgs e)
{
server = new Server();
server.start();
...
}
This will be called when the window has loaded, and can be started. How you update data bindings will depend on how your server object is built.

Form created in Service not showing

I created a Service in C#. I needed a GUI for configuration of the Service so I added a WinForms project to my solution. My plans was to create the Form in the Service and show it in the OnStart() Method of the Service. However, it won't show. The WriteEntry() Methods of the EventLog are all firing, so my code definitely is processed. Anybody know what I'm doing wrong here?
public partial class UrlWatcherService : ServiceBase
{
private UrlWatcherForm _urlwatcherform;
private EventLog _eventLog;
private string _eventLogName = "UrlWatcherEventLog";
private string _eventLogSource = "UrlWatcherSource";
public UrlWatcherService()
{
InitializeComponent();
LoadVariables();
}
public void OnDebug()
{
OnStart(null);
}
private void LoadVariables()
{
_urlwatcherform = new UrlWatcherForm();
_eventLog = new EventLog();
CanPauseAndContinue = true;
if (!EventLog.SourceExists(_eventLogSource))
EventLog.CreateEventSource(_eventLogSource, _eventLogName);
_eventLog.Source = _eventLogSource;
_eventLog.Log = _eventLogName;
_eventLog.WriteEntry("Url Watcher Log Created", EventLogEntryType.Information);
}
protected override void OnStart(string[] args)
{
_eventLog.WriteEntry("Url Watcher Service Started", EventLogEntryType.Information);
_urlwatcherform.Show();
_eventLog.WriteEntry("Url Watcher Form Created", EventLogEntryType.Information);
}
protected override void OnPause()
{
base.OnPause();
_eventLog.WriteEntry("Url Watcher Service Paused", EventLogEntryType.Information);
}
protected override void OnContinue()
{
base.OnContinue();
_eventLog.WriteEntry("Url Watcher Log Continued", EventLogEntryType.Information);
}
protected override void OnStop()
{
_eventLog.WriteEntry("Url Watcher Service Stopped", EventLogEntryType.Information);
}
}
public partial class UrlWatcherForm : Form
{
public UrlWatcherForm()
{
InitializeComponent();
}
private void btnAdd_Click(object sender, EventArgs e)
{
}
private void UrlWatcherGui_Resize(object sender, EventArgs e)
{
if (FormWindowState.Minimized == WindowState)
Hide();
}
private void UrlWatcherGui_FormClosing(object sender, FormClosingEventArgs e)
{
Hide();
e.Cancel = true;
}
private void urlWatcherNofiyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
{
Show();
}
}
EDIT: To clarify, if I debug it like below, the Form shows. I can put the thread to sleep but that won't let me interact with the Form anymore. But the Form definitely shows, it's just in an unresponsive state.
static void Main()
{
#if DEBUG
UrlWatcherService service = new UrlWatcherService();
service.OnDebug();
#else
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new UrlWatcherService()
};
ServiceBase.Run(ServicesToRun);
#endif
}
OK due to Steve's hint, I split the projects up. I use a merged module as per this MSDN article, and instead of referencing the GUI in the Service project, I separated them so I can put both their project outputs in the merged module. I then add the merged module to my installer and now I have the service running after install and the ability to call the form from my start menu. It's not what I originally wanted, but a very plausible alternative.
Thanks for Steve for the hint.

Categories

Resources