Using serilog with .NET MAUI - maui

I have a very simple .NET MAUI app (not a Blazor one). I've installed Seriolog and Serilog.Extensions.Logging (so I can use AppCenter). I have the following in my startup code to initialise Serilog
public static MauiApp CreateMauiApp()
{
// set up logging
var path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
path = Path.Combine(path, "debugcode.txt");
Log.Logger = new LoggerConfiguration()
//.MinimumLevel.Debug()
.WriteTo.File(path)
.WriteTo.AppCenterCrashes(restrictedToMinimumLevel: LogEventLevel.Information)
.WriteTo.Console()
.CreateLogger();
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.Logging.AddSerilog(Log.Logger);
return builder.Build();
}
This compiles without an issue. I've added into the main.xaml.cs the code for logging (the code is just the default .NET MAUI test app shipped with VisualStudio). The minimum debug line is commented out, but it makes no difference if it's not commented out.
protected override void OnDisappearing()
{
base.OnDisappearing();
Log.CloseAndFlush();
}
void OnCounterClicked(object sender, EventArgs e)
{
count++;
var nums = $"Current count: {count}";
CounterLabel.Text = nums;
Log.Debug($"***** Current count: {nums}");
SemanticScreenReader.Announce(CounterLabel.Text);
}
I have tried changing the debug level, but no matter what, nothing is showing in the console for the debug information.

Here is a working example:
Install These nuget packages:
Serilog
Serilog.Extensions.Logging
Serilog.Sinks.File
Code:
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
SetupSerilog();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
builder.Services.AddMauiBlazorWebView();
#if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools();
#endif
builder.Logging.AddSerilog(dispose: true);
return builder.Build();
}
private static void SetupSerilog()
{
var flushInterval = new TimeSpan(0, 0, 1);
var file = Path.Combine(FileSystem.AppDataDirectory, "MyApp.log");
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.File(file, flushToDiskInterval: flushInterval, encoding: System.Text.Encoding.UTF8, rollingInterval: RollingInterval.Day, retainedFileCountLimit: 22)
.CreateLogger();
}

Adding Serilog.Sinks.Xamarin and calling .WriteTo.AndroidLog() in LoggerConfiguration works. For iOS, I think that .WriteTo.NSLog() should work too.

A couple points.
Make sure you include using Serilog; in the classes that write to the log e.g. Log.Debug($"***** Current count: {nums}");
If writing to the AndroidLog, use Tools -> Android -> Device Log. Make sure your LoggerConfiguration includes something you can filter on, such as specifying the Source.
.WriteTo.AndroidLog()
.Enrich.WithProperty(Serilog.Core.Constants.SourceContextPropertyName, ".yourappname")
It's a good idea to also add Serilog.Sinks.Debug to your project and add .WriteTo.Debug() to your LoggerConfiguration.

Related

Using SignalR.Client in Unity - The method or operation is not implemented

I want to use SignalR in a UWP project on the HoloLens 2 and my application is partially running (one way works, one not).
The way it doesn't work
It does not run, when I have HTTPS-Redirection on my .Net-6-Web-API Server enabled and try to ignore it via the ServerCertificateCustomValidationCallback line on unity side.
.Net 6-Project:
app.UseHttpsRedirection(); is active
Unity:
private void Start()
{
_hubConnection = new HubConnectionBuilder()
.WithUrl("http://192.168.178.41:5266/Signaler", (opts) =>
{
opts.HttpMessageHandlerFactory = (message) =>
{
// bypass SSL certificate
if (message is HttpClientHandler clientHandler)
{
clientHandler.ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
}
return message;
};})
.WithAutomaticReconnect()
.Build();
Connect();
}
private async Task Connect()
{
_hubConnection.On<string>("SomeKey", (key) =>
{
Debug.Log($"New Key: {key}");
});
try
{
await _hubConnection.StartAsync(tokenSource.Token);
}
catch (Exception ex)
{ ... }
}
The error (shorted text) I get:
System.NotImplementedException: The method or operation is not implemented.
at XXXXX.Network.MySignalRClient+<>c.<Start>b__1_1 (System.Net.Http.HttpMessageHandler message) [0x0000a] in D:\Repositories\XXXXX\Unity\XXXXX\Assets\Scripts\NetworkClients\MySignalRClient.cs:31
at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.CreateHttpClient () [0x00118] in <d4f814eb48994df3870fe7c8338cc73a>:0
at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection..ctor (Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions httpConnectionOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) [0x0009f] in <d4f814eb48994df3870fe7c8338cc73a>:0
The mentioned line 31 is the line with the ServerCertificateCustomValidationCallback.
How does the application work?
If I disable the HTTPS-Redirection and remove the lines with the callback:
_hubConnection = new HubConnectionBuilder()
.WithUrl("http://192.168.178.41:5266/Signaler")
.WithAutomaticReconnect()
.Build();
Expected Behavior
HTTPS redirection should not be disabled extra on the server side, but the line with the callback on unity side should be enough.
SignalR Version
Microsoft.AspNetCore.SignalR.Client.7.0.0 and all needed referenced Libraries for .Net Standard 2.0
Anything else?
Typical Setup:
Some DLLs are missing in the screenshot, because they are already included by other plugins that I use in Unity. The following packages are not shown: System.Buffers, System.Memory, System.Numerics.Vectors, System.Runtime.CompilerServices.Unsafe
Another project:
I also created another empty .Net-6-Project in Visual Studio with Microsoft.AspNetCore.SignalR.Client.7.0.0 to check if I can connect, trigger the hub and get notified by the hub. There I use exactly the same lines to bypass the ssl certification and it works. So I guess there is something wrong with the .Net Standard 2.0 DLLs?

Connect MQTTnet with Azure IoT Hub

I have created a device that delivers messages. Now I want to send them to an Azure IoT Hub by using MQTTnet Version 2.4.0 because the .NET target Framework is on Version 4.5 and it is not my decision to change it.
My Question:
Is there any other or better method to do this
What MqttClientOption do I take best
What Parameters to set to which value to connect to the Hub
I have tried almost every combination of values for the ClientId/UserName/Password as described here: https://learn.microsoft.com/en-us/azure/iot-hub/iot-hub-mqtt-support#using-the-mqtt-protocol-directly-as-a-device
but none of them worked for me
I have tried outside the Project and build a similar device on the current framework and it worked perfectly with the newer version of MQTTnet.
Sadly I don't get any kind of error message only a MqttCommunicationTimedOutException after about 10 seconds.
Thanks for your help I have been stuck at this problem for almost a week.
The following code snippet is a working example of the simulated device1 using the MQTT protocol directly to the Azure IoT Hub via the MQTTnet Version 2.4.0 library:
using MQTTnet;
using MQTTnet.Core;
using MQTTnet.Core.Client;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
using System;
using System.Text;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var options = new MqttClientTcpOptions()
{
Server = "myIoTHub.azure-devices.net",
Port = 8883,
ClientId = "device1",
UserName = "myIoTHub.azure-devices.net/device1/api-version=2018-06-30",
Password = "SharedAccessSignature sr=myIoTHub.azure-devices.net%2Fdevices%2Fdevice1&sig=****&se=1592830262",
ProtocolVersion = MQTTnet.Core.Serializer.MqttProtocolVersion.V311,
TlsOptions = new MqttClientTlsOptions() { UseTls = true },
CleanSession = true
};
var factory = new MqttClientFactory();
var mqttClient = factory.CreateMqttClient();
// handlers
mqttClient.Connected += delegate (object sender, EventArgs e)
{
Console.WriteLine("Connected");
};
mqttClient.Disconnected += delegate (object sender, EventArgs e)
{
Console.WriteLine("Disconnected");
};
mqttClient.ApplicationMessageReceived += delegate (object sender, MqttApplicationMessageReceivedEventArgs e)
{
Console.WriteLine(Encoding.ASCII.GetString(e.ApplicationMessage.Payload));
};
mqttClient.ConnectAsync(options).Wait();
// subscribe on the topics
var topicFilters = new[] {
new TopicFilter("devices/device1/messages/devicebound/#", MqttQualityOfServiceLevel.AtLeastOnce),
new TopicFilter("$iothub/twin/PATCH/properties/desired/#", MqttQualityOfServiceLevel.AtLeastOnce),
new TopicFilter("$iothub/methods/POST/#", MqttQualityOfServiceLevel.AtLeastOnce)
};
mqttClient.SubscribeAsync(topicFilters).Wait();
// publish message
var topic = $"devices/device1/messages/events/$.ct=application%2Fjson&$.ce=utf-8";
var payload = Encoding.ASCII.GetBytes("Hello IoT Hub");
var message = new MqttApplicationMessage(topic, payload, MqttQualityOfServiceLevel.AtLeastOnce, false);
mqttClient.PublishAsync(message);
Console.Read();
}
}
}
and the following screen snippet shows an example of the output for updating a desired twin property color and receiving a C2D message:

Cannot attach database file when using Entity Framework Core Migration commands

I am using EntityFramework Core commands to migration database. The command I am using is like the docs suggests: dnx . ef migration apply. The problem is when specifying AttachDbFileName in connection string, the following error appear: Unable to Attach database file as database xxxxxxx. This is the connection string I am using:
Data Source=(LocalDB)\mssqllocaldb;Integrated Security=True;Initial Catalog=EfGetStarted2;AttachDbFileName=D:\EfGetStarted2.mdf
Please help how to attach the db file to another location.
Thanks
EF core seem to have troubles with AttachDbFileName or doesn't handle it at all.
EnsureDeleted changes the database name to master but keeps any AttachDbFileName value, which leads to an error since we cannot attach the master database to another file.
EnsureCreated opens a connection using the provided AttachDbFileName value, which leads to an error since the file of the database we want to create does not yet exist.
EF6 has some logic to handle these use cases, see SqlProviderServices.DbCreateDatabase, so everything worked quite fine.
As a workaround I wrote some hacky code to handle these scenarios:
public static void EnsureDatabase(this DbContext context, bool reset = false)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (reset)
{
try
{
context.Database.EnsureDeleted();
}
catch (SqlException ex) when (ex.Number == 1801)
{
// HACK: EF doesn't interpret error 1801 as already existing database
ExecuteStatement(context, BuildDropStatement);
}
catch (SqlException ex) when (ex.Number == 1832)
{
// nothing to do here (see below)
}
}
try
{
context.Database.EnsureCreated();
}
catch (SqlException ex) when (ex.Number == 1832)
{
// HACK: EF doesn't interpret error 1832 as non existing database
ExecuteStatement(context, BuildCreateStatement);
// this takes some time (?)
WaitDatabaseCreated(context);
// re-ensure create for tables and stuff
context.Database.EnsureCreated();
}
}
private static void WaitDatabaseCreated(DbContext context)
{
var timeout = DateTime.UtcNow + TimeSpan.FromMinutes(1);
while (true)
{
try
{
context.Database.OpenConnection();
context.Database.CloseConnection();
}
catch (SqlException)
{
if (DateTime.UtcNow > timeout)
throw;
continue;
}
break;
}
}
private static void ExecuteStatement(DbContext context, Func<SqlConnectionStringBuilder, string> statement)
{
var builder = new SqlConnectionStringBuilder(context.Database.GetDbConnection().ConnectionString);
using (var connection = new SqlConnection($"Data Source={builder.DataSource}"))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = statement(builder);
command.ExecuteNonQuery();
}
}
}
private static string BuildDropStatement(SqlConnectionStringBuilder builder)
{
var database = builder.InitialCatalog;
return $"drop database [{database}]";
}
private static string BuildCreateStatement(SqlConnectionStringBuilder builder)
{
var database = builder.InitialCatalog;
var datafile = builder.AttachDBFilename;
var dataname = Path.GetFileNameWithoutExtension(datafile);
var logfile = Path.ChangeExtension(datafile, ".ldf");
var logname = dataname + "_log";
return $"create database [{database}] on primary (name = '{dataname}', filename = '{datafile}') log on (name = '{logname}', filename = '{logfile}')";
}
It's far from nice, but I'm using it for integration testing anyway. For "real world" scenarios using EF migrations should be the way to go, but maybe the root cause of this issue is the same...
Update
The next version will include support for AttachDBFilename.
There may be a different *.mdf file already attached to a database named EfGetStarted2... Try dropping/detaching that database then try again.
You might also be running into problems if the user LocalDB is running as doesn't have correct permissions to the path.

Entity Framework Migrations - How to create a Unit Test to Ensure Migrations Model is up to Date?

I am using continuous integration with TeamCity, NUnit, and Git. I recently migrated (pardon the pun) from FluentMigrator to Entity Framework Migrations. I primarily did this to take advantage of the scaffolding functionality therein.
However, it is potentially possible to check in some changes to source control without having first scaffolded the changes into migrations (imagine the scenario where the application was not run before committing and pushing the commit). I am using a pre-tested commit workflow, so I would like to detect this problem in the pre-test rather than waiting until calling migrate.exe (which would be too late in my workflow and break the "green repository").
My question is, how to create a unit/integration test to detect when the migrations model doesn't match the context model so I can fail the build before attempting to migrate? Do note that I expect it not to match the database and would prefer not to access the database from the test.
I tried this approach:
[Test]
public void CheckWhetherEntityFrameworkMigrationsContextIsUpToDate()
{
Assert.DoesNotThrow(() =>
{
CallDatabase();
});
}
private void CallDatabase()
{
using (var ctx = new MyContext("SERVER=(local);DATABASE=MyDatabase;Integrated Security=True;"))
{
var tenant = (from t in ctx.Tenant
select t).FirstOrDefault();
}
}
But this will always fail when there are pending migrations (rather than just in the case where the migrations model is not in sync with the context model, which is what I am after).
Update
I have added a work item on the EntityFramework project for this issue. Hopefully, they will look into adding a way to do this.
In case anybody finds this useful, I test migrations by running all of them against a test database, using the following code:
[TestClass]
public class MigrationsTests
{
[TestMethod]
public void RunAll()
{
var configuration = new Configuration();
var migrator = new DbMigrator(configuration);
// back to 0
migrator.Update("0");
// up to current
migrator.Update();
// back to 0
migrator.Update("0");
}
}
This tests all Up and Down migrations, as well as detecting pending changes when automatic migrations is turned off.
NOTE: Make sure you run this against a test database. In my case the test project's app.config has the connectionString to the test database (just a local SQLExpress instance).
Here is some code that will check whether all model changes have been scaffolded to migrations or not.
bool HasPendingModelChanges()
{
// NOTE: Using MigratorScriptingDecorator so changes won't be made to the database
var migrationsConfiguration = new Migrations.Configuration();
var migrator = new DbMigrator(migrationsConfiguration);
var scriptingMigrator = new MigratorScriptingDecorator(migrator);
try
{
// NOTE: Using InitialDatabase so history won't be read from the database
scriptingMigrator.ScriptUpdate(DbMigrator.InitialDatabase, null);
}
catch (AutomaticMigrationsDisabledException)
{
return true;
}
return false;
}
I wanted the best of all the answers given here so far so here's what I've come up with:
[TestClass()]
public class MigrationsTests
{
[TestMethod()]
public void MigrationsUpDownTest()
{
// Unit tests don't have a DataDirectory by default to store DB in
AppDomain.CurrentDomain.SetData("DataDirectory", System.IO.Directory.GetCurrentDirectory());
// Drop and recreate database
BoxContext db = new BoxContext();
db.Database.Delete();
var configuration = new Migrations.Configuration();
var migrator = new DbMigrator(configuration);
// Retrieve migrations
List<string> migrations = new List<string>;
migrations.AddRange(migrator.GetLocalMigrations());
try
{
for (int index = 0; index < migrations.Count; index++)
{
migrator.Update(migrations[index]);
if (index > 0) {
migrator.Update(migrations[index - 1]);
} else {
migrator.Update("0"); //special case to revert initial migration
}
}
migrator.Update(migrations.Last());
}
catch (SqlException ex)
{
Assert.Fail("Should not have any errors when running migrations up and down: " + ex.Errors[0].Message.ToString());
}
// Optional: delete database
db.Database.Delete();
}
[TestMethod()]
public void PendingModelChangesTest()
{
// NOTE: Using MigratorScriptingDecorator so changes won't be made to the database
var migrationsConfiguration = new Migrations.Configuration();
var migrator = new DbMigrator(migrationsConfiguration);
var scriptingMigrator = new MigratorScriptingDecorator(migrator);
try
{
// NOTE: Using InitialDatabase so history won't be read from the database
scriptingMigrator.ScriptUpdate(DbMigrator.InitialDatabase, null);
}
catch (AutomaticMigrationsDisabledException)
{
Assert.Fail("Should be no pending model changes/migrations should cover all model changes.");
}
}
}
Couple things worth noting:
You need to install Entity Framework in your test project
You need to add [assembly: InternalsVisibleTo("MyTestsProject")] to the top of your Configuration class
You need to specify an Entity Framework connection string in App.config that is only for test project since database will be deleted and recreated frequently - if running on build machine keep in mind that parallel runs could result in conflicts so you may want to change string per build
I think this works better than Pablo Romeo's code. This upgrades and then downgrades one step again to catch missing items in the downgrade scripts.
[TestFixture]
class MigrationTest
{
[Test]
public void RunAll()
{
var configuration = new Configuration();
var migrator = new DbMigrator(configuration);
// Retrieve migrations
List<string> migrations = new List<string> {"0"}; // Not sure if "0" is more zero than the first item in list of local migrations
migrations.AddRange(migrator.GetLocalMigrations());
migrator.Update(migrations.First());
// Doe een stapje naar voren, en een stapje terug (www.youtube.com/watch?v=2sg1KAxuWKI)
// (Dutch pun) meaning: take a small step forward, and a small step back ;)
for (int index = 0; index < migrations.Count; index++)
{
migrator.Update(migrations[index]);
if (index > 0)
migrator.Update(migrations[index - 1]);
}
migrator.Update(migrations.Last());
migrator.Update(migrations.First());
}
}
EF Core has a pretty straight forward method:
myDbContext.Database.Migrate();

ADO.NET - Bad Practice?

I was reading an article in MSDN several months ago and have recently started using the following snippet to execute ADO.NET code, but I get the feeling it could be bad. Am I over reacting or is it perfectly acceptable?
private void Execute(Action<SqlConnection> action)
{
SqlConnection conn = null;
try {
conn = new SqlConnection(ConnectionString);
conn.Open();
action.Invoke(conn);
} finally {
if (conn != null && conn.State == ConnectionState.Open) {
try {
conn.Close();
} catch {
}
}
}
}
public bool GetSomethingById() {
SomeThing aSomething = null
bool valid = false;
Execute(conn =>
{
using (SqlCommand cmd = conn.CreateCommand()) {
cmd.CommandText = ....
...
SqlDataReader reader = cmd.ExecuteReader();
...
aSomething = new SomeThing(Convert.ToString(reader["aDbField"]));
}
});
return aSomething;
}
What is the point of doing that when you can do this?
public SomeThing GetSomethingById(int id)
{
using (var con = new SqlConnection(ConnectionString))
{
con.Open();
using (var cmd = con.CreateCommand())
{
// prepare command
using (var rdr = cmd.ExecuteReader())
{
// read fields
return new SomeThing(data);
}
}
}
}
You can promote code reuse by doing something like this.
public static void ExecuteToReader(string connectionString, string commandText, IEnumerable<KeyValuePair<string, object>> parameters, Action<IDataReader> action)
{
using (var con = new SqlConnection(connectionString))
{
con.Open();
using (var cmd = con.CreateCommand())
{
cmd.CommandText = commandText;
foreach (var pair in parameters)
{
var parameter = cmd.CreateParameter();
parameter.ParameterName = pair.Key;
parameter.Value = pair.Value;
cmd.Parameters.Add(parameter);
}
using (var rdr = cmd.ExecuteReader())
{
action(rdr);
}
}
}
}
You could use it like this:
//At the top create an alias
using DbParams = Dictionary<string, object>;
ExecuteToReader(
connectionString,
commandText,
new DbParams() { { "key1", 1 }, { "key2", 2 } }),
reader =>
{
// ...
// No need to dispose
}
)
IMHO it is indeed a bad practice, since you're creating and opening a new database-connection for every statement that you execute.
Why is it bad:
performance wise (although connection pooling helps decrease the performance hit): you should open your connection, execute the statements that have to be executed, and close the connection when you don't know when the next statement will be executed.
but certainly context-wise. I mean: how will you handle transactions ? Where are your transaction boundaries ? Your application-layer knows when a transaction has to be started and committed, but you're unable to span multiple statements into the same sql-transaction with this way of working.
This is a very reasonable approach to use.
By wrapping your connection logic into a method which takes an Action<SqlConnection>, you're helping prevent duplicated code and the potential for introduced error. Since we can now use lambdas, this becomes an easy, safe way to handle this situation.
That's acceptable. I've created a SqlUtilities class two years ago that had a similar method. You can take it one step further if you like.
EDIT: Couldn't find the code, but I typed a small example (probably with many syntax errors ;))
SQLUtilities
public delegate T CreateMethod<T> (SqlDataReader reader);
public static T CreateEntity<T>(string query, CreateMethod<T> createMethod, params SqlParameter[] parameters) {
// Open the Sql connection
// Create a Sql command with the query/sp and parameters
SqlDataReader reader = cmd.ExecuteReader();
return createMethod(reader);
// Probably some finally statements or using-closures etc. etc.
}
Calling code
private SomeThing Create(SqlDataReader reader) {
SomeThing something = new SomeThing();
something.ID = Convert.ToIn32(reader["ID"]);
...
return something;
}
public SomeThing GetSomeThingByID (int id) {
return SqlUtilities.CreateEntity<SomeThing> ("something_getbyid", Create, ....);
}
Of course you could use a lambda expression instead of the Create method, and you could easily make a CreateCollection method and reuse the existing Create method.
However if this is a new project. Check out LINQ to entities. Is far easier and flexible than ADO.Net.
Well, In my opinion check what you do before going through it.Something that is working doesn't mean it is best and good programming practice.Check out and find a concrete example and benefit of using it.But if you are considering using for big projects it would be nice using frameworks like NHibernate.Because there are a lot projects even frameworks developed based on it,like http://www.cuyahoga-project.org/.