Features
ASP.NET Core application (.NET 8)
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
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 calendar = new DayPilot.Calendar("dp", {
viewType: "Week"
});
calendar.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
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")));
// ...
The SQL Server database will be created and initialized automatically on the first start using the following code (Program.cs
):
using (var serviceScope = app.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
try
{
var context = services.GetRequiredService<CalendarContext>();
context.Database.EnsureCreated();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
You can use Visual Studio SQL Server Object Explorer to check that the database has been created:
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...”:
In the next dialog, choose "API Controller with actions, using Entity Framework":
Select the model class (Event
), data context class (CalendarContext
) and confirm the controller name (EventsController
):
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
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 calendar = new DayPilot.Calendar("dp", {
viewType: "Week"
});
calendar.init();
calendar.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=2024-09-22T00:00:00&end=2024-09-29T00: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;
}
Full Source Code of the Client
Here is the full source code of the client-side implementation of the ASP.NET Core weekly calendar (Index.cshtml
):
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<style>
.calendar_default_event,
.calendar_default_event_inner {
border-radius: 15px;
color: white;
}
</style>
<script src="~/lib/daypilot/daypilot-all.min.js" asp-append-version="true"></script>
<div class="wrap">
<div class="left">
<div id="nav"></div>
</div>
<div class="right">
<div id="dp"></div>
</div>
</div>
<script>
const colors = [
{id: "#1066a8", name: "Blue"},
{id: "#6aa84f", name: "Green"},
{id: "#f1c232", name: "Yellow"},
{id: "#cc0000", name: "Red"},
];
const datepicker = new DayPilot.Navigator("nav", {
showMonths: 3,
skipMonths: 3,
selectMode: "Week",
onTimeRangeSelected: args => {
calendar.startDate = args.day;
calendar.update();
calendar.events.load("/api/events");
}
});
datepicker.init();
const calendar = new DayPilot.Calendar("dp", {
viewType: "Week",
durationBarVisible: false,
contextMenu: new DayPilot.Menu({
items: [
{
text: "Delete",
onClick: async (args) => {
const e = args.source;
const {data} = await DayPilot.Http.delete(`/api/events/${e.id()}`);
calendar.events.remove(e.id());
}
},
{
text: "-"
},
{
text: "Blue",
icon: "icon icon-blue",
color: "#1066a8",
onClick: (args) => { app.updateColor(args.source, args.item.color); }
},
{
text: "Green",
icon: "icon icon-green",
color: "#6aa84f",
onClick: (args) => { app.updateColor(args.source, args.item.color); }
},
{
text: "Yellow",
icon: "icon icon-yellow",
color: "#f1c232",
onClick: (args) => { app.updateColor(args.source, args.item.color); }
},
{
text: "Red",
icon: "icon icon-red",
color: "#cc0000",
onClick: (args) => { app.updateColor(args.source, args.item.color); }
},
]
}),
onTimeRangeSelected: async (args) => {
const form = [
{name: "Text", id: "text"},
{name: "Color", id: "color", type: "select", options: colors},
];
const data = {
text: "New event",
color: "#1066a8",
start: args.start,
end: args.end
};
const modal = await DayPilot.Modal.form(form, data);
calendar.clearSelection();
if (modal.canceled) {
return;
}
const params = modal.result;
const { data: event } = await DayPilot.Http.post("/api/events", params);
calendar.events.add(event);
},
onEventMove: async (args) => {
const params = {
id: args.e.id(),
start: args.newStart,
end: args.newEnd
};
const {data} = await DayPilot.Http.put(`/api/events/${args.e.id()}/move`, params);
},
onEventResize: async (args) => {
const params = {
id: args.e.id(),
start: args.newStart,
end: args.newEnd
};
const {data} = await DayPilot.Http.put(`/api/events/${args.e.id()}/move`, params);
},
onBeforeEventRender: (args) => {
args.data.backColor = args.data.color + "99";
args.data.borderColor = args.data.color;
args.data.areas = [
{
top: 6,
right: 6,
width: 20,
height: 20,
backColor: args.data.color,
symbol: "icons/daypilot.svg#minichevron-down-2",
visibility: "Visible",
action: "ContextMenu",
style: "border-radius: 15px; cursor:pointer;"
}
];
}
});
calendar.init();
const app = {
async updateColor(e, color) {
const params = {
color: color
};
const {data} = await DayPilot.Http.put(`/api/events/${e.id()}/color`, params);
e.data.color = color;
calendar.events.update(e);
},
init() {
calendar.events.load("/api/events");
}
};
app.init();
</script>
History
September 17, 2024: Upgraded to .NET 8, DayPilot Lite for JavaScript 2024.3.543, styling updates (event with rounded corners, background color), automatic SQL Server database initialization.
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