diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttDB.db b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttDB.db new file mode 100644 index 00000000..8d116b73 Binary files /dev/null and b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttDB.db differ diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/App.razor b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/App.razor new file mode 100644 index 00000000..bab8ccbe --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/App.razor @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Layout/MainLayout.razor b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Layout/MainLayout.razor new file mode 100644 index 00000000..755d03e3 --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Layout/MainLayout.razor @@ -0,0 +1,10 @@ +@inherits LayoutComponentBase + +
+
+
+ @Body +
+
+
+ diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Layout/MainLayout.razor.css b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Layout/MainLayout.razor.css new file mode 100644 index 00000000..116575a1 --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Layout/MainLayout.razor.css @@ -0,0 +1,9 @@ +.page { + position: relative; + display: flex; + flex-direction: column; +} + +main { + flex: 1; +} diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Layout/ReconnectModal.razor b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Layout/ReconnectModal.razor new file mode 100644 index 00000000..49d916bc --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Layout/ReconnectModal.razor @@ -0,0 +1,31 @@ + + + +
+ +

+ Rejoining the server... +

+

+ Rejoin failed... trying again in seconds. +

+

+ Failed to rejoin.
Please retry or reload the page. +

+ +

+ The session has been paused by the server. +

+ +

+ Failed to resume the session.
Please reload the page. +

+
+
diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Pages/Home.razor b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Pages/Home.razor new file mode 100644 index 00000000..ac55eaad --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Pages/Home.razor @@ -0,0 +1,184 @@ +@page "/" +@rendermode InteractiveServer +@using Syncfusion.Blazor +@using Syncfusion.Blazor.Data +@using Syncfusion.Blazor.Gantt +@using GanttGraphQL.Models + + + + + + + + + + + + + + + + + + + + + + + +@code { + + public List ToolbarItems = new() { "Add", "Edit", "Update", "Delete", "Search" }; + + private Syncfusion.Blazor.Data.GraphQLAdaptorOptions AdaptorOptions => new() + { + ResolverName = "TaskData", + Query = @"query TaskData($dataManager: DataManagerRequestInput!) { + taskData(dataManager: $dataManager) { + count + result { + taskID + taskName + startDate + endDate + duration + progress + predecessor + parentID + } + } + }", + + Mutation = new Syncfusion.Blazor.Data.GraphQLMutation + { + // INSERT (matches InsertTask parameters) + Insert = @"mutation create($record: TaskDataModelInput!, $index: Int!, $action: String!, $additionalParameters: Any) { + createTask(record: $record, index: $index, action: $action, additionalParameters: $additionalParameters) { + taskID + taskName + startDate + endDate + duration + progress + predecessor + parentID + } + }", + Update = @"mutation update($record: TaskDataModelInput!, $action: String!, $primaryColumnName: String!, $primaryColumnValue: Int!, $additionalParameters: Any) { + updateTask(record: $record, action: $action, primaryColumnName: $primaryColumnName, primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters) { + taskID taskName startDate endDate duration progress predecessor parentID + } + }", + + // DELETE (matches DeleteTask parameters) + Delete = @"mutation delete($primaryColumnValue: ID!, $additionalParameters: Any) { + deleteTask(key: $primaryColumnValue, additionalParameters: $additionalParameters) + }", + + Batch = @"mutation batch($changed: [TaskDataModelInput!], $added: [TaskDataModelInput!], $deleted: [TaskDataModelInput!], $action: String!, $primaryColumnName: String!, $additionalParameters: Any, $dropIndex: Int) { + batchUpdate(changed: $changed, added: $added, deleted: $deleted, action: $action, primaryColumnName: $primaryColumnName, additionalParameters: $additionalParameters, dropIndex: $dropIndex) { + taskID + taskName + startDate + endDate + duration + progress + predecessor + parentID + } + }" + } + }; +} + + diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Pages/NotFound.razor b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Pages/NotFound.razor new file mode 100644 index 00000000..917ada1d --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Pages/NotFound.razor @@ -0,0 +1,5 @@ +@page "/not-found" +@layout MainLayout + +

Not Found

+

Sorry, the content you are looking for does not exist.

\ No newline at end of file diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Routes.razor b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Routes.razor new file mode 100644 index 00000000..105855d4 --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/Routes.razor @@ -0,0 +1,6 @@ + + + + + + diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/_Imports.razor b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/_Imports.razor new file mode 100644 index 00000000..b4a23038 --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Components/_Imports.razor @@ -0,0 +1,13 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using GanttGraphQL +@using GanttGraphQL.Components +@using GanttGraphQL.Components.Layout +@using Syncfusion.Blazor.Gantt +@using Syncfusion.Blazor.Navigations \ No newline at end of file diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/GanttGraphQL.csproj b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/GanttGraphQL.csproj new file mode 100644 index 00000000..90cb3772 --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/GanttGraphQL.csproj @@ -0,0 +1,27 @@ + + + + net10.0 + enable + enable + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/GanttGraphQL.csproj.user b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/GanttGraphQL.csproj.user new file mode 100644 index 00000000..c404400a --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/GanttGraphQL.csproj.user @@ -0,0 +1,9 @@ + + + + https + + + ProjectDebugger + + \ No newline at end of file diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/DataManagerRequest.cs b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/DataManagerRequest.cs new file mode 100644 index 00000000..7376a752 --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/DataManagerRequest.cs @@ -0,0 +1,99 @@ +using HotChocolate; +using Syncfusion.Blazor.Data; +using System.Collections.Generic; + +namespace GanttGraphQL.Models +{ + /// + /// Represents the input structure for data manager requests from the Gantt Chart. + /// Contains all parameters needed for data operations like filtering, sorting, and searching. + /// + public class DataManagerRequestInput + { + /// + /// Number of records to skip from the beginning. + /// Example: Skip=10 means start from the 11th record. + /// + [GraphQLName("Skip")] + public int Skip { get; set; } + + /// + /// Number of records to retrieve. + /// Example: Take=10 means retrieve 10 records per page. + /// + [GraphQLName("Take")] + public int Take { get; set; } + + /// + /// Indicates whether the total record count should be calculated. + /// Set to true when pagination requires knowing the total number of records. + /// + [GraphQLName("RequiresCounts")] + public bool RequiresCounts { get; set; } = false; + + /// + /// Additional custom parameters sent from the client. + /// Used for advanced scenarios requiring extra metadata. + /// + [GraphQLName("Params")] + [GraphQLType(typeof(AnyType))] + public IDictionary Params { get; set; } + + /// + /// Collection of search expressions used for keyword matching. + /// Example: searching across task name, duration, or other fields. + /// + [GraphQLName("Search")] + public List? Search { get; set; } + + /// + /// Collection of sorting instructions. + /// Supports multi-column sorting (e.g., sort by StartDate, then by TaskID). + /// + [GraphQLName("Sorted")] + public List? Sorted { get; set; } + + /// + /// Collection of filtering conditions (where clauses). + /// Supports operators like equals, contains, greater-than, etc. + /// + [GraphQLName("Where")] + [GraphQLType(typeof(AnyType))] + public List? Where { get; set; } + + /// + /// Table name associated with the request. + /// Commonly unused but included for DataManager compatibility. + /// + /// List of field names to select from the dataset. + /// Useful when projecting only selected columns. + /// + [GraphQLName("Select")] + public List? Select { get; set; } + + /// + /// Indicates whether server‑side grouping is enabled. + /// Required for hierarchical data scenarios. + /// + [GraphQLName("ServerSideGroup")] + public bool? ServerSideGroup { get; set; } + + /// + /// Indicates whether lazy loading is enabled for hierarchical data. + /// Used mainly by Gantt to load child records on demand. + /// + [GraphQLName("LazyLoad")] + public bool? LazyLoad { get; set; } + + /// + /// Indicates whether all groups should be expanded during lazy loading. + /// Used when the UI expands grouped records automatically. + /// + [GraphQLName("LazyExpandAllGroup")] + public bool? LazyExpandAllGroup { get; set; } + } +} \ No newline at end of file diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/GanttDbContext.cs b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/GanttDbContext.cs new file mode 100644 index 00000000..08ee4b51 --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/GanttDbContext.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore; + +namespace GanttGraphQL.Models +{ + public class GanttDbContext : DbContext + { + public GanttDbContext(DbContextOptions options) : base(options) { } + + public DbSet Tasks => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .ToTable("TaskData") + .HasKey(t => t.TaskID); + + modelBuilder.Entity() + .Property(t => t.TaskID).HasColumnName("TaskID").IsRequired(); + + modelBuilder.Entity() + .Property(t => t.TaskName).HasColumnName("TaskName").IsRequired(); + + modelBuilder.Entity() + .Property(t => t.StartDate).HasColumnName("StartDate"); + + modelBuilder.Entity() + .Property(t => t.EndDate).HasColumnName("EndDate"); + + modelBuilder.Entity() + .Property(t => t.Duration).HasColumnName("Duration"); + + modelBuilder.Entity() + .Property(t => t.Progress).HasColumnName("Progress"); + + modelBuilder.Entity() + .Property(t => t.Predecessor).HasColumnName("Predecessor"); + + modelBuilder.Entity() + .Property(t => t.ParentID).HasColumnName("ParentID"); + } + } +} + diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/GraphQLMutation.cs b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/GraphQLMutation.cs new file mode 100644 index 00000000..1ff4a191 --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/GraphQLMutation.cs @@ -0,0 +1,159 @@ +using HotChocolate; +using HotChocolate.Types; +using Microsoft.EntityFrameworkCore; + +namespace GanttGraphQL.Models +{ + /// + /// GraphQL mutation resolvers for Syncfusion Blazor Gantt. + /// Provides create, update, delete, and batch-update operations for tasks. + /// + public class GraphQLMutation + { + /// + /// Creates a new task record and persists it to the database. + /// Applies defaults for missing values (TaskName, StartDate, Duration, Progress). + /// + /// The task payload received from the client. + /// The target insertion index (unused for EF set ordering). + /// The action name sent by the client (e.g., "insert"). + /// Additional custom parameters sent from the client. + /// The EF Core database context. + /// The created entity. + public TaskDataModel CreateTask(TaskDataModel record, int index, string action, [GraphQLType(typeof(AnyType))] IDictionary additionalParameters, [Service] GanttDbContext db) + { + TaskDataModel? entity = new TaskDataModel + { + TaskName = string.IsNullOrWhiteSpace(record.TaskName) ? "New Task" : record.TaskName!, + StartDate = record.StartDate ?? DateTime.Now, + EndDate = record.EndDate, + Duration = record.Duration ?? "1", + Progress = record.Progress ?? 0, + Predecessor = record.Predecessor, + ParentID = record.ParentID + }; + + db.Tasks.Add(entity); + db.SaveChangesAsync(); + return entity; + } + + /// + /// Updates an existing task by primary key and persists the changes. + /// + /// The incoming task payload with updated values. + /// The action name sent by the client (e.g., "update"). + /// The primary key column name (e.g., "TaskID"). + /// The primary key value of the task to update. + /// Additional custom parameters sent from the client. + /// The EF Core database context. + /// The updated entity. + public TaskDataModel UpdateTask(TaskDataModel record, string action, string primaryColumnName, int primaryColumnValue, [GraphQLType(typeof(AnyType))] IDictionary additionalParameters, [Service] GanttDbContext db) + { + if (record == null) + throw new ArgumentNullException(nameof(record), "Task cannot be null"); + + TaskDataModel? previousRecord = db.Tasks.FirstOrDefault(x => x.TaskID == primaryColumnValue); + + previousRecord.TaskName = record.TaskName; + previousRecord.StartDate = record.StartDate; + previousRecord.EndDate = record.EndDate; + previousRecord.Duration = record.Duration; + previousRecord.Progress = record.Progress; + previousRecord.Predecessor = record.Predecessor; + previousRecord.ParentID = record.ParentID; + + db.SaveChangesAsync(); + return previousRecord; + } + + /// + /// Deletes a task by its key. + /// + /// The primary key value of the task to delete. + /// Additional custom parameters sent from the client. + /// The EF Core database context. + /// + /// if the task was found and removed; otherwise, . + /// + public bool DeleteTask([ID][GraphQLName("key")] int key, [GraphQLType(typeof(AnyType))] IDictionary additionalParameters, [Service] GanttDbContext db) + { + TaskDataModel? record = db.Tasks.FirstOrDefault(t => t.TaskID == key); + if (record == null) return false; + + db.Tasks.Remove(record); + db.SaveChangesAsync(); + return true; + } + + /// + /// Applies batch operations (update, add, delete) to tasks in a single request and saves changes. + /// + /// The collection of modified task records to update. + /// The collection of new task records to insert. + /// The collection of task records to remove. + /// The batch action name sent by the client. + /// The primary key column name (e.g., "TaskID"). + /// Additional custom parameters sent from the client. + /// Target insert index (not applied unless you maintain an order column). + /// The EF Core database context. + /// The full list of tasks after applying the batch update. + public List BatchUpdate(List? changed, List? added, List? deleted, string action, string primaryColumnName, [GraphQLType(typeof(AnyType))] IDictionary additionalParameters, int? dropIndex, [Service] GanttDbContext db) + { + // Update existing tasks + if (changed != null) + { + foreach (TaskDataModel task in changed) + { + TaskDataModel? record = db.Tasks.FirstOrDefault(t => t.TaskID == task.TaskID); + if (record != null) + { + record.TaskName = string.IsNullOrWhiteSpace(task.TaskName) ? record.TaskName : task.TaskName!; + record.StartDate = task.StartDate ?? record.StartDate; + record.EndDate = task.EndDate; + record.Duration = task.Duration ?? record.Duration; + record.Progress = task.Progress ?? record.Progress; + record.Predecessor = task.Predecessor; + record.ParentID = task.ParentID; + } + } + } + + // Add new tasks + if (added != null) + { + foreach (TaskDataModel task in added) + { + TaskDataModel? record = new TaskDataModel + { + // TaskID is assumed to be identity; do not set it + TaskName = string.IsNullOrWhiteSpace(task.TaskName) ? "New Task" : task.TaskName!, + StartDate = task.StartDate ?? DateTime.Now, + EndDate = task.EndDate, + Duration = task.Duration ?? "1", + Progress = task.Progress ?? 0, + Predecessor = task.Predecessor, + ParentID = task.ParentID + }; + db.Tasks.Add(record); + } + } + + // Delete tasks + if (deleted != null) + { + foreach (TaskDataModel task in deleted) + { + TaskDataModel? record = db.Tasks.FirstOrDefault(t => t.TaskID == task.TaskID); + if (record != null) + { + db.Tasks.Remove(record); + } + } + } + + db.SaveChanges(); + return db.Tasks.ToList(); + } + } +} diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/GraphQLQuery.cs b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/GraphQLQuery.cs new file mode 100644 index 00000000..cc3f0d2f --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/GraphQLQuery.cs @@ -0,0 +1,98 @@ +using HotChocolate; +using HotChocolate.Types; +using Microsoft.EntityFrameworkCore; +using Syncfusion.Blazor; +namespace GanttGraphQL.Models +{ + /// + /// GraphQL query resolver for the Syncfusion GraphQLAdaptor used by the Gantt Chart. + /// Exposes a field named taskData that accepts DataManager request parameters + /// and returns a payload containing the result set and total record count. + /// + public class GraphQLQuery + { + /// + /// Retrieves Gantt Chart task data and returns it along with the total record count. + /// Applies searching, filtering, sorting, skip, and take based on the incoming DataManager request parameters from the GraphQLAdaptor. + /// + /// + /// The data manager request input containing query parameters such as search, where (filters), sorted (multi-column sort), skip, take. + /// + /// The EF Core database context used to query task records. + /// + /// An instance of containing the filtered task list + /// and the corresponding record count (total count when RequiresCounts is true). + /// + [GraphQLName("taskData")] + public async Task GetTaskData([GraphQLName("dataManager")] DataManagerRequestInput dataManagerInput, [Service] GanttDbContext db) + { + DataManagerRequestInput dm = dataManagerInput ?? new DataManagerRequestInput + { + Skip = 0, + Take = 0, + RequiresCounts = false + }; + + List dataSource = await db.Tasks + .AsNoTracking() + .OrderBy(t => t.TaskID) + .ToListAsync(); + + IEnumerable query = dataSource; + + // 1) Searching + if (dm.Search is { Count: > 0 }) + { + query = DataOperations.PerformSearching(query, dm.Search); + } + + // Filtering + if (dm.Where != null && dm.Where.Count > 0) + { + if (dm.Where[0].Field != null && dm.Where[0].Field == nameof(TaskDataModel.ParentID)) { } + else + { + query = DataOperations.PerformFiltering(query, dm.Where, dm.Where[0].Operator); + } + } + + // 3) Sorting (multi-column supported) + if (dm.Sorted is { Count: > 0 }) + { + query = DataOperations.PerformSorting(query, dm.Sorted); + } + + int totalCount = query.Count(); + + if (dm.Skip > 0) query = DataOperations.PerformSkip(query, dm.Skip); + if (dm.Take > 0) query = DataOperations.PerformTake(query, dm.Take); + + return new TaskDataResponse + { + Count = dm.RequiresCounts ? totalCount : query.Count(), // return total only if asked + Result = query.ToList() + }; + } + } + + /// + /// Response payload expected by the Syncfusion GraphQLAdaptor for list queries. + /// Must expose count and result fields to enable paging and total count. + /// + public class TaskDataResponse + { + /// + /// Total number of records for the current query. + /// When RequiresCounts is true in the request, this should reflect the total + /// count before paging; otherwise it may be the count of the returned page. + /// + [GraphQLName("count")] + public int Count { get; set; } + + /// + /// The collection of task records returned for the current request. + /// + [GraphQLName("result")] + public List Result { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/TaskDataModel.cs b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/TaskDataModel.cs new file mode 100644 index 00000000..4a99ae66 --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Models/TaskDataModel.cs @@ -0,0 +1,33 @@ +using System.Text.Json.Serialization; + +namespace GanttGraphQL.Models +{ + public class TaskDataModel + { + [JsonPropertyName("taskID")] + public int? TaskID { get; set; } + + [JsonPropertyName("taskName")] + public string? TaskName { get; set; } + + [JsonPropertyName("startDate")] + public DateTime? StartDate { get; set; } + + [JsonPropertyName("endDate")] + public DateTime? EndDate { get; set; } + + [JsonPropertyName("duration")] + public string? Duration { get; set; } + + [JsonPropertyName("progress")] + public int? Progress { get; set; } + + [JsonPropertyName("predecessor")] + public string? Predecessor { get; set; } + + [JsonPropertyName("parentID")] + public int? ParentID { get; set; } + } +} + + diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Program.cs b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Program.cs new file mode 100644 index 00000000..64382e8f --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Program.cs @@ -0,0 +1,47 @@ +using GanttGraphQL.Components; +using GanttGraphQL.Models; +using Microsoft.EntityFrameworkCore; +using Syncfusion.Blazor; + +var builder = WebApplication.CreateBuilder(args); + +var cs = builder.Configuration.GetConnectionString("GanttDb"); +builder.Services.AddDbContext(options => + options.UseSqlite(cs)); + +builder.Services.AddSyncfusionBlazor(); +builder.Services.AddServerSideBlazor() + .AddCircuitOptions(options => { options.DetailedErrors = true; }); + +// Add services to the container. +builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); + +// Register GraphQL services. +builder.Services.AddGraphQLServer().AddQueryType().AddMutationType().ModifyRequestOptions(o => o.IncludeExceptionDetails = true); + +// Add services to the container. +builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error", createScopeForErrors: true); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} +app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true); +app.UseHttpsRedirection(); + +app.UseAntiforgery(); + +app.MapStaticAssets(); +app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + +app.MapGraphQL("/graphql"); // Maps the /graphql endpoint by default. + +app.Run(); diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Properties/launchSettings.json b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Properties/launchSettings.json new file mode 100644 index 00000000..11daf84f --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5167", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7000;http://localhost:5167", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } + } diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/appsettings.Development.json b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/appsettings.json b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/appsettings.json new file mode 100644 index 00000000..c57d37b5 --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/appsettings.json @@ -0,0 +1,13 @@ +{ + "ConnectionStrings": { + "GanttDb": "Data Source=..\\GanttDB.db" + }, + + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/wwwroot/app.css b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/wwwroot/app.css new file mode 100644 index 00000000..73a69d6f --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/Blazor Web app/GanttGraphQL/wwwroot/app.css @@ -0,0 +1,60 @@ +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +a, .btn-link { + color: #006bb7; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; +} + +.content { + padding-top: 1.1rem; +} + +h1:focus { + outline: none; +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid #e50000; +} + +.validation-message { + color: #e50000; +} + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } + +.darker-border-checkbox.form-check-input { + border-color: #929292; +} + +.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder { + color: var(--bs-secondary-color); + text-align: end; +} + +.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder { + text-align: start; +} \ No newline at end of file diff --git a/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/README.md b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/README.md new file mode 100644 index 00000000..501cabfb --- /dev/null +++ b/Gantt/IntegrationWithNet10/Binding SQLite using GraphQL Adaptor/README.md @@ -0,0 +1,225 @@ +# Blazor Gantt Chart with SQLite and GraphQL Adaptor + +## Project Overview + +This repository demonstrates a production-ready pattern for binding **GraphQL** data to **Syncfusion Blazor Gantt Chart** using **Hot Chocolate GraphQL Server**. The sample application provides complete CRUD (Create, Read, Update, Delete) operations, filtering, sorting, searching and hierarchical parent/child support. The implementation follows industry best practices using GraphQL queries, mutations, resolvers, and a GraphQL adaptor for seamless Gantt Chart functionality. + +## Key Features + +- **Hot Chocolate GraphQL Server Integration**: Query resolvers for read operations and mutation resolvers for write operations +- **Syncfusion Blazor Gantt**: Built-in editing, inserting, deleting, filtering, sorting, and parent/child hierarchy (via ParentID). +- **Complete CRUD Operations**: Add, edit, delete, and batch update of records directly from the Gantt Chart +- **GraphQL Adaptor**: Full control over Gantt Chart data operations (read, search, filter, sort) via GraphQL queries and mutations +- **DataManagerRequestInput**: Structured input type for handling complex data operations from the Gantt Chart +- **Configurable GraphQL Endpoint**: Backend port and GraphQL endpoint managed via `launchSettings.json` + +## Prerequisites + +| Component | Version | Purpose | +|-----------|---------|---------| +| Visual Studio 2026 | 18.1.0 or later | Development IDE with Blazor workload | +| .NET SDK | net10.0 or compatible | Runtime and build tools | +| SQLite | Latest stable version | Core framework for database operations | +|Microsoft.EntityFrameworkCore.Sqlite| 10.0.3 or latest|Provides SQLite database provider support for Entity Framework Core.| +| HotChocolate.AspNetCore | 2.1.66 or latest | Lightweight data mapper | +| Syncfusion.Blazor.Gantt | 32.1.25 or Latest | Gantt and UI components | +| Syncfusion.Blazor.Themes | 32.1.25 or Latest | Styling for Gantt components | +| Syncfusion.Blazor.Data | Latest | Data adaptors including GraphQL support | + +## Quick Start + +1. **Clone the repository** + ```bash + git clone + cd "Binding SQLite using GraphQL Adaptor" + cd "Blazor Web app/GanttGraphQL" + ``` + +2. **Create the database and table** + + Open DB Browser for SQLite and Create Database GanttDB in the desired location + ```sql + + CREATE TABLE IF NOT EXISTS TaskData ( + TaskID INTEGER PRIMARY KEY, + TaskName TEXT NOT NULL, + StartDate DATETIME, + EndDate DATETIME, + Duration TEXT, + Progress INTEGER, + Predecessor TEXT, + ParentID INTEGER + ); + ``` + +3. **Update the connection string** + + SQLite uses a **file‑based database**, so to connect to the database, simply point to the location of the .db file. + The key **GanttDb** refers to the database name used by the application and it can be renamed as needed. + + Open `appsettings.json` and configure the SQLite connection: + ```json + { + "ConnectionStrings": { + "GanttDb": "Data Source=D:..\\GanttDB.db" + }, + "AllowedHosts": "*" + } + ``` + +4. **Restore packages and build** + ```bash + dotnet restore + dotnet build + ``` + +5. **Run the application** + ```bash + dotnet run + ``` + +6. **Open the application** + + Navigate to the local URL displayed in the terminal (typically `https://localhost:xxxx`). + +7. **Access GraphQL Playground (Optional)** + + Navigate to `https://localhost:70xx/graphql` (Replace `70xx` with the port number shown in the **launchSettings.json**) to explore the GraphQL schema and test queries/mutations manually. + +## Configuration + +### GraphQL Endpoint Port Configuration + +The GraphQL endpoint port is configured in `Properties/launchSettings.json`. This file controls where the application runs and where the GraphQL endpoint is exposed. + +**Instructions to Change the Port:** + +1. Open `Properties/launchSettings.json` in the project root. +2. Locate the `https` profile section: + +```json +"https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7000;http://localhost:5167", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } +} +``` + +3. Modify the `applicationUrl` property to change port numbers: + - `https://localhost:7000` - HTTPS port for GraphQL endpoint + - `http://localhost:5167` - HTTP port for GraphQL endpoint + +4. Update the Gantt Chart connection URL in `Components/Pages/Home.razor` to match the configured port: + ```html + + ``` + +**Important Notes:** +- Port numbers must be between 1024 and 65535 +- Avoid using ports already in use by other applications +- The GraphQL endpoint is always accessible at `/graphql` path (configured via `app.MapGraphQL()` in Program.cs) + +### GraphQL Adaptor Configuration + +The `GraphQLAdaptorOptions` in `Home.razor` defines how the Gantt Chart communicates with the GraphQL backend. This configuration specifies: + +- **Query**: GraphQL query for reading task records with data operations support +- **ResolverName**: The backend resolver method name — for the Gantt Chart sample this refers to TaskData (or whatever you named your GraphQL query resolver that returns the task list) +- **Mutation.Insert**: GraphQL mutation for creating new tasks +- **Mutation.Update**: GraphQL mutation for updating existing tasks +- **Mutation.Delete**: GraphQL mutation for deleting tasks +- **Mutation.Batch**: GraphQL mutation for batch operations + +## Project Layout + +| File/Folder | Purpose | +|-------------|---------| +| `/Models/TaskDataModel.cs` | Data model representing Task Data | +| `/Models/GraphQLQuery.cs` | Query resolvers for retrieving task data (e.g., GetTaskData) | +| `/Models/GraphQLMutation.cs` | Mutation resolvers for creating, updating, deleting, and batch-updating tasks | +| `/Models/DataManagerRequestInput.cs` | Input type for data operation parameters (filter, sort, search) | +| `/Components/Pages/Home.razor` | Gantt Chart page with GraphQL adaptor configuration and task management UI | +| `/Program.cs` | Service registration for Hot Chocolate GraphQL server | +| `/Properties/launchSettings.json` | Port configuration for GraphQL endpoint | + +## Common Tasks + +### Add a Task +1. Click the **Add** button in the toolbar +2. Fill in the Dialog fields +3. Click **Save** to persist the record to the database + +### Edit a Task +1. Select a task row in the Gantt Chart +2. Click the **Edit** button in the toolbar +3. Modify the required fields +4. Click **Save** to save changes + +### Delete a Task +1. Select a task row in the Gantt Chart +2. Click the **Delete** button in the toolbar +3. Confirm the deletion in the dialog + +### Search Records +1. Use the **Search** box in the toolbar +2. Enter keywords to filter records (searches across all columns) + +### Filter Records +1. Click the filter icon in any column header +2. Select filter criteria (equals, contains, greater than, etc.) +3. Click **Filter** to apply + +### Sort Records +1. Click the column header to sort ascending +2. Click again to sort descending + +## Troubleshooting + +### GraphQL Endpoint Not Responding + +- Verify the application is running: Check terminal for `Now listening on: https://localhost:70xx` (Replace `70xx` with the port number shown in the **launchSettings.json**) +- Confirm the URL in `SfDataManager` matches the running port +- Ensure `app.MapGraphQL()` is configured in `Program.cs` +- Check firewall settings if accessing from a different machine + +### Static Files Not Loading (CSS/Scripts) + +- Verify Syncfusion stylesheet is referenced in `Components/App.razor`: + ```html + + ``` +- Verify Syncfusion scripts are referenced in `Components/App.razor`: + ```html + + ``` +- Check browser developer tools (F12) for 404 errors on static resources + +### CRUD Operations Not Working + +- Verify the GraphQL endpoint URL in `Home.razor` SfDataManager matches the backend port +- Check browser console for GraphQL errors (Network tab → GraphQL requests) +- Ensure all GraphQL mutation resolvers are implemented in `GraphQLMutation.cs` +- Verify the `DataManagerRequestInput` class includes all required properties for data operations + +### Version Conflicts + +- Align HotChocolate.AspNetCore and Syncfusion package versions +- Run `dotnet restore` to update NuGet packages +- Check the `GanttGraphQLAdaptor.csproj` file for conflicting version constraints +- Verify all packages are compatible with .NET 10.0 or later + +### Connection Error +- Verify that the database file exists at the path specified in the connection string. +- Ensure the .db file is not locked or opened exclusively by another program. +- Confirm that the application has read/write permissions to the folder where the database file is stored. +- If you changed the database location or filename, make sure the Data Source= path is updated accordingly in **appsettings.json**. + +### Port Already in Use + +- Change the port numbers in `launchSettings.json` to available ports +- Or identify the process using the port: `Get-Process | Where-Object {$_.Handles -gt 500} | Select-Object Name, Id` +- Update the Gantt Chart connection URL to match the new port \ No newline at end of file