Synchronization with Outlook Calendar

Synchronization with Outlook Calendar

1. Introduction

This documentation explains how to implement calendar with DHTMLX Scheduler .NET in ASP.NET MVC 5 that uses Outlook Calendar as data source, without using own database. After authorization, user will be able to see Outlook Calendar events of his Microsoft Live account. We will use Outlook REST API to interact with calendars and events. It should be noted that all code in this tutorial is valid for MVC 4 application.

 sync with outlook calendar

For executing methods of Outlook REST API we should have access token that is received on successful authorization by OAuth 2.0 protocol. We should set up Windows Live Application and get credentials for authorization in our project.

Note: if we want to get Outlook Calendar events by API, we should select start and end loading date and one of the existing calendars. In this tutorial we will load calendars and offer user to select one of them. Current month events will be loaded.

2. Setting Up Windows Live Application

Sign in to Windows Live application management site.

  1. You will see a page that contains a list of registered apps. Click Create application on this page. (skip this step if you haven’t created applications before)
  2. Set an application name (“Scheduler Demo”, for example) and language, agree with the terms.
  3. In left sidebar open App Settings and copy ClientId and Client secret. We will put them into the configuration file of our application later.
  4. In left sidebar open API Settings and specify Redirect URL*.

*authorization is done by OAuth 2.0 protocol, redirect url is our application address that matches MVC controller method that receives authorization code from Microsoft server as GET parameter. In our example it will be localhost:58342/Auth/. Note, that your port will be different and depend on MVC project properties.

3. Implementation

Outlook REST API Description

For interaction with Outlook Calendar we will use Outlook REST API, see this link. Scheme of interacting with Outlook Calendar by REST API:

  1. Authorization by OAuth 2.0 protocol and getting access token.
  2. Using api methods with received access token.

Architecture

All logic in the solution will be divided into 3 parts: MVC presentation, Outlook API server interaction and OAuth2 authorization. API interaction will be separated from authorization because it is independent from ASP.NET, while authorization is bound to MVC. So, our solution will contain 3 assemblies:

  • Presentation.Mvc5 (MVC 5 app, Razor)
  • OutlookApi.Wrapper (Class library, describes API interaction)
  • OutlookApi.Authorization.Mvc (Class library)

API Interaction. (OutlookApi.Wrapper)

Here will be data entities and helper class, that we will use for interacting with Outlook REST API.

Models folder

Outlook REST API returns calendars and events as JSON object, called Data, that contains collection of entities, which we will deserialize into our models below:

Create Models folder with such model classes:

public class OutlookCalendar
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Permissions { get; set; }
    }
 
public class OutlookEvent
    {
        public string id { get; set; }
        public string name { get; set; }
        public string description { get; set; }
        public DateTime start_time { get; set; }
        public DateTime end_time { get; set; }
    }
 
internal class ReceivedCalendarsData
    {
        public List<OutlookCalendar> Data { get; set; } 
    }
 
internal class ReceivedEventsData
    {
        public List<OutlookEvent> Data { get; set; } 
    }

OutlookCalendarApi.cs

Add reference on the system assembly System.Web.Extensions

Then create OutlookCalendarApi class in the project root.

public class OutlookCalendarApi
    {
        public string AccessToken { get; set; }
 
        private const string UrlBase = "https://apis.live.net/v5.0/";
 
        public OutlookCalendarApi()
        {
        }
 
        public OutlookCalendarApi(string accessToken)
        {
            AccessToken = accessToken;
        }
    }
</>

Requests for all Outlook Calendar api methods are similar, that’s why we will write basic request composing method, that sends it and receives a response from the API. This method is using generic type of entity to return after getting response from Outlook API server. Data parameter is used for sending event fields for update or create. Here are examples of HTTP requests we will compose.

private T GetResponse<T>(string uri, string method, Dictionary<string, object> data)
        {
            var request = (HttpWebRequest) WebRequest.Create(uri);
            request.Method = method;
            request.Headers.Add("Authorization", string.Format("Bearer {0}", AccessToken));
 
            if (data.Count != 0)
            {
                string paramsStr = string.Join("&", data.Select(item => String.Format("{0}={1}", item.Key, item.Value)));
                request.ContentLength = paramsStr.Length;
                request.ContentType = "application/x-www-form-urlencoded";
 
                using (var sw = new StreamWriter(request.GetRequestStream()))
                {
                    sw.Write(paramsStr);
                    sw.Close();
                }
            }
 
            var response = (HttpWebResponse)request.GetResponse();
            string responseText;
            using (var reader = new StreamReader(response.GetResponseStream()))
            {
                responseText = reader.ReadToEnd();
            }
 
            return new JavaScriptSerializer().Deserialize<T>(responseText);
        }

Add same method for data parameter with empty dictionary for simplification

        private T GetResponse<T>(string uri, string method)
        {
            return GetResponse<T>(uri, method, new Dictionary<string, object>());
        }

Add methods for getting calendars:

public IEnumerable<OutlookCalendar> GetCalendars()
        {
            var data = GetResponse<ReceivedCalendarsData>(UrlBase + "me/calendars", "GET");
            return data.Data.Where(item => item.Permissions.Equals("owner"));
        }

Add events CRUD methods (calendarId parameter is id of calendar selected event belongs or will belong):

public string CreateEvent(OutlookEvent @event, string calendarId)
        {
            var sendData = EventAsSendData(@event);
            var receivedData = GetResponse<OutlookEvent>(UrlBase + calendarId + "/events", "POST", sendData);
            return receivedData.id;
        }
 
        public string UpdateEvent(OutlookEvent @event)
        {
            var sendData = EventAsSendData(@event);
            var receivedData = GetResponse<OutlookEvent>(UrlBase + @event.id, "PUT", sendData);
            return receivedData.id;
        }
 
        public IEnumerable<OutlookEvent> ReadEvents(string calendarId, DateTime from, DateTime to)
        {
            string requestStr = String.Format(
                "{0}{1}/events?start_time={2}Z&end_time={3}Z",
                UrlBase,
                calendarId,
                from.ToString("s"),
                to.ToString("s")
                );
 
            var eventsData = GetResponse<ReceivedEventsData>(requestStr, "GET");
            return eventsData.Data;
        }
 
        public void DeleteEvent(string id)
        {
            GetResponse<object>(UrlBase + id, "DELETE");
        }

And a private helper method that we'll use for converting our data entities properties to name-value collection that will be used for API request composing:

private Dictionary<string, object> EventAsSendData(OutlookEvent @event)
        {
            return new Dictionary<string, object>
            {
                {"name", @event.name},
                {"description", @event.description ?? String.Empty},
                {"start_time", @event.start_time.ToUniversalTime().ToString("s") + "Z"},
                {"end_time", @event.end_time.ToUniversalTime().ToString("s") + "Z"}
            };
        }

Authorization. (OutlookApi.Authorization.Mvc)

This assembly will contain auth helper class. OAuth 2.0 authorization implementation is different for each platform/framework, that’s why this assembly will contain non-generic MVC authorization.

Authorization process: user opens a page with calendar, redirects into Microsoft Live log on the page, submits authorization, then returns back to the calendar page as an authorized user.

OAuth 2.0 authorization will be implemented as helper class: MvcAuthHelper.cs. We will authorize each time when we will visit page with calendar in MVC project, because access token expires after 1 hour. So, we will store access token in cookie.

Algorithm of getting access token:

  1. Visit authorization page in web browser, authorize
  2. Get authorization code as GET parameter after redirecting from authorization server
  3. Request access token by sending authorization code

So, our MvcAuthHelper.cs will have 3 public methods:

  • public object Authorize() - will redirect user to Microsoft Live authorization page. When the user is authorized, it will send him back to the page specified in Redirect URL setting, providing authorization code in request parameters
  • public bool IsAuthorized() - return true if authorization was successful
  • public string GetAccessToken() - return access token by our auth code

Firstly, install DotNetOpenAuth package from NuGet:

PM> Install-Package DotNetOpenAuth -ProjectName OutlookApi.Authorization.Mvc

Install ASP.NET MVC package:

PM> Install-Package Microsoft.AspNet.Mvc -Version 5.0.0 -ProjectName OutlookApi.Authorization.Mvc

Than install DotNetOpenAuth MVC5 Extensions package

PM> Install-Package DotNetOpenAuth.Mvc5 -ProjectName OutlookApi.Authorization.Mvc

Add reference on system assembly System.Web

Create OutlookAuthData.cs file with following model class:

public class OutlookAuthData
    {
        public Uri RedirectUri { get; set; }
        public string ClientId { get; set; }
        public string ClientSecret { get; set; }
        public IEnumerable<string> Scopes { get; set; } 
    }

It contains authorization data, that can be found at Microsoft Live Application. Also, it contains permission scopes for interacting with Outlook REST API, see this link. We will use the following scopes:

  • wl.calendars_update
  • wl.signin
  • wl.events_create

Then write MvcAuthHelper class base. WebServerClient, AuthorizationServerDescription and OutgoingWebResponse in the snippet below are members of DotNetOpenAuth library, that implements OAuth 2.0 authorization for .NET.

    public class MvcAuthHelper : IAuthorizable
    {
        private readonly HttpContextBase _httpContext;
        private readonly OutlookAuthData _authData;
        private readonly WebServerClient _client;
        private readonly OutgoingWebResponse _response;
 
        public MvcAuthHelper(HttpContextBase httpContext, OutlookAuthData authData)
        {
            _httpContext = httpContext;
            _authData = authData;
 
            var server = new AuthorizationServerDescription
            {
                AuthorizationEndpoint = new Uri("https://login.live.com/oauth20_authorize.srf"),
                TokenEndpoint = new Uri("https://login.live.com/oauth20_token.srf"),
                ProtocolVersion = ProtocolVersion.V20
            };
 
            _client = new WebServerClient(server, _authData.ClientId, _authData.ClientSecret);
            _response = _client.PrepareRequestUserAuthorization(_authData.Scopes, _authData.RedirectUri);
        }
 
    }

And methods mentioned in the beginning of the authorization section:

public bool IsAuthorized()
        {
            return !String.IsNullOrEmpty(_httpContext.Request.QueryString["code"]);
        }
 
        public object Authorize()
        {
            return _response.AsActionResultMvc5();
        }
 
        public string GetAccessToken()
        {
            return _client.ProcessUserAuthorization().AccessToken;
        }

Presentation. (Presentation.Mvc5)

Creating an empty MVC5 project in Visual Studio 2015 Community is going by this way: Right click your solution > Add > New Project > Visual C# > Web > ASP.NET Web Application > OK

 calendar project and outlook calendar

Thеn select Empty template, check MVC checkbox and click OK

 mvc template for calendars syncronization

Getting started

Before adding controllers, let’s set a basic layout, styles, install Scheduler.NET package and set needed references.

Create Content folder with Style.css:

html, body {
    font-family: "Arial";
}
 
.calendar-container {
    height: 800px;
}
 
.display-settings-item {
    margin: 20px;
}
 
.calendar-select-form {
    width: 400px;
    height: 140px;
    margin: 80px auto 0px auto;
    padding: 10px;
    border: 1px solid lightgray;
 
    text-align: center;
}

Create Shared folder in Views with _Layout.cshtml file:

<!DOCTYPE html>
 
<html>
<head>
    <title>@ViewBag.Title</title>
    <link rel="stylesheet" href="@Url.Content("~/Content/Style.css")"/>
</head>
<body>
    <div>
        @RenderBody()
    </div>
</body>
</html>

Add _Viewport.cshtml in Views folder.

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

Add references on solution projects: OutlookApi.Wrapper and OutlookApi.Authorization.MVC

Install DHTMLX Scheduler.NET by NuGet:

Install-Package DHTMLX.Scheduler.NET -ProjectName Presentation.Mvc5

Controllers

In MVC application we will have 3 controllers: CalendarController - main controller that will define actions for two pages - a page where user can select one of his Outlook Calendars to be displayed and a page with a Scheduler. AuthController - responsible for authorization DataAccessController - will synchronize received data from client with Outlook Calendar API server

Auth Controller

Add AuthController in Controllers folder

        /// <summary>
        /// Gets Outlook api access token, saves as cookie and redirects
        /// to calendar controller
        /// </summary>
        /// <returns></returns>
        public ActionResult Index()
        {
            var authHelper = new MvcAuthHelper(HttpContext, GetAuthData());
 
            // authorize if not authorized
            if (!authHelper.IsAuthorized()) return (ActionResult) authHelper.Authorize();
 
            // sets cookie with token
            var tokenCookie = new HttpCookie("OutlookAccessToken", authHelper.GetAccessToken());
            tokenCookie.Expires = DateTime.Now.AddHours(1);
            HttpContext.Response.SetCookie(tokenCookie);
 
            return RedirectToAction("Select", "Calendar");
        }
 
        /// <summary>
        /// Gets user's data from config file, needed for authorization by OAuth 2.0 protocol
        /// </summary>
        /// <returns></returns>
        private OutlookAuthData GetAuthData()
        {
            return new OutlookAuthData
            {
                ClientId = ConfigurationManager.AppSettings["OutlookApi_ClientId"],
                ClientSecret = ConfigurationManager.AppSettings["OutlookApi_ClientSecret"],
                RedirectUri = new Uri(ConfigurationManager.AppSettings["OutlookApi_RedirectUri"]),
                Scopes = new[] { "wl.calendars_update", "wl.signin", "wl.events_create" }
            };
        }

GetAuthData( ) method will take data needed for authorizing by OAuth 2.0 from Web.config AppSettings section, let’s add them:

<add key="OutlookApi_RedirectUri" value="[REDIRECT_URL]" />
<add key="OutlookApi_ClientId" value="[CLIENT_ID]" />
<add key="OutlookApi_ClientSecret" value="CLIENT_SECRET" />

Redirect url, client ID and client secret can be found in your Microsoft Live dev app settings

Set routing settings in App_Start > RouteConfig.cs, setting AuthControler as the default controller

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Auth", action = "Index", id = UrlParameter.Optional }
            );
        }

Calendar Controller

On successful authorization we will get Select method of Calendar controller, where we will suggest user to select the calendar to display. So, create CalendarController with Select() method:

public ActionResult Select()
        {
            // get available calendars for DropDownList
            var api = new OutlookCalendarApi(Request.Cookies["OutlookAccessToken"].Value);
            var calendars = api.GetCalendars().Select(c => new SelectListItem { Text = c.Name, Value = c.Id }).ToList();
            ViewBag.Calendars = calendars;
 
            return View();
        }

And create its view:

@{
    ViewBag.Title = "Calendar select page";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
<div class="calendar-select-form">
    <h2 style="margin-bottom: 40px;">Select calendar</h2>
 
    @using (Html.BeginForm("Index", "Calendar"))
    {
        <div class="display-settings-item">
            @Html.DropDownList("calendarId", (List<SelectListItem>) ViewBag.Calendars)
            <button type="submit">Display</button>
        </div>
    }
</div>

Then add Index() method to CalendarController, which will initialize and render our calendar:

public ActionResult Index()
        {
            var scheduler = ConfigureScheduler();
            return View(scheduler);
        }
 
private DHXScheduler ConfigureScheduler()
        {
            var scheduler = new DHXScheduler();
 
            scheduler.LoadData = true;
            scheduler.EnableDataprocessor = true;
 
            string calendarId = Request["calendarId"];
 
            // load only this month events
            scheduler.EnableDynamicLoading(SchedulerDataLoader.DynamicalLoadingMode.Month);
 
            // compose data action for sending dateFrom, dateTo and accessToken
scheduler.DataAction = String.Format(
                "{0}?calendarId={1}",
                Url.Action("Data", "DataAccess"),
                Url.Encode(calendarId)
                );
 
 
            scheduler.SaveAction = String.Format(
                "{0}?calendarid={1}",
                Url.Action("Save", "DataAccess"),
                Url.Encode(calendarId)
                );
 
            scheduler.Lightbox.Add(new LightboxText("text", "Name"));
            scheduler.Lightbox.Add(new LightboxText("description", "Details"));
 
            // TODO: implement!
 
            return scheduler;
        }

Add it’s view:

@model DHTMLX.Scheduler.DHXScheduler
@{
    ViewBag.Title = "Outlook Calendar";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
<div class="calendar-container">
    @Html.Raw(Model.Render())
</div>

DataAccess Controller

And add Event model to Models folder

public class Event
    {
        public string id { get; set; }
        public string text { get; set; }
        public string description { get; set; }
        public DateTime start_date { get; set; }
        public DateTime end_date { get; set; }
    }

DataAccess controller will have 2 public methods: Data( ) - will return events data to our client using Outlook API Save(string id, FormCollection actionValues) - will update Outlook Calendar events due to our scheduler calendar

public ActionResult Data()
        {
            var api = new OutlookCalendarApi
            {
                AccessToken = Request.Cookies["OutlookAccessToken"].Value
            };
 
            var events = api.ReadEvents(
                Request["calendarId"],
                DateTime.Parse(Request.QueryString["from"]),
                DateTime.Parse(Request.QueryString["to"])
                );
 
            return new SchedulerAjaxData(events.Select(item => new Event
            {
                id = item.id,
                text = item.name,
                description = item.description,
                start_date = item.start_time,
                end_date = item.end_time
            }));
        }
 
public ActionResult Save(string id, FormCollection actionValues)
        {
            var action = new DataAction<string>(actionValues);
            var @event = DHXEventsHelper.Bind<Event>(actionValues);
 
            try
            {
                var api = new OutlookCalendarApi
                {
                    AccessToken = Request.Cookies["OutlookAccessToken"].Value
                };
 
                switch (action.Type)
                {
                    case DataActionTypes.Insert:
                        action.TargetId = api.CreateEvent(EventToOutlookEvent(@event), Request["calendarId"]);
                        break;
                    case DataActionTypes.Update:
                        api.UpdateEvent(EventToOutlookEvent(@event));
                        break;
                    case DataActionTypes.Delete:
                        api.DeleteEvent(id);
                        break;
                }
            }
            catch
            {
                action.Type = DataActionTypes.Error;
            }
 
            return new AjaxSaveResponse(action);
        }
 
private OutlookEvent EventToOutlookEvent(Event @event)
        {
            return new OutlookEvent
            {
                description = @event.description,
                name = @event.text,
                start_time = @event.start_date,
                end_time = @event.end_date,
                id = @event.id
            };
        }

comments powered by Disqus