feat: Implement ExistingDatabaseContext for managing existing databases with customizable naming strategies and auto-discovery of entities

feat: Add SqlServerSchemaProvider for extracting database schema information from SQL Server

feat: Introduce DatabaseType and NamingStrategy enums for better database management and naming conventions

feat: Create IDatabaseDiscovery and IDatabaseManager interfaces for database operations and metadata retrieval

feat: Develop REST service client architecture with BaseRestServiceClient and SAP Business One specific implementation

feat: Implement REST service discovery page with UI for connecting to SAP Business One Service Layer and displaying discovered entities
This commit is contained in:
2025-04-29 00:16:03 +02:00
parent d1103c4e7d
commit 7346db3b63
22 changed files with 758 additions and 1 deletions
+196
View File
@@ -0,0 +1,196 @@
@page "/rest-discovery"
@using DataConnection.REST.Configuration
@using DataConnection.REST.Implementations
@using DataConnection.REST.Models
@using System.Net.Http
@inject IHttpClientFactory HttpClientFactory
<h3>REST Service Discovery</h3>
<div class="mb-3">
<label for="serviceType" class="form-label">Service Type:</label>
<select id="serviceType" class="form-select" @bind="SelectedServiceType">
<option value="">-- Select Service --</option>
<option value="SAPB1">SAP Business One Service Layer</option>
@* Add other service types here later *@
</select>
</div>
@if (SelectedServiceType == "SAPB1")
{
<div class="card mb-3">
<div class="card-header">SAP Business One Connection Details</div>
<div class="card-body">
<div class="mb-3">
<label for="baseUrl" class="form-label">Service Layer Base URL:</label>
<input type="text" id="baseUrl" class="form-control" @bind="SapB1Options.BaseUrl" placeholder="e.g., https://sap-server:50000" />
</div>
<div class="mb-3">
<label for="companyDb" class="form-label">Company DB:</label>
<input type="text" id="companyDb" class="form-control" @bind="SapB1Options.CompanyDb" />
</div>
<div class="mb-3">
<label for="username" class="form-label">Username:</label>
<input type="text" id="username" class="form-control" @bind="SapB1Options.Username" />
</div>
<div class="mb-3">
<label for="password" class="form-label">Password:</label>
<input type="password" id="password" class="form-control" @bind="SapB1Options.Password" />
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="ignoreSsl" @bind="SapB1Options.IgnoreSslErrors">
<label class="form-check-label" for="ignoreSsl">
Ignore SSL Errors (Use with caution)
</label>
</div>
<button class="btn btn-primary" @onclick="HandleConnectClick" disabled="IsLoading">
@if (IsLoading)
{
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span> Connecting...</span>
}
else
{
<span>Connect and Discover Entities</span>
}
</button>
</div>
</div>
}
@if (!string.IsNullOrEmpty(StatusMessage))
{
<div class="alert @StatusMessageClass mt-3" role="alert">
@StatusMessage
</div>
}
@if (DiscoveredEntities.Any())
{
<h4 class="mt-4">Discovered Entities</h4>
<div style="max-height: 500px; overflow-y: auto;">
<ul class="list-group">
@foreach (var entity in DiscoveredEntities.OrderBy(e => e.Name))
{
<li class="list-group-item">
<h5>@entity.Name</h5>
<ul class="list-unstyled ms-3">
@foreach (var prop in entity.Properties.OrderBy(p => p.Name))
{
<li>
<code>@prop.Name</code> (@prop.Type) @(prop.IsKey ? "[Key]" : "")
</li>
}
</ul>
</li>
}
</ul>
</div>
}
@code {
private string SelectedServiceType { get; set; } = "";
private SapB1ConnectionOptions SapB1Options { get; set; } = new SapB1ConnectionOptions();
private List<RestEntityInfo> DiscoveredEntities { get; set; } = new List<RestEntityInfo>();
private bool IsLoading { get; set; } = false;
private string? StatusMessage { get; set; }
private string StatusMessageClass => !string.IsNullOrEmpty(StatusMessage) && StatusMessage.StartsWith("Error") ? "alert-danger" : "alert-success";
// Helper class to hold SAP B1 specific inputs
private class SapB1ConnectionOptions
{
public string? BaseUrl { get; set; }
public string? CompanyDb { get; set; }
public string? Username { get; set; }
public string? Password { get; set; }
public bool IgnoreSslErrors { get; set; }
}
private async Task HandleConnectClick()
{
IsLoading = true;
StatusMessage = null;
DiscoveredEntities.Clear();
StateHasChanged(); // Update UI to show loading
if (SelectedServiceType == "SAPB1")
{
if (string.IsNullOrWhiteSpace(SapB1Options.BaseUrl) ||
string.IsNullOrWhiteSpace(SapB1Options.CompanyDb) ||
string.IsNullOrWhiteSpace(SapB1Options.Username) ||
string.IsNullOrWhiteSpace(SapB1Options.Password))
{
StatusMessage = "Error: Please fill in all SAP Business One connection details.";
IsLoading = false;
StateHasChanged();
return;
}
try
{
var restOptions = new RestServiceOptions
{
BaseUrl = SapB1Options.BaseUrl,
IgnoreSslErrors = SapB1Options.IgnoreSslErrors
// Username/Password are not set here as SapB1ServiceClient handles them in LoginAsync
};
// SapB1ServiceClient manages its own HttpClient internally for cookie handling
// We don't inject HttpClient directly into it via constructor in this setup.
// The BaseRestServiceClient constructor receives an HttpClient, but SapB1ServiceClient
// creates its own specific one in CreateConfiguredHttpClient.
// We pass the factory-created client to the base, but the SapB1 specific logic uses its own.
// This seems slightly complex, might need refactoring later, but follows current implementation.
// Let's simplify: Create the client directly here for now.
var client = new SapB1ServiceClient(restOptions);
StatusMessage = "Attempting login...";
StateHasChanged();
bool loggedIn = await client.LoginAsync(SapB1Options.CompanyDb, SapB1Options.Username, SapB1Options.Password);
if (loggedIn)
{
StatusMessage = "Login successful. Discovering entities...";
StateHasChanged();
DiscoveredEntities = await client.DiscoverEntitiesAsync();
if (DiscoveredEntities.Any())
{
StatusMessage = $"Discovery complete. Found {DiscoveredEntities.Count} entities.";
}
else
{
StatusMessage = "Login successful, but failed to discover entities or no entities found.";
}
// Optional: Logout after discovery if desired
// await client.LogoutAsync();
}
else
{
StatusMessage = "Error: SAP B1 Login failed. Check credentials and Service Layer status.";
}
}
catch (Exception ex)
{
// Log the full exception details somewhere appropriate
Console.WriteLine($"Error during SAP B1 connection/discovery: {ex}");
StatusMessage = $"Error: An exception occurred: {ex.Message}";
}
finally
{
IsLoading = false;
StateHasChanged(); // Update UI after completion/error
}
}
else
{
StatusMessage = "Error: Please select a service type.";
IsLoading = false;
StateHasChanged();
}
}
}
+3
View File
@@ -23,6 +23,9 @@ builder.Services.AddScoped<DatabaseConnectionService>();
builder.Services.AddSingleton<IDatabaseDiscovery, SqlServerDatabaseDiscovery>();
builder.Services.AddSingleton<DbManagerOptions>();
// Register IHttpClientFactory
builder.Services.AddHttpClient();
var app = builder.Build();
+5
View File
@@ -34,6 +34,11 @@
<span class="oi oi-list" aria-hidden="true"></span> Struttura Database
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="rest-discovery">
<span class="oi oi-cloud" aria-hidden="true"></span> REST Discovery
</NavLink>
</div>
</nav>
</div>