Custom error handling in DNN 7? - dotnetnuke

Anyone here have experience with custom error handling inside DNN 7?
The built in logging is fine, but my company has the need to extend DNN's built in error handling to include sending custom emails when ALL exceptions occur. We created a HttpModule that added an event listener on Application_Error, and have been emailing exceptions that way. However, after an exception is emailed, we are not being consistently redirected to the specified 500 Error page set in DNN's properties under Admin > Site Settings. We have different behavior depending on the exception type. Some exceptions (NullReferenceException) result in Application_Error firing and an email being sent but no redirection, others (HttpException) result in a redirect to the 500 page without the Application_Error event being fired. Is it possible something in DNN is catching these errors before Application_Error fires, and any ideas on how to fix these issues?
Here is the httpModule we added to the web.config:
///
/// Class for error handling and emailing exceptions to the error distribution list.
///
public class ErrorModule : IHttpModule {
#region Private Properties
///
/// Gets the requested URL.
///
/// The requested URL.
private string requestedUrl {
get {
//TODO: create CmsPathTranslationFactory to create ICmsPathTranslator object for getting requested URL path
return !string.IsNullOrEmpty(HttpContext.Current.Items["UrlRewrite:OriginalUrl"].ToString()) ?
new Uri(HttpContext.Current.Items["UrlRewrite:OriginalUrl"].ToString()).AbsolutePath : HttpContext.Current.Request.Url.AbsolutePath;
}
}
#endregion
#region IHttpModule Members
///
/// Initializes the specified application.
///
/// The application.
public void Init(HttpApplication application) {
application.Error += new EventHandler(application_Error);
}
///
/// Disposes of the resources (other than memory) used by the module that implements .
///
public void Dispose() { }
#endregion
#region Public Methods
///
/// Handles the Error event of the application control.
///
/// The source of the event.
/// The instance containing the event data.
public void application_Error(object sender, EventArgs e) {
HttpApplication application = (HttpApplication)sender;
// get the last exception
Exception exception = application.Server.GetLastError();
if (exception == null) {
// exception is null, nothing to send
sendErrorMessage(new Exception("Exception is null."));
return;
}
// get the inner exception if not null
if (exception.InnerException != null) {
exception = exception.InnerException;
}
if (exception is HttpException && ((HttpException)exception).GetHttpCode() == 404) {
//don't send exception email for 404
}
else {
sendErrorMessage(exception);
}
}
#endregion
#region Private Methods
///
/// Sends an email message with the specified error.
///
/// The exception.
private void sendErrorMessage(Exception ex) {
using (MailMessage message = new MailMessage()) {
message.To.Add(new MailAddress(ConfigurationManager.AppSettings["errorEmailToAddress"]));
message.ReplyToList.Add(new MailAddress(ConfigurationManager.AppSettings["errorEmailReplyToAddress"]));
message.From = new MailAddress(ConfigurationManager.AppSettings["errorEmailFromAddress"], Environment.MachineName);
message.Subject = getErrorEmailSubject(ex, requestedUrl);
message.Body = ex.ToString();
message.Priority = MailPriority.High;
using (SmtpClient client = new SmtpClient()) {
client.Host = ConfigurationManager.AppSettings["SMTPServer"];
client.Send(message);
}
}
}
///
/// Gets the error email subject based on the specified exception.
///
/// The ex.
/// The requested URL path.
/// System.String.
private string getErrorEmailSubject(Exception ex, string requestedPath) {
return !string.IsNullOrEmpty(ex.Message) ? string.Concat(requestedPath, " - ", ex.Message) : requestedPath;
}
#endregion
}

DNN can send emails for every exception already. You can set all this up in the Event Log, editing the Types of events, and configuring the email options.

Related

WPF and EntityFrameworkCore - Adding migration gives "No database provider has been configured for this DbContext"

Note I have read the large number of SO answers that appear to be similar, but I am already doing what they suggested, so I don't know if there is some difference with WPF (they all seem to relate to ASP.NET). Also, most answers relate to run-time errors, not ones when adding migrations.
I'm trying to set up a .NET Core 3 WPF project that uses EntityFrameWork Core, but am having problems adding a migration. I have set up my context as follows...
public class ApplicationDbContext : DbContext {
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options) {
}
public ApplicationDbContext() {
}
public DbSet<Product> Products { get; set; }
}
The parameterless constructor is there, as without it I get an exception Unable to create an object of type 'ApplicationDbContext' when trying to add a migration.
My App.xaml.cs contains the following...
public partial class App {
public IServiceProvider ServiceProvider { get; private set; }
public IConfiguration Configuration { get; private set; }
protected override void OnStartup(StartupEventArgs e) {
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appSettings.json", optional: false, reloadOnChange: true);
Configuration = builder.Build();
ServiceCollection serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
ServiceProvider = serviceCollection.BuildServiceProvider();
MainWindow mainWindow = ServiceProvider.GetRequiredService<MainWindow>();
mainWindow.Show();
}
private void ConfigureServices(IServiceCollection services) {
// Configuration
services.Configure<AppSettings>(Configuration.GetSection(nameof(AppSettings)));
// Database
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SqlConnection")));
// Windows
services.AddTransient(typeof(MainWindow));
}
}
I realise that some of this is irrelevant, but thought I'd show the whole class in case it reveals something I missed. The code is based on this blog post.
However, when I try to add a migration, I get an exception "No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext."
As far as I can see, I have configured the database provider. I put a breakpoint in the ConfigureServices method, and can see that services.AddDbContext is called with the correct connection string.
Anyone any ideas what I've missed?
UPDATE I tried connecting to an existing database, and it worked absolutely fine, so it looks like the database provider has been configured correctly. It's only when I try to add a migration that I get the exception.
UPDATE 2 It seems that the migration tool is using the parameterless constructor on the context, which is why it thinks the provider hasn't been configured. If I remove the lines that configure it from App.xaml.cs, and instead override the OnConfiguringmethod to call UseSqlServer then the migration works fine. However, apart from the fact that I've not seen anyone else doing this (which makes me wonder if it's really the right way to do it), I don't see how to get the connection string from the config file. I can't inject an IConfiguration parameter, as the whole issue is that migrations requires a parameterless constructor.
It's actually quite simple with .Net Core 3.1 and EF Core Version 5, Entity Framework will look at the entry point class for the static function CreateHostBuilder, in my case that would be the App class in app.xaml.cs.
Not entirely sure the convention required prior to .Net Core 3.1. From my experience it had something to do with having a Startup class with .Net Core 2.1 and ASP.Net.
https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli
My solution:
public partial class App : Application
{
/// <summary>
/// Necessary for EF core to be able to find and construct
/// the DB context.
/// </summary>
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
// Configure Application services
.ConfigureServices((context, services) =>
{
ConfigureServices(context, services);
});
}
/// <summary>
/// Not necessary but I prefer having a separate function for
/// configuring services.
/// </summary>
private static void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{
...
}
/// <summary>
/// Hold the built Host for starting and stopping
/// </summary>
private readonly IHost AppHost;
/// <summary>
/// Constructor
/// </summary>
public App()
{
// Create Application host
AppHost = CreateHostBuilder(new string[] { }).Build();
}
/// <summary>
/// App Startup Event Handler
/// </summary>
private async void Application_Startup(object sender, StartupEventArgs e)
{
// Start the application host
await AppHost.StartAsync();
...
}
/// <summary>
/// App Exit Event Handler
/// </summary>
private async void Application_Exit(object sender, ExitEventArgs e)
{
// Kill the application host gracefully
await AppHost.StopAsync(TimeSpan.FromSeconds(5));
// Dispose of the host at the end of execution
AppHost.Dispose();
}
}
You need to implement IDesignTimeDbContextFactory. There is a lot of hidden plumbing in an ASP.NET Core app that deals with wiring up the apps service provider so it can be found by the dotnet ef tooling, but no such plumbing exists in WPF. In addition the ef tools know nothing about WPF events so your OnStartup method isn't going to even be called (an instance the class wont even get created) to create your DI setup so that the ef tools can find your DBContext.
Move the code that creates the ServiceProvider into the constructor, other than the bit that looks up the main window and displays it.
Implement IDesignTimeDbContextFactory<ApplicationDbContext>. In the implemented CreateDbContext method return ServiceProvider.GetRequiredService<ApplicationDbContext>()
The tooling will then create an instance of your class, which will setup DI, and call that method to get your (now configured) DB Context and it should work.
I'd also recommend moving to HostBuilder based config (that blog post was written before the final version of Core 3 was released). You will find an updated version of the same post here

correct access of a windows form inside backgroundworker thread

I have a winforms app, and I need to access the Handle property of a main form, inside a Backgroundworker thread.
I have made a thread safe method that calls the main form with InvokeRequired. My question is - why do I still get "InvalidOperationException cross-thread operation not valid" error, even when calling this thread safe method like this:
ProcessStartInfo psi = new ProcessStartInfo(file);
psi.ErrorDialogParentHandle = Utils.GetMainAppFormThreadSafe().Handle;
And below is the code of the thread safe method (my main app form is called Updater):
/// <summary>
/// delegate used to retrieve the main app form
/// </summary>
/// <returns></returns>
private delegate Updater delegateGetMainForm();
/// <summary>
/// gets the mainform thread safe, to avoid cross-thread exception
/// </summary>
/// <returns></returns>
public static Updater GetMainAppFormThreadSafe()
{
Updater updaterObj = null;
if (GetMainAppForm().InvokeRequired)
{
delegateGetMainForm deleg = new delegateGetMainForm(GetMainAppForm);
updaterObj = GetMainAppForm().Invoke(deleg) as Updater;
}
else
{
updaterObj = GetMainAppForm();
}
return updaterObj;
}
/// <summary>
/// retrieves the main form of the application
/// </summary>
/// <returns></returns>
public static Updater GetMainAppForm()
{
Updater mainForm = System.Windows.Forms.Application.OpenForms[Utils.AppName] as Updater;
return mainForm;
}
Am I doing smth wrong?
Thank you in advance.
LATER EDIT: I'll post the reason why I need the handle in the first place, perhaps there is another solution/approach. In My Backgroundworker thread I need to install multiple programs in a loop, and I start a process for each installer. However I need to ask for elevation so that this operation can work for standard users as well, not only admins. In short, I am trying to follow the tutorial here
You aren't getting the handle in a thread-safe way. Instead you get the Form instance in a thread-safe way and then access the Handle property in an unsafe way.
You should add a method GetMainAppFormHandle() that directly returns the handle and call that one in a thread-safe way:
public static IntPtr GetMainAppFormHandle()
{
return System.Windows.Forms.Application.OpenForms[Utils.AppName].Handle;
}
Update:
In addition you need GetMainAppFormHandleThreadSafe() instead of GetMainAppFormThreadSafe():
public static IntPtr GetMainAppFormHandleThreadSafe()
{
Form form = GetMainAppForm();
if (form.InvokeRequired)
{
return (IntPtr)form.Invoke(new Func<IntPtr>(GetMainAppFormHandle));
}
else
{
return GetMainAppFormHandle();
}
}

Is there a way to know if a WPF application is shutting down?

I am writing some code that checks that my resources are properly cleaned up.
When the application is shutdown, resources are not cleaned up, which is fine. However, this makes my check code fail.
Is there a way to know if a WPF application is in the process of shutting down? - Something like Application.Current.IsShuttingDown?
There is Application.Exit event, you should be able to do with that.
If you really need it to be a property, then create a property into your App class (your class inheriting Windows.Application) and set it to true in with the Application.Exit event.
/// <summary>
/// Hack to check if the application is shutting down.
/// </summary>
public static bool IsShuttingDown()
{
try
{
Application.Current.ShutdownMode = Application.Current.ShutdownMode;
return false;
}
catch (Exception)
{
return true;
}
}
just add this to your App.cs file
public bool IsShuttingDown { get; private set; }
public new void Shutdown(int exitCode = 0)
{
this.IsShuttingDown = true;
base.Shutdown(exitCode);
}

how to cleanup view model properly?

I have a view model that is used as the data source for my custom control. In the view model's constructor I set up a WMI ManagementEventWatcher and start it. My view model implements IDisposable, so I stop the watcher in the Dispose method.
When I embed the custom control into a window, and then close the window to exit the application it throws an InvalidComObjectException saying "COM object that has been separated from its underlying RCW cannot be used". This happens because of my watcher, and if I do not create it, there is no exception. there is no additional information about the exception such as stack trace, etc.
My guess is that something keeps the view model until the thread that the watcher uses terminates but before the watcher is stopped, and I do not know how to handle this.
Any advice?
Thanks
Konstantin
public abstract class ViewModelBase : IDisposable, ...
{
...
protected virtual void OnDispose() { }
void IDisposable.Dispose()
{
this.OnDispose();
}
}
public class DirectorySelector : ViewModelBase
{
private ManagementEventWatcher watcher;
private void OnWMIEvent(object sender, EventArrivedEventArgs e)
{
...
}
protected override void OnDispose()
{
if (this.watcher != null)
{
this.watcher.Stop();
this.watcher = null;
}
base.OnDispose();
}
public DirectorySelector()
{
try
{
this.watcher = new ManagementEventWatcher(new WqlEventQuery(...));
this.watcher.EventArrived += new EventArrivedEventHandler(this.OnWMIEvent);
this.watcher.Start();
}
catch (ManagementException)
{
this.watcher = null;
}
}
}
this article has the solution: Disposing WPF User Controls
basically, WPF dos not seem to use IDisposable anywhere, so the app needs to cleanup itself explicitly. so in my case, i subscribe to the Dispatcher.ShutdownStarted event from my control that uses the view model that needs to be disposed, and dispose the control's DataContext from the event handler.

How can I get the current exception in a WinForms TraceListener

I am modifying an existing WinForms app which is setup with a custom TraceListener which logs any unhandled errors that occur in the app. It seems to me like the TraceListener gets the message part of the exception (which is what gets logged), but not the other exception information. I would like to be able to get at the exception object (to get the stacktrace and other info).
In ASP.NET, which I am more familiar with, I would call Server.GetLastError to get the most recent exception, but of course that won't work in WinForms.
How can I get the most recent exception?
I assume that you have set an event handler that catches unhandled domain exceptions and thread exceptions. In that delegate you probably call the trace listener to log the exception. Simply issue an extra call to set the exception context.
[STAThread]
private static void Main()
{
// Add the event handler for handling UI thread exceptions
Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
// Add the event handler for handling non-UI thread exceptions
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
...
Application.Run(new Form1());
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
MyTraceListener.Instance.ExceptionContext = e;
Trace.WriteLine(e.ToString());
}
private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
// similar to above CurrentDomain_UnhandledException
}
...
Trace.Listeners.Add(MyTraceListener.Instance);
...
class MyTraceListener : System.Diagnostics.TraceListener
{
...
public Object ExceptionContext { get; set; }
public static MyTraceListener Instance { get { ... } }
}
On the Write methods in MyTraceListener you can get the exception context and work with that. Remember to sync exception context.

Resources