Blackbaud CRM™ Integration with Google Calendar Part 2

In this post, part 2 of 3, we’re going to take a look at using 2 different APIs to create calendar items. The goal will be to create a web service in Visual Studio that can accept an event, and use that to create calendar events in Google, or in Outlook 365.

In the third and final installment, we will take a look at what we’ve done in parts 1&2, and use Blackbaud CRM™ as the trigger for adding events to Google and Outlook 365.

 

“Why do we need to have a separate web service?” you may wonder, as the Blackbaud Infinity platform allows developers to create rich customizations, and it’s certainly capable of calling external APIs. This may be true, but in reality, the APIs for Google and Outlook rely on some very specific dependencies, which will load a lot of extra components into CRM’s bin\custom directory, and may not play well either with CRM, or other customizations you will be writing. Additionally, separating the API calls into their own service allows some extra flexibility around testing, as well as the ability to be consumed by applications other than CRM.

 

Finally, as the Google API uses a proprietary security file, it gives administrators a little more leeway in maintaining credentials, and not have to shoehorn them into a CRM customization.

 

In this exercise what we’ll do is create a web service that can take a simple event with a collection of attendees, and add it to either a Google Calendar or an Outlook 365 calendar. In doing so we’ll revisit some of the Google API code we saw in Part 1, and also look at some code to work with the Outlook API.

 

Before you start this exercise, it’s assumed you went through part 1 and created a Google Developer’s account and project, and downloaded a .json security file to your local system. As long as you went up to the point where Part 1 discussed creating a Visual Studio project, you’ll be ok to start here. In this post, we’ll be creating a brand new VS project.

 

First thing is to create a new web service project:

 

As we saw in Part one, in order to use the Google API, we need to import some references:

Tools>NuGet Package Manager>Manage NuGet Packages for Solution

 

Google APIs Auth Client Library

 

 

 

Then do the same thing, searching for “Google.Apis.Calendar.v3 Client Library”

 

Rename IService1.vb and Service1.svc to ICalendarSvc.vb and CalendarSvc.scv, respectively. Then open up our interface, ICalendarSvc.vb

Now replace the default operation contracts with the following:

   <OperationContract()>

   Sub AddCalendarEvent(Details As EventDetails)

 

Next, remove the default Data Contracts and create the following, which is a new composite type. This type will be a relatively simple version of a calendar event, and is sent into our web service as the request:

<DataContract()>

Public Class EventDetails

   <DataMember()>

   Public Property Mode() As enumCalendarMode

   <DataMember()>

   Public Property EventTitle() As String

   <DataMember()>

   Public Property EventBody() As String

   <DataMember()>

   Public Property EmailAddresses() As List(Of String)

   <DataMember()>

   Public Property dtStart() As Date

   <DataMember()>

   Public Property dtEnd() As Date

 

End Class

The last thing we will do in this file is add an enum to tell the service what mode to use. In our example we will build in two modes – one for Google and another for Outlook365:

Public Enum enumCalendarMode As Integer

   CALENDAR_MODE_GOOGLE = 0

   CALENDAR_MODE_OUTLOOK = 1

End Enum

The entire file should now look like this:

<ServiceContract()>

Public Interface ICalendarSvc

   <OperationContract()>

   Sub AddCalendarEvent(Details As EventDetails)

End Interface

 

<DataContract()>

Public Class EventDetails

   <DataMember()>

   Public Property Mode() As enumCalendarMode

   <DataMember()>

   Public Property EventTitle() As String

   <DataMember()>

   Public Property EventBody() As String

   <DataMember()>

   Public Property EmailAddresses() As List(Of String)

   <DataMember()>

   Public Property dtStart() As Date

   <DataMember()>

   Public Property dtEnd() As Date

End Class

 

Public Enum enumCalendarMode As Integer

   CALENDAR_MODE_GOOGLE = 0

   CALENDAR_MODE_OUTLOOK = 1

End Enum

 

Open CalendarSvc.svc, remove what’s there and paste the following:

Public Class CalendarSvc

 

   Implements ICalendarSvc

 

   Public Sub New()

   End Sub

 

   Sub AddCalendarEvent(Details As EventDetails) Implements ICalendarSvc.AddCalendarEvent

       Dim cal As ICalendarApi

       If Details.Mode = enumCalendarMode.CALENDAR_MODE_GOOGLE Then

           cal = New GoogleCalendarSvc

           cal.AddEvent(Details)

       ElseIf Details.Mode = enumCalendarMode.CALENDAR_MODE_OUTLOOK Then

           cal = New Outlook365CalendarSvc

           cal.AddEvent(Details)

       End If

   End Sub

 

End Class

 

What we’ll do next is fill in the actual code that does the work for adding the Google and Outlook events. Since the implementation is similar for both, it makes some sense to create an interface. Looking at the code above, we can infer that we’ll be making an interface called “ICalendarApi” and two classes which implement the interface: “GoogleCalendarSvc” and “Outlook365CalendarSvc”. So go ahead and create ICalendarApi, which should be very simple and look like this:

Public Interface ICalendarApi

 

   Sub AddEvent(Details As EventDetails)

End Interface

 

Next, create a new class called “GoogleCalendarSvc.vb” and paste in the following:

 

Imports System.IO

Imports System.Threading

Imports Google.Apis.Calendar.v3

Imports Google.Apis.Calendar.v3.Data

Imports Google.Apis.Services

Imports Google.Apis.Auth.OAuth2

Imports Google.Apis.Util.Store

Imports System.Net.Http

 

Public Class GoogleCalendarSvc

   Implements ICalendarApi

 

   Dim _scopes As IList(Of String) = New List(Of String)()

   Dim _service As Google.Apis.Calendar.v3.CalendarService

   Dim _AllEvents As New List(Of Data.Event)

   Dim _Details As EventDetails

 

   Public Sub AddEvent(Details As EventDetails) Implements ICalendarApi.AddEvent

       _Details = Details

       InitializeService()

       CreateEvent()

   End Sub

 

   Private Sub InitializeService()

       _scopes.Add(Google.Apis.Calendar.v3.CalendarService.Scope.Calendar)

 

       Dim jsonFile As String = "C:\Credentials\client_secret_791792756839-c1ev8q9gjgvep2qm1v2cl872joh2pd98.apps.googleusercontent.com.json"

 

       Dim credential As UserCredential

       Dim secrets As ClientSecrets

       Dim fds As New FileDataStore("C:\Credentials", True)

       Dim googleAccount As String = "brightvinesolutions@gmail.com"

 

       Try

           Using stream As New FileStream(jsonFile, FileMode.Open, FileAccess.Read)

               secrets = GoogleClientSecrets.Load(stream).Secrets

               credential = GoogleWebAuthorizationBroker.AuthorizeAsync(secrets, _scopes, googleAccount, CancellationToken.None, fds).Result

           End Using

       Catch ex As Exception

           Throw ex

       End Try

 

       ' Create the calendar service using an initializer instance

       Dim initializer As New BaseClientService.Initializer()

       initializer.HttpClientInitializer = credential

       initializer.ApplicationName = "VB.NET Calendar Sample"

       _service = New Google.Apis.Calendar.v3.CalendarService(initializer)

 

   End Sub

 

   Private Sub CreateEvent()

       Try

           Dim calEvent As New Google.Apis.Calendar.v3.Data.Event

 

           calEvent.Summary = _Details.EventTitle

           calEvent.Description = _Details.EventBody

           calEvent.Location = ""

 

           Dim attendees As New List(Of EventAttendee)

           Dim evAttendee As EventAttendee

           For Each curAttendee As String In _Details.EmailAddresses

               evAttendee = New EventAttendee

               evAttendee.Email = curAttendee

               attendees.Add(evAttendee)

           Next

           calEvent.Attendees = attendees

 

           Dim evStart As New EventDateTime

           evStart.DateTime = _Details.dtStart

           Dim evEnd As New Data.EventDateTime

           evEnd.DateTime = _Details.dtEnd

           calEvent.Start = evStart

           calEvent.End = evEnd

 

           Dim createdEvent As [Event] = _service.Events.Insert(calEvent, "primary").Execute

 

       Catch ex As HttpRequestException

           Throw ex

       Catch ex As Exception

           Throw ex

       End Try

 

   End Sub

 

End Class

 

Note that in InitializeService, you’ll need to change the “dim jsonFile…” to reflect the real location of the json file you downloaded from Google. This location, of course, will need to be accessible by IIS, so make sure the AppPool you’re running under has proper rights.

At this point if we were to go back into CalendarSvc.svc and comment out the following lines:

           cal = New Outlook365CalendarSvc

           cal.AddEvent(Details)

…we should be able to use this service to create new Google events. To those who read through all of part 1, the “InitializeService” section should look familiar, and the CreateEvent sub should look very straightforward, its purpose mostly being to create the correct Google Event object, set some properties (we iterate the Attendees ArrayList), and finally, execute the request.

This can be easily tested by creating a Win Forms test project in the same solution, creating a service reference to CalendarSvc named “ProjSvc” and running the following code either on form load or in a button click event:

       Dim gRef As New ProjSvc.CalendarSvcClient

       Dim ed As New ProjSvc.EventDetails

       Dim emailAddresses As New ArrayList

       Try

           emailAddresses.Add("my.account@gmail.com")

           ed.Mode = ProjSvc.enumCalendarMode.CALENDAR_MODE_GOOGLE

           ed.EmailAddresses = emailAddresses.ToArray(GetType(String))

           ed.EventTitle = "Yet another conference call"

           ed.EventBody = "Make sure you're not on mute when speaking!"

           ed.dtStart = Now().AddHours(1)

           ed.dtEnd = Now().AddHours(2)

 

           gRef.AddCalendarEvent(ed)

       Catch ex As Exception

           MsgBox(ex.Message)

       End Try

 

In the downloadable code, we’ll have a richer form for testing, and will include Outlook testing, as well, but the above snippet should let you try this service out now.

 

Ok, let’s get back to the Calendar Service. If you commented out the following:

           cal = New Outlook365CalendarSvc

           cal.AddEvent(Details)

…uncomment them, and add a new class called CallbackMethods.vb with the following code:

' This source is subject to the Microsoft Public License.

' See http://www.microsoft.com/en-us/openness/licenses.aspx#MPL.

' All other rights reserved.

'

' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,

' EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED

' WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.

'**************************************************************************/

 

Imports System.Security.Cryptography.X509Certificates

 

Public NotInheritable Class CallbackMethods

   Private Sub New()

   End Sub

   Public Shared Function RedirectionUrlValidationCallback(ByVal redirectionUrl As String) As Boolean

       ' The default for the validation callback is to reject the URL.

       Dim result As Boolean = False

 

       Dim redirectionUri As New Uri(redirectionUrl)

 

       ' Validate the contents of the redirection URL. In this simple validation

       ' callback, the redirection URL is considered valid if it is using HTTPS

       ' to encrypt the authentication credentials.   

       If redirectionUri.Scheme = "https" Then

           result = True

       End If

       Return result

   End Function

 

   Public Shared Function CertificateValidationCallBack(ByVal sender As Object,

       ByVal certificate As System.Security.Cryptography.X509Certificates.X509Certificate,

       ByVal chain As System.Security.Cryptography.X509Certificates.X509Chain,

       ByVal sslPolicyErrors As System.Net.Security.SslPolicyErrors) As Boolean

       'return true;

       ' If the certificate is a valid, signed certificate, return true.

       If sslPolicyErrors = System.Net.Security.SslPolicyErrors.None Then

           Return True

       End If

 

       ' If there are errors in the certificate chain, look at each error to determine the cause.

       If (sslPolicyErrors And System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) <> 0 Then

           If chain IsNot Nothing AndAlso chain.ChainStatus IsNot Nothing Then

               For Each status As System.Security.Cryptography.X509Certificates.X509ChainStatus In chain.ChainStatus

                   If (certificate.Subject = certificate.Issuer) AndAlso

                       (status.Status =

           System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.UntrustedRoot) Then

                       ' Self-signed certificates with an untrusted root are valid.

                       Continue For

                   Else

                       If status.Status <>

           System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.NoError Then

                           ' If there are any other errors in the certificate chain, the certificate is invalid,

                           ' so the method returns false.

                           Return False

                       End If

                   End If

               Next status

           End If

 

           ' When processing reaches this line, the only errors in the certificate chain are

           ' untrusted root errors for self-signed certificates. These certificates are valid

           ' for default Exchange server installations, so return true.

           Return True

       Else

           ' In all other cases, return false.

           Return False

       End If

   End Function

End Class

 

This code snippet came from this project:

https://code.msdn.microsoft.com/office365/How-to-Search-Calendar-11437894

You can go here to get the full context, but the gist is that when authenticating with Outlook, using AutodiscoverUrl may throw a redirect to https, which could throw an error. This class, used as a callback, will override that.

The last file in the project is the Outlook365CalendarSvc.vb class. Go ahead and add that, then past in the following:

Imports Microsoft.Exchange.WebServices.Data

Imports System.Net

 

Public Class Outlook365CalendarSvc

   Implements ICalendarApi

 

   Public Sub AddEvent(Details As EventDetails) Implements ICalendarApi.AddEvent

       'ServicePointManager.ServerCertificateValidationCallback = AddressOf CallbackMethods.CertificateValidationCallBack

       Dim service As New ExchangeService(ExchangeVersion.Exchange2010_SP2)

 

       ' Get the information of the account.

       Dim user As New User

 

       Try

           user.Account = "my.account@brightvinesolutions.com"

           User.Pwd = "MyPassword"

           service.Credentials = New WebCredentials(user.Account, user.Pwd)

 

       ' Set the url of server.

       If AutodiscoverUrl(service, User) Then

 

           Dim appt As New Appointment(service)

 

           'Set the properties on the appointment object to create the appointment.

           appt.Subject = Details.EventTitle

           appt.Body = Details.EventBody

           appt.Start = Details.dtStart

           appt.End = Details.dtEnd

           appt.Location = ""

           appt.ReminderDueBy = DateTime.Now

           For Each curAttendee As String In Details.EmailAddresses

               appt.RequiredAttendees.Add(curAttendee)

           Next

 

           'Save the appointment to your calendar.

           appt.Save(SendInvitationsMode.SendToNone)

 

           End If

       Catch ex As Exception

           Throw ex

           Exit Sub

       End Try

 

   End Sub



 

   Private Shared Function AutodiscoverUrl(ByVal service As ExchangeService, ByVal user As User) As Boolean

       Dim isSuccess As Boolean = False

 

       Try

           service.AutodiscoverUrl(user.Account, AddressOf CallbackMethods.RedirectionUrlValidationCallback)

           isSuccess = True

       Catch ex As Exception

           Throw ex

       End Try

 

       Return isSuccess

   End Function
 

End Class

 

Friend Class User

   Private accountInfo As String = Nothing

   Private pwdInfo As String = Nothing

 

   Public Sub New()

   End Sub

 

   Public Property Account() As String

       Get

           Return accountInfo

       End Get

       Set(value As String)

           accountInfo = value

       End Set

   End Property

 

   Public Property Pwd() As String

       Get

           Return pwdInfo

       End Get

       Set(value As String)

           pwdInfo = value

       End Set

   End Property

End Class

 

Of course, you’ll want to make sure to change the user name and password to be something more appropriate. In a production situation, we’d probably want to set up some service account in Outlook, and at the very least, store the credentials in a config file.

 

At this point you can build your project and using the same code snippet as above, set the mode to “OUTLOOK” and you should be getting calendar items added to your Outlook 365 Calendar.

 

Next week, we'll look at part 3 of this series, how add these objects to Blackbaud CRM. 



 

Please reload

If you'd like more information about this topic, please connect with us today!
RSS Feed
Recent Posts
Have an idea for a blog post? Let us know!
Please reload

Search By Topics