How to create the Batch URL/API endpoint on the API

I have a Scheduler component using DataManager and the WebAPIAdapter. I can successfully make the Get/Update/Delete calls, however, while interacting with recurring events/appointments I am receiving errors and it looks like I need the Batch endpoint as well. There is little documentation on how to format API endpoints and I had to use the URL Adapter controller example as a framework and modify it until it worked. 


The error when trying to delete a recurring event:

Syncfusion.Blazor.Schedule.ActionEventArgs`1[SFTownCenter.Components.Partials.EventsTab+AppointmentData]Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer: Warning: Unhandled exception rendering component: The format of value 'batch_6a756608-da6f-46c6-b698-ea90b80df50f' is invalid.



System.FormatException: The format of value 'batch_6a756608-da6f-46c6-b698-ea90b80df50f' is invalid.
   at System.Net.Http.Headers.MediaTypeHeaderValue.CheckMediaTypeFormat(String mediaType, String parameterName)
   at System.Net.Http.StringContent..ctor(String content, Encoding encoding, String mediaType)
   at Syncfusion.Blazor.Data.HttpHandler.PrepareRequest(RequestOptions options)
   at Syncfusion.Blazor.DataManager.SaveChanges[T](Object changed, Object added, Object deleted, String keyField, Nullable`1 dropIndex, String tableName, Query query, Object Original)
   at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.SaveChanges(SaveChanges`1 editParams, Query query)
   at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.ProcessEntireSeries(List`1 eventsData, ActionType actionType)
   at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.DeleteEvents(List`1 deleteEvents, Nullable`1 action)
   at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.DeleteEvents(List`1 deleteEvents, Nullable`1 action)
   at Syncfusion.Blazor.Schedule.Internal.AlertWindow`1.OnDeleteSeriesClick(MouseEventArgs args)
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Syncfusion.Blazor.Internal.SfBaseUtils.InvokeEvent[T](Object eventFn, T eventArgs)
   at Syncfusion.Blazor.Buttons.SfButton.OnClickHandler(MouseEventArgs args)
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost: Error: Unhandled exception in circuit 'zfihqVGFzn7Xcv5vUizmGyXu8xssFkCG9QbszVrGQb0'.


System.FormatException: The format of value 'batch_6a756608-da6f-46c6-b698-ea90b80df50f' is invalid.
   at System.Net.Http.Headers.MediaTypeHeaderValue.CheckMediaTypeFormat(String mediaType, String parameterName)
   at System.Net.Http.StringContent..ctor(String content, Encoding encoding, String mediaType)
   at Syncfusion.Blazor.Data.HttpHandler.PrepareRequest(RequestOptions options)
   at Syncfusion.Blazor.DataManager.SaveChanges[T](Object changed, Object added, Object deleted, String keyField, Nullable`1 dropIndex, String tableName, Query query, Object Original)
   at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.SaveChanges(SaveChanges`1 editParams, Query query)
   at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.ProcessEntireSeries(List`1 eventsData, ActionType actionType)
   at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.DeleteEvents(List`1 deleteEvents, Nullable`1 action)
   at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.DeleteEvents(List`1 deleteEvents, Nullable`1 action)
   at Syncfusion.Blazor.Schedule.Internal.AlertWindow`1.OnDeleteSeriesClick(MouseEventArgs args)
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Syncfusion.Blazor.Internal.SfBaseUtils.InvokeEvent[T](Object eventFn, T eventArgs)
   at Syncfusion.Blazor.Buttons.SfButton.OnClickHandler(MouseEventArgs args)
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

The databinding server side controller example for batching (URLAdapter):

[HttpPost]
        [Route("api/Default/Batch")]
        public async Task Batch([FromBody] CRUDModel<Event> args)
        {
            if (args.Changed.Count > 0)
            {
                foreach (Event appointment in args.Changed)
                {
                    var entity = await dbContext.Events.FindAsync(appointment.Id);
                    if (entity != null)
                    {
                        dbContext.Entry(entity).CurrentValues.SetValues(appointment);
                    }
                }
            }
            if (args.Added.Count > 0)
            {
                foreach (Event appointment in args.Added)
                {
                    dbContext.Events.Add(appointment);


                }
            }
            if (args.Deleted.Count > 0)
            {
                foreach (Event appointment in args.Deleted)
                {
                    var app = dbContext.Events.Find(appointment.Id);
                    if (app != null)
                    {
                        dbContext.Events.Remove(app);
                    }
                }
            }
            await dbContext.SaveChangesAsync();
        }

The question I have, is that if this is for the URLAdapter, what is the format for the APIAdapter? Furthermore, if it uses the same format, what is the input object forthe endpoint (CRUDModel<Event> args). In my code I use API endpoints that use the AppointmentData class. Here is my code:

Front end:

<div class="d-flex align-items-center justify-content-center">
    <SfSchedule TValue="AppointmentData" EnableAutoRowHeight="true" Width="75vw" Height="75vh" @bind-SelectedDate="@CurrentDate">
        <ScheduleEvents TValue="AppointmentData" OnActionFailure="OnActionFailure"></ScheduleEvents>
        <ScheduleTimeScale Enable="false"></ScheduleTimeScale>
        <ScheduleEventSettings TValue="AppointmentData">
            <ScheduleViews>
                <ScheduleView Option="View.Day"></ScheduleView>
                <ScheduleView Option="View.Week"></ScheduleView>
                <ScheduleView Option="View.WorkWeek"></ScheduleView>
                <ScheduleView Option="View.Month"></ScheduleView>
            </ScheduleViews>
            <SfDataManager Url="https://localhost:7254/api/AppointmentData" UpdateUrl="https://localhost:7254/api/Default/Update" RemoveUrl="https://localhost:7254/api/Default/Delete" InsertUrl="https://localhost:7254/api/Default/Add" Adaptor="Adaptors.WebApiAdaptor"></SfDataManager>
        </ScheduleEventSettings>
    </SfSchedule>
</div>


@code {
    DateTime CurrentDate = DateTime.Now;
    public void OnActionFailure(Syncfusion.Blazor.Schedule.ActionEventArgs<AppointmentData> args)
    {
        Debug.Write(args);
    }


    public class AppointmentData
    {
        public int Id { get; set; }
        public string? Subject { get; set; }
        public string? Location { get; set; }
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
        public string? Description { get; set; }
        public bool IsAllDay { get; set; }
        public string? RecurrenceRule { get; set; }
        public string? RecurrenceException { get; set; }
        public Nullable<int> RecurrenceID { get; set; }
    }
}

API Controller:

[ApiController]
public class AppointmentDataController : ControllerBase
{
    private readonly DataContext _context;


    public AppointmentDataController(DataContext context)
    {
        _context = context;
    }


    [HttpGet]
    [Route("api/[controller]")]
    public object Get()
    {
        var appointments = _context.AppointmentData.ToList();
        return Ok(appointments);
    }


    [HttpPost]
    [Route("api/Default/Add")]
    public void Add([FromBody] AppointmentData appointmentData)
    {
        try
        {
            _context.AppointmentData.Add(appointmentData);
            _context.SaveChanges();
        } catch (Exception ex)
        {
            Debug.WriteLine(ex);
        }


    }


    [HttpPut]
    [Route("api/Default/Update")]
    public async Task Update([FromBody]AppointmentData data)
    {
        AppointmentData _data = _context.AppointmentData.Where(x => x.Id.Equals(data.Id)).FirstOrDefault();
        if (_data != null)
        {
            _context.Entry(_data).CurrentValues.SetValues(data);
            await _context.SaveChangesAsync();
        }
    }


    [HttpDelete]
    [Route("api/Default/Delete/{id}")]
    public async Task Delete(int id)
    {
        var app = _context.AppointmentData.Find(id);
        if (app != null)
        {
            _context.AppointmentData.Remove(app);
            await _context.SaveChangesAsync();
        }
    }



12 Replies 1 reply marked as answer

AK Ashokkumar Karuppasamy Syncfusion Team May 23, 2024 02:17 PM UTC

Hi Daniel Tujo,

We suggest enabling batching to achieve your requirement and ensure that recurrence events work without errors. Additionally, use the OData-Adaptor instead of the WebAPI adaptor. We have attached a blog link below with steps on how to enable batching. Please refer to the following steps. Try this solution and let us know if you need any further assistance.

https://www.syncfusion.com/blogs/post/batch-crud-blazor-scheduler-odata

Regards,

Ashok



DT Daniel Tujo replied to Ashokkumar Karuppasamy May 23, 2024 03:13 PM UTC

This was no help, with .NET 8 this way of doing it is deprecated. That blog link needs to be updated.



AK Ashokkumar Karuppasamy Syncfusion Team May 29, 2024 06:50 AM UTC

Hi  Daniel Tujo,


We have considered the blog update and will correct it in any of our upcoming release. We suggest you use the schedule code below to meet your requirements. I have attached the service link; host the link and use the URL in Data Manager to achieve your requirements.

[index.razor]

@using Syncfusion.Blazor.Data;

@using Syncfusion.Blazor.Schedule

 

<SfSchedule TValue="Event" Height="550px" @bind-SelectedDate="@currentDate" AllowMultiDrag="true">

    <ScheduleEventSettings TValue="Event">

        <SfDataManager Url=http://localhost:54738/Home/LoadData Adaptor="Adaptors.UrlAdaptor">

        </SfDataManager>

    </ScheduleEventSettings>

    <ScheduleViews>

        <ScheduleView Option="View.Month"></ScheduleView>

    </ScheduleViews>

</SfSchedule>

 

@code {

    DateTime currentDate = new DateTime(2021, 1, 15);

 

    public class Event

    {

        public int Id { get; set; }

        public DateTime StartTime { get; set; }

        public DateTime EndTime { get; set; }

        public string Subject { get; set; }

        public string Location { get; set; }

        public string Description { get; set; }

        public bool IsAllDay { get; set; }

        public string StartTimezone { get; set; }

        public string EndTimezone { get; set; }

        public string RecurrenceRule { get; set; }

        public int? RecurrenceID { get; set; }

        public string RecurrenceException { get; set; }

        // Add any other properties you have in ScheduleEventData

    }

}


Service:
https://www.syncfusion.com/downloads/support/forum/170725/ze/ScheduleCRUD1166139915

Please get back to us if you need any further assistance.


Regards,

Ashok



DT Daniel Tujo May 29, 2024 02:06 PM UTC

This will not work either. I am not going to redesign my back end API into a MVC framework.



AK Ashokkumar Karuppasamy Syncfusion Team May 31, 2024 10:39 AM UTC

Hi Daniel Tujo,

Sorry for the inconvenience. We suggest using the service as an ASP.NET Core controller and hosting the ASP.NET project. You can then use the URL in the Data Manager to achieve your requirements.

You can use the UrlAdaptor with the Schedule to load and update the appointments as shown in the below code snippet.

<SfSchedule TValue="Event" Height="550px" @bind-SelectedDate="currentDate" AllowMultiDrag="true">

    <ScheduleEventSettings TValue="Event">

// You can then use your host URL in the ASP.NET Core        <DataManager Url=https://localhost:44301/Index?handler=LoadData crudUrl=https://localhost:44301/Index?handler=UpdateData Adaptor="Adaptors.UrlAdaptor"></DataManager>

    </ScheduleEventSettings>

 

    <ScheduleViews>

        <ScheduleView Option="View.Month"></ScheduleView>

    </ScheduleViews>

</SfSchedule>

 



[ASP core controller]

namespace ej2_core_schedule_app.Pages

{

    [IgnoreAntiforgeryToken(Order = 1001)]

    public class IndexModel : PageModel

    {

        AppointmentContext _context;

        public IndexModel(AppointmentContext Context)

        {

            _context = Context;

        }

 

        public void OnGet()

        {

 

        }

 

        public JsonResult OnPostLoadData([FromBody] Params param)

        {

            var data = _context.Appointments.ToList();

            return new JsonResult(data);

        }

 

        public JsonResult OnPostUpdateData([FromBody] EditParams param)

        {

            if (param.action == "insert" || (param.action == "batch" && param.added.Count > 0)) // this block of code will execute while inserting the appointments

            {

                var value = (param.action == "insert") ? param.value : param.added[0];

                DateTime startTime = Convert.ToDateTime(value.StartTime);

                DateTime endTime = Convert.ToDateTime(value.EndTime);

                Appointment appointment = new Appointment()

                {

                    StartTime = startTime.ToLocalTime(),

                    EndTime = endTime.ToLocalTime(),

                    Subject = value.Subject,

                    IsAllDay = value.IsAllDay,

                    RecurrenceRule = value.RecurrenceRule,

                    RecurrenceID = value.RecurrenceID,

                    RecurrenceException = value.RecurrenceException,

                    Description = value.Description,

                    Location = value.Location

                };

                _context.Appointments.Add(appointment);

                _context.SaveChanges();

            }

            if (param.action == "update" || (param.action == "batch" && param.changed.Count > 0)) // this block of code will execute while removing the appointment

            {

                var value = (param.action == "update") ? param.value : param.changed[0];

                var filterData = _context.Appointments.Where(c => c.Id == Convert.ToInt32(value.Id));

                if (filterData.Count() > 0)

                {

                    DateTime startTime = Convert.ToDateTime(value.StartTime);

                    DateTime endTime = Convert.ToDateTime(value.EndTime);

                    Appointment appointment = _context.Appointments.Single(A => A.Id == Convert.ToInt32(value.Id));

                    appointment.StartTime = startTime.ToLocalTime();

                    appointment.EndTime = endTime.ToLocalTime();

                    appointment.Subject = value.Subject;

                    appointment.IsAllDay = value.IsAllDay;

                    appointment.RecurrenceRule = value.RecurrenceRule;

                    appointment.RecurrenceID = value.RecurrenceID;

                    appointment.RecurrenceException = value.RecurrenceException;

                    appointment.Description = value.Description;

                    appointment.Location = value.Location;

                }

                _context.SaveChanges();

            }

            if (param.action == "remove" || (param.action == "batch" && param.deleted.Count > 0)) // this block of code will execute while updating the appointment

            {

                if (param.action == "remove")

                {

                    int key = Convert.ToInt32(param.key);

                    Appointment appointment = _context.Appointments.Where(c => c.Id == key).FirstOrDefault();

                    if (appointment != null) _context.Appointments.Remove(appointment);

                }

                else

                {

                    foreach (var apps in param.deleted)

                    {

                        Appointment appointment = _context.Appointments.Where(c => c.Id == apps.Id).FirstOrDefault();

                        if (apps != null) _context.Appointments.Remove(appointment);

                    }

                }

                _context.SaveChanges();

            }

            var data = _context.Appointments.ToList();

            return new JsonResult(data);

        }

    }

    public class Params

    {

        public string StartDate { get; set; }

        public string EndDate { get; set; }

    }

    public class EditParams

    {

        public string key { get; set; }

        public string action { get; set; }

        public List<Appointment> added { get; set; }

        public List<Appointment> changed { get; set; }

        public List<Appointment> deleted { get; set; }

        public Appointment value { get; set; }

    }

}



Regards,
Ashok



DT Daniel Tujo replied to Ashokkumar Karuppasamy May 31, 2024 09:25 PM UTC

I tried implementing this and keep getting a 404 when the adapter makes a request:

Error:

Syncfusion.Blazor.Schedule.ActionEventArgs`1[SFTownCenter.Components.Partials.EventsTab+AppointmentData]Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer: Warning: Unhandled exception rendering component: Response status code does not indicate success: 404 (Not Found).


System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found).
 ---> System.Net.Http.HttpRequestException
   --- End of inner exception stack trace ---
   at Syncfusion.Blazor.Data.HttpHandler.SendRequest(HttpRequestMessage data)
   at Syncfusion.Blazor.Data.UrlAdaptor.PerformDataOperation[T](Object queries)
   at Syncfusion.Blazor.DataManager.ExecuteQuery[T](DataManagerRequest queries)
   at Syncfusion.Blazor.DataManager.ExecuteQuery[T](Query query)
   at Syncfusion.Blazor.Schedule.Internal.EventBase`1.RefreshDataManager()
   at Syncfusion.Blazor.Schedule.Internal.EventBase`1.RefreshDataManager()
   at Syncfusion.Blazor.Schedule.SfSchedule`1.OnAfterScriptRendered()
   at Syncfusion.Blazor.SfBaseComponent.OnAfterRenderAsync(Boolean firstRender)
   at Syncfusion.Blazor.Schedule.SfSchedule`1.OnAfterRenderAsync(Boolean firstRender)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost: Error: Unhandled exception in circuit 'mutIuCwfbW9FHpbMi4yLjH3L2xwIs0U0JK45zMPnNtI'.


System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found).
 ---> System.Net.Http.HttpRequestException
   --- End of inner exception stack trace ---
   at Syncfusion.Blazor.Data.HttpHandler.SendRequest(HttpRequestMessage data)
   at Syncfusion.Blazor.Data.UrlAdaptor.PerformDataOperation[T](Object queries)
   at Syncfusion.Blazor.DataManager.ExecuteQuery[T](DataManagerRequest queries)
   at Syncfusion.Blazor.DataManager.ExecuteQuery[T](Query query)
   at Syncfusion.Blazor.Schedule.Internal.EventBase`1.RefreshDataManager()
   at Syncfusion.Blazor.Schedule.Internal.EventBase`1.RefreshDataManager()
   at Syncfusion.Blazor.Schedule.SfSchedule`1.OnAfterScriptRendered()
   at Syncfusion.Blazor.SfBaseComponent.OnAfterRenderAsync(Boolean firstRender)
   at Syncfusion.Blazor.Schedule.SfSchedule`1.OnAfterRenderAsync(Boolean firstRender)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

Component:

    <SfSchedule TValue="AppointmentData" EnableAutoRowHeight="true" Width="75vw" Height="75vh" @bind-SelectedDate="@CurrentDate">
        <ScheduleEvents TValue="AppointmentData" OnActionFailure="OnActionFailure"></ScheduleEvents>
        <ScheduleTimeScale Enable="false"></ScheduleTimeScale>
        <ScheduleEventSettings TValue="AppointmentData">
            <ScheduleViews>
                <ScheduleView Option="View.Day"></ScheduleView>
                <ScheduleView Option="View.Week"></ScheduleView>
                <ScheduleView Option="View.WorkWeek"></ScheduleView>
                <ScheduleView Option="View.Month"></ScheduleView>
            </ScheduleViews>
            <SfDataManager Url="https://localhost:7254/Index?handler=LoadData" crudUrl="https://localhost:7254/Index?handler=UploadData" Adaptor="Adaptors.UrlAdaptor"></SfDataManager>
        </ScheduleEventSettings>
    </SfSchedule>

On the Asp.net API

Page:

@page "/Index"
@model SFTownCenterAPI.Pages.IndexModel
@{
}


Page Model:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using SFTownCenterAPI.Data;
using SFTownCenterAPI.Entities;


namespace SFTownCenterAPI.Pages
{
    [IgnoreAntiforgeryToken(Order = 1001)]
    public class IndexModel : PageModel
    {
        DataContext _context;


        public IndexModel(DataContext context)
        {
            _context = context;
        }


        public void OnGet()
        {


        }


        public JsonResult OnPostLoadData([FromBody] Params param)
        {
            var data = _context.EventsData.ToList();
            return new JsonResult(data);
        }


        public JsonResult OnPostUpdateData([FromBody] EditParams param)
        {
            if (param.action == "insert" || param.action == "batch" && param.added.Count > 0) // this block of code will execute while inserting the appointments
            {
                var value = param.action == "insert" ? param.value : param.added[0];
                DateTime startTime = Convert.ToDateTime(value.StartTime);
                DateTime endTime = Convert.ToDateTime(value.EndTime);
                AppointmentData appointment = new AppointmentData()
                {
                    StartTime = startTime.ToLocalTime(),
                    EndTime = endTime.ToLocalTime(),
                    Subject = value.Subject,
                    IsAllDay = value.IsAllDay,
                    RecurrenceRule = value.RecurrenceRule,
                    RecurrenceID = value.RecurrenceID,
                    RecurrenceException = value.RecurrenceException,
                    Description = value.Description,
                    Location = value.Location
                };


                _context.EventsData.Add(appointment);
                _context.SaveChanges();


            }
            if (param.action == "update" || param.action == "batch" && param.changed.Count > 0) // this block of code will execute while removing the appointment
            {
                var value = param.action == "update" ? param.value : param.changed[0];
                var filterData = _context.EventsData.Where(c => c.Id == Convert.ToInt32(value.Id));
                if (filterData.Count() > 0)
                {
                    DateTime startTime = Convert.ToDateTime(value.StartTime);
                    DateTime endTime = Convert.ToDateTime(value.EndTime);
                    AppointmentData appointment = _context.EventsData.Single(A => A.Id == Convert.ToInt32(value.Id));
                    appointment.StartTime = startTime.ToLocalTime();
                    appointment.EndTime = endTime.ToLocalTime();
                    appointment.Subject = value.Subject;
                    appointment.IsAllDay = value.IsAllDay;
                    appointment.RecurrenceRule = value.RecurrenceRule;
                    appointment.RecurrenceID = value.RecurrenceID;
                    appointment.RecurrenceException = value.RecurrenceException;
                    appointment.Description = value.Description;
                    appointment.Location = value.Location;
                }
                _context.SaveChanges();
            }


            if (param.action == "remove" || param.action == "batch" && param.deleted.Count > 0) // this block of code will execute while updating the appointment
            {
                if (param.action == "remove")
                {
                    int key = Convert.ToInt32(param.key);
                    AppointmentData appointment = _context.EventsData.Where(c => c.Id == key).FirstOrDefault();
                    if (appointment != null) _context.EventsData.Remove(appointment);
                }
                else
                {
                    foreach (var apps in param.deleted)
                    {
                        AppointmentData appointment = _context.EventsData.Where(c => c.Id == apps.Id).FirstOrDefault();
                        if (apps != null) _context.EventsData.Remove(appointment);
                    }
                }
                _context.SaveChanges();
            }


            var data = _context.EventsData.ToList();
            return new JsonResult(data);
        }


    }


    public class Params
    {
        public string StartDate { get; set; }
        public string EndDate { get; set; }
    }


    public class EditParams
    {


        public string key { get; set; }
        public string action { get; set; }
        public List<AppointmentData> added { get; set; }
        public List<AppointmentData> changed { get; set; }
        public List<AppointmentData> deleted { get; set; }
        public AppointmentData value { get; set; }
    }
}


Added RazorPages and routing to the program.cs file:using Microsoft.EntityFrameworkCore;

using SFTownCenterAPI.Data;
using System.Reflection;


var builder = WebApplication.CreateBuilder(args);


// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
//builder.Services.AddRazorPages();
builder.Services.AddControllers();
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
    {
            // stuff
    });
    options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory,
        $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"));
});


builder.Services.AddDbContext<DataContext>(options =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});








var app = builder.Build();


// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}


app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();


app.MapControllers();


app.Run();


RR Ram Raju Elaiyaperumal Syncfusion Team June 13, 2024 03:51 AM UTC

Hi Daniel Tujo,

We would like to inform you that we have replicated the issue you reported on our end. We are currently working on developing a solution to address this problem.

We understand the inconvenience this may cause and assure you that we are making every effort to resolve this issue promptly. We will keep you updated on our progress and provide further details as soon as they become available.

We greatly appreciate your patience and understanding during this time.


Regards,

Ram



RR Ram Raju Elaiyaperumal Syncfusion Team June 18, 2024 04:28 PM UTC

Hi Daniel Tujo,

We have prepared a sample ASP.NET Core CRUD service using .NET 8, along with a Blazor client sample for your reference. Please find the attached sample and review it at your convenience.

In the service, please modify the connection string to match your specific requirements.

We hope this will be helpful for your requirements. Please don't hesitate to reach out if you have any further questions or need additional assistance.

Regards,

Ram


Attachment: service_and_client_397decd1.zip


DT Daniel Tujo replied to Ram Raju Elaiyaperumal June 24, 2024 03:23 PM UTC

This partially works and is the same as my initial post. Creating, reading, deleting single events work. Creating and reading repeating event work. However, when trying to delete a reoccurring event errors.

warn: Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer[100]
      Unhandled exception rendering component: The JSON value could not be converted to Syncfusion.Blazor.Data.CRUDModel`1[SFTownCenter.Components.Partials.EventsTab+AppointmentData]. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
      System.Text.Json.JsonException: The JSON value could not be converted to Syncfusion.Blazor.Data.CRUDModel`1[SFTownCenter.Components.Partials.EventsTab+AppointmentData]. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
         at System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType)
         at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
         at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
         at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
         at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo`1 jsonTypeInfo, Nullable`1 actualByteCount)
         at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo`1 jsonTypeInfo)
         at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
         at Syncfusion.Blazor.Data.UrlAdaptor.ProcessCrudResponse[T](Object data, DataManagerRequest queries)
         at Syncfusion.Blazor.DataManager.SaveChanges[T](Object changed, Object added, Object deleted, String keyField, Nullable`1 dropIndex, String tableName, Query query, Object Original)
         at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.SaveChanges(SaveChanges`1 editParams, Query query)
         at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.ProcessEntireSeries(List`1 eventsData, ActionType actionType)
         at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.DeleteEvents(List`1 deleteEvents, Nullable`1 action)
         at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.DeleteEvents(List`1 deleteEvents, Nullable`1 action)
         at Syncfusion.Blazor.Schedule.Internal.AlertWindow`1.OnDeleteSeriesClick(MouseEventArgs args)
         at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
         at Syncfusion.Blazor.Internal.SfBaseUtils.InvokeEvent[T](Object eventFn, T eventArgs)
         at Syncfusion.Blazor.Buttons.SfButton.OnClickHandler(MouseEventArgs args)
         at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
         at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost[111]
      Unhandled exception in circuit 'lY1rVCnQgrJ6UkWoTJ6fjv-betBvLMNtgDEPn3F4XFg'.
      System.Text.Json.JsonException: The JSON value could not be converted to Syncfusion.Blazor.Data.CRUDModel`1[SFTownCenter.Components.Partials.EventsTab+AppointmentData]. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
         at System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType)
         at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
         at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
         at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
         at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo`1 jsonTypeInfo, Nullable`1 actualByteCount)
         at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo`1 jsonTypeInfo)
         at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
         at Syncfusion.Blazor.Data.UrlAdaptor.ProcessCrudResponse[T](Object data, DataManagerRequest queries)
         at Syncfusion.Blazor.DataManager.SaveChanges[T](Object changed, Object added, Object deleted, String keyField, Nullable`1 dropIndex, String tableName, Query query, Object Original)
         at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.SaveChanges(SaveChanges`1 editParams, Query query)
         at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.ProcessEntireSeries(List`1 eventsData, ActionType actionType)
         at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.DeleteEvents(List`1 deleteEvents, Nullable`1 action)
         at Syncfusion.Blazor.Schedule.Internal.CrudModule`1.DeleteEvents(List`1 deleteEvents, Nullable`1 action)
         at Syncfusion.Blazor.Schedule.Internal.AlertWindow`1.OnDeleteSeriesClick(MouseEventArgs args)
         at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
         at Syncfusion.Blazor.Internal.SfBaseUtils.InvokeEvent[T](Object eventFn, T eventArgs)
         at Syncfusion.Blazor.Buttons.SfButton.OnClickHandler(MouseEventArgs args)
         at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
         at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)



Client Code:

<br />
<div class="d-flex align-items-center justify-content-center">
<SfSchedule TValue="AppointmentData" EnableAutoRowHeight="true" Width="75vw" Height="75vh" @bind-SelectedDate="@CurrentDate">
<ScheduleEvents TValue="AppointmentData" OnActionFailure="OnActionFailure"></ScheduleEvents>
<ScheduleTimeScale Enable="false"></ScheduleTimeScale>
<ScheduleEventSettings TValue="AppointmentData">
<ScheduleViews>
<ScheduleView Option="View.Day"></ScheduleView>
<ScheduleView Option="View.Week"></ScheduleView>
<ScheduleView Option="View.WorkWeek"></ScheduleView>
<ScheduleView Option="View.Month"></ScheduleView>
</ScheduleViews>
<SfDataManager Url="https://localhost:44348/api/scheduler" UpdateUrl="https://localhost:44348/api/update" RemoveUrl="https://localhost:44348/api/delete" InsertUrl="https://localhost:44348/api/add" Adaptor="Adaptors.UrlAdaptor"></SfDataManager>
</ScheduleEventSettings>
</SfSchedule>
</div>

@code {
DateTime CurrentDate = DateTime.Now;
public void OnActionFailure(Syncfusion.Blazor.Schedule.ActionEventArgs<AppointmentData> args)
{
Debug.Write(args);
}

public class AppointmentData
{
public int Id { get; set; }
public string? Subject { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public string? StartTimezone { get; set; }
public string? EndTimezone { get; set; }
public string? Location { get; set; }
public string? Description { get; set; }
public bool? IsAllDay { get; set; }
public bool? IsBlock { get; set; }
public bool? IsReadOnly { get; set; }
public int? FollowingID { get; set; }
public int? RecurrenceID { get; set; }
public string? RecurrenceRule { get; set; }
public string? RecurrenceException { get; set; }
public int? OwnerId { get; set; }
public Guid? Guid { get; set; }
}
}


Server:

[ApiController]
public class SchedulerController(DataContext context) : ControllerBase
{
private readonly DataContext? _context = context;

[HttpPost]
[Route("api/scheduler")]
public ActionResult<IEnumerable<AppointmentData>> GetEvents()
{
return Ok(_context?.EventsData.ToList());
}

[HttpPost]
[Route("api/add")]
public async Task Add([FromBody] CRUDModel<AppointmentData> args)
{
var newEvent = new AppointmentData()
{
Subject = args.Value.Subject,
StartTime = args.Value.StartTime,
EndTime = args.Value.EndTime,
StartTimezone = args.Value.StartTimezone,
EndTimezone = args.Value.EndTimezone,
Location = args.Value.Location,
Description = args.Value.Description,
IsAllDay = args.Value.IsAllDay,
IsBlock = args.Value.IsBlock,
IsReadOnly = args.Value.IsReadOnly,
FollowingID = args.Value.FollowingID,
RecurrenceID = args.Value.RecurrenceID,
RecurrenceRule = args.Value.RecurrenceRule,
RecurrenceException = args.Value.RecurrenceException,
OwnerId = args.Value.OwnerId,
Guid = args.Value.Guid
};

_context!.EventsData.Add(newEvent);
await _context.SaveChangesAsync();
}

[HttpPost]
[Route("api/update")]
public async Task Update(CRUDModel<AppointmentData> args)
{
var entity = await _context!.EventsData.FindAsync(args.Value.Id);
if (entity != null)
{
_context.Entry(entity).CurrentValues.SetValues(args.Value);
await _context.SaveChangesAsync();
}
}

[HttpPost]
[Route("api/delete")]
public async Task Delete(CRUDModel<AppointmentData> args)
{
var key = Convert.ToInt32(Convert.ToString(args.Key));
var app = await _context!.EventsData.FindAsync(key);
if (app != null)
{
_context.EventsData.Remove(app);
await _context.SaveChangesAsync();
}
}
}


DT Daniel Tujo June 28, 2024 03:38 PM UTC

I loaded your solution and ran it and it gives the same error I put above when trying to delete a series.



DT Daniel Tujo June 28, 2024 05:07 PM UTC

I was able to fix deleting and editing of recurring events by adding the crud batch url:

CrudUrl="/api/Default/Batch"

And another endpoint on the server.


Marked as answer

AK Ashokkumar Karuppasamy Syncfusion Team July 1, 2024 07:07 AM UTC

Hi Daniel Tujo,
 
We are glad to know that your issue has been resolved. Please get back to us if you need any other assistance.

Regards,

Ashok


Loader.
Up arrow icon