Where is the back end / controller code for the scheduler. I see in your docs that other people have been asking for it as well. Why only 50% of the demo shown?

7 Answers 49 Views
Scheduler
Les Baker
Top achievements
Rank 1
Iron
Les Baker asked on 27 Nov 2024, 01:36 AM
$("#scheduler").kendoScheduler({
    date: new Date("2022/6/13"),
    timezone: "Etc/UTC", // Setting the timezone is recommended when binding to a remote service.
    dataSource: {
        batch: true, // Enable the batch updates.
        transport: {
            read: {
                url: "https://demos.telerik.com/kendo-ui/service/tasks",
                dataType: "jsonp"
            },
            update: {
                url: "https://demos.telerik.com/kendo-ui/service/tasks/update",
                dataType: "jsonp"
            },
            create: {
                url: "https://demos.telerik.com/kendo-ui/service/tasks/create",
                dataType: "jsonp"
            },
            destroy: {
                url: "https://demos.telerik.com/kendo-ui/service/tasks/destroy",
                dataType: "jsonp"
            },
            parameterMap: function(options, operation) {
                if (operation !== "read" && options.models) {
                    return {models: kendo.stringify(options.models)};

7 Answers, 1 is accepted

Sort by
0
Neli
Telerik team
answered on 27 Nov 2024, 04:28 PM

Hello Lee,

Please find below a link to the public GitHub repository that contains the service used in our Demos:

- https://github.com/telerik/kendo-ui-demos-service/tree/master/demos-and-odata-v3

And below is a link specifically for the TaskController:

https://github.com/telerik/kendo-ui-demos-service/blob/master/demos-and-odata-v3/KendoCRUDService/Controllers/TasksController.cs

Let me know in case you have any additional question on the matter.

Regards,
Neli
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.

0
Les Baker
Top achievements
Rank 1
Iron
answered on 28 Nov 2024, 12:02 AM
I find you incomplete documentation on this control very disappointing.  You need to show the output/posts and the response(s)/formats, similar to all standard API documentation, along with "any required server-side code" which should be included with the demo. 

Giving me a pile of demo code to sift through completely negates the reason anyone buys professional toolsets. 

The efficiency goes to zero. 

I mixed and matched some of your code emulating the structures and the return patterns.  It kind of works, but the dialog on the page does not close properly and I have no idea why. 

Again, the benefit of paying for commercial controls has now been negated.  Many open-source equivalents have clearer example documentation.  

In this case the predicted task timeline/cost has now been exceeded and the benefit to choosing your products are negated.  
0
Les Baker
Top achievements
Rank 1
Iron
answered on 28 Nov 2024, 12:25 AM
This code in the demo you pointed me to. 

kendo-ui-demos-service/demos-and-odata-v3/KendoCRUDService/Controllers/TasksController.cs at master · telerik/kendo-ui-demos-service

why are you using jsonp for the returns for the scheduler control.  In fact why are you using jsonp at all?

JSONP - Wikipedia

JSONP is vulnerable to the data source replacing the innocuous function call with malicious code, which is why it has been superseded by cross-origin resource sharing (available since 2009[3]) in modern applications.

After seeing this I did a search for jsonp, jsonp scheduler looking for it in the documentation.  I figured you would at least have an explanation for it.  Nothing. 

At this point your "commercial" control becomes a time sucking lab experiment, where we try different return formats to see what works.  See the problem??? 

Here are some additional notes on that approach demonstrated in your public demo. 

JSONP Overview:

  • JSONP (JSON with Padding) is used to bypass the same-origin policy by enabling cross-domain requests in a way that JSON alone cannot.
  • JSONP is often considered insecure because it allows cross-domain script injection, which can potentially open up XSS vulnerabilities.

Impact on Your Application:

  1. Security Concerns:

    • Enabling EnableJSONP = true makes your application vulnerable to cross-site scripting (XSS) attacks. Any domain can send requests to your server, and if the server responds, it will execute potentially harmful scripts.
    • If you have controllers or APIs that deal with sensitive data, enabling JSONP for those could lead to data exposure to unauthorized domains.
  2. Cross-Origin Requests:

    • CORS vs JSONP: It seems that app.UseCors(CorsOptions.AllowAll) is also in use. CORS is generally a safer approach compared to JSONP for allowing cross-origin resource sharing.
    • If CORS is enabled, it is usually more secure than JSONP and allows more granular control over which domains can access your resources.
  3. Impact on Controllers:

    • If your application uses JSONP for other parts of the system, such as the controllers you've shown, enabling JSONP on SignalR might make cross-origin compatibility smoother for those components.
    • However, if other controllers are not configured to handle JSONP, you might face inconsistencies. Controllers currently returning JsonResult will need to ensure they are properly configured to work with JSONP when cross-domain requests are involved.
  4. Scenarios to Consider:

    • If you have existing security configurations (e.g., request authentication, domain restrictions), enabling JSONP might bypass some of these, as JSONP doesn’t handle cross-origin headers as securely as CORS.
    • Ensure that enabling JSONP does not interfere with security practices like token validation, as requests that originate from any domain may be processed by the server.

Recommendation:

  • Use CORS (UseCors) to specify which domains are allowed instead of using JSONP unless you have a very specific reason to use JSONP, such as supporting very old browsers that don't work well with CORS.
  • If you must use JSONP, be extremely cautious with which endpoints and controllers you expose to JSONP requests. Limit JSONP responses only to specific, non-sensitive endpoints and validate all input thoroughly.
Perhaps this was the only way you could get your demo to work. 

Or more likely, no one has reviewed this code in a very, very long time.   

I request you start providing/including actual best practices and working controller code for your commercial controls, not clearly outdated versions.   

And start including such code in the demo area for each control, don't make it a sea hunt. 

This, as a customer of yours, is my base expectations for commercial development products. 

I have mentioned issues regarding your lack of clear and complete documentation for years now.  This is just another glaring example. 

0
Neli
Telerik team
answered on 28 Nov 2024, 02:26 PM

Hi Les,

Thank you for the detailed feedback. We always appreciate the feedback from our customers as it is very important for us and for improving our products and services.

We are working on improvements in our service that will allow us to remove the usage of JSONP from it. However, the effort is in progress. Thus I could not commit to a timeframe for when the migration of the demo service will be finished. There is a related public pull request that you could take a look at. However, please keep in mind that the item is still in progress. 

Please note that the service used in our demos is just a sample and its purpose is to give the opportunity to the users to test our components. Kendo UI for jQuery provides UI components. The exact implementation of the backend is up to the developer. Even if the demo service is exposed it should be considered as an example, but not as a guide. 

I remain at your disposal in case you have additional questions.

Regards,
Neli
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.

0
Les Baker
Top achievements
Rank 1
Iron
answered on 30 Nov 2024, 08:09 PM

// Controller
//--------------------------------------------------------------------------------------------------------
using ScheduledDates.Models;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using Kendo.Mvc.Extensions;
using Kendo.Mvc.UI;
using YourDataContextHere.Data;
using System.Net.Http;
using Newtonsoft.Json;
using Microsoft.EntityFrameworkCore;

namespace ExcludedDates.Controllers
{
    public class ExcludedDatesController : Controller
    {
        private readonly ApplicationDbContext _dbContext;
        private readonly ILogger<ExcludedDatesController> _logger;

        public ExcludedDatesController(ApplicationDbContext dbContext, ILogger<ExcludedDatesController> logger)
        {
            _logger = logger;
            _dbContext = dbContext;
        }

        [HttpGet]
        public IActionResult GetTasks()
        {
            var tasks = _dbContext.ExcludedDate.ToList();

            return Json(tasks);
        }

        [HttpPost]
        public JsonResult SaveTasks([FromForm] string models)
        {
            if (string.IsNullOrEmpty(models))
            {
                return Json(new { error = "No task data provided." });
            }

            List<ExcludedDate> tasks;
            try
            {
                tasks = JsonConvert.DeserializeObject<List<ExcludedDate>>(models);
            }
            catch (JsonException)
            {
                return Json(new { error = "Invalid JSON format." });
            }

            if (tasks == null || tasks.Count == 0)
            {
                return Json(new { error = "No valid task data provided." });
            }

            foreach (var task in tasks)
            {
                if (task.TaskID == 0)
                {
                    // Create operation
                    _dbContext.ExcludedDate.Add(task);
                }
                else
                {
                    // Update operation
                    _dbContext.Entry(task).State = EntityState.Modified;
                }
            }

            try
            {
                _dbContext.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                return Json(new { error = "A concurrency error occurred." });
            }
            catch (DbUpdateException)
            {
                return Json(new { error = "An error occurred while updating the database." });
            }

            return Json(tasks);
        }

        [HttpPost]
        public JsonResult UpdateTasks([FromForm] string models)
        {
            if (string.IsNullOrEmpty(models))
            {
                return Json(new { error = "No task data provided." });
            }

            List<ExcludedDate> tasks;
            try
            {
                tasks = JsonConvert.DeserializeObject<List<ExcludedDate>>(models);
            }
            catch (JsonException)
            {
                return Json(new { error = "Invalid JSON format." });
            }

            if (tasks == null || tasks.Count == 0)
            {
                return Json(new { error = "No valid task data provided." });
            }

            foreach (var task in tasks)
            {
                var existingTask = _dbContext.ExcludedDate.Find(task.TaskID);
                if (existingTask != null)
                {
                    _dbContext.Entry(existingTask).CurrentValues.SetValues(task);
                    existingTask.ModifiedDate = DateTime.UtcNow;
                }
            }

            try
            {
                _dbContext.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                return Json(new { error = "A concurrency error occurred." });
            }
            catch (DbUpdateException)
            {
                return Json(new { error = "An error occurred while updating the database." });
            }

            return Json(tasks);
        }

        [HttpPost]
        public JsonResult DeleteTasks([FromForm] string models)
        {
            if (string.IsNullOrEmpty(models))
            {
                return Json(new { error = "No task data provided." });
            }

            List<ExcludedDate> tasks;
            try
            {
                tasks = JsonConvert.DeserializeObject<List<ExcludedDate>>(models);
            }
            catch (JsonException)
            {
                return Json(new { error = "Invalid JSON format." });
            }

            if (tasks == null || tasks.Count == 0)
            {
                return Json(new { error = "No valid task data provided." });
            }

            foreach (var task in tasks)
            {
                var existingTask = _dbContext.ExcludedDate.Find(task.TaskID);
                if (existingTask != null)
                {
                    _dbContext.ExcludedDate.Remove(existingTask);
                }
            }

            try
            {
                _dbContext.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                return Json(new { error = "A concurrency error occurred." });
            }
            catch (DbUpdateException)
            {
                return Json(new { error = "An error occurred while updating the database." });
            }

            return Json(tasks);
        }
    }
}

//--------------------------------------------------------------------------------------------------------

// View

    <div id="excluded-dates">
    <div id="scheduler"></div>
</div>
<script>$(function() {
    $("#scheduler").kendoScheduler({
            date: new Date(),
            startTime: new Date("@DateTime.Now.ToString("yyyy/MM/dd hh:mm tt")"),
        height: 600,
        views: [
            "day",
            { type: "workWeek", selected: true },
            "week",
            "month",
            "year",
            "agenda",
            { type: "timeline", eventHeight: 50}
        ],
        timezone: "Etc/UTC",  // Your preferred timezone
        dataSource: {
            batch: true,
            transport: {
                read: {
                        url: "/ExcludedDates/GetTasks",
                    dataType: "json",
                        type: "GET"
                },
                update: {
                        url: "/ExcludedDates/UpdateTasks",
                    dataType: "json",
                        type: "POST"
                },
                create: {
                        url: "/ExcludedDates/SaveTasks",
                    dataType: "json",
                        type: "POST"
                },
                destroy: {
                        url: "/ExcludedDates/DeleteTasks",
                    dataType: "json",
                        type: "POST"
                },
                parameterMap: function(options, operation) {
                    if (operation !== "read" && options.models) {
                        return {models: kendo.stringify(options.models)};
                    }
                }
            },
            schema: {
                model: {
                    id: "taskId",
                    fields: {
                        taskId: { from: "TaskID", type: "number" },
                        title: { from: "Title", defaultValue: "No title", validation: { required: true } },
                        start: { type: "date", from: "Start" },
                        end: { type: "date", from: "End" },
                        startTimezone: { from: "StartTimezone" },
                        endTimezone: { from: "EndTimezone" },
                        description: { from: "Description" },
                        recurrenceId: { from: "RecurrenceID" },
                        recurrenceRule: { from: "RecurrenceRule" },
                        recurrenceException: { from: "RecurrenceException" },
                        ownerId: 1,
                        isAllDay: { type: "boolean", from: "IsAllDay" }
                    }
                }
            }
        }
    });

});
</script>

//--------------------------------------------------------------------------------------------------------
// Model

using System;
using System.ComponentModel.DataAnnotations;

namespace ScheduledDates.Models
{
    public partial class ExcludedDate
    {
        [Key]
        public int TaskID { get; set; }

        [Required]
        public int OwnerID { get; set; }

        [Required]
        public required string Title { get; set; }

        public string? Description { get; set; }

        public string? StartTimezone { get; set; }

        [Required]
        public DateTimeOffset Start { get; set; }

        public string? EndTimezone { get; set; }

        [Required]
        public DateTimeOffset End { get; set; }


        public string? RecurrenceRule { get; set; }

        public int? RecurrenceID { get; set; }

        public string? RecurrenceException { get; set; }

        [Required]
        public required bool IsAllDay { get; set; }

        [Required]
        public required DateTime CreateDate { get; set; } = DateTime.UtcNow;

        public DateTime? ModifiedDate { get; set; }
    }
}

//--------------------------------------------------------------------------------------------------------

Please post this somewhere this is "highly visible" please, so the next poor soul won't lose a day like I did trying to piece together and test a scheduling solution from your out of date, incomplete and disconnected documentation.      

This uses Json on the page and the controller, BTW unlike your sample. 

        
0
Neli
Telerik team
answered on 02 Dec 2024, 04:43 PM

Hello Les,

Thank you for providing your approach for avoiding using jsonp. We appreciate sharing details about how you manage to resolve the issue on your side.

Could you please confirm if you agree with me create a forum thread containing your solution, so your approach can be publicly visible?

Regards,
Neli
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.

0
Les Baker
Top achievements
Rank 1
Iron
answered on 02 Dec 2024, 10:11 PM
Sure.  Go ahead.  Wish something like that had been there earlier.  



Tags
Scheduler
Asked by
Les Baker
Top achievements
Rank 1
Iron
Answers by
Neli
Telerik team
Les Baker
Top achievements
Rank 1
Iron
Share this question
or