Last active
September 24, 2024 16:26
-
-
Save bc3tech/cfd323a43d3cdc2b9bde78d3fc82fa23 to your computer and use it in GitHub Desktop.
Helpful classes for C# projects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <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(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal static class StringExtensions | |
{ | |
public static string? UnlessNullOrWhiteSpaceThen(this string? value, string? otherValue) => string.IsNullOrWhiteSpace(value) ? otherValue : value; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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