August 22, 2024

Immediate Window in Visual Studio

Sometimes I find I want to dump an object that I see in debugging for further analysis, testing or just to get a feel for the shape of some data. To do this I stop at a location in the debugger where the data is visible grab a copy of the required variable and then switch to the VS Immediate Window.
In Immediate window I can dump a variables contents to a json file by typing in the following C#:

File.WriteAllText(@"c:\Somewhere\delme.json", Newtonsoft.Json.JsonConvert.SerializeObject(myobject, Formatting.Indented));

July 12, 2024

Time Providers

In .Net 8.0 there is now a means to inject a time provider. Here is a link https://blog.nimblepros.com/blogs/finally-an-abstraction-for-time-in-net/


1. It can be passed via dependency injection:
ServiceCollection services = new ServiceCollection();
...
services.AddSingleton<TimeProvider>(TimeProvider.System);

2. Pass it in to your constructor
private readonly TimeProvider _timeProvider;
public MyConstructor(..., TimeProvider timeProvider)
{
    _timeProvider = timeProvider;
}

3. Use it somewhere
void SomeMethod()
{
…
   var nowUtc = _timeProvider.GetUtcNow();
…
}

4. Use the FakeTimeProvider in unit tests
using Microsoft.Extensions.Time.Testing; // Get from Package from NuGet (same name as namespace)
...
FakeTimeProvider fakeTimeProvider = new();
fakeTimeProvider.SetUtcNow(new DateTimeOffset(2025, 3, 4, 13, 22, 42, new TimeSpan(0)));
...
fakeTimeProvider.Advance(new TimeSpan(entry.TimeMs * TimeSpan.TicksPerMillisecond));

April 11, 2024

Using Caller Context with Microsoft Logging

Here is the CallerContext and ILoggerExtension class

using System.Runtime.CompilerServices;

/// <summary>
/// A class to record the context of a method call.
/// Records the member name, file and line number of where the method call was made.
/// </summary>
public record CallerContext
{
    public CallerContext(
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string sourceFilePath = "",
        [CallerLineNumber] int sourceLineNumber = 0)
    {
        MemberName = memberName;
        FilePath = sourceFilePath;
       LineNumber = sourceLineNumber;
    }

    /// <summary>
    /// Member where the call was made
    /// </summary>
    public string MemberName { get; init; }
    /// <summary>
    /// File where the call was made
    /// </summary>
    public string FilePath { get; init; }
    /// <summary>
    /// Line number in the file where the call was made
    /// </summary>
    public int LineNumber { get; init; }
}

/// <summary>
/// To get the code context using caller member attributes
/// </summary>
public static class Code
{
    public static CallerContext Context(
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string sourceFilePath = "",
        [CallerLineNumber] int sourceLineNumber = 0)
    {
        return new CallerContext(memberName, sourceFilePath, sourceLineNumber);
    }
}

/// <summary>
/// ILogger extensions
/// </summary>
public static class ILoggerExt
{
    /// <summary>
    /// Log something but with extra Caller context information <seealso cref="CallerContext"/>
    /// </summary>
    /// <param name="logger">ILogger being invoked</param>
    /// <param name="logLevel">Level of the logging</param>
    /// <param name="context">The caller context <see cref="CallerContext"/></param>
    /// <param name="message">The message to log</param>
    /// <param name="args">The message parameters to log.</param>
    public static void Log(this ILogger logger,
        LogLevel logLevel,
        CallerContext context,
        string message,
        params object[] args)
    {
        Debug.Assert(logger != null, "trying to use a null logger to log something");
        var enhancedMessage = "{@CallerContext} "  + (message ?? "");

        Debug.Assert(args != null, "args cannot be null");

        object[] newArgs = new object[args.Length + 1];
        newArgs[0] = context; // Prepend the context argument
        Array.Copy(args, sourceIndex: 0, newArgs, destinationIndex:1, args.Length); // add the other arguments

        logger.Log(logLevel, enhancedMessage, newArgs.ToArray());
    }
}

Example Usage

ILogger logger = ...
logger.Log(new CallerContext(), LogLevel.Info, "Message with {@parameters} goes here", parameters);
// OR
logger.Log(Code.Context(), LogLevel.Info, "Message with {@parameters} goes here", parameters);