initial commit

This commit is contained in:
Matthias Heil
2026-04-04 13:30:13 +02:00
commit 6bed9b284c
186 changed files with 10650 additions and 0 deletions
@@ -0,0 +1,16 @@
<Application x:Class="AvaloniaLog4NetDI.App"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AvaloniaLog4NetDI">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
</Application.Styles>
</Application>
@@ -0,0 +1,161 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using AvaloniaLog4NetDI.ViewModels;
using AvaloniaLog4NetDI.Views;
using Log4Net.Appender.LogView.Core;
using LogViewer.Avalonia;
using MessageBox.Avalonia;
using MessageBox.Avalonia.Enums;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using RandomLogging.Service;
using System;
using System.Reflection;
using System.Threading;
using Icon = MessageBox.Avalonia.Enums.Icon;
namespace AvaloniaLog4NetDI;
public partial class App : Application
{
public override void Initialize()
=> AvaloniaXamlLoader.Load(this);
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
// Line below is needed to remove Avalonia data validation.
// Without this line you will get duplicate validations from both Avalonia and CT
ExpressionObserver.DataValidators.RemoveAll(x => x is DataAnnotationsValidationPlugin);
// catch all unhandled errors
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
HostApplicationBuilder builder = Host.CreateApplicationBuilder();
builder
/*
* Note: For information on launch profiles for debugging,
* see article: https://www.codeproject.com/Articles/5354478/NET-App-Settings-Demystified-Csharp-VB
*/
// Register the Random Logging Service
.AddRandomBackgroundService()
// visual debugging tools
.AddLogViewer()
// Log4Net
.Logging.AddLog4Net(builder.Configuration);
// uncomment to use custom logging colors (note: System.Drawing namespace)
//
//.Logging.AddLog4Net(
// builder.Configuration,
// options =>
//{
// options.Colors[LogLevel.Trace] = new()
// {
// Foreground = Color.White,
// Background = Color.DarkGray
// };
// options.Colors[LogLevel.Debug] = new()
// {
// Foreground = Color.White,
// Background = Color.Gray
// };
// options.Colors[LogLevel.Information] = new()
// {
// Foreground = Color.White,
// Background = Color.DodgerBlue
// };
// options.Colors[LogLevel.Warning] = new()
// {
// Foreground = Color.White,
// Background = Color.Orchid
// };
//});
IServiceCollection services = builder.Services;
services
.AddSingleton<MainViewModel>()
.AddSingleton<MainWindow>(service => new MainWindow
{
DataContext = service.GetRequiredService<MainViewModel>()
});
_host = builder.Build();
_cancellationTokenSource = new();
try
{
LogStartingMode();
// set and show
desktop.MainWindow = _host.Services.GetRequiredService<MainWindow>();
desktop.ShutdownRequested += OnShutdownRequested;
// startup background services
_ = _host.StartAsync(_cancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
// skip
}
catch (Exception ex)
{
ShowMessageBox("Unhandled Error", ex.Message);
return;
}
}
base.OnFrameworkInitializationCompleted();
}
private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e)
=> _ = _host!.StopAsync(_cancellationTokenSource!.Token);
#region Fields
private IHost? _host;
private CancellationTokenSource? _cancellationTokenSource;
#endregion
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
=> ShowMessageBox("Unhandled Error", ((Exception)e.ExceptionObject).Message);
private void ShowMessageBox(string title, string message)
{
MessageBox.Avalonia.BaseWindows.Base.IMsBoxWindow<ButtonResult> messageBoxStandardWindow = MessageBoxManager
.GetMessageBoxStandardWindow(title, message, ButtonEnum.Ok, Icon.Stop);
messageBoxStandardWindow.Show();
}
private void LogStartingMode()
{
// Get the Launch mode
bool isDevelopment = string.Equals(Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES"), "debug",
StringComparison.InvariantCultureIgnoreCase);
// initialize a logger & EventId
ILogger<App> logger = _host!.Services.GetRequiredService<ILogger<App>>();
EventId eventId = new EventId(id: 0, name: Assembly.GetEntryAssembly()!.GetName().Name);
// log a test pattern for each log level
logger.TestPattern(eventId: eventId);
// log that we have started...
logger.Emit(eventId, LogLevel.Information, $"Running in {(isDevelopment ? "Debug" : "Release")} mode");
}
}
@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Content Include="appsettings.Development.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.Production.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MessageBox.Avalonia" Version="2.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Resources\Avalonia.Resources\Avalonia.Resources.vbproj" />
<ProjectReference Include="..\..\Background Services\RandomLogging.Service\RandomLogging.Service.csproj" />
<ProjectReference Include="..\..\Controls\LogViewer.Avalonia\LogViewer.Avalonia.csproj" />
<ProjectReference Include="..\..\Core\Log4Net.Appender.LogView.Core\Log4Net.Appender.LogView.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="log4net.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,21 @@
using Avalonia;
using System;
namespace AvaloniaLog4NetDI;
internal class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args)
=> BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
}
@@ -0,0 +1,19 @@
{
"profiles": {
"Development": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
},
"Staging": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Staging"
}
},
"Production": {
"commandName": "Project"
}
}
}
@@ -0,0 +1,27 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using AvaloniaLog4NetDI.ViewModels;
using System;
namespace AvaloniaLog4NetDI;
public class ViewLocator : IDataTemplate
{
public IControl Build(object data)
{
string name = data.GetType().FullName!.Replace("ViewModel", "View");
Type? type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
return new TextBlock { Text = "Not Found: " + name };
}
public bool Match(object data)
{
return data is ViewModelBase;
}
}
@@ -0,0 +1,21 @@
using LogViewer.Core.ViewModels;
namespace AvaloniaLog4NetDI.ViewModels;
public class MainViewModel : ViewModelBase
{
#region Constructor
public MainViewModel(LogViewerControlViewModel logViewer)
{
LogViewer = logViewer;
}
#endregion
#region Properties
public LogViewerControlViewModel LogViewer { get; }
#endregion
}
@@ -0,0 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace AvaloniaLog4NetDI.ViewModels;
public class ViewModelBase : ObservableObject
{
}
@@ -0,0 +1,19 @@
<Window x:Class="AvaloniaLog4NetDI.Views.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
xmlns:control="clr-namespace:LogViewer.Avalonia;assembly=LogViewer.Avalonia"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
Title="C# AVALONIA | LOG4NET LogViewer Control Example - Dot Net 7.0"
Icon="avares://Avalonia.Resources/Assets/avalonia-logo.ico"
WindowStartupLocation="CenterScreen" Height="634" Width="600">
<control:LogViewerControl DataContext="{Binding LogViewer}" />
</Window>
@@ -0,0 +1,8 @@
using Avalonia.Controls;
namespace AvaloniaLog4NetDI.Views;
public partial class MainWindow : Window
{
public MainWindow() => InitializeComponent();
}
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embeded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>
@@ -0,0 +1,35 @@
{
"Logging": {
"LogLevel": {
"Default": "Trace",
"System.Net.Http.HttpClient": "Trace"
}
},
"Log4NetCore": {
"Name": "Log4NetLogViewer_Dev",
"LoggerRepository": "LogViewerRepository",
"OverrideCriticalLevelWith": "Critical",
"Watch": false,
"UseWebOrAppConfig": false,
"PropertyOverrides": [
{
"XPath": "/log4net/appender[@name='ConsoleAppender']/layout/conversionPattern",
"Attributes": {
"Value": "%date [%thread] %-5level | %logger | %message%newline"
}
},
{
"XPath": "/log4net/appender[@name='ConsoleAppender']/threshold",
"Attributes": {
"Value": "Trace"
}
},
{
"XPath": "/log4net/appender[@name='DataStoreLogger']/threshold",
"Attributes": {
"Value": "Trace"
}
}
]
}
}
@@ -0,0 +1,35 @@
{
"Logging": {
"LogLevel": {
"Default": "Trace",
"System.Net.Http.HttpClient": "Trace"
}
},
"Log4NetCore": {
"Name": "Log4NetLogViewer_Prod",
"LoggerRepository": "LogViewerRepository",
"OverrideCriticalLevelWith": "Critical",
"Watch": false,
"UseWebOrAppConfig": false,
"PropertyOverrides": [
{
"XPath": "/log4net/appender[@name='ConsoleAppender']/layout/conversionPattern",
"Attributes": {
"Value": "%date [%thread] %-5level | %logger | %message%newline"
}
},
{
"XPath": "/log4net/appender[@name='ConsoleAppender']/threshold",
"Attributes": {
"Value": "Warn"
}
},
{
"XPath": "/log4net/appender[@name='DataStoreLogger']/threshold",
"Attributes": {
"Value": "Warn"
}
}
]
}
}
@@ -0,0 +1,35 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"System.Net.Http.HttpClient": "Information"
}
},
"Log4NetCore": {
"Name": "Log4NetLogViewer_default",
"LoggerRepository": "LogViewerRepository",
"OverrideCriticalLevelWith": "Critical",
"Watch": false,
"UseWebOrAppConfig": false,
"PropertyOverrides": [
{
"XPath": "/log4net/appender[@name='ConsoleAppender']/layout/conversionPattern",
"Attributes": {
"Value": "%date [%thread] %-5level | %logger | %message%newline"
}
},
{
"XPath": "/log4net/appender[@name='ConsoleAppender']/threshold",
"Attributes": {
"Value": "Info"
}
},
{
"XPath": "/log4net/appender[@name='DataStoreLogger']/threshold",
"Attributes": {
"Value": "Info"
}
}
]
}
}
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<appender name="DebugAppender" type="log4net.Appender.DebugAppender" >
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<threshold value="ALL" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<appender name="DataStoreLogger" type="Log4Net.Appender.LogView.Core.DataStoreLoggerServiceAppender">
<threshold value="ALL" />
</appender>
<root>
<Level value="ALL" />
<appender-ref ref="DebugAppender" />
<appender-ref ref="ConsoleAppender" />
<appender-ref ref="DataStoreLogger" />
</root>
</log4net>
@@ -0,0 +1,9 @@
<Application x:Class="AvaloniaLog4NetNoDI.App"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Styles>
<FluentTheme Mode="Light"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
</Application.Styles>
</Application>
@@ -0,0 +1,29 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using AvaloniaLog4NetNoDI.Views;
namespace AvaloniaLog4NetNoDI;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
// Line below is needed to remove Avalonia data validation.
// Without this line you will get duplicate validations from both Avalonia and CT
ExpressionObserver.DataValidators.RemoveAll(x => x is DataAnnotationsValidationPlugin);
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
}
@@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Content Include="appsettings.Development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.Production.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Resources\Avalonia.Resources\Avalonia.Resources.vbproj" />
<ProjectReference Include="..\..\Background Services\RandomLogging.Service\RandomLogging.Service.csproj" />
<ProjectReference Include="..\..\Controls\LogViewer.Avalonia\LogViewer.Avalonia.csproj" />
<ProjectReference Include="..\..\Core\Common.Core\Common.Core.csproj" />
<ProjectReference Include="..\..\Core\Log4Net.Appender.LogView.Core\Log4Net.Appender.LogView.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="log4net.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,10 @@
using LogViewer.Core;
using LogDataStore = LogViewer.Avalonia.Logging.LogDataStore;
namespace AvaloniaLog4NetNoDI.DataStores;
// Application-wide shared instance of the LogDataStore logging entries
public static class MainControlsDataStore
{
public static ILogDataStore DataStore { get; } = new LogDataStore();
}
@@ -0,0 +1,30 @@
using AvaloniaLog4NetNoDI.DataStores;
using LogViewer.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using Log4Net.Appender.LogView.Core;
namespace AvaloniaLog4NetNoDI.Extensions;
public static class ServiceExtension
{
public static ILoggingBuilder AddLog4NetNoDI(this ILoggingBuilder builder, IConfiguration config)
{
// We need to use a shared instance of the DataStore to pass to the LogViewerControl
builder.Services.AddSingleton(MainControlsDataStore.DataStore);
// call core Log4Net ServiceExtension initializer
builder.AddLog4Net(config);
return builder;
}
public static ILoggingBuilder AddLog4NetNoDI(this ILoggingBuilder builder, IConfiguration config, Action<DataStoreLoggerConfiguration> configure)
{
builder.AddLog4NetNoDI(config);
builder.Services.Configure(configure);
return builder;
}
}
@@ -0,0 +1,73 @@
using System;
using System.Drawing;
using Microsoft.Extensions.Logging;
using AvaloniaLog4NetNoDI.Extensions;
using Microsoft.Extensions.Configuration;
using Common.Core;
using Common.Core.Extensions;
namespace AvaloniaLog4NetNoDI.Helpers;
// application-wide DataStoreLogger Factory ... returns a wired up Logger instance
public static class LoggingHelper
{
#region Constructors
static LoggingHelper()
{
// retrieve the log level from 'appsettings'
string value = AppSettings<string>.Current("Logging:LogLevel", "Default") ?? "Information";
Enum.TryParse(value, out LogLevel logLevel);
IConfigurationRoot configuration = new ConfigurationBuilder()
.Initialize()
.Build();
// wire up the loggers
Factory = LoggerFactory.Create(builder => builder
// visual debugging tools
.AddLog4NetNoDI(configuration)
// uncomment to use custom logging colors (note: System.Drawing namespace)
//
//.AddLog4NetNoDI(configuration, options =>
//{
// options.Colors[LogLevel.Trace] = new()
// {
// Foreground = Color.White,
// Background = Color.DarkGray
// };
// options.Colors[LogLevel.Debug] = new()
// {
// Foreground = Color.White,
// Background = Color.Gray
// };
// options.Colors[LogLevel.Information] = new()
// {
// Foreground = Color.White,
// Background = Color.DodgerBlue
// };
// options.Colors[LogLevel.Warning] = new()
// {
// Foreground = Color.White,
// Background = Color.Orchid
// };
//})
// set minimum log level from 'appsettings*.json'
.SetMinimumLevel(logLevel));
}
#endregion
#region Properties
public static ILoggerFactory Factory { get; }
#endregion
}
@@ -0,0 +1,21 @@
using Avalonia;
using System;
namespace AvaloniaLog4NetNoDI;
internal class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args)
=> BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
}
@@ -0,0 +1,19 @@
{
"profiles": {
"Development": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
},
"Staging": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Staging"
}
},
"Production": {
"commandName": "Project"
}
}
}
@@ -0,0 +1,19 @@
<Window x:Class="AvaloniaLog4NetNoDI.Views.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
xmlns:control="clr-namespace:LogViewer.Avalonia;assembly=LogViewer.Avalonia"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="C# AVALONIA MINIMAL | LOG4NET LogViewer Control Example - Dot Net 7.0"
Icon="avares://Avalonia.Resources/Assets/avalonia-logo.ico"
WindowStartupLocation="CenterScreen" Height="634" Width="600">
<control:LogViewerControl x:Name="LogViewerControl" />
</Window>
@@ -0,0 +1,48 @@
using Avalonia.Controls;
using AvaloniaLog4NetNoDI.DataStores;
using AvaloniaLog4NetNoDI.Helpers;
using LogViewer.Core;
using Microsoft.Extensions.Logging;
using RandomLogging.Service;
using System;
using System.Reflection;
using System.Threading;
namespace AvaloniaLog4NetNoDI.Views;
public partial class MainWindow : Window, ILogDataStoreImpl
{
public MainWindow()
{
InitializeComponent();
// Initialize service and pass in the Logger
RandomLoggingService service = new(new Logger<RandomLoggingService>(LoggingHelper.Factory));
// Get the Launch mode
bool isDevelopment = string.Equals(Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES"), "debug",
StringComparison.InvariantCultureIgnoreCase);
// initialize a logger & EventId
Logger<MainWindow> logger = new Logger<MainWindow>(LoggingHelper.Factory);
EventId eventId = new EventId(id: 0, name: Assembly.GetEntryAssembly()!.GetName().Name);
// log a test pattern for each log level
logger.TestPattern(eventId: eventId);
// log that we have started...
logger.Emit(eventId, LogLevel.Information, $"Running in {(isDevelopment ? "Debug" : "Release")} mode");
// Start generating log entries
_ = service.StartAsync(CancellationToken.None);
// manually wire up the logging to the view ... the control will show backlog entries...
DataStore = MainControlsDataStore.DataStore;
// we can't bind the controls' DataContext to a static object, so assign the DataStore to the Window
// and pass a reference to the Window itself
LogViewerControl.DataContext = this;
}
public ILogDataStore DataStore { get; init; }
}
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embeded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>
@@ -0,0 +1,35 @@
{
"Logging": {
"LogLevel": {
"Default": "Trace",
"System.Net.Http.HttpClient": "Trace"
}
},
"Log4NetCore": {
"Name": "Log4NetLogViewer_Dev",
"LoggerRepository": "LogViewerRepository",
"OverrideCriticalLevelWith": "Critical",
"Watch": false,
"UseWebOrAppConfig": false,
"PropertyOverrides": [
{
"XPath": "/log4net/appender[@name='ConsoleAppender']/layout/conversionPattern",
"Attributes": {
"Value": "%date [%thread] %-5level | %logger | %message%newline"
}
},
{
"XPath": "/log4net/appender[@name='ConsoleAppender']/threshold",
"Attributes": {
"Value": "Trace"
}
},
{
"XPath": "/log4net/appender[@name='DataStoreLogger']/threshold",
"Attributes": {
"Value": "Trace"
}
}
]
}
}
@@ -0,0 +1,35 @@
{
"Logging": {
"LogLevel": {
"Default": "Trace",
"System.Net.Http.HttpClient": "Trace"
}
},
"Log4NetCore": {
"Name": "Log4NetLogViewer_Prod",
"LoggerRepository": "LogViewerRepository",
"OverrideCriticalLevelWith": "Critical",
"Watch": false,
"UseWebOrAppConfig": false,
"PropertyOverrides": [
{
"XPath": "/log4net/appender[@name='ConsoleAppender']/layout/conversionPattern",
"Attributes": {
"Value": "%date [%thread] %-5level | %logger | %message%newline"
}
},
{
"XPath": "/log4net/appender[@name='ConsoleAppender']/threshold",
"Attributes": {
"Value": "Warn"
}
},
{
"XPath": "/log4net/appender[@name='DataStoreLogger']/threshold",
"Attributes": {
"Value": "Warn"
}
}
]
}
}
@@ -0,0 +1,35 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"System.Net.Http.HttpClient": "Information"
}
},
"Log4NetCore": {
"Name": "Log4NetLogViewer_default",
"LoggerRepository": "LogViewerRepository",
"OverrideCriticalLevelWith": "Critical",
"Watch": false,
"UseWebOrAppConfig": false,
"PropertyOverrides": [
{
"XPath": "/log4net/appender[@name='ConsoleAppender']/layout/conversionPattern",
"Attributes": {
"Value": "%date [%thread] %-5level | %logger | %message%newline"
}
},
{
"XPath": "/log4net/appender[@name='ConsoleAppender']/threshold",
"Attributes": {
"Value": "Info"
}
},
{
"XPath": "/log4net/appender[@name='DataStoreLogger']/threshold",
"Attributes": {
"Value": "Info"
}
}
]
}
}
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<appender name="DebugAppender" type="log4net.Appender.DebugAppender" >
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<threshold value="ALL" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<appender name="DataStoreLogger" type="Log4Net.Appender.LogView.Core.DataStoreLoggerServiceAppender">
<threshold value="ALL" />
</appender>
<root>
<Level value="ALL" />
<appender-ref ref="DebugAppender" />
<appender-ref ref="ConsoleAppender" />
<appender-ref ref="DataStoreLogger" />
</root>
</log4net>
@@ -0,0 +1,16 @@
<Application x:Class="AvaloniaLoggingDI.App"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AvaloniaLoggingDI">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
</Application.Styles>
</Application>
@@ -0,0 +1,160 @@
using System;
using System.Drawing;
using System.Reflection;
using System.Threading;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using AvaloniaLoggingDI.ViewModels;
using AvaloniaLoggingDI.Views;
using LogViewer.Avalonia;
using MessageBox.Avalonia;
using MessageBox.Avalonia.Enums;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MsLogger.Core;
using RandomLogging.Service;
using Icon = MessageBox.Avalonia.Enums.Icon;
namespace AvaloniaLoggingDI;
public partial class App : Application
{
public override void Initialize()
=> AvaloniaXamlLoader.Load(this);
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
// Line below is needed to remove Avalonia data validation.
// Without this line you will get duplicate validations from both Avalonia and CT
ExpressionObserver.DataValidators.RemoveAll(x => x is DataAnnotationsValidationPlugin);
// catch all unhandled errors
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
HostApplicationBuilder builder = Host.CreateApplicationBuilder();
builder
/*
* Note: For information on launch profiles for debugging,
* see article: https://www.codeproject.com/Articles/5354478/NET-App-Settings-Demystified-Csharp-VB
*/
// Register the Random Logging Service
.AddRandomBackgroundService()
// visual debugging tools
.AddLogViewer()
// Microsoft Logger
//.Logging.AddDefaultDataStoreLogger();
// uncomment to use custom logging colors (note: System.Drawing namespace)
//
.Logging.AddDefaultDataStoreLogger(options =>
{
options.Colors[LogLevel.Trace] = new()
{
Foreground = Color.White,
Background = Color.DarkGray
};
options.Colors[LogLevel.Debug] = new()
{
Foreground = Color.White,
Background = Color.Gray
};
options.Colors[LogLevel.Information] = new()
{
Foreground = Color.White,
Background = Color.DodgerBlue
};
options.Colors[LogLevel.Warning] = new()
{
Foreground = Color.White,
Background = Color.Orchid
};
});
IServiceCollection services = builder.Services;
services
.AddSingleton<MainViewModel>()
.AddSingleton<MainWindow>(service => new MainWindow
{
DataContext = service.GetRequiredService<MainViewModel>()
});
_host = builder.Build();
_cancellationTokenSource = new();
try
{
LogStartingMode();
// set and show
desktop.MainWindow = _host.Services.GetRequiredService<MainWindow>();
desktop.ShutdownRequested += OnShutdownRequested;
// startup background services
_ = _host.StartAsync(_cancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
// skip
}
catch (Exception ex)
{
ShowMessageBox("Unhandled Error", ex.Message);
return;
}
}
base.OnFrameworkInitializationCompleted();
}
private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e)
=> _ = _host!.StopAsync(_cancellationTokenSource!.Token);
#region Fields
private IHost? _host;
private CancellationTokenSource? _cancellationTokenSource;
#endregion
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
=> ShowMessageBox("Unhandled Error", ((Exception)e.ExceptionObject).Message);
private void ShowMessageBox(string title, string message)
{
MessageBox.Avalonia.BaseWindows.Base.IMsBoxWindow<ButtonResult> messageBoxStandardWindow = MessageBoxManager
.GetMessageBoxStandardWindow(title, message, ButtonEnum.Ok, Icon.Stop);
messageBoxStandardWindow.Show();
}
private void LogStartingMode()
{
// Get the Launch mode
bool isDevelopment = string.Equals(Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES"), "debug",
StringComparison.InvariantCultureIgnoreCase);
// initialize a logger & EventId
ILogger<App> logger = _host!.Services.GetRequiredService<ILogger<App>>();
EventId eventId = new EventId(id: 0, name: Assembly.GetEntryAssembly()!.GetName().Name);
// log a test pattern for each log level
logger.TestPattern(eventId: eventId);
// log that we have started...
logger.Emit(eventId, LogLevel.Information, $"Running in {(isDevelopment ? "Debug" : "Release")} mode");
}
}
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Content Include="appsettings.Development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.Production.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MessageBox.Avalonia" Version="2.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Resources\Avalonia.Resources\Avalonia.Resources.vbproj" />
<ProjectReference Include="..\..\Background Services\RandomLogging.Service\RandomLogging.Service.csproj" />
<ProjectReference Include="..\..\Controls\LogViewer.Avalonia\LogViewer.Avalonia.csproj" />
<ProjectReference Include="..\..\Core\MsLogger.Core\MsLogger.Core.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,21 @@
using Avalonia;
using System;
namespace AvaloniaLoggingDI;
internal class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args)
=> BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
}
@@ -0,0 +1,19 @@
{
"profiles": {
"Development": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
},
"Staging": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Staging"
}
},
"Production": {
"commandName": "Project"
}
}
}
@@ -0,0 +1,27 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using AvaloniaLoggingDI.ViewModels;
using System;
namespace AvaloniaLoggingDI;
public class ViewLocator : IDataTemplate
{
public IControl Build(object data)
{
string name = data.GetType().FullName!.Replace("ViewModel", "View");
Type? type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
return new TextBlock { Text = "Not Found: " + name };
}
public bool Match(object data)
{
return data is ViewModelBase;
}
}
@@ -0,0 +1,21 @@
using LogViewer.Core.ViewModels;
namespace AvaloniaLoggingDI.ViewModels;
public class MainViewModel : ViewModelBase
{
#region Constructor
public MainViewModel(LogViewerControlViewModel logViewer)
{
LogViewer = logViewer;
}
#endregion
#region Properties
public LogViewerControlViewModel LogViewer { get; }
#endregion
}
@@ -0,0 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace AvaloniaLoggingDI.ViewModels;
public class ViewModelBase : ObservableObject
{
}
@@ -0,0 +1,19 @@
<Window x:Class="AvaloniaLoggingDI.Views.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
xmlns:control="clr-namespace:LogViewer.Avalonia;assembly=LogViewer.Avalonia"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
Title="C# AVALONIA | LogViewer Control Example - Dot Net 7.0"
Icon="avares://Avalonia.Resources/Assets/avalonia-logo.ico"
WindowStartupLocation="CenterScreen" Height="634" Width="600">
<control:LogViewerControl DataContext="{Binding LogViewer}" />
</Window>
@@ -0,0 +1,8 @@
using Avalonia.Controls;
namespace AvaloniaLoggingDI.Views;
public partial class MainWindow : Window
{
public MainWindow() => InitializeComponent();
}
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embeded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Trace",
"System.Net.Http.HttpClient": "Trace"
}
}
}
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"System.Net.Http.HttpClient": "Warning"
}
}
}
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"System.Net.Http.HttpClient": "Information"
}
}
}
@@ -0,0 +1,9 @@
<Application x:Class="AvaloniaLoggingNoDI.App"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Styles>
<FluentTheme Mode="Light"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
</Application.Styles>
</Application>
@@ -0,0 +1,29 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using AvaloniaLoggingNoDI.Views;
namespace AvaloniaLoggingNoDI;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
// Line below is needed to remove Avalonia data validation.
// Without this line you will get duplicate validations from both Avalonia and CT
ExpressionObserver.DataValidators.RemoveAll(x => x is DataAnnotationsValidationPlugin);
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
}
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Content Include="appsettings.Development.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.Production.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Resources\Avalonia.Resources\Avalonia.Resources.vbproj" />
<ProjectReference Include="..\..\Background Services\RandomLogging.Service\RandomLogging.Service.csproj" />
<ProjectReference Include="..\..\Controls\LogViewer.Avalonia\LogViewer.Avalonia.csproj" />
<ProjectReference Include="..\..\Core\Common.Core\Common.Core.csproj" />
<ProjectReference Include="..\..\Core\MsLogger.Core\MsLogger.Core.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,10 @@
using LogViewer.Core;
using LogDataStore = LogViewer.Avalonia.Logging.LogDataStore;
namespace AvaloniaLoggingNoDI.DataStores;
// Application-wide shared instance of the LogDataStore logging entries
public static class MainControlsDataStore
{
public static ILogDataStore DataStore { get; } = new LogDataStore();
}
@@ -0,0 +1,30 @@
using System;
using LogViewer.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;
using MsLogger.Core;
using AvaloniaLoggingNoDI.DataStores;
namespace AvaloniaLoggingNoDI.Extensions;
public static class LoggerExtension
{
public static ILoggingBuilder AddDataStoreLogger(this ILoggingBuilder builder)
{
builder.AddConfiguration();
// We need to use a shared instance of the DataStore to pass to the LogViewerControl
builder.Services.AddSingleton(MainControlsDataStore.DataStore);
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, DataStoreLoggerProvider>());
return builder;
}
public static ILoggingBuilder AddDataStoreLogger(this ILoggingBuilder builder, Action<DataStoreLoggerConfiguration> configure)
{
builder.AddDataStoreLogger();
builder.Services.Configure(configure);
return builder;
}
}
@@ -0,0 +1,78 @@
using System;
using System.Drawing;
using Common.Core;
using Microsoft.Extensions.Logging;
using AvaloniaLoggingNoDI.Extensions;
namespace AvaloniaLoggingNoDI.Helpers;
// application-wide DataStoreLogger Factory ... returns a wired up Logger instance
public static class LoggingHelper
{
#region Constructors
static LoggingHelper()
{
// retrieve the log level from 'appsettings'
string value = AppSettings<string>.Current("Logging:LogLevel", "Default") ?? "Information";
Enum.TryParse(value, out LogLevel logLevel);
// wire up the loggers
Factory = LoggerFactory.Create(builder => builder
// visual debugging tools
//.AddDataStoreLogger()
// uncomment to use custom logging colors (note: System.Drawing namespace)
//
.AddDataStoreLogger(options =>
{
options.Colors[LogLevel.Trace] = new()
{
Foreground = Color.White,
Background = Color.DarkGray
};
options.Colors[LogLevel.Debug] = new()
{
Foreground = Color.White,
Background = Color.Gray
};
options.Colors[LogLevel.Information] = new()
{
Foreground = Color.White,
Background = Color.DodgerBlue
};
options.Colors[LogLevel.Warning] = new()
{
Foreground = Color.White,
Background = Color.Orchid
};
})
// examples of adding other loggers...
.AddSimpleConsole(options =>
{
options.SingleLine = true;
options.TimestampFormat = "hh:mm:ss ";
})
// note:
// * The IDE will automatically add the Debugger Logger, even though not visible
// * Adding the DebugLogger is useful for remote debugging
//.AddDebug()
// set minimum log level from 'appsettings'
.SetMinimumLevel(logLevel));
}
#endregion
#region Properties
public static ILoggerFactory Factory { get; }
#endregion
}
@@ -0,0 +1,21 @@
using Avalonia;
using System;
namespace AvaloniaLoggingNoDI;
internal class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args)
=> BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
}
@@ -0,0 +1,19 @@
{
"profiles": {
"Development": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
},
"Staging": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Staging"
}
},
"Production": {
"commandName": "Project"
}
}
}
@@ -0,0 +1,19 @@
<Window x:Class="AvaloniaLoggingNoDI.Views.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
xmlns:control="clr-namespace:LogViewer.Avalonia;assembly=LogViewer.Avalonia"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="C# AVALONIA MINIMAL | LogViewer Control Example - Dot Net 7.0"
Icon="avares://Avalonia.Resources/Assets/avalonia-logo.ico"
WindowStartupLocation="CenterScreen" Height="634" Width="600">
<control:LogViewerControl x:Name="LogViewerControl" />
</Window>
@@ -0,0 +1,48 @@
using Avalonia.Controls;
using AvaloniaLoggingNoDI.DataStores;
using AvaloniaLoggingNoDI.Helpers;
using LogViewer.Core;
using Microsoft.Extensions.Logging;
using RandomLogging.Service;
using System;
using System.Reflection;
using System.Threading;
namespace AvaloniaLoggingNoDI.Views;
public partial class MainWindow : Window, ILogDataStoreImpl
{
public MainWindow()
{
InitializeComponent();
// Initialize service and pass in the Logger
RandomLoggingService service = new(new Logger<RandomLoggingService>(LoggingHelper.Factory));
// Get the Launch mode
bool isDevelopment = string.Equals(Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES"), "debug",
StringComparison.InvariantCultureIgnoreCase);
// initialize a logger & EventId
Logger<MainWindow> logger = new Logger<MainWindow>(LoggingHelper.Factory);
EventId eventId = new EventId(id: 0, name: Assembly.GetEntryAssembly()!.GetName().Name);
// log a test pattern for each log level
logger.TestPattern(eventId: eventId);
// log that we have started...
logger.Emit(eventId, LogLevel.Information, $"Running in {(isDevelopment ? "Debug" : "Release")} mode");
// Start generating log entries
_ = service.StartAsync(CancellationToken.None);
// manually wire up the logging to the view ... the control will show backlog entries...
DataStore = MainControlsDataStore.DataStore;
// we can't bind the controls' DataContext to a static object, so assign the DataStore to the Window
// and pass a reference to the Window itself
LogViewerControl.DataContext = this;
}
public ILogDataStore DataStore { get; init; }
}
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embeded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Trace",
"System.Net.Http.HttpClient": "Trace"
}
}
}
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"System.Net.Http.HttpClient": "Warning"
}
}
}
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"System.Net.Http.HttpClient": "Information"
}
}
}
@@ -0,0 +1,16 @@
<Application x:Class="AvaloniaNlogDI.App"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AvaloniaNlogDI">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
</Application.Styles>
</Application>
@@ -0,0 +1,160 @@
using System;
using System.Drawing;
using System.Reflection;
using System.Threading;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using AvaloniaNlogDI.ViewModels;
using AvaloniaNlogDI.Views;
using LogViewer.Avalonia;
using MessageBox.Avalonia;
using MessageBox.Avalonia.Enums;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NLog.Target.LogView.Core.Extensions;
using RandomLogging.Service;
using Icon = MessageBox.Avalonia.Enums.Icon;
namespace AvaloniaNlogDI;
public partial class App : Application
{
public override void Initialize()
=> AvaloniaXamlLoader.Load(this);
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
// Line below is needed to remove Avalonia data validation.
// Without this line you will get duplicate validations from both Avalonia and CT
ExpressionObserver.DataValidators.RemoveAll(x => x is DataAnnotationsValidationPlugin);
// catch all unhandled errors
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
HostApplicationBuilder builder = Host.CreateApplicationBuilder();
builder
/*
* Note: For information on launch profiles for debugging,
* see article: https://www.codeproject.com/Articles/5354478/NET-App-Settings-Demystified-Csharp-VB
*/
// Register the Random Logging Service
.AddRandomBackgroundService()
// visual debugging tools
.AddLogViewer()
// Nlog Target
//.Logging.AddNLogTargets(builder.Configuration);
// uncomment to use custom logging colors (note: System.Drawing namespace)
//
.Logging.AddNLogTargets(builder.Configuration, options =>
{
options.Colors[LogLevel.Trace] = new()
{
Foreground = Color.White,
Background = Color.DarkGray
};
options.Colors[LogLevel.Debug] = new()
{
Foreground = Color.White,
Background = Color.Gray
};
options.Colors[LogLevel.Information] = new()
{
Foreground = Color.White,
Background = Color.DodgerBlue
};
options.Colors[LogLevel.Warning] = new()
{
Foreground = Color.White,
Background = Color.Orchid
};
});
IServiceCollection services = builder.Services;
services
.AddSingleton<MainViewModel>()
.AddSingleton<MainWindow>(service => new MainWindow
{
DataContext = service.GetRequiredService<MainViewModel>()
});
_host = builder.Build();
_cancellationTokenSource = new();
try
{
LogStartingMode();
// set and show
desktop.MainWindow = _host.Services.GetRequiredService<MainWindow>();
desktop.ShutdownRequested += OnShutdownRequested;
// startup background services
_ = _host.StartAsync(_cancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
// skip
}
catch (Exception ex)
{
ShowMessageBox("Unhandled Error", ex.Message);
return;
}
}
base.OnFrameworkInitializationCompleted();
}
private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e)
=> _ = _host!.StopAsync(_cancellationTokenSource!.Token);
#region Fields
private IHost? _host;
private CancellationTokenSource? _cancellationTokenSource;
#endregion
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
=> ShowMessageBox("Unhandled Error", ((Exception)e.ExceptionObject).Message);
private void ShowMessageBox(string title, string message)
{
MessageBox.Avalonia.BaseWindows.Base.IMsBoxWindow<ButtonResult> messageBoxStandardWindow = MessageBoxManager
.GetMessageBoxStandardWindow(title, message, ButtonEnum.Ok, Icon.Stop);
messageBoxStandardWindow.Show();
}
private void LogStartingMode()
{
// Get the Launch mode
bool isDevelopment = string.Equals(Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES"), "debug",
StringComparison.InvariantCultureIgnoreCase);
// initialize a logger & EventId
ILogger<App> logger = _host!.Services.GetRequiredService<ILogger<App>>();
EventId eventId = new EventId(id: 0, name: Assembly.GetEntryAssembly()!.GetName().Name);
// log a test pattern for each log level
logger.TestPattern(eventId: eventId);
// log that we have started...
logger.Emit(eventId, LogLevel.Information, $"Running in {(isDevelopment ? "Debug" : "Release")} mode");
}
}
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Content Include="appsettings.Development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.Production.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MessageBox.Avalonia" Version="2.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Resources\Avalonia.Resources\Avalonia.Resources.vbproj" />
<ProjectReference Include="..\..\Background Services\RandomLogging.Service\RandomLogging.Service.csproj" />
<ProjectReference Include="..\..\Controls\LogViewer.Avalonia\LogViewer.Avalonia.csproj" />
<ProjectReference Include="..\..\Core\NLog.Target.LogView.Core\NLog.Target.LogView.Core.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,21 @@
using Avalonia;
using System;
namespace AvaloniaNlogDI;
internal class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args)
=> BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
}
@@ -0,0 +1,19 @@
{
"profiles": {
"Development": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
},
"Staging": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Staging"
}
},
"Production": {
"commandName": "Project"
}
}
}
@@ -0,0 +1,27 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using AvaloniaNlogDI.ViewModels;
using System;
namespace AvaloniaNlogDI;
public class ViewLocator : IDataTemplate
{
public IControl Build(object data)
{
string name = data.GetType().FullName!.Replace("ViewModel", "View");
Type? type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
return new TextBlock { Text = "Not Found: " + name };
}
public bool Match(object data)
{
return data is ViewModelBase;
}
}
@@ -0,0 +1,21 @@
using LogViewer.Core.ViewModels;
namespace AvaloniaNlogDI.ViewModels;
public class MainViewModel : ViewModelBase
{
#region Constructor
public MainViewModel(LogViewerControlViewModel logViewer)
{
LogViewer = logViewer;
}
#endregion
#region Properties
public LogViewerControlViewModel LogViewer { get; }
#endregion
}
@@ -0,0 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace AvaloniaNlogDI.ViewModels;
public class ViewModelBase : ObservableObject
{
}
@@ -0,0 +1,19 @@
<Window x:Class="AvaloniaNlogDI.Views.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
xmlns:control="clr-namespace:LogViewer.Avalonia;assembly=LogViewer.Avalonia"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
Title="C# AVALONIA | NLOG LogViewer Control Example - Dot Net 7.0"
Icon="avares://Avalonia.Resources/Assets/avalonia-logo.ico"
WindowStartupLocation="CenterScreen" Height="634" Width="600">
<control:LogViewerControl DataContext="{Binding LogViewer}" />
</Window>
@@ -0,0 +1,8 @@
using Avalonia.Controls;
namespace AvaloniaNlogDI.Views;
public partial class MainWindow : Window
{
public MainWindow() => InitializeComponent();
}
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embeded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>
@@ -0,0 +1,29 @@
{
"Logging": {
"LogLevel": {
"Default": "Trace",
"System.Net.Http.HttpClient": "Trace"
}
},
"NLog": {
"throwConfigExceptions": true,
"targets": {
"async": true,
"logconsole": {
"type": "Console",
"layout": "${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}"
},
"DataStoreLogger": {
"type": "DataStoreLogger",
"layout": "${message}"
}
},
"rules": [
{
"logger": "*",
"minLevel": "Trace",
"writeTo": "logconsole, DataStoreLogger"
}
]
}
}
@@ -0,0 +1,29 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"System.Net.Http.HttpClient": "Warning"
}
},
"NLog": {
"throwConfigExceptions": true,
"targets": {
"async": true,
"logconsole": {
"type": "Console",
"layout": "${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}"
},
"DataStoreLogger": {
"type": "DataStoreLogger",
"layout": "${message}"
}
},
"rules": [
{
"logger": "*",
"minLevel": "Warn",
"writeTo": "logconsole, DataStoreLogger"
}
]
}
}
@@ -0,0 +1,29 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"System.Net.Http.HttpClient": "Information"
}
},
"NLog": {
"throwConfigExceptions": true,
"targets": {
"async": true,
"logconsole": {
"type": "Console",
"layout": "${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}"
},
"DataStoreLogger": {
"type": "DataStoreLogger",
"layout": "${message}"
}
},
"rules": [
{
"logger": "*",
"minLevel": "Info",
"writeTo": "logconsole, DataStoreLogger"
}
]
}
}
@@ -0,0 +1,9 @@
<Application x:Class="AvaloniaNlogNoDI.App"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Styles>
<FluentTheme Mode="Light"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
</Application.Styles>
</Application>
@@ -0,0 +1,29 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using AvaloniaNlogNoDI.Views;
namespace AvaloniaNlogNoDI;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
// Line below is needed to remove Avalonia data validation.
// Without this line you will get duplicate validations from both Avalonia and CT
ExpressionObserver.DataValidators.RemoveAll(x => x is DataAnnotationsValidationPlugin);
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
}
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Content Include="appsettings.Development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.Production.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Resources\Avalonia.Resources\Avalonia.Resources.vbproj" />
<ProjectReference Include="..\..\Background Services\RandomLogging.Service\RandomLogging.Service.csproj" />
<ProjectReference Include="..\..\Controls\LogViewer.Avalonia\LogViewer.Avalonia.csproj" />
<ProjectReference Include="..\..\Core\Common.Core\Common.Core.csproj" />
<ProjectReference Include="..\..\Core\NLog.Target.LogView.Core\NLog.Target.LogView.Core.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,10 @@
using LogViewer.Core;
using LogDataStore = LogViewer.Avalonia.Logging.LogDataStore;
namespace AvaloniaNlogNoDI.DataStores;
// Application-wide shared instance of the LogDataStore logging entries
public static class MainControlsDataStore
{
public static ILogDataStore DataStore { get; } = new LogDataStore();
}
@@ -0,0 +1,30 @@
using AvaloniaNlogNoDI.DataStores;
using LogViewer.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NLog.Target.LogView.Core.Extensions;
using System;
namespace AvaloniaNlogNoDI.Extensions;
public static class ServicesExtension
{
public static ILoggingBuilder AddNLogTargetsNoDI(this ILoggingBuilder builder, IConfiguration config)
{
// We need to use a shared instance of the DataStore to pass to the LogViewerControl
builder.Services.AddSingleton(MainControlsDataStore.DataStore);
// call core NLog ServiceExtension initializer
builder.AddNLogTargets(config);
return builder;
}
public static ILoggingBuilder AddNLogTargetsNoDI(this ILoggingBuilder builder, IConfiguration config, Action<DataStoreLoggerConfiguration> configure)
{
builder.AddNLogTargetsNoDI(config);
builder.Services.Configure(configure);
return builder;
}
}
@@ -0,0 +1,73 @@
using System;
using System.Drawing;
using Microsoft.Extensions.Logging;
using AvaloniaNlogNoDI.Extensions;
using Microsoft.Extensions.Configuration;
using Common.Core;
using Common.Core.Extensions;
namespace AvaloniaNlogNoDI.Helpers;
// application-wide DataStoreLogger Factory ... returns a wired up Logger instance
public static class LoggingHelper
{
#region Constructors
static LoggingHelper()
{
// retrieve the log level from 'appsettings'
string value = AppSettings<string>.Current("Logging:LogLevel", "Default") ?? "Information";
Enum.TryParse(value, out LogLevel logLevel);
IConfigurationRoot configuration = new ConfigurationBuilder()
.Initialize()
.Build();
// wire up the loggers
Factory = LoggerFactory.Create(builder => builder
// visual debugging tools
.AddNLogTargetsNoDI(configuration)
// uncomment to use custom logging colors (note: System.Drawing namespace)
//
//.AddNLogTargets(configuration, options =>
//{
// options.Colors[LogLevel.Trace] = new()
// {
// Foreground = Color.White,
// Background = Color.DarkGray
// };
// options.Colors[LogLevel.Debug] = new()
// {
// Foreground = Color.White,
// Background = Color.Gray
// };
// options.Colors[LogLevel.Information] = new()
// {
// Foreground = Color.White,
// Background = Color.DodgerBlue
// };
// options.Colors[LogLevel.Warning] = new()
// {
// Foreground = Color.White,
// Background = Color.Orchid
// };
//})
// set minimum log level from 'appsettings*.json'
.SetMinimumLevel(logLevel));
}
#endregion
#region Properties
public static ILoggerFactory Factory { get; }
#endregion
}
@@ -0,0 +1,21 @@
using Avalonia;
using System;
namespace AvaloniaNlogNoDI;
internal class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args)
=> BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
}
@@ -0,0 +1,19 @@
{
"profiles": {
"Development": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
},
"Staging": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Staging"
}
},
"Production": {
"commandName": "Project"
}
}
}
@@ -0,0 +1,19 @@
<Window x:Class="AvaloniaNlogNoDI.Views.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
xmlns:control="clr-namespace:LogViewer.Avalonia;assembly=LogViewer.Avalonia"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="C# AVALONIA MINIMAL | LogViewer Control Example - Dot Net 7.0"
Icon="avares://Avalonia.Resources/Assets/avalonia-logo.ico"
WindowStartupLocation="CenterScreen" Height="634" Width="600">
<control:LogViewerControl x:Name="LogViewerControl" />
</Window>
@@ -0,0 +1,48 @@
using Avalonia.Controls;
using AvaloniaNlogNoDI.DataStores;
using AvaloniaNlogNoDI.Helpers;
using LogViewer.Core;
using Microsoft.Extensions.Logging;
using RandomLogging.Service;
using System;
using System.Reflection;
using System.Threading;
namespace AvaloniaNlogNoDI.Views;
public partial class MainWindow : Window, ILogDataStoreImpl
{
public MainWindow()
{
InitializeComponent();
// Initialize service and pass in the Logger
RandomLoggingService service = new(new Logger<RandomLoggingService>(LoggingHelper.Factory));
// Get the Launch mode
bool isDevelopment = string.Equals(Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES"), "debug",
StringComparison.InvariantCultureIgnoreCase);
// initialize a logger & EventId
Logger<MainWindow> logger = new Logger<MainWindow>(LoggingHelper.Factory);
EventId eventId = new EventId(id: 0, name: Assembly.GetEntryAssembly()!.GetName().Name);
// log a test pattern for each log level
logger.TestPattern(eventId: eventId);
// log that we have started...
logger.Emit(eventId, LogLevel.Information, $"Running in {(isDevelopment ? "Debug" : "Release")} mode");
// Start generating log entries
_ = service.StartAsync(CancellationToken.None);
// manually wire up the logging to the view ... the control will show backlog entries...
DataStore = MainControlsDataStore.DataStore;
// we can't bind the controls' DataContext to a static object, so assign the DataStore to the Window
// and pass a reference to the Window itself
LogViewerControl.DataContext = this;
}
public ILogDataStore DataStore { get; init; }
}
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embeded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>
@@ -0,0 +1,29 @@
{
"Logging": {
"LogLevel": {
"Default": "Trace",
"System.Net.Http.HttpClient": "Trace"
}
},
"NLog": {
"throwConfigExceptions": true,
"targets": {
"async": true,
"logconsole": {
"type": "Console",
"layout": "${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}"
},
"DataStoreLogger": {
"type": "DataStoreLogger",
"layout": "${message}"
}
},
"rules": [
{
"logger": "*",
"minLevel": "Trace",
"writeTo": "logconsole, DataStoreLogger"
}
]
}
}
@@ -0,0 +1,29 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"System.Net.Http.HttpClient": "Warning"
}
},
"NLog": {
"throwConfigExceptions": true,
"targets": {
"async": true,
"logconsole": {
"type": "Console",
"layout": "${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}"
},
"DataStoreLogger": {
"type": "DataStoreLogger",
"layout": "${message}"
}
},
"rules": [
{
"logger": "*",
"minLevel": "Warn",
"writeTo": "logconsole, DataStoreLogger"
}
]
}
}
@@ -0,0 +1,29 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"System.Net.Http.HttpClient": "Information"
}
},
"NLog": {
"throwConfigExceptions": true,
"targets": {
"async": true,
"logconsole": {
"type": "Console",
"layout": "${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}"
},
"DataStoreLogger": {
"type": "DataStoreLogger",
"layout": "${message}"
}
},
"rules": [
{
"logger": "*",
"minLevel": "Info",
"writeTo": "logconsole, DataStoreLogger"
}
]
}
}
@@ -0,0 +1,14 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AvaloniaSerilogDI"
x:Class="AvaloniaSerilogDI.App">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
</Application.Styles>
</Application>
@@ -0,0 +1,207 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using AvaloniaSerilogDI.ViewModels;
using AvaloniaSerilogDI.Views;
using Common.Core.Extensions;
using LogViewer.Avalonia;
using LogViewer.Core;
using MessageBox.Avalonia;
using MessageBox.Avalonia.Enums;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using RandomLogging.Service;
using Serilog;
using Serilog.Sinks.LogView.Core;
using System;
using System.Drawing;
using System.Reflection;
using System.Threading;
using Icon = MessageBox.Avalonia.Enums.Icon;
namespace AvaloniaSerilogDI;
public partial class App : Application
{
#region Constructors
public override void Initialize()
=> AvaloniaXamlLoader.Load(this);
#endregion
#region Fields
private IHost? _host;
private CancellationTokenSource? _cancellationTokenSource;
#endregion
#region Methods
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
// Line below is needed to remove Avalonia data validation.
// Without this line you will get duplicate validations from both Avalonia and CT
ExpressionObserver.DataValidators.RemoveAll(x => x is DataAnnotationsValidationPlugin);
// catch all unhandled errors
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
HostApplicationBuilder builder = Host.CreateApplicationBuilder();
builder
// Register the Random Logging Service
.AddRandomBackgroundService()
// visual debugging tools
.AddLogViewer();
IServiceCollection services = builder.Services;
// Serilog Logger
// Azure: https://devblogs.microsoft.com/dotnet/asp-net-core-logging/
// ApplicationInsights: https://github.com/serilog-contrib/serilog-sinks-applicationinsights
// AmazonCloudWatch: https://blog.ivankahl.com/logging-dotnet-to-aws-cloudwatch-using-serilog/
// video: https://www.youtube.com/watch?v=nVAkSBpsuTk (How Structured Logging With Serilog Can Make Your Life Easier)
// video: https://www.youtube.com/watch?v=_iryZxv8Rxw (C# Logging with Serilog and Seq - Structured Logging Made Easy)
// ps: docker run -d --restart unless-stopped --name seq -e ACCEPT_EULA=Y -v c:\WIP\LogData:/data -p 8081:80 datalust/seq:latest
// docker rmi datalust/seq --force
// ref: https://stackoverflow.com/questions/66304596/how-to-dependency-inject-serilog-into-the-rest-of-my-classes-in-net-console-app
services.AddLogging(configure: cfg =>
{
Log.Logger = new LoggerConfiguration()
//Serilog.Core.Logger logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.WriteTo.DataStoreLoggerSink(
dataStoreProvider: () => _host!.Services.TryGetService<ILogDataStore>()!
//dataStoreProvider: () => _host!.Services.TryGetService<ILogDataStore>()!,
//options =>
//{
// options.Colors[LogLevel.Trace] = new()
// {
// Foreground = Color.White,
// Background = Color.DarkGray
// };
// options.Colors[LogLevel.Debug] = new()
// {
// Foreground = Color.White,
// Background = Color.Gray
// };
// options.Colors[LogLevel.Information] = new()
// {
// Foreground = Color.White,
// Background = Color.DodgerBlue
// };
// options.Colors[LogLevel.Warning] = new()
// {
// Foreground = Color.White,
// Background = Color.Orchid
// };
//}
)
.CreateLogger();
cfg.ClearProviders()
.AddSerilog(Log.Logger);
});
services
.AddSingleton<MainViewModel>()
.AddSingleton(service => new MainWindow
{
DataContext = service.GetRequiredService<MainViewModel>()
});
_host = builder.Build();
_cancellationTokenSource = new();
try
{
LogStartingMode();
// set and show
desktop.MainWindow = _host.Services.GetRequiredService<MainWindow>();
desktop.ShutdownRequested += OnShutdownRequested;
// startup background services
_ = _host.StartAsync(_cancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
// skip
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
ShowMessageBox("Unhandled Error", ex.Message);
CleanUp();
return;
}
}
base.OnFrameworkInitializationCompleted();
}
private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e)
=> CleanUp();
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Exception exception = (Exception)e.ExceptionObject;
Log.Fatal(exception, "Application terminated unexpectedly");
ShowMessageBox("Unhandled Error", exception.Message);
CleanUp();
}
private void ShowMessageBox(string title, string message)
{
MessageBox.Avalonia.BaseWindows.Base.IMsBoxWindow<ButtonResult> messageBoxStandardWindow = MessageBoxManager
.GetMessageBoxStandardWindow(title, message, ButtonEnum.Ok, Icon.Stop);
messageBoxStandardWindow.Show();
}
private void LogStartingMode()
{
// Get the Launch mode
bool isDevelopment = string.Equals(Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES"), "debug",
StringComparison.InvariantCultureIgnoreCase);
// initialize a logger & EventId
ILogger<App> logger = _host!.Services.GetRequiredService<ILogger<App>>();
EventId eventId = new EventId(id: 0, name: Assembly.GetEntryAssembly()!.GetName().Name);
// log a test pattern for each log level
logger.TestPattern(eventId: eventId);
// log that we have started...
logger.Emit(eventId, LogLevel.Information, $"Running in {(isDevelopment ? "Debug" : "Release")} mode");
}
private void CleanUp()
{
// tell the background services that we are shutting down
_ = _host!.StopAsync(_cancellationTokenSource!.Token);
// flush logs
Log.CloseAndFlush();
}
#endregion
}
@@ -0,0 +1,49 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Content Include="appsettings.Development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.Production.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.2.0" />
<PackageReference Include="Serilog.Enrichers.Process" Version="2.0.2" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Seq" Version="5.2.2" />
<PackageReference Include="MessageBox.Avalonia" Version="2.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Resources\Avalonia.Resources\Avalonia.Resources.vbproj" />
<ProjectReference Include="..\..\Background Services\RandomLogging.Service\RandomLogging.Service.csproj" />
<ProjectReference Include="..\..\Controls\LogViewer.Avalonia\LogViewer.Avalonia.csproj" />
<ProjectReference Include="..\..\Core\Common.Core\Common.Core.csproj" />
<ProjectReference Include="..\..\Core\Serilog.Sinks.LogView.Core\Serilog.Sinks.LogView.Core.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,20 @@
using Avalonia;
using System;
namespace AvaloniaSerilogDI;
internal class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
}
@@ -0,0 +1,19 @@
{
"profiles": {
"Development": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
},
"Staging": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Staging"
}
},
"Production": {
"commandName": "Project"
}
}
}
@@ -0,0 +1,27 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using AvaloniaSerilogDI.ViewModels;
using System;
namespace AvaloniaSerilogDI;
public class ViewLocator : IDataTemplate
{
public IControl Build(object data)
{
string name = data.GetType().FullName!.Replace("ViewModel", "View");
Type? type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
return new TextBlock { Text = "Not Found: " + name };
}
public bool Match(object data)
{
return data is ViewModelBase;
}
}
@@ -0,0 +1,19 @@
using LogViewer.Core.ViewModels;
namespace AvaloniaSerilogDI.ViewModels;
public class MainViewModel : ViewModelBase
{
#region Constructor
public MainViewModel(LogViewerControlViewModel logViewer)
=> LogViewer = logViewer;
#endregion
#region Properties
public LogViewerControlViewModel LogViewer { get; }
#endregion
}
@@ -0,0 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace AvaloniaSerilogDI.ViewModels;
public class ViewModelBase : ObservableObject
{
}
@@ -0,0 +1,19 @@
<Window x:Class="AvaloniaSerilogDI.Views.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
xmlns:control="clr-namespace:LogViewer.Avalonia;assembly=LogViewer.Avalonia"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
Title="C# AVALONIA SeriLog | LogViewer Control Example - Dot Net 7.0"
Icon="avares://Avalonia.Resources/Assets/avalonia-logo.ico"
WindowStartupLocation="CenterScreen" Height="634" Width="600">
<control:LogViewerControl DataContext="{Binding LogViewer}" />
</Window>
@@ -0,0 +1,8 @@
using Avalonia.Controls;
namespace AvaloniaSerilogDI.Views;
public partial class MainWindow : Window
{
public MainWindow() => InitializeComponent();
}
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embeded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>
@@ -0,0 +1,45 @@
{
"Logging": {
"LogLevel": {
"Default": "Trace",
"System.Net.Http.HttpClient": "Trace"
}
},
"Serilog": {
"Using": [ "Serilog.Sinks.File" ],
"LevelSwitches": { "controlSwitch": "Verbose" },
"MinimumLevel": {
"Default": "Verbose",
"Override": {
"Microsoft": "Verbose"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {EventId} | {Message:lj} {NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "c:\\WIP\\LogData\\log-.txt",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"outputTemplate": "{Timestamp:G} {Message}{NewLine:1}{Exception:1}"
}
},
{
"Name": "File",
"Args": {
"path": "c:\\WIP\\LogData\\log-.json",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"formatter": "Serilog.Formatting.Json.JsonFormatter"
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ]
}
}
@@ -0,0 +1,46 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"System.Net.Http.HttpClient": "Warning"
}
},
"Serilog": {
"Using": [ "Serilog.Sinks.File" ],
"LevelSwitches": { "controlSwitch": "Warning" },
"MinimumLevel": {
"Default": "Warning",
"Override": {
"Microsoft": "Warning"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {EventId.Name} | {Message:lj} {NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "c:\\WIP\\LogData\\log-.txt",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"outputTemplate": "{Timestamp:G} {Message}{NewLine:1}{Exception:1}"
}
},
{
"Name": "File",
"Args": {
"path": "c:\\WIP\\LogData\\log-.json",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"formatter": "Serilog.Formatting.Json.JsonFormatter"
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ]
}
}
@@ -0,0 +1,46 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"System.Net.Http.HttpClient": "Information"
}
},
"Serilog": {
"Using": [ "Serilog.Sinks.File" ],
"LevelSwitches": { "controlSwitch": "Information" },
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {EventId.Name} | {Message:lj} {NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "c:\\WIP\\LogData\\log-.txt",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"outputTemplate": "{Timestamp:G} {Message}{NewLine:1}{Exception:1}"
}
},
{
"Name": "File",
"Args": {
"path": "c:\\WIP\\LogData\\log-.json",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"formatter": "Serilog.Formatting.Json.JsonFormatter"
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ]
}
}

Some files were not shown because too many files have changed in this diff Show More