Introduction

By now you have learnt how to get a basic ASP.NET MVC event calendar and a simple ASP.NET scheduling solution for your application. We decided to show how you can extend the functionality of our web-control to create an online room booking calendar or other reservation service. For your convenience, we have drafted a tutorial with code snippets, screenshots and detailed descriptions.

The online booking calendar we are going to build looks like the one on the picture below:

booking calendar screenshot

You can also check this online demo.

Sign up and get the final package.

To begin with, download the latest update of DHTMLX Scheduler .NET. The initial steps (1-4) to create project and a database are set in the documentation

As far as it’s going to be an online booking service, let’s name it RoomBookingCalendar.

We have introduced some new features in this booking application:

  1. All users should be authorized to add events to the calendar;
  2. Every user has access only to his events, which he can modify or delete;
  3. Expired calendar events can’t be deleted or edited;
  4. Only one room for one event can be selected from a drop-down list;
  5. Different event colors are set for different users;
  6. Agenda and Room views in addition to Week and Month views.

Part I

Database and model creation

First of all, you have to build up a user infrastructure. We decided to use a built-in ASP.NET Membership.To implement this run the ASP.NET SQL Server Registration Tool. It can be found here: C:\WINDOWS\Microsoft.NET\Framework\v.1-4\aspnet_regsql.exe.This tool will automatically create the tables listed below:

  1. aspnet_Applications
  2. aspnet_Membership
  3. aspnet_SchemaVersions
  4. aspnet_Users

Run this tool with the following keys:

aspnet_regsql.exe -E -S <servername> -d <databasename> -A m

All rooms and users information will be stored in the calendar database. Let’s create two tables ‘Rooms’ and ‘Events’.

The ‘Rooms’ table should have only two columns:

 rooms

[key] is the ID of the room;

label is the name of the room.

The ‘Events’ table should look like this one:

events

Here we have added two new columns: room_id (ID of the room) and user_id (ID of the user) to link users and rooms with calendar events; text column has text data type in this case.

Note that a primary key and an identity column should be enabled for [key] and id.

In the table ‘aspnet_Users’ we have to add one more column ‘color’ to set the color of calendar events for each user:

asp net calendar users table

Once you’ve added all tables to your booking calendar project, you can set up models. Right-click on the folder Model -> Add New Item. Select LINQ to SQL Classes and create a new data model (e.g. MyEvents.dbml).

Now you can get down to the creation of models. The models are created for only three tables: Rooms, Events and aspnet_User, which you drag from the Server Explorer to the designer area. The ‘aspnet_User’ model should have only UserId, UserName and color properties, the rest should be removed. Rename this model into “User”.

In the end, your calendar database should look like this:

 my events

Part II

Creating controllers and views

In addition to AccountController.cs generated by default, create a BookingController.cs with the three methods set below:

using RoomBookingCalendar.Models;
using DHTMLX.Scheduler;
using DHTMLX.Common;
using DHTMLX.Scheduler.Data;
namespace RoomBookingCalendar.Controllers
{
    public class BookingController : Controller
    {
        public ActionResult Index()
        {
            var sched = new DHXScheduler(this);


            return View(sched);
        }

        public ActionResult Data()
        {
            return (new SchedulerAjaxData((new MyEventsDataContext()).Events));         
        }

		public ActionResult Save(int? id, FormCollection actionValues)
		{
			var action = new DataAction(actionValues);
            var changedEvent = (Event)DHXEventsHelper.Bind(typeof(Event), actionValues);
            MyEventsDataContext data = new MyEventsDataContext();
               
                try
                {
                    switch (action.Type)
                    {
                        case DataActionTypes.Insert:
                            data.Events.InsertOnSubmit(changedEvent);
                            break;
                        case DataActionTypes.Delete:
                            changedEvent = data.Events.SingleOrDefault(ev => ev.id == action.SourceId);
                            data.Events.DeleteOnSubmit(changedEvent);
                            break;
                        default:// "update"                          
                            var eventToUpdate = data.Events.SingleOrDefault(ev => ev.id == action.SourceId);
                            DHXEventsHelper.Update(eventToUpdate, changedEvent, new List<string>() { "id" });
                            break;
                    }
                    data.SubmitChanges();
                    action.TargetId = changedEvent.id;
                }
                catch(Exception a)
                {
                    action.Type = DataActionTypes.Error;
                }
            
            

            return new AjaxSaveResponse(action);
    }
}
}

Remember to change the redirect path from “Home” to “Booking” in the AccountController.cs

Then go to Views -> Shared -> Site.Master that has been generated automatically when you created a new project. Amend the coding in the following manner:

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<!DOCTYPE html>
<html>
<head id="Head1" runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
    <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
</head>

<body>
    <div class="page">
        <div id="main">
            <asp:ContentPlaceHolder ID="MainContent" runat="server" />

            <div id="footer">
            </div>
        </div>
    </div>
</body>
</html>

 Let’s create a booking calendar page by adding a new folder ‘Booking’ to Views and creating ‘Index.aspx’:

<%@ Page Title="" Language="C#"  MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
<asp:Content ID="pageTitle" ContentPlaceHolderID="TitleContent" runat="server">
    Booking Calendar
</asp:Content>
<asp:Content ID="mainContent" ContentPlaceHolderID="MainContent" runat="server">
<style>
#footer
{
    display:none;   
}
#main
{
    height:510px;
}
</style>
    <div id="logindisplay">
        <% Html.RenderPartial("LogOnUserControl"); %>
    </div> 
    <%= Model.Render() %>

</asp:Content>

Now go to Content -> Site.css and remove all unnecessary styles, except for body, link, visited, hover, active, page, #logindisplay and etc. as it is shown below:

body
{
    background-color: #5c87b2;
    font-size: .75em;
    font-family: Verdana, Helvetica, Sans-Serif;
    margin: 0;
    padding: 0;
    color: #696969;
}

a:link
{
    color: #034af3;
    text-decoration: underline;
}
a:visited
{
    color: #505abc;
}
a:hover
{
    color: #1d60ff;
    text-decoration: none;
}
a:active
{
    color: #12eb87;
}


.page
{
    width: 90%;
    margin-left: auto;
    margin-right: auto;
}


#logindisplay
{
    font-size:1.1em;
    display:block;
    text-align:right;
    margin:10px;
    color:White;
}

#logindisplay a:link
{
    color: white;
    text-decoration: underline;
}

#logindisplay a:visited
{
    color: white;
    text-decoration: underline;
}

#logindisplay a:hover
{
    color: white;
    text-decoration: none;
}

Proceed with changing the default route for ‘booking’ in Global.asax: 

new { controller = "Booking", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );

A simple online booking calendar will look like an ordinary ASP.NET event calendar with only Day, Week and Month views:

 basic event calendar asp net

Part III

Authentication and basic functionality

When you succeed with the above steps, create one more model Calendar.Models.cs for your events by right-clicking on Model -> Class. Add the following coding:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using DHTMLX.Scheduler;
namespace RoomBookingCalendar.Models
{
    public class CalendarModel
    {
        public string Rooms { get; set; }
        public string Colors { get; set; }
        public DHXScheduler Scheduler { get; set; }
    }
}

By now we have set rooms, limited user access rights and different colors for events in our booking calendar. It’s time to customize Scheduler .NET general settings. Go back to BookingController.cs and follow the instructions given below:

1. Set user permissions. Scheduler .NET will create rules allowing logged in users to amend only their events and forbidding not logged in users to edit any events in the calendar: 

sched.SetEditMode(EditModes.OwnEventsOnly, EditModes.AuthenticatedOnly);

2.  Connect the extension forbidding to add several events at a time:

sched.Extensions.Add(SchedulerExtensions.Extension.Collision);

3. Connect the extension forbidding to add events in the past:

sched.Extensions.Add(SchedulerExtensions.Extension.Limit);

4. Customize other calendar settings:

sched.Config.first_hour = 8;
sched.Config.last_hour = 19;
sched.XY.scroll_width = 0;
sched.Config.time_step = 30; // minimum event length
sched.Config.multi_day = true;
sched.Config.limit_time_select = true;

sched.Config.cascade_event_display = true;

sched.AfterInit = new List<string>() { "attachRules();" };// The required client-side handlers have to be added after Scheduler initialization but before events load.

sched.LoadData = true;
sched.PreventCache();

if (Request.IsAuthenticated)
{
	sched.EnableDataprocessor = true;
}

Then we should customize views by removing DayView and adding units and WeekAgendaView:

sched.Views.Items.RemoveAt(2);//remove DayView
         
var units = new UnitsView("rooms", "room_id");
         
units.Label = "Rooms";
units.AddOptions(context.Rooms);          
sched.Views.Add(units);
sched.Views.Add(new WeekAgendaView());

Note that context is MyEventsDataContext throughout the code.

Let’s set a lightbox by adding a control that allows users to choose a room:

var select = new LightboxSelect("room_id", "Room");
select.AddOptions(context.Rooms);
sched.Lightbox.AddDefaults();
sched.Lightbox.Items.Insert(1, select);

Customize settings for authorized users:

if (Request.IsAuthenticated)
{
      var user = context.Users.SingleOrDefault(u => u.UserId == (Guid)Membership.GetUser().ProviderUserKey);
      sched.SetUserDetails(user, "UserId");// render the current user to scheduler
      sched.Authentication.EventUserIdKey = "user_id"; User ID is assigned to his newly added events. Specify the property to be used for this purpose. 
      sched.InitialValues.Add("textColor", user.color); // set default values
      sched.InitialValues.Add("room_id", context.Rooms.First().key);
}

Now bind a model to render it in view.

At first, connect a namespace for serialization:

using System.Web.Script.Serialization;

Information about rooms and event colors is used in the client-side API. It should be rendered to the client-side. The information will be kept in the model as serialized to json, that’s why rooms and colors properties have a String type:

var serializer = new JavaScriptSerializer();
var rooms = serializer.Serialize(context.Rooms);
var colors = serializer.Serialize(
      (from us in context.Users select new { id = us.UserId.ToString(), color = us.color })
       .ToDictionary(t => t.id.ToLower(), t => t.color));
return View(new CalendarModel() { Rooms = rooms, Colors = colors, Scheduler = sched });

To verify whether the user is authorized or not and whether he’s trying to edit someone else’s or past events, we add the following logics to the Save action (that will be also double-checked on the client-side API):

if (Request.IsAuthenticated && changedEvent.user_id == (Guid)Membership.GetUser().ProviderUserKey && changedEvent.start_date > DateTime.Now)
{
…
}
else
{
	action.Type = DataActionTypes.Error;
}

Here is the controller full code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

using System.Web.Security;

using DHTMLX.Common;
using DHTMLX.Scheduler;
using DHTMLX.Scheduler.Authentication;
using DHTMLX.Scheduler.Controls;
using DHTMLX.Scheduler.Data;
using RoomBookingCalendar.Models;
using System.Web.Script.Serialization;

namespace RoomBookingCalendar.Controllers
{
    public class BookingController : Controller
    {
        protected void _ConfigureLightbox(DHXScheduler sched, MyEventsDataContext context)
        {
            var select = new LightboxSelect("room_id", "Room");
            select.AddOptions(context.Rooms);
            sched.Lightbox.AddDefaults();
            sched.Lightbox.Items.Insert(1, select); 
        }

        protected void _ConfigureViewsAndTemplates(DHXScheduler sched, MyEventsDataContext context)
        {
            sched.Views.Items.RemoveAt(2);//remove DayView
         
            var units = new UnitsView("rooms", "room_id");
         
            units.Label = "Rooms";
            units.AddOptions(context.Rooms);          
            sched.Views.Add(units);
            sched.Views.Add(new WeekAgendaView());

            
        }

        protected void _ConfigureScheduler(DHXScheduler sched, MyEventsDataContext context)
        {
            sched.SetEditMode(EditModes.OwnEventsOnly, EditModes.AuthenticatedOnly);
            sched.Extensions.Add(SchedulerExtensions.Extension.Collision);
            sched.Extensions.Add(SchedulerExtensions.Extension.Limit);
           

            sched.Config.first_hour = 8;
            sched.Config.last_hour = 19;
            sched.XY.scroll_width = 0;
            sched.Config.time_step = 30;
            sched.Config.multi_day = true;
            sched.Config.limit_time_select = true;

            sched.Config.cascade_event_display = true;


            sched.AfterInit = new List<string>() { "attachRules();" };

            sched.LoadData = true;
            sched.PreventCache();

            if (Request.IsAuthenticated)
            {
                sched.EnableDataprocessor = true;
            }  
        }
        protected void _HandleAuthorization(DHXScheduler sched, MyEventsDataContext context)
        {
            if (Request.IsAuthenticated)
            {
                var user = context.Users.SingleOrDefault(u => u.UserId == (Guid)Membership.GetUser().ProviderUserKey);
                sched.SetUserDetails(user, "UserId");
                sched.Authentication.EventUserIdKey = "user_id";

                sched.InitialValues.Add("textColor", user.color);
                sched.InitialValues.Add("room_id", context.Rooms.First().key);
            }

        }


        public ActionResult Index()
        {
            var sched = new DHXScheduler(this);
            var context = new MyEventsDataContext();

            _ConfigureScheduler(sched, context);
            _ConfigureViewsAndTemplates(sched, context);
            _ConfigureLightbox(sched, context);
            _HandleAuthorization(sched, context);

                      
            var serializer = new JavaScriptSerializer();
            var rooms = serializer.Serialize(context.Rooms);
            var colors = serializer.Serialize(
                (from us in context.Users select new { id = us.UserId.ToString(), color = us.color })
                    .ToDictionary(t => t.id.ToLower(), t => t.color));


            return View(new CalendarModel() { Rooms = rooms, Colors = colors, Scheduler = sched });
        }

        public ActionResult Data()
        {
            return (new SchedulerAjaxData((new MyEventsDataContext()).Events));         
        }

	public ActionResult Save(int? id, FormCollection actionValues)
	{
		var action = new DataAction(actionValues);
            var changedEvent = (Event)DHXEventsHelper.Bind(typeof(Event), actionValues);
            MyEventsDataContext data = new MyEventsDataContext();

            if (Request.IsAuthenticated && changedEvent.user_id == (Guid)Membership.GetUser().ProviderUserKey && changedEvent.start_date > DateTime.Now)
            {
               
                try
                {
                    switch (action.Type)
                    {
                        case DataActionTypes.Insert:
                            data.Events.InsertOnSubmit(changedEvent);
                            break;
                        case DataActionTypes.Delete:
                            changedEvent = data.Events.SingleOrDefault(ev => ev.id == action.SourceId);
                            data.Events.DeleteOnSubmit(changedEvent);
                            break;
                        default:// "update"                          
                            var eventToUpdate = data.Events.SingleOrDefault(ev => ev.id == action.SourceId);
                            DHXEventsHelper.Update(eventToUpdate, changedEvent, new List<string>() { "id" });
                            break;
                    }
                    data.SubmitChanges();
                    action.TargetId = changedEvent.id;
                }
                catch(Exception a)
                {
                    action.Type = DataActionTypes.Error;
                }
            }
            else
            {
                action.Type = DataActionTypes.Error;
            }

            return new AjaxSaveResponse(action);
		}

    }
}

Go to Index.aspx to add the required client scripts. Create a tag script and render model data to the page:

//list of rooms
    var rooms = <%=Model.Rooms %> ;
    var colors = <%=Model.Colors %> ;

//Add a helper that we’ll need later:
//returns room name by id
	function getRoom(id) {
	for (var i in rooms) {
	      if (rooms[i].key == id)
	      return rooms[i].label;
	    }
         return "";
	    }

Declare the function ‘attachRules’ mentioned above and make the following updates:

1. block the past calendar events:

	var isEditable = function(event_id){
		if(!event_id)
			return true;
		var ev = scheduler.getEvent(event_id);
			return (ev && ev.start_date.getTime() > new Date().getTime());
	};
		
		 
	scheduler.attachEvent("onBeforeLightbox", isEditable);
	scheduler.attachEvent("onBeforeDrag", isEditable);
	scheduler.attachEvent("onClick", isEditable);
	scheduler.attachEvent("onDblClick",isEditable);      		        
	scheduler.attachEvent("onEventCreated", function(event_id){
		scheduler.config.limit_start = new Date();// refresh limit extension settings                           
	});

2. Set colors for loaded events (we do not keep colors in the model so it has to be added additionally):

scheduler.attachEvent("onEventLoading", function(event_object){
	if(event_object.user_id && colors[event_object.user_id]){
            if(event_object.start_date.getTime() < new Date().getTime()){
                event_object.textColor = "gray";
            }else{    
                event_object.textColor = colors[event_object.user_id];
            }
        }
        return true;    
});
scheduler.config.limit_start = new Date();

3. Change the template of an event label to display a room name:

scheduler.templates.event_text = scheduler.templates.agenda_text = scheduler.templates.event_bar_text = function(start,end,ev){ 
	return getRoom(ev.room_id)+' : ' + ev.text;
}
scheduler.templates.week_agenda_event_text = function(start,end,ev){
	return scheduler.templates.event_date(ev.start_date) + ' ' +getRoom(ev.room_id)+' : ' + ev.text;
}
The full code will look like this:
<%@ Page Title="" Language="C#"  MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
<asp:Content ID="pageTitle" ContentPlaceHolderID="TitleContent" runat="server">
    Booking Calendar
</asp:Content>

<asp:Content ID="mainContent" ContentPlaceHolderID="MainContent" runat="server">
<style>
#footer
{
    display:none;   
}
#main
{
    height:510px;
}
</style>

    <script>
        //list of rooms
        var rooms = <%=Model.Rooms %> ;
        var colors = <%=Model.Colors %> ;
	    //returns room name by id
	    function getRoom(id) {
	        for (var i in rooms) {
	            if (rooms[i].key == id)
	                return rooms[i].label;
	        }
            return "";
	    }

        function attachRules(){
        //check is event belongs to the user and is it not started yet
		    var isEditable = function(event_id){
                  if(!event_id)
                    return true;
			      var ev = scheduler.getEvent(event_id);
	    	      return (ev && ev.start_date.getTime() > new Date().getTime());
		    };
		
		    scheduler.attachEvent("onBeforeLightbox", isEditable);
		    scheduler.attachEvent("onBeforeDrag", isEditable);
		    scheduler.attachEvent("onClick", isEditable);
		    scheduler.attachEvent("onDblClick",isEditable);      		        
		    scheduler.attachEvent("onEventCreated", function(event_id){
                  scheduler.config.limit_start = new Date();               
		    });
            scheduler.attachEvent("onEventLoading", function(event_object){
		if(event_object.user_id && colors[event_object.user_id]){
                    if(event_object.start_date.getTime() < new Date().getTime()){
                        event_object.textColor = "gray";
                    }else{    
                        event_object.textColor = colors[event_object.user_id];
                    }
                }
		        return true;    
	    });

            scheduler.templates.event_text = scheduler.templates.agenda_text = scheduler.templates.event_bar_text = function(start,end,ev){ 
                return getRoom(ev.room_id)+' : ' + ev.text;
            }
            scheduler.templates.week_agenda_event_text = function(start,end,ev){
                return scheduler.templates.event_date(ev.start_date) + ' ' +getRoom(ev.room_id)+' : ' + ev.text;
            }


        }

    </script>
    <div id="logindisplay">
                <% Html.RenderPartial("LogOnUserControl"); %>
    </div> 
    <%= Model.Render() %>

</asp:Content>

Part IV

User Authentication

There are several steps to user authentication.

At first, customize the Web.config file as it is shown below:

<membership defaultProvider="CustomMembershipProvider">
    <providers>
     <clear/>
      <add name="CustomMembershipProvider" type="System.Web.Security.SqlMembershipProvider"
        connectionStringName="bookingConnectionString"
        enablePasswordRetrieval="false"
        enablePasswordReset="true"
        requiresQuestionAndAnswer="false"
        requiresUniqueEmail="false"
        maxInvalidPasswordAttempts="5"
        minRequiredPasswordLength="1"
        minRequiredNonalphanumericCharacters="0"
        passwordAttemptWindow="10"
        applicationName="/" />
 </providers>
</membership>

We have added three users with the following names/passwords:  guest1/ guest1, guest2/ guest2 and guest3/ guest3 to log in this room booking calendar. Colors for users and the required number of rooms are set in the database.

Tip: To add rooms right-click on the table “Rooms” -> New Query -> Rooms and copy this line:

INSERT INTO Rooms (label) VALUES ('Room #1'), ('Room #2'),('Room #3'),('Room #4')

New users for the booking calendar are created by means of the Web Site Administration Tool that you can access via ASP.NET configurations.

Make final customizations to Index.aspx. Update Model.Render() to Model.Scheduler.Render():

<div id="logindisplay">
          <% Html.RenderPartial("LogOnUserControl"); %>
</div> 
<%= Model.Scheduler.Render() %>

You can also set additional styles to your online booking calendar. For example, change the calendar background or the authorization field.

After completing all the above steps, you get a ready online booking calendar service with a user login form and Agenda, Rooms, Week and Month view. View online demo.

Save up your time! Sign up and get the final package.

Take a minute to evaluate also a new room booking calendar in MVC3 Razor

If you find this tutorial helpful, feel free to comment below and recommend it to your friends.