Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

Expand All @@ -8,15 +9,22 @@ namespace CrestApps.Core.AI.Documents;
/// </summary>
public sealed class DocumentFileSystemFileStoreOptionsConfiguration : IConfigureOptions<DocumentFileSystemFileStoreOptions>
{
private const string ConfigurationKey = "CrestApps:AI:Documents:BasePath";

private readonly IHostEnvironment _env;
private readonly IConfiguration _configuration;

/// <summary>
/// Initializes a new instance of the <see cref="DocumentFileSystemFileStoreOptionsConfiguration"/> class.
/// </summary>
/// <param name="env">The env.</param>
public DocumentFileSystemFileStoreOptionsConfiguration(IHostEnvironment env)
/// <param name="env">The host environment.</param>
/// <param name="configuration">The application configuration.</param>
public DocumentFileSystemFileStoreOptionsConfiguration(
IHostEnvironment env,
IConfiguration configuration)
{
_env = env;
_configuration = configuration;
}

/// <summary>
Expand All @@ -29,6 +37,11 @@ public void Configure(DocumentFileSystemFileStoreOptions options)

var configuredBasePath = options.BasePath;

if (string.IsNullOrWhiteSpace(configuredBasePath))
{
configuredBasePath = _configuration[ConfigurationKey];
}

if (string.IsNullOrWhiteSpace(configuredBasePath))
{
options.BasePath = Path.Combine(_env.ContentRootPath, "App_Data", "Documents");
Expand Down
56 changes: 56 additions & 0 deletions src/Startup/CrestApps.Core.Aspire.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ void WriteCrashEntry(string label, object data)
WriteCrashEntry("Process exit signaled", $"Exit code: {Environment.ExitCode}");
};

// When running under Visual Studio, all mutable data (database, logs, documents,
// site settings) must be stored outside the project source tree. VS monitors the
// source directory for file changes, and any new file triggers VS to stop the debug
// session. Redirecting App_Data and document storage to a temp location avoids this.
var appDataBasePath = Path.Combine(Path.GetTempPath(), "CrestApps", "AppData");
var documentsBasePath = Path.Combine(Path.GetTempPath(), "CrestApps", "Documents");

var builder = DistributedApplication.CreateBuilder(args);

builder.Services.Configure<HostOptions>(options =>
Expand All @@ -63,12 +70,22 @@ void WriteCrashEntry(string label, object data)
.WithHttpsEndpoint(5001, name: "HttpsMvcWeb")
.WithEnvironment((options) =>
{
var mvcAppData = Path.Combine(appDataBasePath, "MvcWeb");
options.EnvironmentVariables.Add("CrestApps__AppDataPath", mvcAppData);
options.EnvironmentVariables.Add("CRESTAPPS_LOG_DIR", Path.Combine(mvcAppData, "logs"));
options.EnvironmentVariables.Add("CrestApps__AI__Documents__BasePath", documentsBasePath);
options.EnvironmentVariables.Add("CrestApps__AI__Providers__Ollama__DefaultDeploymentName", ollamaModelName);
options.EnvironmentVariables.Add("CrestApps__AI__Providers__Ollama__Connections__Default__Endpoint", "http://localhost:11434");
options.EnvironmentVariables.Add("CrestApps__AI__Providers__Ollama__Connections__Default__ChatDeploymentName", ollamaModelName);
options.EnvironmentVariables.Add("CrestApps__MvcApp__MCP__Server__AuthenticationType", "None");
options.EnvironmentVariables.Add("CrestApps__MvcApp__A2A__Host__AuthenticationType", "None");
options.EnvironmentVariables.Add("CrestApps__MvcApp__A2A__Host__ExposeAgentsAsSkill", "true");

// Prevent VS-injected startup hooks (BrowserRefresh, DeltaApplier, BrowserLink)
// from loading into child processes. These middlewares can interfere with
// multipart file uploads when running under VS + Aspire.
options.EnvironmentVariables["DOTNET_STARTUP_HOOKS"] = "";
options.EnvironmentVariables["__ASPNETCORE_BROWSER_TOOLS"] = "";
});

var blazorWeb = builder.AddProject<Projects.CrestApps_Core_Blazor_Web>("BlazorWeb")
Expand All @@ -78,12 +95,22 @@ void WriteCrashEntry(string label, object data)
.WithHttpsEndpoint(5201, name: "HttpsBlazorWeb")
.WithEnvironment((options) =>
{
var blazorAppData = Path.Combine(appDataBasePath, "BlazorWeb");
options.EnvironmentVariables.Add("CrestApps__AppDataPath", blazorAppData);
options.EnvironmentVariables.Add("CRESTAPPS_LOG_DIR", Path.Combine(blazorAppData, "logs"));
options.EnvironmentVariables.Add("CrestApps__AI__Documents__BasePath", documentsBasePath);
options.EnvironmentVariables.Add("CrestApps__AI__Providers__Ollama__DefaultDeploymentName", ollamaModelName);
options.EnvironmentVariables.Add("CrestApps__AI__Providers__Ollama__Connections__Default__Endpoint", "http://localhost:11434");
options.EnvironmentVariables.Add("CrestApps__AI__Providers__Ollama__Connections__Default__ChatDeploymentName", ollamaModelName);
options.EnvironmentVariables.Add("CrestApps__BlazorApp__MCP__Server__AuthenticationType", "None");
options.EnvironmentVariables.Add("CrestApps__BlazorApp__A2A__Host__AuthenticationType", "None");
options.EnvironmentVariables.Add("CrestApps__BlazorApp__A2A__Host__ExposeAgentsAsSkill", "true");

// Prevent VS-injected startup hooks (BrowserRefresh, DeltaApplier, BrowserLink)
// from loading into child processes. These middlewares can interfere with
// multipart file uploads when running under VS + Aspire.
options.EnvironmentVariables["DOTNET_STARTUP_HOOKS"] = "";
options.EnvironmentVariables["__ASPNETCORE_BROWSER_TOOLS"] = "";
});

builder.AddProject<Projects.CrestApps_Core_Mvc_Samples_McpClient>("McpClientSample")
Expand Down Expand Up @@ -112,6 +139,35 @@ void WriteCrashEntry(string label, object data)

var app = builder.Build();

// Open the Aspire Dashboard in the default browser after the app starts.
// We do this from code instead of using launchBrowser:true in launchSettings because
// VS attaches a browser management connection to browsers it launches, and that
// connection crashes when a native file dialog (e.g. file upload picker) opens.
// Opening the browser from code means VS has no management link to it.
var dashboardUrl = builder.Configuration["ASPNETCORE_URLS"]?.Split(';')
.FirstOrDefault(u => u.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
?? builder.Configuration["ASPNETCORE_URLS"]?.Split(';').FirstOrDefault();

var lifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
lifetime.ApplicationStarted.Register(() =>
{
if (!string.IsNullOrEmpty(dashboardUrl))
{
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = dashboardUrl,
UseShellExecute = true,
});
}
catch
{
// Non-critical: browser open is best-effort.
}
}
});

try
{
await app.RunAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,29 @@
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchBrowser": false,
"hotReloadEnabled": false,
"applicationUrl": "https://localhost:17260;http://localhost:15207",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "https://localhost:17260;http://localhost:15207",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21194",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22004",
"DOTNET_DbgEnableMiniDump": "1",
"DOTNET_DbgMiniDumpType": "4",
"DOTNET_CreateDumpDiagnostics": "1"
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22004"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchBrowser": false,
"hotReloadEnabled": false,
"applicationUrl": "http://localhost:15207",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "http://localhost:15207",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19030",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20028",
"DOTNET_DbgEnableMiniDump": "1",
"DOTNET_DbgMiniDumpType": "4",
"DOTNET_CreateDumpDiagnostics": "1"
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20028"
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Startup/CrestApps.Core.Blazor.Web/nlog.config
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
autoReload="true"
throwConfigExceptions="true"
internalLogLevel="Warn"
internalLogFile="App_Data/logs/nlog-internal.log">
internalLogFile="${environment:variable=CRESTAPPS_LOG_DIR:whenEmpty=${aspnet-appbasepath}/App_Data/logs}/nlog-internal.log">

<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>

<variable name="logDir" value="${aspnet-appbasepath}/App_Data/logs" />
<variable name="logDir" value="${environment:variable=CRESTAPPS_LOG_DIR:whenEmpty=${aspnet-appbasepath}/App_Data/logs}" />

<targets>
<target xsi:type="File" name="allfile"
Expand Down
4 changes: 2 additions & 2 deletions src/Startup/CrestApps.Core.Mvc.Web/nlog.config
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
autoReload="true"
throwConfigExceptions="true"
internalLogLevel="Warn"
internalLogFile="App_Data/logs/nlog-internal.log">
internalLogFile="${environment:variable=CRESTAPPS_LOG_DIR:whenEmpty=${aspnet-appbasepath}/App_Data/logs}/nlog-internal.log">

<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>

<variable name="logDir" value="${aspnet-appbasepath}/App_Data/logs" />
<variable name="logDir" value="${environment:variable=CRESTAPPS_LOG_DIR:whenEmpty=${aspnet-appbasepath}/App_Data/logs}" />

<targets>
<target xsi:type="File" name="allfile"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ namespace CrestApps.Core.Startup.Shared.Services;

public static class SharedWebApplicationBuilderExtensions
{
private const string AppDataPathConfigurationKey = "CrestApps:AppDataPath";

/// <summary>
/// Applies the shared sample-host infrastructure used by the MVC and Blazor
/// sample applications and returns the resolved <c>App_Data</c> path.
Expand All @@ -29,10 +31,24 @@ public static string AddSharedSampleHostDefaults(this WebApplicationBuilder buil
builder.Logging.ClearProviders();
builder.WebHost.UseNLog();

var appDataPath = Path.Combine(builder.Environment.ContentRootPath, "App_Data");
// Allow the App_Data path to be overridden via configuration (e.g. an environment
// variable CrestApps__AppDataPath). When running under Aspire + Visual Studio the
// content root points to the project source directory, and any file writes there
// can trigger VS to stop the debug session. Redirecting App_Data outside the
// source tree avoids that problem.
var configuredAppDataPath = builder.Configuration[AppDataPathConfigurationKey];

var appDataPath = !string.IsNullOrWhiteSpace(configuredAppDataPath)
? configuredAppDataPath
: Path.Combine(builder.Environment.ContentRootPath, "App_Data");

Directory.CreateDirectory(appDataPath);

builder.Configuration.AddJsonFile("App_Data/appsettings.json", optional: true, reloadOnChange: false);
builder.Configuration.AddJsonFile(
Path.Combine(appDataPath, "appsettings.json"),
optional: true,
reloadOnChange: false);

builder.Services.AddSharedSiteSettings(appDataPath);

return appDataPath;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text;
using CrestApps.Core.AI.Documents;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
Expand All @@ -17,6 +18,7 @@ public async Task AddCoreAIDocumentProcessing_RegistersDefaultFileSystemStore()
{
var services = new ServiceCollection()
.AddSingleton<IHostEnvironment>(new TestHostEnvironment(contentRoot))
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build())
.AddLogging()
.AddCoreAIDocumentProcessing();
await using var provider = services.BuildServiceProvider();
Expand Down Expand Up @@ -46,6 +48,7 @@ public async Task AddCoreAIDocumentProcessing_UsesConfiguredFileSystemBasePath()
{
var services = new ServiceCollection()
.AddSingleton<IHostEnvironment>(new TestHostEnvironment(contentRoot))
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build())
.Configure<DocumentFileSystemFileStoreOptions>(options => options.BasePath = "CustomDocuments")
.AddLogging()
.AddCoreAIDocumentProcessing();
Expand Down Expand Up @@ -75,6 +78,7 @@ public async Task AddCoreAIDocumentProcessing_PostConfigureOverridesDefaultBaseP
{
var services = new ServiceCollection()
.AddSingleton<IHostEnvironment>(new TestHostEnvironment(contentRoot))
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build())
.AddSingleton<IPostConfigureOptions<DocumentFileSystemFileStoreOptions>>(
new PostConfigureOptions<DocumentFileSystemFileStoreOptions>(Options.DefaultName, options =>
{
Expand Down
Loading