Skip to content

Instantly share code, notes, and snippets.

@bc3tech
Last active September 24, 2024 16:26
Show Gist options
  • Save bc3tech/cfd323a43d3cdc2b9bde78d3fc82fa23 to your computer and use it in GitHub Desktop.
Save bc3tech/cfd323a43d3cdc2b9bde78d3fc82fa23 to your computer and use it in GitHub Desktop.
Helpful classes for C# projects
/// <summary>
/// Created to handle *named* configuration
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Registers a type that will have all of its <see cref="IConfigureOptions{TOptions}"/>,
/// <see cref="IPostConfigureOptions{TOptions}"/>, and <see cref="IValidateOptions{TOptions}"/>
/// registered.
/// </summary>
/// <typeparam name="TConfigureOptions">The type that will configure options.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection ConfigureNamedOptions<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TConfigureOptions>(
this IServiceCollection services, string name)
where TConfigureOptions : class
{
services.AddOptions<TConfigureOptions>(name);
var configureType = typeof(TConfigureOptions);
bool added = false;
foreach (Type serviceType in FindConfigurationServices(configureType))
{
services.AddTransient(serviceType, configureType);
added = true;
}
services.AddTransient<IConfigureNamedOptions<TConfigureOptions>>(sp => new NamedConfigureFromConfigurationOptions<TConfigureOptions>(name, sp.GetRequiredService<IConfiguration>()));
if (!added)
{
throw new InvalidOperationException($"Configuration not added for {configureType.FullName}");
}
return services;
}
private static IEnumerable<Type> FindConfigurationServices(Type type)
{
foreach (Type t in GetInterfacesOnType(type))
{
if (t.IsGenericType)
{
Type gtd = t.GetGenericTypeDefinition();
if (gtd == typeof(IConfigureOptions<>) ||
gtd == typeof(IPostConfigureOptions<>) ||
gtd == typeof(IValidateOptions<>))
{
yield return t;
}
}
}
// Extracted the suppression to a local function as trimmer currently doesn't handle suppressions
// on iterator methods correctly.
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",
Justification = "This method only looks for interfaces referenced in its code. " +
"The trimmer will keep the interface and thus all of its implementations in that case. " +
"The call to GetInterfaces may return less results in trimmed apps, but it will " +
"include the interfaces this method looks for if they should be there.")]
static Type[] GetInterfacesOnType(Type t)
=> t.GetInterfaces();
}
}
namespace Microsoft.Extensions.Logging;
using System.Runtime.CompilerServices;
public static class LogExtensions
{
public static IDisposable CreateMethodScope(this ILogger logger, [CallerMemberName] string? methodName = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(methodName);
return logger.BeginScope(methodName)!;
}
public static void LogSurroundTraceDebug(this ILogger logger, string traceTemplate, Action action, string debugTemplate, params object[] args)
{
LogSurroundTraceDebug(logger, traceTemplate,
() =>
{
action();
return true; // So it becomes a Func<bool> to satisfies the overload
},
debugTemplate, args);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "Helper method")]
public static T LogSurroundTraceDebug<T>(this ILogger logger, string traceTemplate, Func<T> operation, string debugTemplate, params object[] args)
{
logger.LogIf(string.IsNullOrWhiteSpace(traceTemplate), LogLevel.Warning, "Empty Trace template");
logger.LogIf(string.IsNullOrWhiteSpace(debugTemplate), LogLevel.Warning, "Empty Debug template");
logger.LogTrace(traceTemplate, args);
try
{
var retVal = operation();
logger.LogDebug(debugTemplate, args);
return retVal;
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while executing the operation");
throw;
}
}
public static async Task LogSurroundTraceDebugAsync(this ILogger logger, string traceTemplate, Func<Task> operation, string debugTemplate, params object[] args)
{
await LogSurroundTraceDebugAsync(logger, traceTemplate,
async () =>
{
await operation();
return true;
},
debugTemplate, args).ConfigureAwait(false);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "Helper method")]
public static async Task<T> LogSurroundTraceDebugAsync<T>(this ILogger logger, string traceTemplate, Func<Task<T>> operation, string debugTemplate, params object[] args)
{
logger.LogIf(string.IsNullOrWhiteSpace(traceTemplate), LogLevel.Warning, "Empty Trace template");
logger.LogIf(string.IsNullOrWhiteSpace(debugTemplate), LogLevel.Warning, "Empty Debug template");
logger.LogTrace(traceTemplate, args);
try
{
var retVal = await operation().ConfigureAwait(false);
logger.LogDebug(debugTemplate, args);
return retVal;
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while executing the operation");
throw;
}
}
public static void LogIf(this ILogger logger, Func<bool> condition, LogLevel logLevel, string message, params object[] args) => logger.LogIf(condition(), logLevel, message, args);
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "Helper method")]
public static void LogIf(this ILogger logger, bool condition, LogLevel logLevel, string message, params object[] args)
{
if (logger.IsEnabled(logLevel) && condition)
{
logger.Log(logLevel, message, args);
}
}
}
using Microsoft.SemanticKernel;
using System.Text.RegularExpressions;
internal static partial class KernelArgumentsExtensions
{
private static readonly Regex promptKernelArgumentsFinder = KernelArgumentsPrompt();
public static void Validate(this KernelArguments args, string againstPrompt, ILoggerFactory? loggerFactory = null)
{
var log = loggerFactory?.CreateLogger<KernelArguments>();
using var logScope = log?.CreateMethodScope();
var matches = promptKernelArgumentsFinder.Matches(againstPrompt);
foreach (Match match in matches)
{
var key = match.Groups[1].Value;
if (!args.TryGetValue(key, out _))
{
log?.LogError("Validation failed for prompt. Expected argument {PromptArgument} to be defined in KernelArguments but it was not found.", key);
throw new ArgumentException($"KernelArguments does not contain prompt argument '{key}'");
}
log?.LogDebug("Matched prompt argument {PromptArgument}", key);
}
}
public static async Task<FunctionResult> InvokePromptWithValidationAsync(this Kernel kernel, string promptTemplate, KernelArguments arguments, string? templateFormat = null, IPromptTemplateFactory? promptTemplateFactory = null, CancellationToken cancellationToken = default)
{
arguments.Validate(promptTemplate, kernel.Services.GetService<ILoggerFactory>());
return await kernel.InvokePromptAsync(promptTemplate, arguments, templateFormat, promptTemplateFactory, cancellationToken).ConfigureAwait(false);
}
[GeneratedRegex(@"\{\{\$(\w+)\}\}", RegexOptions.Compiled)]
private static partial Regex KernelArgumentsPrompt();
public static async Task<ChatMessageContent> InvokePromptWithTemplatedSystemPromptAsync(this Kernel kernel, IPromptTemplate systemPrompt, ChatHistory chatMessageContents, PromptExecutionSettings? promptExecutionSettings = null, CancellationToken cancellationToken = default)
{
if (promptExecutionSettings is OpenAIPromptExecutionSettings openAIPromptSettings)
{
openAIPromptSettings.ChatSystemPrompt = await systemPrompt.RenderAsync(kernel, new(promptExecutionSettings), cancellationToken: cancellationToken);
}
return await kernel.GetRequiredService<IChatCompletionService>().GetChatMessageContentAsync(chatMessageContents, promptExecutionSettings, kernel, cancellationToken).ConfigureAwait(false);
}
public static async Task<FunctionResult> InvokePromptWithTemplatedSystemPromptAsync(this Kernel kernel, IPromptTemplate systemPrompt, string promptTemplate, KernelArguments? arguments = null, string? templateFormat = null, IPromptTemplateFactory? promptTemplateFactory = null, CancellationToken cancellationToken = default)
{
if (arguments?.ExecutionSettings?[PromptExecutionSettings.DefaultServiceId] is OpenAIPromptExecutionSettings openAIPromptSettings)
{
openAIPromptSettings.ChatSystemPrompt = await systemPrompt.RenderAsync(kernel, arguments, cancellationToken: cancellationToken);
}
return await kernel.InvokePromptAsync(promptTemplate, arguments, templateFormat, promptTemplateFactory, cancellationToken).ConfigureAwait(false);
}
}
internal static class StringExtensions
{
public static string? UnlessNullOrWhiteSpaceThen(this string? value, string? otherValue) => string.IsNullOrWhiteSpace(value) ? otherValue : value;
}
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
internal static class Throw
{
[return: NotNull]
public static string IfNullOrWhiteSpace([NotNull] string? value, string? message = null, [CallerArgumentExpression(nameof(value))] string? variableName = null) => string.IsNullOrWhiteSpace(value) ? throw new ArgumentException(variableName, message) : value;
[return: NotNull]
public static string IfNullOrEmpty([NotNull] string? value, string? message = null, [CallerArgumentExpression(nameof(value))] string? variableName = null) => string.IsNullOrEmpty(value) ? throw new ArgumentException(variableName, message) : value;
[return: NotNull]
public static T IfNull<T>([NotNull] T? value, string? message = null, [CallerArgumentExpression(nameof(value))] string? variableName = null) => value ?? throw new ArgumentNullException(variableName, message);
[return: NotNullIfNotNull(nameof(value))]
public static T? ArgExceptionIf<T>(T? value, Predicate<T?> check, string? message = null, [CallerArgumentExpression(nameof(value))] string? variableName = null) where T : class => !check(value) ? throw new ArgumentException(variableName, message) : value;
[return: NotNull]
public static T ArgExceptionIfNullOr<T>(T? value, Predicate<T> check, string? message = null, [CallerArgumentExpression(nameof(value))] string? variableName = null) where T : class =>
value is not null ? !check(value) ? throw new ArgumentException(variableName, message) : value : throw new ArgumentNullException(variableName, message);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment