如何发送应用程序中的自动操作的预约
[原文发表地址] 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。我们也需要追踪预约中更新的次序,以使邮件用户可以把它们关联在一起。任何时候发出去一个已更新的预约邮件,我们可以简单地增加一个数列数量。这是预约实体的模式(点击放大)。
值得注意的是在本例中我还为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项目中添加一个类:
我给帮助类起名为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。我正在扮演职员,因此我添加了我自己的微软邮件地址。现在当我创建了一个新的预约,填充图片,点击保存,在我的收件箱我收到了一个预约!不错!
现在,通过改变时间、位置、主题或标注在LightSwitch中更新预约。点一下保存就会向参加会议的人发送一个更新。
很好!这意味着任何时候我们在LightSwitch中改变预约数据,一个已经更新的预约就会通过邮件自动地发送出去。切记,尽管用户在LightSwitch之外对预约做了变更,但是这些变更不会被反映到数据库的。并且我也不允许用户在创建好之后又在预约上修改customer和employee,不然的话这个修改的更新就不会被发送到原始参加者那里。相反,当预约被删除后,取消信息就会发送出去。就是会议参加者需要更改的话就创建一个新的预约记录。
我认为我更喜欢通过COM这个方法,像我在以前的博客中显示的那样自动地使用。在发送之前你的确会失去能力来让用户和预约互动,但是这个代码更加擅长保持数据和会议请求同步协调,并使用于任意支持iCalendar格式的客户邮件。
用得开心!