diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..99b7926
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,15 @@
+version: 2
+updates:
+ - package-ecosystem: nuget
+ directory: /src
+ schedule:
+ interval: weekly
+ groups:
+ nuget-dependencies:
+ patterns:
+ - "*"
+
+ - package-ecosystem: github-actions
+ directory: /
+ schedule:
+ interval: weekly
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 51663e0..5f07b1f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -28,7 +28,9 @@ jobs:
- name: Set up .NET
uses: actions/setup-dotnet@v5
with:
- dotnet-version: 10.0.x
+ dotnet-version: |
+ 8.0.x
+ 10.0.x
- name: Restore dependencies
run: dotnet restore src/core.slnx
@@ -49,7 +51,15 @@ jobs:
--configuration Release
--no-build
--output artifacts
- -p:PackageVersion=1.0.0-ci.${{ github.run_number }}
+ -p:Version=2.0.0-ci.${{ github.run_number }}
+
+ - name: Pack ASP.NET Core integration
+ run: >-
+ dotnet pack src/Core.AspNetCore/Core.AspNetCore.csproj
+ --configuration Release
+ --no-build
+ --output artifacts
+ -p:Version=2.0.0-ci.${{ github.run_number }}
- name: Pack DB
run: >-
@@ -57,7 +67,7 @@ jobs:
--configuration Release
--no-build
--output artifacts
- -p:PackageVersion=1.0.0-ci.${{ github.run_number }}
+ -p:Version=2.0.0-ci.${{ github.run_number }}
- name: Upload packages
uses: actions/upload-artifact@v7
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index c0c5b9f..bcb2ed9 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -15,7 +15,7 @@ env:
jobs:
publish:
- name: Publish Core and DB
+ name: Publish KitchenPC packages
runs-on: ubuntu-latest
environment: nuget
@@ -37,7 +37,9 @@ jobs:
- name: Set up .NET
uses: actions/setup-dotnet@v5
with:
- dotnet-version: 10.0.x
+ dotnet-version: |
+ 8.0.x
+ 10.0.x
- name: Restore dependencies
run: dotnet restore src/core.slnx
@@ -58,7 +60,7 @@ jobs:
--configuration Release
--no-build
--output artifacts
- -p:PackageVersion=${{ steps.version.outputs.value }}
+ -p:Version=${{ steps.version.outputs.value }}
- name: Pack DB
run: >-
@@ -66,7 +68,15 @@ jobs:
--configuration Release
--no-build
--output artifacts
- -p:PackageVersion=${{ steps.version.outputs.value }}
+ -p:Version=${{ steps.version.outputs.value }}
+
+ - name: Pack ASP.NET Core integration
+ run: >-
+ dotnet pack src/Core.AspNetCore/Core.AspNetCore.csproj
+ --configuration Release
+ --no-build
+ --output artifacts
+ -p:Version=${{ steps.version.outputs.value }}
- name: Upload release packages
uses: actions/upload-artifact@v7
@@ -96,3 +106,11 @@ jobs:
--api-key ${{ steps.nuget-login.outputs.NUGET_API_KEY }}
--source https://api.nuget.org/v3/index.json
--skip-duplicate
+
+ - name: Publish ASP.NET Core integration to NuGet
+ run: >-
+ dotnet nuget push
+ artifacts/KitchenPC.Core.AspNetCore.${{ steps.version.outputs.value }}.nupkg
+ --api-key ${{ steps.nuget-login.outputs.NUGET_API_KEY }}
+ --source https://api.nuget.org/v3/index.json
+ --skip-duplicate
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..6c87c4a
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,7 @@
+
+
+ true
+ all
+ $(WarningsAsErrors);NU1901;NU1902;NU1903;NU1904
+
+
diff --git a/README.md b/README.md
index 5557acf..87e891c 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ Example applications and a small static data snapshot are available in the
Building and Testing
====
-Install the .NET 10 SDK, then restore, build, and test from the repository root:
+Install the .NET 8 and .NET 10 SDKs, then restore, build, and test from the repository root:
```bash
dotnet restore src/core.slnx
@@ -37,7 +37,20 @@ dotnet build src/core.slnx --configuration Release --no-restore
dotnet test src/UnitTests/UnitTests.csproj --configuration Release --no-build --no-restore
```
-The build includes `KitchenPC.Core`, `KitchenPC.DB`, and the unit tests.
+The build includes `KitchenPC.Core`, `KitchenPC.DB`, `KitchenPC.Core.AspNetCore`, and the unit tests.
+
+`KitchenPC.Core` contains the engine and static context. Add `KitchenPC.DB` when using PostgreSQL,
+and add `KitchenPC.Core.AspNetCore` only when registering a context with ASP.NET Core dependency
+injection. Applications can connect KitchenPC to standard Microsoft logging through either context
+builder:
+
+```csharp
+var context = DBContext.Configure
+ .Logging(loggerFactory)
+ .Adapter(/* database adapter configuration */)
+ .Identity(() => AuthIdentity.Anonymous)
+ .Create();
+```
Database Schema Naming
====
@@ -80,11 +93,13 @@ sample-data startup and memory measurements.
Packages and Releases
====
-Every push and pull request builds and tests the solution, then creates matching prerelease packages for CI validation. Version tags publish `KitchenPC.Core` and `KitchenPC.DB` to NuGet with the same version. For example:
+Every push and pull request builds and tests the solution, then creates matching prerelease packages
+for CI validation. Version tags publish `KitchenPC.Core`, `KitchenPC.DB`, and
+`KitchenPC.Core.AspNetCore` to NuGet with the same version. For example:
```bash
-git tag -a v1.0.0 -m "KitchenPC 1.0.0"
-git push origin v1.0.0
+git tag -a v2.0.0 -m "KitchenPC 2.0.0"
+git push origin v2.0.0
```
NuGet package versions are immutable. Always increment the version for a subsequent release.
diff --git a/src/Core.AspNetCore/Core.AspNetCore.csproj b/src/Core.AspNetCore/Core.AspNetCore.csproj
new file mode 100644
index 0000000..2d71603
--- /dev/null
+++ b/src/Core.AspNetCore/Core.AspNetCore.csproj
@@ -0,0 +1,32 @@
+
+
+ net8.0;net10.0
+ KitchenPC.Core.AspNetCore
+ KitchenPC.Core
+ ASP.NET Core integration for the KitchenPC recipe engine
+ Mike Christensen
+ KitchenPC
+ KitchenPC.Core.AspNetCore
+ 2.0.0
+ https://github.com/KitchenPC/core
+ https://github.com/KitchenPC/core.git
+ git
+ MIT
+ README.md
+ false
+ recipes;aspnetcore;dependency-injection
+ true
+ true
+ true
+ snupkg
+ true
+ $(NoWarn);1591
+
+
+
+
+
+
+
+
+
diff --git a/src/Core/Middleware/KPCMiddleware.cs b/src/Core.AspNetCore/KPCMiddleware.cs
similarity index 50%
rename from src/Core/Middleware/KPCMiddleware.cs
rename to src/Core.AspNetCore/KPCMiddleware.cs
index d51d39a..dd3f941 100644
--- a/src/Core/Middleware/KPCMiddleware.cs
+++ b/src/Core.AspNetCore/KPCMiddleware.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Security.Claims;
using KitchenPC.Core.Context;
using Microsoft.AspNetCore.Http;
@@ -8,33 +8,31 @@ namespace KitchenPC.Core.Middleware;
public static class KPCMiddleware
{
- ///
- /// Adds KitchenPC OWIN Middleware components into Service Collection
- ///
- ///
- ///
+ /// Adds a configured KitchenPC context to an ASP.NET Core service collection.
public static void AddKPCContext(
this IServiceCollection services,
IConfiguration configuration
)
where T : class, IKPCContext
{
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(configuration);
+
var kpcContext = configuration.InitializeContext();
services.AddHttpContextAccessor();
services.AddScoped(ctx =>
{
- IHttpContextAccessor contextAccessor = ctx.GetService();
+ var contextAccessor = ctx.GetService();
if (contextAccessor?.HttpContext?.User?.Identity?.IsAuthenticated == true)
{
- string id = contextAccessor.HttpContext.User.FindFirst(ClaimTypes.Sid)?.Value;
- string alias = contextAccessor.HttpContext.User.FindFirst(ClaimTypes.Name)?.Value;
+ var id = contextAccessor.HttpContext.User.FindFirst(ClaimTypes.Sid)?.Value;
+ var alias = contextAccessor.HttpContext.User.FindFirst(ClaimTypes.Name)?.Value;
- if (Guid.TryParse(id, out Guid guidId) && !string.IsNullOrWhiteSpace(alias))
+ if (Guid.TryParse(id, out var guidId) && !string.IsNullOrWhiteSpace(alias))
{
- var identity = new AuthIdentity(guidId, alias);
- return kpcContext.AsUserContext(identity) as T;
+ return kpcContext.AsUserContext(new AuthIdentity(guidId, alias)) as T;
}
}
diff --git a/src/Core/Context/DBContext.cs b/src/Core/Context/DBContext.cs
index 34e6628..6e82ae2 100644
--- a/src/Core/Context/DBContext.cs
+++ b/src/Core/Context/DBContext.cs
@@ -11,6 +11,8 @@
using KitchenPC.Core.Provisioning;
using KitchenPC.Core.Recipes;
using KitchenPC.Core.ShoppingLists;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
using IngredientUsage = KitchenPC.Core.Ingredients.IngredientUsage;
namespace KitchenPC.Core.Context;
@@ -31,7 +33,8 @@ private AsUser(
Parser parser,
IDBAdapter adapter,
AuthIdentity identity,
- DBContextCapabilities capabilities
+ DBContextCapabilities capabilities,
+ ILoggerFactory loggerFactory
)
{
this.ingParser = ingParser;
@@ -41,6 +44,7 @@ DBContextCapabilities capabilities
this.Identity = identity;
this.GetIdentity = () => identity;
this.Capabilities = capabilities;
+ this.LoggerFactory = loggerFactory;
}
public static AsUser Clone(DBContext context, AuthIdentity identity) =>
@@ -50,7 +54,8 @@ public static AsUser Clone(DBContext context, AuthIdentity identity) =>
context.parser,
context.Adapter,
identity,
- context.Capabilities
+ context.Capabilities,
+ context.LoggerFactory
);
}
@@ -63,6 +68,7 @@ public static AsUser Clone(DBContext context, AuthIdentity identity) =>
/// Gets the optional in-memory capabilities configured for this context.
public DBContextCapabilities Capabilities { get; internal set; } = DBContextCapabilities.All;
+ public ILoggerFactory LoggerFactory { get; internal set; } = NullLoggerFactory.Instance;
/// Gets or sets the IDBAdapter used to directly talk with the database.
public IDBAdapter Adapter { get; set; }
@@ -166,6 +172,7 @@ public virtual void Initialize()
if (HasCapability(DBContextCapabilities.IngredientParsing))
{
+ NlpTracer.SetTracer(new DefaultTracer(LoggerFactory));
IngredientSynonyms.InitIndex(Adapter.IngredientLoader);
UnitSynonyms.InitIndex(Adapter.UnitLoader);
FormSynonyms.InitIndex(Adapter.FormLoader);
diff --git a/src/Core/Context/DBContextBuilder.cs b/src/Core/Context/DBContextBuilder.cs
index ea8c137..1733ba3 100644
--- a/src/Core/Context/DBContextBuilder.cs
+++ b/src/Core/Context/DBContextBuilder.cs
@@ -1,4 +1,5 @@
using System;
+using Microsoft.Extensions.Logging;
namespace KitchenPC.Core.Context;
@@ -24,6 +25,12 @@ public DBContextBuilder Identity(Func getIdentity)
return this;
}
+ public DBContextBuilder Logging(ILoggerFactory loggerFactory)
+ {
+ context.LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
+ return this;
+ }
+
///
/// Selects the optional in-memory indexes initialized by this context. All capabilities are
/// enabled by default for backward compatibility.
diff --git a/src/Core/Context/IKPCContext.cs b/src/Core/Context/IKPCContext.cs
index fb1c6b4..31e24f9 100644
--- a/src/Core/Context/IKPCContext.cs
+++ b/src/Core/Context/IKPCContext.cs
@@ -9,6 +9,7 @@
using KitchenPC.Core.NLP;
using KitchenPC.Core.Recipes;
using KitchenPC.Core.ShoppingLists;
+using Microsoft.Extensions.Logging;
using IngredientUsage = KitchenPC.Core.Ingredients.IngredientUsage;
namespace KitchenPC.Core.Context;
@@ -18,6 +19,7 @@ public interface IKPCContext
{
void Initialize();
AuthIdentity Identity { get; }
+ ILoggerFactory LoggerFactory { get; }
// Autocomplete support
IEnumerable AutocompleteIngredient(string query);
diff --git a/src/Core/Context/StaticContext.cs b/src/Core/Context/StaticContext.cs
index 806e751..97a7301 100644
--- a/src/Core/Context/StaticContext.cs
+++ b/src/Core/Context/StaticContext.cs
@@ -15,6 +15,8 @@
using KitchenPC.Core.Provisioning.DTO;
using KitchenPC.Core.Recipes;
using KitchenPC.Core.ShoppingLists;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
using IngredientUsage = KitchenPC.Core.Ingredients.IngredientUsage;
namespace KitchenPC.Core.Context;
@@ -27,6 +29,7 @@ public class StaticContext : IKPCContext, IProvisionTarget, IProvisionSource
public Func GetIdentity { get; set; }
public Parser Parser { get; private set; }
public ModelerProxy ModelerProxy { get; private set; }
+ public ILoggerFactory LoggerFactory { get; internal set; } = NullLoggerFactory.Instance;
private DataStore store;
private IngredientParser ingParser;
@@ -51,6 +54,7 @@ private StaticContext()
///
public void Initialize()
{
+ NlpTracer.SetTracer(new DefaultTracer(LoggerFactory));
var file = CompressedStore ? "KPCData.gz" : "KPCData.xml";
var path = Path.Combine(DataDirectory, file);
// TODO Fix logging
diff --git a/src/Core/Context/StaticContextBuilder.cs b/src/Core/Context/StaticContextBuilder.cs
index 113c386..7ab23d0 100644
--- a/src/Core/Context/StaticContextBuilder.cs
+++ b/src/Core/Context/StaticContextBuilder.cs
@@ -1,4 +1,5 @@
using System;
+using Microsoft.Extensions.Logging;
namespace KitchenPC.Core.Context;
@@ -34,5 +35,11 @@ public StaticContextBuilder Identity(Func getIdentity)
return this;
}
+ public StaticContextBuilder Logging(ILoggerFactory loggerFactory)
+ {
+ context.LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
+ return this;
+ }
+
public StaticContext Create() => context;
}
diff --git a/src/Core/Context/StaticIngredientLoader.cs b/src/Core/Context/StaticIngredientLoader.cs
index 2fc669b..fe10b01 100644
--- a/src/Core/Context/StaticIngredientLoader.cs
+++ b/src/Core/Context/StaticIngredientLoader.cs
@@ -84,7 +84,7 @@ public StaticIngredientLoader(DataStore store)
if (nodes.ContainsKey(ingId))
{
- Parser.Log.ErrorFormat(
+ NlpTracer.Trace(TraceLevel.Error,
"[NLP Loader] Duplicate ingredient key due to bad DB data: {0} ({1})",
name,
ingId
diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj
index 6c6fc01..6570905 100644
--- a/src/Core/Core.csproj
+++ b/src/Core/Core.csproj
@@ -1,6 +1,6 @@
- netstandard2.0
+ net8.0;net10.0
default
true
$(NoWarn);1591
@@ -12,7 +12,7 @@
bin\$(Configuration)\
Mike Christensen
KitchenPC.Core
- 1.0.0
+ 2.0.0
https://github.com/KitchenPC/core
https://github.com/KitchenPC/core.git
git
@@ -31,13 +31,6 @@
-
-
-
-
-
+
diff --git a/src/Core/Modeler/DBSnapshot.cs b/src/Core/Modeler/DBSnapshot.cs
index d4108ff..8814835 100644
--- a/src/Core/Modeler/DBSnapshot.cs
+++ b/src/Core/Modeler/DBSnapshot.cs
@@ -4,6 +4,7 @@
using System.Linq;
using KitchenPC.Core.Context;
using KitchenPC.Core.Recipes;
+using Microsoft.Extensions.Logging;
namespace KitchenPC.Core.Modeler;
@@ -52,8 +53,8 @@ public void Index(IKPCContext context)
ratingGraph.AddRating(r, uid, rid);
}
- ModelingSession.Log.InfoFormat(
- "Building Rating Graph took {0}ms.",
+ snapshot.logger.LogInformation(
+ "Building Rating Graph took {ElapsedMilliseconds}ms.",
timer.ElapsedMilliseconds
);
timer.Reset();
@@ -72,8 +73,8 @@ from o in loader.LoadRecipeGraph()
}
).ToDictionary(k => k.RecipeId);
- ModelingSession.Log.InfoFormat(
- "Building empty RecipeNodes took {0}ms.",
+ snapshot.logger.LogInformation(
+ "Building empty RecipeNodes took {ElapsedMilliseconds}ms.",
timer.ElapsedMilliseconds
);
timer.Reset();
@@ -108,8 +109,8 @@ from o in loader.LoadRecipeGraph()
}
}
- ModelingSession.Log.InfoFormat(
- "Indexing recipes by tag took {0}ms.",
+ snapshot.logger.LogInformation(
+ "Indexing recipes by tag took {ElapsedMilliseconds}ms.",
timer.ElapsedMilliseconds
);
timer.Reset();
@@ -173,8 +174,8 @@ from o in loader.LoadRecipeGraph()
);
}
- ModelingSession.Log.InfoFormat(
- "Creating IngredientUsage vertices took {0}ms.",
+ snapshot.logger.LogInformation(
+ "Creating IngredientUsage vertices took {ElapsedMilliseconds}ms.",
timer.ElapsedMilliseconds
);
timer.Reset();
@@ -189,8 +190,8 @@ select snapshot.recipeMap[s]
).ToArray();
}
- ModelingSession.Log.InfoFormat(
- "Building suggestions for each recipe took {0}ms.",
+ snapshot.logger.LogInformation(
+ "Building suggestions for each recipe took {ElapsedMilliseconds}ms.",
timer.ElapsedMilliseconds
);
timer.Reset();
@@ -233,8 +234,8 @@ public void Dispose()
GC.Collect(); //Force garbage collection now, since there might be several hundred megs of unreachable allocations
timer.Stop();
- ModelingSession.Log.InfoFormat(
- "Cleaning up Indexer took {0}ms.",
+ snapshot.logger.LogInformation(
+ "Cleaning up Indexer took {ElapsedMilliseconds}ms.",
timer.ElapsedMilliseconds
);
}
@@ -243,6 +244,7 @@ public void Dispose()
public sealed partial class DBSnapshot
{
+ private readonly ILogger logger;
private Dictionary recipeMap; //Recipe Index (will include hidden recipes)
private Dictionary ingredientMap; //Ingredient Index
private IEnumerable[] recipeList; //Ordinal recipe index keyed by tag (for picking random recipes)
@@ -254,6 +256,7 @@ public int RecipeCount
public DBSnapshot(IKPCContext context)
{
+ logger = context.LoggerFactory.CreateLogger();
var timer = new Stopwatch();
timer.Start();
@@ -263,8 +266,8 @@ public DBSnapshot(IKPCContext context)
}
timer.Stop();
- ModelingSession.Log.InfoFormat(
- "Total time building snapshot was {0}ms.",
+ logger.LogInformation(
+ "Total time building snapshot was {ElapsedMilliseconds}ms.",
timer.ElapsedMilliseconds
);
}
diff --git a/src/Core/Modeler/ModelingSession.cs b/src/Core/Modeler/ModelingSession.cs
index 5db201d..0598e3c 100644
--- a/src/Core/Modeler/ModelingSession.cs
+++ b/src/Core/Modeler/ModelingSession.cs
@@ -4,7 +4,7 @@
using System.Linq;
using KitchenPC.Core.Context;
using KitchenPC.Core.Recipes;
-using log4net;
+using Microsoft.Extensions.Logging;
namespace KitchenPC.Core.Modeler
{
@@ -36,7 +36,7 @@ public class ModelingSession
private readonly DBSnapshot db;
private readonly IKPCContext context;
private readonly IUserProfile profile;
- public static ILog Log = LogManager.GetLogger(typeof(ModelingSession));
+ private readonly ILogger logger;
///
/// Create a ModelingSession instance.
@@ -49,6 +49,7 @@ public ModelingSession(IKPCContext context, DBSnapshot db, IUserProfile profile)
this.db = db;
this.context = context;
this.profile = profile;
+ this.logger = context.LoggerFactory.CreateLogger();
this.favTags = new bool[RecipeTag.NUM_TAGS];
this.favIngs = new int[profile.FavoriteIngredients.Length];
@@ -190,8 +191,8 @@ public Model Generate(int recipes, byte scale)
}
timer.Stop();
- Log.InfoFormat(
- "Generating set of {0} recipes took {1}ms.",
+ logger.LogInformation(
+ "Generating set of {RecipeCount} recipes took {ElapsedMilliseconds}ms.",
recipes,
timer.ElapsedMilliseconds
);
diff --git a/src/Core/NLP/DefaultTracer.cs b/src/Core/NLP/DefaultTracer.cs
index 96217ce..6ba3a3b 100644
--- a/src/Core/NLP/DefaultTracer.cs
+++ b/src/Core/NLP/DefaultTracer.cs
@@ -1,37 +1,35 @@
-using log4net;
+using System;
+using System.Globalization;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
namespace KitchenPC.Core.NLP;
-/// Implementation of ITracer that uses Log4Net
+/// Implementation of ITracer that uses Microsoft.Extensions.Logging.
public class DefaultTracer : ITracer
{
- private readonly ILog log;
+ private readonly ILogger log;
- public DefaultTracer()
- {
- log = LogManager.GetLogger(typeof(Parser));
- log.Info("Initialized logger for new NLP parser.");
- }
+ public DefaultTracer() : this(NullLoggerFactory.Instance) { }
+
+ public DefaultTracer(ILoggerFactory loggerFactory) =>
+ log = (loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)))
+ .CreateLogger();
public void Trace(TraceLevel level, string message, params object[] args)
{
- switch (level)
- {
- case TraceLevel.Debug:
- log.DebugFormat(message, args);
- break;
- case TraceLevel.Error:
- log.ErrorFormat(message, args);
- break;
- case TraceLevel.Fatal:
- log.FatalFormat(message, args);
- break;
- case TraceLevel.Info:
- log.InfoFormat(message, args);
- break;
- case TraceLevel.Warn:
- log.WarnFormat(message, args);
- break;
- }
+ var formattedMessage = string.Format(CultureInfo.InvariantCulture, message, args);
+ log.Log(MapLevel(level), "{Message}", formattedMessage);
}
+
+ private static LogLevel MapLevel(TraceLevel level) =>
+ level switch
+ {
+ TraceLevel.Debug => LogLevel.Debug,
+ TraceLevel.Error => LogLevel.Error,
+ TraceLevel.Fatal => LogLevel.Critical,
+ TraceLevel.Info => LogLevel.Information,
+ TraceLevel.Warn => LogLevel.Warning,
+ _ => LogLevel.None,
+ };
}
diff --git a/src/Core/NLP/Parser.cs b/src/Core/NLP/Parser.cs
index a79bc4e..6c7618a 100644
--- a/src/Core/NLP/Parser.cs
+++ b/src/Core/NLP/Parser.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
-using log4net;
namespace KitchenPC.Core.NLP;
@@ -13,7 +12,6 @@ public class Parser
private List templates;
private static readonly Regex reWhitespace = new Regex(@"[ ]{2,}", RegexOptions.Compiled);
- public static ILog Log = LogManager.GetLogger(typeof(Parser));
public NoMatchEvent OnNoMatch;
public TemplateStatistics Stats { get; private set; }
diff --git a/src/Core/NLP/SynonymTree.cs b/src/Core/NLP/SynonymTree.cs
index b3420be..9c4f4d5 100644
--- a/src/Core/NLP/SynonymTree.cs
+++ b/src/Core/NLP/SynonymTree.cs
@@ -14,7 +14,7 @@ protected static void IndexString(string value, T node)
if (synonymMap.ContainsKey(parsedIng)) //Uh oh
{
- Parser.Log.Error(
+ NlpTracer.Trace(TraceLevel.Error,
String.Format("The ingredient synonym '{0}' also exists as a root ingredient.", value)
);
}
diff --git a/src/Core/NLP/Tokens/StaticToken.cs b/src/Core/NLP/Tokens/StaticToken.cs
index 728a8b3..22eb312 100644
--- a/src/Core/NLP/Tokens/StaticToken.cs
+++ b/src/Core/NLP/Tokens/StaticToken.cs
@@ -26,7 +26,15 @@ public bool Read(Stream stream, MatchData matchdata)
//Read the stream to make sure it matches the complete token, return false if not
var count = this.phrase.Length;
var readBytes = new byte[count];
- stream.Read(readBytes, 0, count);
+ var bytesRead = 0;
+ while (bytesRead < count)
+ {
+ var read = stream.Read(readBytes, bytesRead, count - bytesRead);
+ if (read == 0)
+ return false;
+
+ bytesRead += read;
+ }
return (Encoding.Default.GetString(readBytes) == this.phrase);
}
diff --git a/src/DB/DB.csproj b/src/DB/DB.csproj
index ea366b6..c7c99a1 100644
--- a/src/DB/DB.csproj
+++ b/src/DB/DB.csproj
@@ -11,7 +11,7 @@
bin\$(Configuration)\
Mike Christensen
KitchenPC.DB
- 1.0.0
+ 2.0.0
https://github.com/KitchenPC/core
https://github.com/KitchenPC/core.git
git
@@ -25,27 +25,16 @@
snupkg
false
KitchenPC.DB
- netstandard2.0
+ net8.0;net10.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/src/DB/DatabaseAdapter.cs b/src/DB/DatabaseAdapter.cs
index 278f556..fd6c12f 100644
--- a/src/DB/DatabaseAdapter.cs
+++ b/src/DB/DatabaseAdapter.cs
@@ -23,6 +23,8 @@
using NHibernate.Criterion;
using NHibernate.Tool.hbm2ddl;
using NHibernate.Transform;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
using IngredientNode = KitchenPC.Core.NLP.IngredientNode;
using IngredientUsage = KitchenPC.Core.Ingredients.IngredientUsage;
@@ -38,6 +40,8 @@ public class DatabaseAdapter : IDBAdapter, IDisposable
public IPersistenceConfigurer DatabaseConfiguration { get; set; }
public List DatabaseConventions { get; set; }
public ISearchProvider SearchProvider { get; set; }
+ public Microsoft.Extensions.Logging.ILoggerFactory LoggerFactory { get; set; } =
+ NullLoggerFactory.Instance;
public static DatabaseAdapterBuilder Configure => new DatabaseAdapter().builder;
@@ -74,6 +78,7 @@ private ISessionFactory InitializeSessionFactory()
public void Initialize(IKPCContext context)
{
+ LoggerFactory = context.LoggerFactory;
sessionFactory ??= InitializeSessionFactory();
}
@@ -1272,7 +1277,7 @@ public ShoppingListResult UpdateShoppingList(
public DataStore Export()
{
var store = new DataStore();
- using var exporter = new DatabaseExporter(GetStatelessSession());
+ using var exporter = new DatabaseExporter(GetStatelessSession(), LoggerFactory);
store.IngredientForms = exporter.IngredientForms();
store.IngredientMetadata = exporter.IngredientMetadata();
store.Ingredients = exporter.Ingredients();
@@ -1306,7 +1311,7 @@ public void Import(IProvisionSource source)
if (sessionFactory == null)
InitializeSessionFactory();
- using (var importer = new DatabaseImporter(GetSession()))
+ using (var importer = new DatabaseImporter(GetSession(), LoggerFactory))
{
// Note: Import order is important to maintain referential integrity of database
importer.Import(store.Ingredients);
diff --git a/src/DB/DatabaseAdapterBuilder.cs b/src/DB/DatabaseAdapterBuilder.cs
index c101160..e8b40a9 100644
--- a/src/DB/DatabaseAdapterBuilder.cs
+++ b/src/DB/DatabaseAdapterBuilder.cs
@@ -3,6 +3,7 @@
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Conventions;
using KitchenPC.Core.Context;
+using Microsoft.Extensions.Logging;
namespace KitchenPC.DB;
@@ -37,5 +38,11 @@ public DatabaseAdapterBuilder SearchProvider(Func createP
return this;
}
+ public DatabaseAdapterBuilder Logging(ILoggerFactory loggerFactory)
+ {
+ adapter.LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
+ return this;
+ }
+
public DatabaseAdapter Create() => adapter;
}
diff --git a/src/DB/DatabaseExporter.cs b/src/DB/DatabaseExporter.cs
index d0f1d23..0dd3fe0 100644
--- a/src/DB/DatabaseExporter.cs
+++ b/src/DB/DatabaseExporter.cs
@@ -5,7 +5,8 @@
using KitchenPC.Core;
using KitchenPC.Core.Provisioning;
using KitchenPC.Core.Provisioning.DTO;
-using log4net;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
using NHibernate;
using NHibernate.Persister.Entity;
@@ -14,11 +15,17 @@ namespace KitchenPC.DB;
public class DatabaseExporter : IDisposable, IProvisioner
{
private readonly IStatelessSession session;
- private static readonly ILog logger = LogManager.GetLogger(typeof(DatabaseExporter));
+ private readonly ILogger logger;
- public DatabaseExporter(IStatelessSession session)
+ public DatabaseExporter(IStatelessSession session) : this(session, NullLoggerFactory.Instance) { }
+
+ public DatabaseExporter(
+ IStatelessSession session,
+ Microsoft.Extensions.Logging.ILoggerFactory loggerFactory
+ )
{
this.session = session;
+ logger = loggerFactory.CreateLogger();
}
private IEnumerable ImportTableData(Func action)
@@ -54,7 +61,7 @@ public IngredientForms[] IngredientForms()
})
.ToArray();
- logger.DebugFormat("Read {0} row(s) from IngredientForms.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from IngredientForms.", list.Count());
return list;
}
@@ -81,7 +88,7 @@ public IngredientMetadata[] IngredientMetadata()
)
.ToArray();
- logger.DebugFormat("Read {0} row(s) from IngredientMetadata.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from IngredientMetadata.", list.Count());
return list;
}
@@ -101,7 +108,7 @@ public Ingredients[] Ingredients()
})
.ToArray();
- logger.DebugFormat("Read {0} row(s) from shoppingingredients.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from shoppingingredients.", list.Count());
return list;
}
@@ -120,7 +127,7 @@ public NlpAnomalousIngredients[] NlpAnomalousIngredients()
)
.ToArray();
- logger.DebugFormat("Read {0} row(s) from NlpAnomalousIngredients.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from NlpAnomalousIngredients.", list.Count());
return list;
}
@@ -138,7 +145,7 @@ public NlpDefaultPairings[] NlpDefaultPairings()
)
.ToArray();
- logger.DebugFormat("Read {0} row(s) from NlpDefaultPairings.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from NlpDefaultPairings.", list.Count());
return list;
}
@@ -153,7 +160,7 @@ public NlpFormSynonyms[] NlpFormSynonyms()
})
.ToArray();
- logger.DebugFormat("Read {0} row(s) from NlpFormSynonyms.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from NlpFormSynonyms.", list.Count());
return list;
}
@@ -170,7 +177,7 @@ public NlpIngredientSynonyms[] NlpIngredientSynonyms()
)
.ToArray();
- logger.DebugFormat("Read {0} row(s) from NlpIngredientSynonyms.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from NlpIngredientSynonyms.", list.Count());
return list;
}
@@ -182,7 +189,7 @@ public NlpPrepNotes[] NlpPrepNotes()
})
.ToArray();
- logger.DebugFormat("Read {0} row(s) from NlpPrepNotes.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from NlpPrepNotes.", list.Count());
return list;
}
@@ -197,7 +204,7 @@ public NlpUnitSynonyms[] NlpUnitSynonyms()
})
.ToArray();
- logger.DebugFormat("Read {0} row(s) from NlpUnitSynonyms.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from NlpUnitSynonyms.", list.Count());
return list;
}
@@ -221,7 +228,7 @@ public List Recipes()
})
.ToList();
- logger.DebugFormat("Read {0} row(s) from Recipes.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from Recipes.", list.Count());
return list;
}
@@ -261,7 +268,7 @@ public List RecipeMetadata()
})
.ToList();
- logger.DebugFormat("Read {0} row(s) from RecipeMetadata.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from RecipeMetadata.", list.Count());
return list;
}
@@ -284,7 +291,7 @@ public List RecipeIngredients()
)
.ToList();
- logger.DebugFormat("Read {0} row(s) from RecipeIngredients.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from RecipeIngredients.", list.Count());
return list;
}
@@ -299,7 +306,7 @@ public List Favorites()
})
.ToList();
- logger.DebugFormat("Read {0} row(s) from Favorites.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from Favorites.", list.Count());
return list;
}
@@ -314,7 +321,7 @@ public List Menus()
})
.ToList();
- logger.DebugFormat("Read {0} row(s) from Menus.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from Menus.", list.Count());
return list;
}
@@ -329,7 +336,7 @@ public List QueuedRecipes()
})
.ToList();
- logger.DebugFormat("Read {0} row(s) from QueuedRecipes.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from QueuedRecipes.", list.Count());
return list;
}
@@ -344,7 +351,7 @@ public List RecipeRatings()
})
.ToList();
- logger.DebugFormat("Read {0} row(s) from RecipeRatings.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from RecipeRatings.", list.Count());
return list;
}
@@ -358,7 +365,7 @@ public List ShoppingLists()
})
.ToList();
- logger.DebugFormat("Read {0} row(s) from ShoppingLists.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from ShoppingLists.", list.Count());
return list;
}
@@ -380,7 +387,7 @@ public List ShoppingListItems()
)
.ToList();
- logger.DebugFormat("Read {0} row(s) from ShoppingListItems.", list.Count());
+ logger.LogDebug("Read {RowCount} row(s) from ShoppingListItems.", list.Count());
return list;
}
diff --git a/src/DB/DatabaseImporter.cs b/src/DB/DatabaseImporter.cs
index 1c4f73e..e13625e 100644
--- a/src/DB/DatabaseImporter.cs
+++ b/src/DB/DatabaseImporter.cs
@@ -2,7 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using KitchenPC.Core.Provisioning.DTO;
-using log4net;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
using NHibernate;
namespace KitchenPC.DB;
@@ -10,11 +11,17 @@ namespace KitchenPC.DB;
public class DatabaseImporter : IDisposable
{
private readonly ISession session;
- private static readonly ILog logger = LogManager.GetLogger(typeof(DatabaseImporter));
+ private readonly ILogger logger;
- public DatabaseImporter(ISession session)
+ public DatabaseImporter(ISession session) : this(session, NullLoggerFactory.Instance) { }
+
+ public DatabaseImporter(
+ ISession session,
+ Microsoft.Extensions.Logging.ILoggerFactory loggerFactory
+ )
{
this.session = session;
+ logger = loggerFactory.CreateLogger();
}
public void Import(IEnumerable data)
@@ -39,7 +46,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.IngredientId);
}
- logger.DebugFormat("Created {0} row(s) in shoppingingredients", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in shoppingingredients", d.Count());
transaction.Commit();
session.Flush();
}
@@ -65,7 +72,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.IngredientFormId);
}
- logger.DebugFormat("Created {0} row(s) in IngredientForms", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in IngredientForms", d.Count());
transaction.Commit();
session.Flush();
}
@@ -97,7 +104,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.IngredientMetadataId);
}
- logger.DebugFormat("Created {0} row(s) in IngredientMetadata", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in IngredientMetadata", d.Count());
transaction.Commit();
session.Flush();
}
@@ -127,7 +134,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.AnomalousIngredientId);
}
- logger.DebugFormat("Created {0} row(s) in NlpAnomalousIngredients", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in NlpAnomalousIngredients", d.Count());
transaction.Commit();
session.Flush();
}
@@ -156,7 +163,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.DefaultPairingId);
}
- logger.DebugFormat("Created {0} row(s) in NlpDefaultPairings", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in NlpDefaultPairings", d.Count());
transaction.Commit();
session.Flush();
}
@@ -178,7 +185,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.FormSynonymId);
}
- logger.DebugFormat("Created {0} row(s) in NlpFormSynonyms", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in NlpFormSynonyms", d.Count());
transaction.Commit();
session.Flush();
}
@@ -200,7 +207,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.IngredientSynonymId);
}
- logger.DebugFormat("Created {0} row(s) in NlpIngredientSynonyms", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in NlpIngredientSynonyms", d.Count());
transaction.Commit();
session.Flush();
}
@@ -216,7 +223,7 @@ public void Import(IEnumerable data)
session.Save(dbRow);
}
- logger.DebugFormat("Created {0} row(s) in NlpPrepNotes", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in NlpPrepNotes", d.Count());
transaction.Commit();
session.Flush();
}
@@ -238,7 +245,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.UnitSynonymId);
}
- logger.DebugFormat("Created {0} row(s) in NlpUnitSynonyms", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in NlpUnitSynonyms", d.Count());
transaction.Commit();
session.Flush();
}
@@ -270,7 +277,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.RecipeId);
}
- logger.DebugFormat("Created {0} row(s) in Recipes", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in Recipes", d.Count());
transaction.Commit();
session.Flush();
}
@@ -300,7 +307,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.RecipeIngredientId);
}
- logger.DebugFormat("Created {0} row(s) in RecipeIngredients", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in RecipeIngredients", d.Count());
transaction.Commit();
session.Flush();
}
@@ -347,7 +354,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.RecipeMetadataId);
}
- logger.DebugFormat("Created {0} row(s) in RecipeMetadata", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in RecipeMetadata", d.Count());
transaction.Commit();
session.Flush();
}
@@ -369,7 +376,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.MenuId);
}
- logger.DebugFormat("Created {0} row(s) in Menus", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in Menus", d.Count());
transaction.Commit();
session.Flush();
}
@@ -391,7 +398,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.FavoriteId);
}
- logger.DebugFormat("Created {0} row(s) in Favorites", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in Favorites", d.Count());
transaction.Commit();
session.Flush();
}
@@ -413,7 +420,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.QueueId);
}
- logger.DebugFormat("Created {0} row(s) in QueuedRecipes", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in QueuedRecipes", d.Count());
transaction.Commit();
session.Flush();
}
@@ -435,7 +442,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.RatingId);
}
- logger.DebugFormat("Created {0} row(s) in RecipeRatings", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in RecipeRatings", d.Count());
transaction.Commit();
session.Flush();
}
@@ -456,7 +463,7 @@ public void Import(IEnumerable data)
session.Save(dbRow, row.ShoppingListId);
}
- logger.DebugFormat("Created {0} row(s) in ShoppingLists", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in ShoppingLists", d.Count());
transaction.Commit();
session.Flush();
}
@@ -487,7 +494,7 @@ public void Import(IEnumerable data)
session.Save(dbItem, row.ItemId);
}
- logger.DebugFormat("Created {0} row(s) in ShoppingListItems", d.Count());
+ logger.LogDebug("Created {RowCount} row(s) in ShoppingListItems", d.Count());
transaction.Commit();
session.Flush();
}
diff --git a/src/DB/EnumMapper.cs b/src/DB/EnumMapper.cs
index 75acaac..998e278 100644
--- a/src/DB/EnumMapper.cs
+++ b/src/DB/EnumMapper.cs
@@ -13,7 +13,7 @@ public class EnumMapper : EnumStringType
public static IPropertyConvention Convention =>
ConventionBuilder.Property.When(
- c => c.Expect(x => x.Type == typeof(GenericEnumMapper)),
+ c => c.Expect(x => x.Type == typeof(EnumStringType)),
x =>
{
x.CustomType>();
diff --git a/src/DB/NLP/IngredientLoader.cs b/src/DB/NLP/IngredientLoader.cs
index 69e7c44..52f7949 100644
--- a/src/DB/NLP/IngredientLoader.cs
+++ b/src/DB/NLP/IngredientLoader.cs
@@ -96,7 +96,7 @@ public IEnumerable LoadSynonyms()
if (nodes.ContainsKey(ingId))
{
- Parser.Log.ErrorFormat(
+ NlpTracer.Trace(TraceLevel.Error,
"[NLP Loader] Duplicate ingredient key due to bad DB data: {0} ({1})",
name,
ingId
diff --git a/src/UnitTests/DBContextCapabilities.cs b/src/UnitTests/DBContextCapabilities.cs
index a5fbb89..87b0f1b 100644
--- a/src/UnitTests/DBContextCapabilities.cs
+++ b/src/UnitTests/DBContextCapabilities.cs
@@ -44,7 +44,7 @@ public void CapabilitiesArePreservedByUserContext()
Assert.AreEqual(DBContextCapabilities.IngredientParsing, userContext.Capabilities);
}
- [DataTestMethod]
+ [TestMethod]
[DataRow(DBContextCapabilities.None, false, false, false)]
[DataRow(DBContextCapabilities.IngredientAutocomplete, true, false, false)]
[DataRow(DBContextCapabilities.IngredientParsing, false, true, false)]
@@ -237,14 +237,14 @@ public void ModelerAggregationDoesNotRequireAutocompleteIndex()
[TestMethod]
public void RejectsUnknownCapabilities()
{
- Assert.ThrowsException(() =>
+ Assert.ThrowsExactly(() =>
DBContext.Configure.Capabilities((DBContextCapabilities)8)
);
}
private static void AssertCapabilityError(DBContextCapabilities expected, Action action)
{
- var exception = Assert.ThrowsException(action);
+ var exception = Assert.ThrowsExactly(action);
Assert.AreEqual(expected, exception.Capability);
}
diff --git a/src/UnitTests/Mock/MockContext.cs b/src/UnitTests/Mock/MockContext.cs
index adb23e7..1869179 100644
--- a/src/UnitTests/Mock/MockContext.cs
+++ b/src/UnitTests/Mock/MockContext.cs
@@ -11,6 +11,8 @@
using KitchenPC.Core.NLP;
using KitchenPC.Core.Recipes;
using KitchenPC.Core.ShoppingLists;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
using IngredientNode = KitchenPC.Core.Context.IngredientNode;
using IngredientUsage = KitchenPC.Core.Ingredients.IngredientUsage;
@@ -18,6 +20,7 @@ namespace KitchenPC.UnitTests.Mock;
internal class MockContext : IKPCContext
{
+ public ILoggerFactory LoggerFactory => NullLoggerFactory.Instance;
public void Initialize()
{
ModelerProxy = new ModelerProxy(this);
diff --git a/src/UnitTests/Modeler.cs b/src/UnitTests/Modeler.cs
index 5c68ded..1d4ca53 100644
--- a/src/UnitTests/Modeler.cs
+++ b/src/UnitTests/Modeler.cs
@@ -42,25 +42,24 @@ public void TestNoRatingModeler()
}
[TestMethod]
- [ExpectedException(typeof(ImpossibleQueryException))]
public void TestImpossibleFilterModeler()
{
Trace.WriteLine("Running ImpossibleFilter Test.");
var profile = new MockImpossibleFilterUserProfile(); // Only No Pork recipes are allowed, of which there are none in our mock data
- var session = context.CreateModelingSession(profile);
- session.Generate(5, 1);
+ Assert.ThrowsExactly(() =>
+ context.CreateModelingSession(profile).Generate(5, 1)
+ );
}
[TestMethod]
- [ExpectedException(typeof(ImpossibleQueryException))]
public void TestImpossiblePantryModeler()
{
Trace.WriteLine("Running ImpossiblePantry Test.");
var profile = new MockImpossiblePantryUserProfile();
- var session = context.CreateModelingSession(profile);
-
- session.Generate(5, 1);
+ Assert.ThrowsExactly(() =>
+ context.CreateModelingSession(profile).Generate(5, 1)
+ );
}
[TestMethod]
diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj
index 450c4ef..cb78c65 100644
--- a/src/UnitTests/UnitTests.csproj
+++ b/src/UnitTests/UnitTests.csproj
@@ -1,6 +1,6 @@
- net10.0
+ net8.0;net10.0
true
default
KitchenPC.UnitTests
@@ -11,10 +11,10 @@
-
-
-
-
+
+
+
+
diff --git a/src/core.slnx b/src/core.slnx
index 4e02f6c..72d9e5c 100644
--- a/src/core.slnx
+++ b/src/core.slnx
@@ -7,6 +7,9 @@
+
+
+