This tutorial shows how to create a simple monthly calendar application using ASP.NET Core and DayPilot Pro. It uses the monthly event calendar UI component from DayPilot Pro library to display the monthly calendar. It loads data from the server using AJAX. The server-side API is created using ASP.NET Core Web API controller. It's a simple REST API that accepts and returns JSON messages. The data is stored in an SQL Server database (LocalDB). The application uses Entity Framework to create the data access layer.

Features

  • ASP.NET Core 2.0
  • Entity Framework 2.0
  • SQL Server database
  • Visual Studio 2017
  • Integrated datepicker and previous/next buttons
  • Custom calendar event color, customizable using a context menu
  • Drag and drop event creating and moving supported
  • Uses HTML5/JavaScript monthly event calendar component from DayPilot Pro for JavaScript package
  • Includes a trial version of DayPilot Pro for JavaScript (see License below)

License

Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript. Buy a license.

Monthly Calendar Initialization in ASP.NET Core View

html5-monthly-calendar-asp.net-core-initialization.png

We will start with a simple HTML5 view that displays the monthly calendar. It won't load any data at this moment.

Adding the monthly calendar to the view is very simple - it only requires a placeholder div and two lines of initialization code:

Views/Home/Index.cshtml

<script src="~/js/daypilot/daypilot-all.min.js" asp-append-version="true"></script>

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

<script>

    var dp = new DayPilot.Month("dp");
    dp.init();

</script>

The DayPilot Pro library is loaded by including daypilot-all.min.js script in the application.

Database Access using Entity Framework

html5-monthly-calendar-asp.net-core-sql-server-database.png

We want our ASP.NET Core web application to display event data from a database - so we need to create the backend API. We'll start with defining the data model.

There will be a simple class (CalendarEvent) that represents calendar event data. We will define it as follows:

Models/Data.cs

using Microsoft.EntityFrameworkCore;
using System;

namespace TutorialMonthlyCalendarAspNetCore.Models
{
    public class CalendarDbContext : DbContext
    {
        public DbSet<CalendarEvent> Events { get; set; }

        public CalendarDbContext(DbContextOptions<CalendarDbContext> options):base(options) { }
    }

    public class CalendarEvent
    {
        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; }
    }

}

The Migrations folder of the web application project includes the necessary database initialization code generated from the model class. In order to create a new SQL Server database from our model, simple run the migrations using the Package Manger Console in the Visual Studio:

Update-Database

This command will run the InitialMigration and create a new database called "DayPilot.TutorialMonthlyCalendar" using LocalDB instance of the SQL Server.

The database connection string is defined in appsettings.json. It can be modified if needed:

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

ASP.NET Core Web API Controller

html5-monthly-calendar-asp.net-core-api-controller.png

As soon as the database access code is ready (and a new database created) we can generate a new web API controller using a Visual Studio "New Controller" wizard.

The new web API controller can be found in Controllers/EventsController.cs file.

In the next steps, we will use it as a skeleton of the backend API - we will extend it with custom actions needed for specific operations (such as changing the event color).

Loading Calendar Events

html5-monthly-calendar-asp.net-core-loading-events.png

After the monthly calendar initialization, the calendar data can be loaded using a simple DayPilot.Month.events.load() call:

HTML5 View (Index.cshtml)

<script>

    var dp = new DayPilot.Month("dp");
    // ... configuration
    dp.init();

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

This method will request the data from our web API controller using an GET request. The request URL will be automatically extended with "start" and "end" query string parameters which will specify the first and last day of the current view.

We will modify the auto-generated GetEvents() method of the web API controller to read the query string parameters and limit the database query to only load events from the specified date range.

ASP.NET Core Web API Controller (EventsController.cs)

namespace TutorialMonthlyCalendarAspNetCore.Controllers
{
    [Produces("application/json")]
    [Route("api/Events")]
    public class EventsController : Controller
    {
    
        // ...

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

        // ...
 
    }

}

Creating a New Event

html5-monthly-calendar-asp.net-core-event-creating.png

The monthly calendar component supports selecting a time range using drag and drop. This behavior is enabled by default. We just need to add a custom event handler that will create a new event.

The following code creates a new onTimeRangeSelected event handler that asks for a new event name using a simplified modal dialog created using DayPilot.Modal class.

As soon as the event name is confirmed by the user it calls the web API controller using an AJAX call. If the AJAX call is successful the new event is created using the returned event data (which now include an ID generated on the server side) and added to the calendar data source and displayed.

HTML5 View (Index.cshtml)

<script>

    var dp = new DayPilot.Month("dp");

    dp.onTimeRangeSelected = function (args) {
        DayPilot.Modal.prompt("Create a new event:", "Event").then(function (modal) {
            var dp = args.control;
            dp.clearSelection();
            if (!modal.result) {
                return;
            }
            var params = {
                start: args.start.toString(),
                end: args.end.toString(),
                text: modal.result,
                resource: args.resource
            };
            $.ajax({
                type: 'POST',
                url: '/api/events',
                data: JSON.stringify(params),
                success: function (data) {
                    dp.events.add(new DayPilot.Event(data));
                    dp.message("Event created");
                },
                contentType: "application/json",
                dataType: 'json'
            });
        });
    };
    
    // ...

    dp.init();
    
</script>

ASP.NET Core Web API Controller (EventsController.cs)

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

namespace TutorialMonthlyCalendarAspNetCore.Controllers
{
    [Produces("application/json")]
    [Route("api/Events")]
    public class EventsController : Controller
    {
    
        // ...
 
        // POST: api/Events
        [HttpPost]
        public async Task<IActionResult> PostEvent([FromBody] CalendarEvent @event)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

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

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

        // ...

    }

}

Deleting a Calendar Event using a Context Menu

html5-monthly-calendar-asp.net-core-event-deleting.png

HTML5 View (Index.cshtml)

<script>

    var dp = new DayPilot.Month("dp");

    dp.onBeforeEventRender = function (args) {
        args.data.backColor = args.data.color;
        args.data.areas = [
            { top: 3, right: 3, bottom: 3, icon: "icon-triangle-down", visibility: "Hover", action: "ContextMenu", style: "font-size: 12px; background-color: rgba(255, 255, 255, .5); border: 1px solid #aaa; padding: 3px; cursor:pointer;" }
        ];
    };

    dp.contextMenu = new DayPilot.Menu({
        items: [
            {
                text: "Delete",
                onClick: function (args) {
                    var e = args.source;
                    $.ajax({
                        type: 'DELETE',
                        url: '/api/events/' + e.id(),
                        success: function (data) {
                            dp.events.remove(e);
                            dp.message("Event deleted");                            
                        },
                        contentType: "application/json",
                        dataType: 'json'
                    });
                }
            },
            
            // ...
        ]
    });
    dp.init();

    
</script>

ASP.NET Core Web API Controller (EventsController.cs)

namespace TutorialMonthlyCalendarAspNetCore.Controllers
{
    [Produces("application/json")]
    [Route("api/Events")]
    public class EventsController : Controller
    {
    
        // ...

        // 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);
        }

        // ...

    }

}

Moving and Resizing an Event

html5-monthly-calendar-asp.net-core-event-moving.png

Index.cshtml

<script>
    

    var dp = new DayPilot.Month("dp");
    dp.onEventMove = function (args) {
        var params = {
            id: args.e.id(),
            start: args.newStart.toString(),
            end: args.newEnd.toString()
        };
        $.ajax({
            type: 'PUT',
            url: '/api/events/' + args.e.id() + "/move",
            data: JSON.stringify(params),
            success: function (data) {
                dp.message("Event moved");
            },
            contentType: "application/json",
            dataType: 'json'
        });
    };
    dp.onEventResize = function (args) {
        var params = {
            id: args.e.id(),
            start: args.newStart.toString(),
            end: args.newEnd.toString()
        };
        $.ajax({
            type: 'PUT',
            url: '/api/events/' + args.e.id() + "/move",
            data: JSON.stringify(params),
            success: function (data) {
                dp.message("Event resized");
            },
            contentType: "application/json",
            dataType: 'json'
        });
    };
    
    // ...
    
    dp.init();

    
</script>

EventsController.cs

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

namespace TutorialMonthlyCalendarAspNetCore.Controllers
{
    [Produces("application/json")]
    [Route("api/Events")]
    public class EventsController : Controller
    {

        // ...

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

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

            @event.Start = param.Start;
            @event.End = param.End;

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

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

    }

    public class EventMoveParams
    {
        public DateTime Start { get; set; }
        public DateTime End { get; set; }
    }

    public class EventColorParams
    {
        public string Color { get; set; }
    }
}

Changing Event Color

html5-monthly-calendar-asp.net-core-event-color.png

Index.cshtml

<script>

    var dp = new DayPilot.Month("dp");

    dp.onBeforeEventRender = function (args) {
        args.data.backColor = args.data.color;
        args.data.areas = [
            { top: 3, right: 3, bottom: 3, icon: "icon-triangle-down", visibility: "Hover", action: "ContextMenu", style: "font-size: 12px; background-color: rgba(255, 255, 255, .5); border: 1px solid #aaa; padding: 3px; cursor:pointer;" }
        ];
    };

    dp.contextMenu = new DayPilot.Menu({
        items: [
            {
                text: "Delete",
                onClick: function (args) {
                    var e = args.source;
                    $.ajax({
                        type: 'DELETE',
                        url: '/api/events/' + e.id(),
                        success: function (data) {
                            dp.events.remove(e);
                            dp.message("Event deleted");                            
                        },
                        contentType: "application/json",
                        dataType: 'json'
                    });
                }
            },
            {
                text: "-"
            },
            {
                text: "Blue",
                icon: "icon icon-blue",
                color: "#a2c4c9",
                onClick: function (args) { updateColor(args.source, args.item.color); }
            },
            {
                text: "Green",
                icon: "icon icon-green",
                color: "#b6d7a8",
                onClick: function (args) { updateColor(args.source, args.item.color); }
            },
            {
                text: "Yellow",
                icon: "icon icon-yellow",
                color: "#ffe599",
                onClick: function (args) { updateColor(args.source, args.item.color); }
            },
            {
                text: "Red",
                icon: "icon icon-red",
                color: "#ea9999",
                onClick: function (args) { updateColor(args.source, args.item.color); }
            },
            {
                text: "Auto",
                color: "auto",
                onClick: function (args) { updateColor(args.source, args.item.color); }
            },
        ]
    });
    dp.init();

    // ...
    
    function updateColor(e, color) {
        var params = {
            color: color
        };
        $.ajax({
            type: 'PUT',
            url: '/api/events/' + e.id() + '/color',
            data: JSON.stringify(params),
            success: function (data) {
                e.data.color = color;
                dp.events.update(e);
                dp.message("Color updated");
            },
            contentType: "application/json",
            dataType: 'json'
        });
    }
    
</script>

EventsController.cs

namespace TutorialMonthlyCalendarAspNetCore.Controllers
{
    [Produces("application/json")]
    [Route("api/Events")]
    public class EventsController : Controller
    {
    
        // ...

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

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

            @event.Color = param.Color;

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

            return NoContent();
        }

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

    }

}