如何发送应用程序中的自动操作的预约

[原文发表地址] How to Send Automated Appointments from a LightSwitch Application

[原文发表时间] 9 Feb 2011 7:06 AM

 

在上篇文章中我写了有关如何使用LightSwitch应用程序中屏幕上的按钮自动地发送Outlook预约。如果你错过了它:

如何在LightSwitch中创建一个 Outlook 预约

该解决方案使Outlook自动地在LightSwitch屏幕上的实体数据中创建一个预约并允许用户能和预约互动。在本篇博客中我将给大家演示如何使用iCalendar标准的格式(许多邮件客户可以读的,包括Outlook)自动地发送预约。我也会演示当LightSwitch应用程序中的数据发生更改时如何向这些预约发送。作为一条商用规则,我们将使用SMTP来创建并发送一个会议请求。这和两周之前我演示的第一个HTML邮件的例子很相似。当数据被插入或更新数据源时,自动生成的邮件会从服务器(中间层)发送出来。让我们来看看如何创建这个功能。

 

预约实体

由于当系统中的预约数据被更新或删除时我们也想发送已更新的和已取消的会议请求,我们需要向预约实体添加两个额外的属性来追踪正在发送的消息。首先我们需要一个唯一的消息ID,它是可以被存储为一个字符串的GUID。我们也需要追踪预约中更新的次序,以使邮件用户可以把它们关联在一起。任何时候发出去一个已更新的预约邮件,我们可以简单地增加一个数列数量。这是预约实体的模式(点击放大)。

 

Description: Description: Description: image

值得注意的是在本例中我还为Customer和Employee建立了关系。我们将要为这两方发送会议请求,并且我们将使Employee成为会议的组织者而使Customer作为会议的出席者。在这个实体中我将不想在屏幕上显示MsgID和MsgSequence属性。这些只会被用在代码中。既然已经定义了预约实体,就让我们添加一些商用规则来自动设置这些属性值。下拉实体设计右上角的“编写代码”按钮,选择Appointments_Inserting和Appointments_Updating。在被发送到数据存储器之前编写下面的代码来设置这些属性值:

 

  Public Class ApplicationDataService

    Private Sub Appointments_Inserting(ByVal entity As Appointment)

        'used to track any iCalender appointment requests

        entity.MsgID = Guid.NewGuid.ToString()

        entity.MsgSequence = 0

    End Sub

 

    Private Sub Appointments_Updating(ByVal entity As Appointment)

        'Update the sequence anytime the appointment is updated

        entity.MsgSequence += 1

    End Sub

End Class 

 

我也打算在StartTime和EndTime属性上添加一个商用规则,这样就使得开始时间总是在结束时间之前。在实体上选择StartTime属性,现在当你向下拉“编写代码”按钮时就会在顶端看见StartTime_Validate。选中它并写出代码:

 

Public Class Appointment

    Private Sub StartTime_Validate(ByVal results As EntityValidationResultsBuilder)

        If Me.StartTime >= Me.EndTime Then

            results.AddPropertyError("Start time cannot be after end time.")

        End If

    End Sub

 

    Private Sub EndTime_Validate(ByVal results As Microsoft.LightSwitch.EntityValidationResultsBuilder)

        If Me.EndTime < Me.StartTime Then

            results.AddPropertyError("End time cannot be before start time.")

        End If

    End Sub

End Class

 

最后确保你为这个预约实体创建一个新的数据画面

 

创建邮件预约帮助类

 

现在我们已经有了用来输入它们的预约实体和新的数据屏幕,我们需要创建一个帮助类—可以连接到服务器来发送自动生成的预约邮件。就像以前一样,我们向服务器应用程序添加一个帮助类. 在解决方案资源管理器中切换为文件试图并在Sever项目中添加一个类:

Description: Description: Description: image

 

我给帮助类起名为SMTPMailHelper。发送邮件的基本代码是很简单的。你只需要通过修改类顶端的常量来指定SMTP服务器,用户id,密码和端口。提示:如果只知道用户ID和密码,那么可以试试使用 Outlook 2010 来自动获取其余信息

 

创建会议请求的诀窍是创建一个iCalendar格式的附件,并把它添加为一个文本/记事录内容类型。实际上,这个代码在任意的.NET应用程序中运行都是一样的,这里对于LightSwitch没有什么特别之处。我正在设置会议请求的基本的属性,但是按照你想要进行的行为,有大量附加属性可以使用。想了解更多信息请查看详细说明(iCalendar是一个开放的详细说明,在这可以获取。有一个比这里的导航稍早一些的省略的版本)。

 

Imports System.Net

Imports System.Net.Mail

Imports System.Text

 

Public Class SMTPMailHelper

  Public Shared Function SendAppointment(ByVal sendFrom As String,

                                           ByVal sendTo As String,

                                           ByVal subject As String,

                                           ByVal body As String,

                                           ByVal location As String,

                                           ByVal startTime As Date,

                                           ByVal endTime As Date,

                                           ByVal msgID As String,

                                           ByVal sequence As Integer,

                                           ByVal isCancelled As Boolean) As Boolean

 

        Dim result = False

        Try

            If sendTo = "" OrElse sendFrom = "" Then

                Throw New InvalidOperationException("sendTo and sendFrom email addresses must both be specified.")

            End If

 

            Dim fromAddress = New MailAddress(sendFrom)

            Dim toAddress = New MailAddress(sendTo)

            Dim mail As New MailMessage

 

            With mail

                .Subject = subject

                .From = fromAddress

 

                'Need to send to both parties to organize the meeting

                .To.Add(toAddress)

                .To.Add(fromAddress)

            End With

 

            'Use the text/calendar content type

            Dim ct As New System.Net.Mime.ContentType("text/calendar")

            ct.Parameters.Add("method", "REQUEST")

            'Create the iCalendar format and add it to the mail

            Dim cal = CreateICal(sendFrom, sendTo, subject, body, location,
startTime, endTime, msgID, sequence, isCancelled)

            mail.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(cal, ct))

 

            'Send the meeting request

            Dim smtp As New SmtpClient(SMTPServer, SMTPPort)

            smtp.Credentials = New NetworkCredential(SMTPUserId, SMTPPassword)

            smtp.Send(mail)

 

            result = True

        Catch ex As Exception

            Throw New InvalidOperationException("Failed to send Appointment.", ex)

        End Try

        Return result

    End Function

  

    Private Shared Function CreateICal(ByVal sendFrom As String,

                                       ByVal sendTo As String,

                                       ByVal subject As String,

                                       ByVal body As String,

                                       ByVal location As String,

                                       ByVal startTime As Date,

                                       ByVal endTime As Date,

                                       ByVal msgID As String,

                                       ByVal sequence As Integer,

                                       ByVal isCancelled As Boolean) As String

 

        Dim sb As New StringBuilder()

        If msgID = "" Then

            msgID = Guid.NewGuid().ToString()

        End If

 

        'See iCalendar spec here: https://tools.ietf.org/html/rfc2445

        'Abridged version here: https://www.kanzaki.com/docs/ical/

        sb.AppendLine("BEGIN:VCALENDAR")

        sb.AppendLine("PRODID:-//Northwind Traders Automated Email")

        sb.AppendLine("VERSION:2.0")

        If isCancelled Then

            sb.AppendLine("METHOD:CANCEL")

        Else

            sb.AppendLine("METHOD:REQUEST")

        End If

        sb.AppendLine("BEGIN:VEVENT")

        If isCancelled Then

            sb.AppendLine("STATUS:CANCELLED")

            sb.AppendLine("PRIORITY:1")

        End If

        sb.AppendLine(String.Format("ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT:MAILTO:{0}", sendTo))

        sb.AppendLine(String.Format("ORGANIZER:MAILTO:{0}", sendFrom))

        sb.AppendLine(String.Format("DTSTART:{0:yyyyMMddTHHmmssZ}", startTime.ToUniversalTime))

        sb.AppendLine(String.Format("DTEND:{0:yyyyMMddTHHmmssZ}", endTime.ToUniversalTime))

        sb.AppendLine(String.Format("LOCATION:{0}", location))

        sb.AppendLine("TRANSP:OPAQUE")

        'You need to increment the sequence anytime you update the meeting request.

        sb.AppendLine(String.Format("SEQUENCE:{0}", sequence))

        'This needs to be a unique ID. A GUID is created when the appointment entity is inserted

        sb.AppendLine(String.Format("UID:{0}", msgID))

        sb.AppendLine(String.Format("DTSTAMP:{0:yyyyMMddTHHmmssZ}", DateTime.UtcNow))

        sb.AppendLine(String.Format("DESCRIPTION:{0}", body))

        sb.AppendLine(String.Format("SUMMARY:{0}", subject))

        sb.AppendLine("CLASS:PUBLIC")

        'Create a 15min reminder

        sb.AppendLine("BEGIN:VALARM")

        sb.AppendLine("TRIGGER:-PT15M")

        sb.AppendLine("ACTION:DISPLAY")

        sb.AppendLine("DESCRIPTION:Reminder")

        sb.AppendLine("END:VALARM")

 

        sb.AppendLine("END:VEVENT")

        sb.AppendLine("END:VCALENDAR")

 

        Return sb.ToString()

    End Function
End Class

 

写出服务器端商用规则

 

现在,在服务器应用程序中已经有了帮助类,我们可以从服务器端商用规则中来调用它。再次地下拉实体设计的右上段的“编写代码”按钮,现在向ApplicationDataService添加Appointments_Inserted,Appointments_Updated和Appointments_Deleting方法。经过实体属性调用SendAppointment方法。在 Appointment_Deleting 的情况下,也通过删除后将 isCancelled 标志为 True。因此现在ApplicationDataService看起来应该像这样:

 

Public Class ApplicationDataService

 

    Private Sub Appointments_Inserted(ByVal entity As Appointment)

        Try

            SMTPMailHelper.SendAppointment(entity.Employee.Email,

                                     entity.Customer.Email,

                                     entity.Subject,

                                     entity.Notes,

                                     entity.Location,

                                     entity.StartTime,

                                     entity.EndTime,

                                     entity.MsgID,

                                     entity.MsgSequence,

                                     False)

        Catch ex As Exception

            System.Diagnostics.Trace.WriteLine(ex.ToString)

        End Try

    End Sub

 

    Private Sub Appointments_Updated(ByVal entity As Appointment)

        Try

            SMTPMailHelper.SendAppointment(entity.Employee.Email,

                                    entity.Customer.Email,

                                    entity.Subject,

                                    entity.Notes,

                                    entity.Location,

                                    entity.StartTime,

                                    entity.EndTime,

                                    entity.MsgID,

                                    entity.MsgSequence,

                                    False)

        Catch ex As Exception

            System.Diagnostics.Trace.WriteLine(ex.ToString)

        End Try

    End Sub

 

    Private Sub Appointments_Deleting(ByVal entity As Appointment)

        Try

            SMTPMailHelper.SendAppointment(entity.Employee.Email,

                                    entity.Customer.Email,

                                    entity.Subject,

                                    entity.Notes,

                                    entity.Location,

                                    entity.StartTime,

                                    entity.EndTime,

                                    entity.MsgID,

                                    entity.MsgSequence,

                                    True)

        Catch ex As Exception

            System.Diagnostics.Trace.WriteLine(ex.ToString)

        End Try

    End Sub

 

    Private Sub Appointments_Inserting(ByVal entity As Appointment)

        'used to track any iCalender appointment requests

        entity.MsgID = Guid.NewGuid.ToString()

        entity.MsgSequence = 0

    End Sub

 

    Private Sub Appointments_Updating(ByVal entity As Appointment)

        'Update the sequence anytime the appointment is updated

        entity.MsgSequence += 1

    End Sub

End Class

 

好,现在我们来运行下并检测下它是否起作用。首先我用有效的邮件地址添加了一个employee和customer。我正在扮演职员,因此我添加了我自己的微软邮件地址。现在当我创建了一个新的预约,填充图片,点击保存,在我的收件箱我收到了一个预约!不错!

Description: Description: Description: image

Description: Description: Description: image

 

现在,通过改变时间、位置、主题或标注在LightSwitch中更新预约。点一下保存就会向参加会议的人发送一个更新。

 

Description: Description: Description: image

 

很好!这意味着任何时候我们在LightSwitch中改变预约数据,一个已经更新的预约就会通过邮件自动地发送出去。切记,尽管用户在LightSwitch之外对预约做了变更,但是这些变更不会被反映到数据库的。并且我也不允许用户在创建好之后又在预约上修改customer和employee,不然的话这个修改的更新就不会被发送到原始参加者那里。相反,当预约被删除后,取消信息就会发送出去。就是会议参加者需要更改的话就创建一个新的预约记录。

 

我认为我更喜欢通过COM这个方法,像我在以前的博客中显示的那样自动地使用。在发送之前你的确会失去能力来让用户和预约互动,但是这个代码更加擅长保持数据和会议请求同步协调,并使用于任意支持iCalendar格式的客户邮件。

 

用得开心!