diff --git a/blazor-toc.html b/blazor-toc.html index 841faecc35..f12ab596dc 100644 --- a/blazor-toc.html +++ b/blazor-toc.html @@ -4304,7 +4304,12 @@
  • Appointment customization
  • -
  • Data Binding
  • +
  • + Data Binding + +
  • CRUD Actions
  • Virtual Scrolling
  • Editor Window Customization
  • diff --git a/blazor/scheduler/images/blazor-minimalAPI-frontend.png b/blazor/scheduler/images/blazor-minimalAPI-frontend.png new file mode 100644 index 0000000000..ddfe31541f Binary files /dev/null and b/blazor/scheduler/images/blazor-minimalAPI-frontend.png differ diff --git a/blazor/scheduler/minimalapi.md b/blazor/scheduler/minimalapi.md new file mode 100644 index 0000000000..51fe7a5cc3 --- /dev/null +++ b/blazor/scheduler/minimalapi.md @@ -0,0 +1,676 @@ +--- +layout: post +title: Minimal API Data Binding in Blazor Scheduler Component | Syncfusion +description: Learn about consuming data from ASP.NET Core Minimal API and binding it to Syncfusion Blazor Scheduler Component, and performing CRUD operations. +platform: Blazor +control: Scheduler +documentation: ug +--- + +# Connecting ASP.NET Core Minimal API to Scheduler Component + +The Syncfusion® Blazor Scheduler component supports binding data from ASP.NET Core [Minimal API](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis) endpoints hosted within the same application. This approach enables seamless integration of appointment data with full CRUD (Create, Read, Update, Delete) functionality without requiring a separate backend project. + +This documentation explains how to create a Blazor Server application that hosts Minimal API endpoints in the same project, enabling the Scheduler component to perform CRUD operations on in-memory appointment data through RESTful HTTP requests. + +## Prerequisites + +Before starting, ensure you have the following installed: + +- **.NET SDK 8.0 or later** +- **Visual Studio 2022 (v17.8+) or later** or **Visual Studio Code** with C# extension + +## Binding data from Minimal API endpoints + +This section explains how to create an ASP.NET Core Minimal API backend within a Blazor Server application and bind appointment data to the Syncfusion® Blazor Scheduler component. The following steps demonstrate the actual project creation flow. + +### Step 1: Create a Blazor Web App + +Create a **Blazor Web App** using Visual Studio 2022 or .NET CLI. + +**Using Visual Studio 2022:** +1. Open Visual Studio 2022 +2. Click **Create a new project** +3. Search for **Blazor Web App** template +4. Configure project name as **BlazorSchedulerApp** +5. Select **.NET 8.0** as the target framework +6. Set **Interactive render mode** to **Server** +7. Set **Interactivity location** to **Per page/component** +8. Click **Create** + +**Using .NET CLI:** +```bash +dotnet new blazor -n BlazorSchedulerApp --interactivity Server +cd BlazorSchedulerApp +``` + +> Configure the Interactive render mode to **InteractiveServer** during project creation as the Scheduler requires interactivity for CRUD operations. + +### Step 2: Install Required NuGet Packages + +Install the Syncfusion Blazor packages using the .NET CLI: + +**Using .NET CLI:** +```bash +dotnet add package Syncfusion.Blazor.Schedule +dotnet add package Syncfusion.Blazor.Themes +``` + +> After installing packages, build the project to ensure all dependencies are restored correctly: `dotnet build` + +## Step 3: Add Import Namespaces + +Open the **Components/_Imports.razor** file and import the `Syncfusion.Blazor` and `Syncfusion.Blazor.Schedule` namespaces. + +{% tabs %} +{% highlight razor tabtitle="Components/_Imports.razor" %} + +@using Syncfusion.Blazor +@using Syncfusion.Blazor.Schedule + +{% endhighlight %} +{% endtabs %} + +## Step 4: Register Syncfusion® Blazor Service + +Register the Syncfusion® Blazor Service in the **Program.cs** file of your Blazor Web App. + +{% tabs %} +{% highlight csharp tabtitle="Program.cs" %} + +using Syncfusion.Blazor; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddSyncfusionBlazor(); + +{% endhighlight %} +{% endtabs %} + +## Step 5: Add stylesheet and script resources + +The theme stylesheet and script can be accessed from NuGet through [Static Web Assets](https://blazor.syncfusion.com/documentation/appearance/themes#static-web-assets). Include the stylesheet reference in the `` section and the script reference at the end of the `` in the **/Components/App.razor** file as shown below: + +```html + + .... + + + + + .... + + + //Blazor Scheduler Component script reference. + + +``` + +### Step 6: Create the Appointment Model + +Create a model class to represent scheduler appointments with all required properties. + +{% tabs %} +{% highlight csharp tabtitle="Models/AppointmentData.cs" %} + + namespace BlazorSchedulerApp.Models; + + public class AppointmentData + { + public int Id { get; set; } + public string Subject { get; set; } = "Add Title"; + public string Location { get; set; } = string.Empty; + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + public string Description { get; set; } = string.Empty; + public bool IsAllDay { get; set; } + public string? RecurrenceRule { get; set; } + public int? RecurrenceID { get; set; } + public string? RecurrenceException { get; set; } + } +{% endhighlight %} +{% endtabs %} + +Open the **Components/_Imports.razor** file and import the `BlazorSchedulerApp.Models` namespaces. + +{% tabs %} +{% highlight razor tabtitle="Components/_Imports.razor" %} + +@using BlazorSchedulerApp.Models + +{% endhighlight %} +{% endtabs %} + +**Key Properties Explanation:** + +- **Id**: Primary key for the appointment (auto-generated by API) +- **Subject**: Title of the appointment +- **StartTime/EndTime**: Date and time range for the appointment +- **Location**: Where the appointment takes place +- **IsAllDay**: Flag for all-day events +- **RecurrenceRule**: Rule for recurring appointments (e.g., daily, weekly) +- **RecurrenceID**: Links recurring appointment instances +- **RecurrenceException**: Handles exceptions in recurring series + +> **Note**: All properties use default values to avoid null reference issues. The API will handle generating unique IDs for new appointments. + +### Step 7: Create the Appointment Service + +Implement a service layer to handle HTTP communication with the Minimal API endpoints. + +{% tabs %} +{% highlight csharp tabtitle="Services/AppointmentService.cs" %} + + using BlazorSchedulerApp.Models; + using System.Net.Http.Json; + + namespace BlazorSchedulerApp.Services; + + public class AppointmentService + { + private readonly HttpClient _httpClient; + + public AppointmentService(HttpClient httpClient) + { + _httpClient = httpClient; + } + + /// + /// Retrieves all appointments from the API + /// + public async Task> GetAppointmentsAsync() + { + try + { + var result = await _httpClient.GetFromJsonAsync>("/api/appointments"); + return result ?? new List(); + } + catch (Exception ex) + { + Console.WriteLine($"Error fetching appointments: {ex.Message}"); + return new List(); + } + } + + /// + /// Retrieves a single appointment by ID + /// + public async Task GetAppointmentByIdAsync(int id) + { + try + { + return await _httpClient.GetFromJsonAsync($"/api/appointments/{id}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error fetching appointment: {ex.Message}"); + return null; + } + } + + /// + /// Creates a new appointment via the API + /// + public async Task CreateAppointmentAsync(AppointmentData appointment) + { + try + { + var response = await _httpClient.PostAsJsonAsync("/api/appointments", appointment); + if (response.IsSuccessStatusCode) + { + return await response.Content.ReadFromJsonAsync(); + } + return null; + } + catch (Exception ex) + { + Console.WriteLine($"Error creating appointment: {ex.Message}"); + return null; + } + } + + /// + /// Updates an existing appointment via the API + /// + public async Task UpdateAppointmentAsync(int id, AppointmentData appointment) + { + try + { + var response = await _httpClient.PutAsJsonAsync($"/api/appointments/{id}", appointment); + return response.IsSuccessStatusCode; + } + catch (Exception ex) + { + Console.WriteLine($"Error updating appointment: {ex.Message}"); + return false; + } + } + + /// + /// Deletes an appointment via the API + /// + public async Task DeleteAppointmentAsync(int id) + { + try + { + var response = await _httpClient.DeleteAsync($"/api/appointments/{id}"); + return response.IsSuccessStatusCode; + } + catch (Exception ex) + { + Console.WriteLine($"Error deleting appointment: {ex.Message}"); + return false; + } + } + } +{% endhighlight %} +{% endtabs %} + +Open the **Components/_Imports.razor** file and import the `BlazorSchedulerApp.Services` namespaces. + +{% tabs %} +{% highlight razor tabtitle="Components/_Imports.razor" %} + +@using BlazorSchedulerApp.Services + +{% endhighlight %} +{% endtabs %} + + +### Step 8: Configure Application Services + +The `Program.cs` file must be updated to register required services, including Syncfusion Blazor components, HttpClient, CORS, and Razor components.This section configures the foundational services used across the Scheduler application. + +{% tabs %} +{% highlight csharp tabtitle="Program.cs" %} + + using BlazorSchedulerApp.Components; + using BlazorSchedulerApp.Models; + using BlazorSchedulerApp.Services; + using Microsoft.AspNetCore.Components; + using Syncfusion.Blazor; + + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); + + // Add HttpClient for AppointmentService + builder.Services.AddScoped(sp => + { + var navigationManager = sp.GetRequiredService(); + return new HttpClient + { + BaseAddress = new Uri(navigationManager.BaseUri) + }; + }); + builder.Services.AddScoped(); + + // Add Syncfusion Blazor service + builder.Services.AddSyncfusionBlazor(); + + // Configure CORS + builder.Services.AddCors(options => + { + options.AddDefaultPolicy(policy => + { + policy.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader(); + }); + }); + + var app = builder.Build(); + + // In-memory data storage for appointments + var appointments = new List + { + new AppointmentData + { + Id = 1, + Subject = "Team Meeting", + Location = "Conference Room A", + StartTime = DateTime.Today.AddHours(10), + EndTime = DateTime.Today.AddHours(11), + Description = "Weekly team sync-up meeting" + }, + new AppointmentData + { + Id = 2, + Subject = "Client Presentation", + Location = "Boardroom", + StartTime = DateTime.Today.AddDays(1).AddHours(14), + EndTime = DateTime.Today.AddDays(1).AddHours(16), + Description = "Quarterly review with client" + }, + new AppointmentData + { + Id = 3, + Subject = "Project Planning", + Location = "Room 301", + StartTime = DateTime.Today.AddDays(2).AddHours(9), + EndTime = DateTime.Today.AddDays(2).AddHours(10).AddMinutes(30), + Description = "Planning session for new project" + } + }; + + // Configure the HTTP request pipeline. + if (!app.Environment.IsDevelopment()) + { + app.UseExceptionHandler("/Error", createScopeForErrors: true); + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseAntiforgery(); + app.UseCors(); + +{% endhighlight %} +{% endtabs %} + +### Step 9: Create Minimal API Endpoints + +Update `Program.cs` to define Minimal API endpoints for CRUD operations. + +{% tabs %} +{% highlight csharp tabtitle="Program.cs" %} + + // GET: Get all appointments + app.MapGet("/api/appointments", () => + { + return Results.Ok(appointments); + }) + .WithName("GetAppointments"); + + // GET: Get appointment by ID + app.MapGet("/api/appointments/{id}", (int id) => + { + var appointment = appointments.FirstOrDefault(a => a.Id == id); + return appointment is not null ? Results.Ok(appointment) : Results.NotFound(); + }) + .WithName("GetAppointmentById"); + + // POST: Create new appointment + app.MapPost("/api/appointments", (AppointmentData appointment) => + { + appointment.Id = appointments.Any() ? appointments.Max(a => a.Id) + 1 : 1; + appointments.Add(appointment); + return Results.Created($"/api/appointments/{appointment.Id}", appointment); + }) + .WithName("CreateAppointment"); + + // PUT: Update appointment + app.MapPut("/api/appointments/{id}", (int id, AppointmentData updatedAppointment) => + { + var appointment = appointments.FirstOrDefault(a => a.Id == id); + if (appointment is null) + return Results.NotFound(); + + appointment.Subject = updatedAppointment.Subject; + appointment.Location = updatedAppointment.Location; + appointment.StartTime = updatedAppointment.StartTime; + appointment.EndTime = updatedAppointment.EndTime; + appointment.Description = updatedAppointment.Description; + appointment.IsAllDay = updatedAppointment.IsAllDay; + appointment.RecurrenceRule = updatedAppointment.RecurrenceRule; + appointment.RecurrenceID = updatedAppointment.RecurrenceID; + appointment.RecurrenceException = updatedAppointment.RecurrenceException; + + return Results.Ok(appointment); + }) + .WithName("UpdateAppointment"); + + // DELETE: Delete appointment + app.MapDelete("/api/appointments/{id}", (int id) => + { + var appointment = appointments.FirstOrDefault(a => a.Id == id); + if (appointment is null) + return Results.NotFound(); + + appointments.Remove(appointment); + return Results.NoContent(); + }) + .WithName("DeleteAppointment"); + + // ============= End of Minimal API Endpoints ============= + + app.MapStaticAssets(); + app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + + app.Run(); +{% endhighlight %} +{% endtabs %} + + + +**Minimal API Benefits:** + +- No controllers or routing configuration required +- Lightweight and performant +- Easy to understand and maintain +- Perfect for small APIs within the same application + + +### Step 10: Create the Scheduler Component + +Replace the contents of `Home.razor` with the following Blazor Scheduler component that provides full CRUD functionality. + +{% tabs %} +{% highlight razor tabtitle="Components/Pages/Home.razor" %} + + @page "/" + @rendermode InteractiveServer + @inject AppointmentService AppointmentService + + Appointment Scheduler + +
    + + + + + + + + + + + + + + + + + +
    + + @code { + private SfSchedule? ScheduleRef; + private DateTime CurrentDate = DateTime.Today; + private View CurrentView = View.Week; + private List Appointments = new(); + + protected override async Task OnInitializedAsync() + { + await LoadAppointments(); + } + + private async Task LoadAppointments() + { + Appointments = await AppointmentService.GetAppointmentsAsync(); + StateHasChanged(); + } + + private async Task OnActionBegin(ActionEventArgs args) + { + try + { + if (args.ActionType == ActionType.EventCreate && args.AddedRecords != null && args.AddedRecords.Any()) + { + foreach (var appointment in args.AddedRecords) + { + var created = await AppointmentService.CreateAppointmentAsync(appointment); + if (created != null) + { + // Update the appointment with the server-generated ID + appointment.Id = created.Id; + } + } + } + else if (args.ActionType == ActionType.EventChange && args.ChangedRecords != null && args.ChangedRecords.Any()) + { + foreach (var appointment in args.ChangedRecords) + { + await AppointmentService.UpdateAppointmentAsync(appointment.Id, appointment); + } + } + else if (args.ActionType == ActionType.EventRemove && args.DeletedRecords != null && args.DeletedRecords.Any()) + { + foreach (var appointment in args.DeletedRecords) + { + await AppointmentService.DeleteAppointmentAsync(appointment.Id); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Error in OnActionBegin: {ex.Message}"); + args.Cancel = true; + } + } + + } + +{% endhighlight %} +{% endtabs %} + +**Key Implementation Details:** + +- **@rendermode InteractiveServer**: Enables server-side interactivity required for CRUD operations +- **OnActionBegin**: Handles all CRUD operations by calling the AppointmentService +- **OnPopupOpen**: Sets default values when opening the appointment editor +- **Default Title Logic**: Automatically sets "Add Title" for appointments with empty subjects +- **Error Handling**: Cancels the operation if an error occurs to prevent data inconsistency + +**Why Use OnActionBegin?** + +The `OnActionBegin` event fires before any Scheduler action completes, allowing you to: +1. Intercept the operation and call your own API +2. Add custom validation and business logic +3. Handle errors gracefully +4. Control exactly when and how data is saved + + +### Step 11: Run the Application + +**Step 1: Build the Application** + +Ensure there are no compilation errors: + +```bash +dotnet build +``` + +**Step 2: Run the Application** + +Start the application: + +```bash +dotnet run +``` + +Or use watch mode for automatic reload during development: + +```bash +dotnet watch +``` + +**Step 3: Access the Scheduler** + +The application will start at `http://localhost:5235` (or the port configured in your `launchSettings.json`). + +Open your browser and navigate to: +``` +http://localhost:5235/scheduler +``` + + + +## Output Preview +![Frontend Preview](./images/blazor-minimalAPI-frontend.png) +*Image illustrating the Syncfusion Blazor Scheduler with Minimal API* + +## Complete Sample Repository + +A complete, working sample implementation is available in the [GitHub repository.](https://github.com/SyncfusionExamples/How-to-integrate-Syncfusion-Blazor-Scheduler-with-MinimalAPI) + +## Troubleshooting + +### JavaScript Errors: 'sfBlazor' is undefined + +**Issue**: Errors like `Could not find 'sfBlazor.Toolbar.initialize'` + +**Fix**: Add Syncfusion script in `App.razor` before ``: +```html + +``` + +### API Returns 404 Not Found + +**Issue**: API calls fail with 404 errors + +**Fix**: +- Ensure endpoints are defined **before** `app.MapRazorComponents()` in `Program.cs` +- Verify routes match (e.g., `/api/appointments`) +- Check browser console for actual URL + +### Service Injection Error + +**Issue**: `Cannot provide a value for property 'AppointmentService'` + +**Fix**: +- Register service in `Program.cs`: `builder.Services.AddScoped();` +- Add `@inject AppointmentService AppointmentService` in `Scheduler.razor` +- Rebuild project + +### Theme Not Applying + +**Issue**: Scheduler looks unstyled + +**Fix**: +- Verify theme CSS in `App.razor`: `_content/Syncfusion.Blazor.Themes/tailwind3.css` +- Clear browser cache +- Check Syncfusion.Blazor.Themes package is installed + +### Duplicate Appointments Created + +**Issue**: Creating one appointment makes multiple copies + +**Fix**: +- Remove duplicate `OnActionBegin` handlers +- Don't call `LoadAppointments()` inside `OnActionBegin` +- Let Scheduler manage the appointments list + +### Navigation Controls Don't Work + +**Issue**: View switching buttons unresponsive + +**Fix**: +- Check `@bind-CurrentView` and `@bind-SelectedDate` are set +- Don't cancel navigation actions in `OnActionBegin` +- Verify Syncfusion script is loaded + +To learn more about the functionality of the Schedule component, refer to the [documentation](https://blazor.syncfusion.com/documentation/scheduler/getting-started).