Features

  • ASP.NET Core application (.NET 7)

  • Entity Framework Core

  • SQL Server (LocalDB)

  • Visual Studio

  • The project uses JavaScript weekly calendar component from the open-source DayPilot Lite for JavaScript scheduling library to display drag and drop calendar UI.

  • You can use a date picker component to switch the visible week.

  • The application loads calendar data from SQL Server using HTTP calls to REST API.

  • It includes a lightweight backend built using ASP.NET Core Web API.

License

Apache License 2.0

Adding a Weekly Calendar to an ASP.NET Core Application

asp.net core weekly calendar open source initialization

Our ASP.NET Core web application will have a single view (Pages/Index.cshtml) that displays a weekly calendar using DayPilot JavaScript calendar component.

As the first step, we need to includes the DayPilot library to be included:

<script src="~/js/daypilot/daypilot-all.min.js"></script>

Now you can add a placeholder <div> at the target location:

<div id="dp"></div>

And the last step is the initialization JavaScript code that renders the calendar at the location specified using the placeholder:

<script>
  const dp = new DayPilot.Calendar("dp", {
    viewType: "Week"
  });
  dp.init();
</script>

These simple steps let us display a weekly calendar UI in our ASP.NET Core application. Now we will take a look at the data layer and server-side API.

Note: If you want to show resources as columns instead of days, you can switch the calendar to the resources mode - see ASP.NET Core Resource Scheduling Calendar (Open-Source).

Data Access using Entity Framework

javascript weekly calendar asp.net core data access

We want the weekly calendar to display event data from an SQL Server database. First, we need to create our data model and data access layer using Entity Framework.

Our model class is called Event and it includes these basic calendar event properties:

  • id

  • start

  • end

  • text

  • color

The id, start, end and text properties are required by the calendar component. The color property is a custom field that we will use to store an event color.

Our Event class will look like this:

public class Event
{
  public int Id { get; set; }
  public DateTime Start { get; set; }
  public DateTime End { get; set; }
  public string Text { get; set; }
  public string? Color { get; set; }
}

We also need to define a database context class that will manage the Event class database access for us:

public class CalendarContext : DbContext
{
  public DbSet<Event> Events { get; set; }
  public CalendarContext(DbContextOptions<CalendarContext> options):base(options) { }
}

Both classes are defined in Models/Data.cs file:

using Microsoft.EntityFrameworkCore;
using System;

namespace Project.Models
{
    public class CalendarContext : DbContext
    {
        public DbSet<Event> Events { get; set; }
        public CalendarContext(DbContextOptions<CalendarContext> options):base(options) { }
    }

    public class Event
    {
        public int Id { get; set; }
        public DateTime Start { get; set; }
        public DateTime End { get; set; }
        public string Text { get; set; }
        public string? Color { get; set; }
    }

}

We want to use SQL Server database called DayPilot.TutorialAspNetCoreWeeklyCalendar to store the calendar data. This is our database connection string:

Server=(localdb)\\mssqllocaldb;Database=DayPilot.TutorialAspNetCoreWeeklyCalendar;Trusted_Connection=True

We will store the connection string under "CalendarContext" key in the "ConnectionStrings" section of appsettings.json:

{
    "Logging": {
        "IncludeScopes": false,
        "LogLevel": {
            "Default": "Warning"
        }
    },
    "ConnectionStrings": {
        "CalendarContext": "Server=(localdb)\\mssqllocaldb;Database=DayPilot.TutorialAspNetWeeklyCalendar;Trusted_Connection=True"
    }
}

The last step is to register the CalendarContext service in Program.cs:

using Microsoft.EntityFrameworkCore;
using Project.Models;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddDbContext<CalendarContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("CalendarContext")));

// ...

Now the data model and database configuration are ready and we need to create the initial migration classes. The migration classes will be generated in the Migrations directory and will contain code that can generate the database from our model.

Run the following command in the Package Manager Console:

Add-Migration InitialCreate

Now the migrations are ready and we can use the database initialization command to create the database:

Update-Database

You can use the Visual Studio SQL Server Object Explorer to check that the database has been created:

javascript weekly calendar asp.net core sql server database

API Controller for Handling the Calendar Data

Now we need to create a Web API controller that will let us manage the calendar event data using AJAX calls. Visual Studio can help us with generating a new Web API controller from our data model class. Right click the Controllers folder in the the Solution Explorer and choose “Add” -> “Controller...”:

javascript weekly calendar asp.net core web api controller

In the next dialog, choose "API Controller with actions, using Entity Framework":

javascript weekly calendar asp.net core api entity framework

Select the model class (Event), data context class (CalendarContext) and confirm the controller name (EventsController):

javascript weekly calendar asp.net core api controller details

Now we have a new controller class (Controllers/EventsController.cs) with REST endpoints for common operations:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Project.Models;

namespace Project.Controllers
{
    [Produces("application/json")]
    [Route("api/Events")]
    public class EventsController : Controller
    {
        private readonly CalendarContext _context;

        public EventsController(CalendarContext context)
        {
            _context = context;
        }

        // GET: api/Events
        [HttpGet]
        public IEnumerable<Event> GetEvents([FromQuery] DateTime start, [FromQuery] DateTime end)
        {
            return _context.Events;
        }

        // GET: api/Events/5
        [HttpGet("{id}")]
        public async Task<IActionResult> GetEvent([FromRoute] int id)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var @event = await _context.Events.SingleOrDefaultAsync(m => m.Id == id);

            if (@event == null)
            {
                return NotFound();
            }

            return Ok(@event);
        }

        // PUT: api/Events/5
        [HttpPut("{id}")]
        public async Task<IActionResult> PutEvent([FromRoute] int id, [FromBody] Event @event)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            if (id != @event.Id)
            {
                return BadRequest();
            }

            _context.Entry(@event).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!EventExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }

        // POST: api/Events
        [HttpPost]
        public async Task<IActionResult> PostEvent([FromBody] Event @event)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            _context.Events.Add(@event);
            await _context.SaveChangesAsync();

            return CreatedAtAction("GetEvent", new { id = @event.Id }, @event);
        }

        // DELETE: api/Events/5
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteEvent([FromRoute] int id)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var @event = await _context.Events.SingleOrDefaultAsync(m => m.Id == id);
            if (@event == null)
            {
                return NotFound();
            }

            _context.Events.Remove(@event);
            await _context.SaveChangesAsync();

            return Ok(@event);
        }

        private bool EventExists(int id)
        {
            return _context.Events.Any(e => e.Id == id);
        }
    }

}

Loading Calendar Data from SQL Server Database

asp.net core weekly calendar component open source events

Now we have our backend ready and we can configure the weekly calendar to load the event data from the server. This can be done using dp.events.load() method:

<script>
  const dp = new DayPilot.Calendar("dp", {
    viewType: "Week"
  });
  dp.init();

  dp.events.load("/api/events");
</script>

This method will use the specified URL (/api/events) to load the event data and display them. It adds the visible range (start and end of the current week)  to the URL as start and end query string parameters:

/api/events?start=2023-10-15T00:00:00&end=2023-10-22T00:00:00

We will modify the GetEvents() method of the API controller (EventsController class) to read the query string parameters and use them to limit the event data:

// GET: api/Events
[HttpGet]
public IEnumerable<Event> GetEvents([FromQuery] DateTime start, [FromQuery] DateTime end)
{
    return from e in _context.Events where !((e.End <= start) || (e.Start >= end)) select e;
}

History

  • February 16, 2023: Upgraded to .NET 7, jQuery dependency removed, switched to DayPilot Lite for JavaScript (open source)

  • December 20, 2020: Upgraded to DayPilot Pro for JavaScript 2020.4.4807; layout CSS updates

  • October 18, 2017: Initial release