如何使用Excel导入扩展来导入数据

[原文发表地址]How Do I: Export Data to a Word Mailing Labels Document – (Matt Sampson)

[原文发表时间]26 Apr 2011 9:04 AM

Matt Sampson今天在他的博客中发表了一篇文章,讲述了如何将保存的数据导出到Word 文档邮寄标签中,以及如何显示“文件另存为”对话框,从而允许用户选择文件保存的位置。

代码示例也发布在这里的代码库中。

请从查看博客文章。

如何使用Excel导入扩展来导入数据

[原文发表地址]How to Import Data from Excel Using the Excel Import Extension

[原文发表时间]13 Apr 2011 8:14 AM

我们客户的一个最普遍的需求就是向应用程序导入数据的能力。然而这个可以通过编程来实现,在Excel文档中保存数据是相当普通的。本教程将逐步解说使用一个我们团队创建的Excel导入扩展。

下载 Excel Import Extension 示样

功能

Excel导入扩展将允许你添加“Import from Excel”功能到任意已有的LightSwitch界面。LightSwitch应用程序的最终用户就可以在需要的时候导入他们的商用数据。这个扩展将允许用户把Excel文档中的列映射倒LightSwitch表的字段中。然后它将使数据生效并在应用程序中创建记录。

准备安装

首先我们要创建两个将要导入数据的Excel文件。创建一个标题为Categories的Excel文件,它包含下面的信息。确保文件保存在了Documents文件夹下。在文件系统上Silverlight具有关于他能访问的什么内容的局限。

Name Description
Beverages Soda and juice
Condiments Sauces, relishes, spreads, and seasonings
Dairy Products Prepared and fresh meats
Grains and Cereals Breads, cereals, crackers and pasta
Produce Dried and fresh fruit and vegetables
Seafood Seaweed and fish

创建另一个标题为Products的Excel文档,它包含下面的信息。同样要确保文档被保存在了Documents目录下。

Name Quantity Category
Sasquatch Ale 10 Beverages
Aniseed Syrup 15 Condiments
Steeleye Stout 8 Beverages
Spiced Okra 24 Produce
Carpet Clean Plus 6 Cleaners
Scottish Longbreads None Grains and Cereals
Boston Crab Meat 9 Seafood

安装Extension

把本文中的扩展下载到已经安装了LightSwitch的机器上。打开压缩文件,打开Binaries目录,双击LightSwitchUtilities.vsix文件。这样就为LightSwitch安装了扩展。你需要重新启动已经打开的Visual Studio实例。

clip_image001

创建一个新的LightSwitch应用程序。双击应用程序资源管理器中的属性节点。在应用程序属性页面,选Extensions选项卡。使得LightSwitch程序扩展对这个应用程序可以使用。如果愿意,你可以把它设置对所有功能应用程序都默认设为可用。

clip_image002

定义应用程序的数据

添加一个带有下列字段的Category表:

· Name (String, Required)

· Description (String, Not Required)

clip_image003

现在点击命令行的“+ Relationship”按钮,打开“添加新关联”对话框。我们将设置ProductCategory之间的关联。在我们的例子中,每个Category有许多Products,而每个Product必须有一个Category.

clip_image004

创建屏幕

设置了数据之后,我们就来添加屏幕,编辑CategoriesProducts。给Categories添加新的Editable Grid界面。

clip_image005

在这个界面上,添加一个按钮来从Excel上导入数据。右击屏幕命令栏节点,选择“添加按钮…”。把方法命名为ImportFromExcel,点击Ok。

clip_image006

clip_image007

Import From Excel按钮上双击,添加下面的代码。

Private Sub ImportFromExcel_Execute()

‘ Write your code here.

LightSwitchUtilities.Client.ImportFromExcel(Me.Categories)

End Sub

类似地,添加一个视图编辑Products。为Products添加一个新的可编辑的网格界面Editable Grid界面。

clip_image008

给这个视图添加一个相似的ImportFromExcel按钮,包括下面的代码。

Private Sub ImportFromExcel_Execute()

‘ Write your code here.

LightSwitchUtilities.Client.ImportFromExcel(Me.Products)

End Sub

测试应用程序

创建并运行应用程序。“Editable Categories Grid”界面应该显示出来了。点击“Import From Excel”按钮。这就会打开一个对话框,允许你选择你想导入的源文件。选择Categories excel文档,点击OK。

image

现在,扩展应该打开一个选择映射文件对话框。这将允许你把Excel文档中的列映射倒LightSwitch表的字段中。对话框试着把Excel列的名字和域相匹配。点击“继续”。

clip_image010

 

 

现在扩展将把Excel文档中每一行记录添加到LightSwitch屏幕的表格中。如果必要的话,你可以在保存数据之前先编辑它们。保存并关掉这个界面

clip_image011

现在打开“Editable Products Grid”屏幕。点击“Import From Excel”按钮,选择Products excel文档。

clip_image012

映射文件对话框会再次将Excel列名和LightSwitch字段名匹配起来。在本例中,选择Category字段,尽管它代表一个关联。当从这个文档中导入数据时,扩展将会尝试查看Category表中的匹配条目。点击“继续”。

clip_image013

扩展将会在导入数据之前对所有数据进行效验。并把效验后的错误信息返回给用户。在本例中,一个被称为“Cleaners”的category被用来表示某种产品。这个category将不会被发现。此外,“None”用来表示一个产品的数量。这不能用整型数据类型来表示。点击“继续”,改正LightSwitch屏幕中的所有数据错误

clip_image014

在LightSwitch屏幕中修改名为Carpet Clean Plus产品的Category,使之能够被保存。如果需要的话你也可以修改Scottish Longbreads的数量。完成修复数据之后,保存界面。

clip_image015

附件包括扩展的源代码和可以安装的VSIX包。要创建这个源代码,需要安装Visual Studio Professional和Visual Studio SDK。我们并不能保证这个扩展的质量(例如当用在Web应用程序时它将不会奏效)。

要开始创建你自己的扩展,请查阅Extensibility Cookbook

 

更多中文博客列表

嗨,我们最近开通了六个新的中文博客。去看看吧。 🙂

 

如何创建复合标量的查询(Ravi Eda)

[原文发表地址]How to Create Composed and Scalar Queries (Ravi Eda)

[原文发表时间]18 Apr 2011 8:00 AM

这篇文章呈现了一个在销售团队的场景,它展示了使用复合标量查询的益处。它展示了如何在LightSwitch中创建复合标量查询和创建屏幕展示查询结果的详细步骤。

复合查询是基于另一个查询结果的操作。标量查询是返回一个且仅一行的查询。

商用事例

假设为一个中型机构的销售部门设计一个商业应用程序。这个应用程序应该配有销售团队来按照各种不同的参数划分订单。参数包括例如某个特定地理区域上的顾客,一个特定日期范围的订单,以高价售出且有大的订购额的产品。销售团队也想掌握在单个订单上花费最多的顾客的信息。

针对销售团队的LightSwitch应用程序

我们现在创建一个能帮助销售团队的LightSwitch应用程序。要逐步完成例子你除了有LightSwitch.还要一个Northwind数据库。

创建一个工程,连接到 Northwind 数据库并导入Order Details, Orders, Products 和Customers表。我们想创建一个可以显示住在某一个国家的用户的所有订单。为了实现这个,在Order_Details表下添加一个查询,命名为“OrderDetailsForCustomersInCountry”。

在查询设计面板上点击“+ Add Filter”.。对于过滤器左边的操作域选择“Order.Customer.Country”,运算符是“=”(即等于),然后选择值数类型为“@ Parameter”。对于右边的操作域的值,点击“Add New”。这就会创建一个名为"Country”的参数。把这个参数设为可选。为实现这一步,打开参数的属性页窗口,选择“Is Optional”。查询设计器应该就类似于图1。

clip_image002

图1:带有“OrderDetailsForCustomersInCountry” 查询的查询设计器。

参数“Country”的特点是可选的,因为该值设置为默认值是基于一定的逻辑基础。例如,一个美国销售团队成员会对美国顾客的订单更感兴趣。如果参数不是可选项,那么团队人员每次执行过滤器时都不得不输入参数值。PreprocessQuery()方法使得我们可以编写代码为“Country”设置默认值。

在属性窗口中单击“Edit Additional Query Code”链接,并在PreprocessQuery()方法下添加以下的代码行。这个代码首先核查是否传递参数,如果没的话,滤出美国顾客的查询结果。

Visual Basic

Private Sub OrderDetailsForCustomersInCountry_PreprocessQuery

(Country As String, ByRef query As

System.Linq.IQueryable(Of LightSwitchApplication.Order_Detail))

If String.IsNullOrEmpty(Country) Then

query = From detail In query

Where detail.Order.Customer.Country = "USA"

End If

End Sub

C#:

partial void OrderDetailsForCustomersInCountry_PreprocessQuery

(string Country,

ref IQueryable<LightSwitchApplication.Order_Detail> query)

{

if (String.IsNullOrEmpty(Country))

{

query = from details in query

where details.Order.Customer.Country == "USA"

select details;

}

}

搜索界面

让我们来创建一个显示“OrderDetailsForCustomersInCountry”.的结果的屏幕。在查询设计器中打开“OrderDetailsForCustomersInCountry”,点击设计器的命令行上的“Add Screen”。选择“Search Data Screen”模板。输入屏幕名“CustomersByCountry”。选择“OrderDetailsForCustomersInCountry”作为屏幕数据。参考图2。

clip_image002[5]

图2:为CustomerByCountry搜索屏幕添加新的屏幕对话框

在屏幕设计器上选择“Order_DetailCountry”,并且打开它的属性页。输入“Filter by Country”作为显示名。 选择“Is Parameter”。取消选择“Is Required”..查看图3。

clip_image004

图3:参数化的搜索界面

运行(按F5键)应用程序。运行时应该是如图4中那样显示。注意“Filter by Country:”是空白的并且返回的页码数是8。逻辑上PreprocessQuery()方法筛选并返回的仅仅是美国顾客的订单。没有筛选的话返回的页码会是比8还大的数。

clip_image006

图4:美国顾客的订单详情

假设销售团队人员想查看一些其他国家的顾客订单,比如是奥地利的。在那种情况下,他们在 “Filter by Country:”区域输入“Austria”,敲入Enter或Tab键。筛选的结果将如下图5所示,注意返回的页码是3。

clip_image008

图5:奥地利顾客的订单详情

复合查询

使用一个查询结果作为另一个查询的数据源有一些好处。复合查询重用建立在源查询中PreprocessQuery()方法上的设计和代码编写的逻辑。另外,它对于维修很有帮助。更新单个查询比更改多个查询更容易。

“OrderDetailsForCustomersInCountry”的数据源是订单明细表,在LightSwitch中,选择一个查询作为另一个查询的数据源是可行的。复合查询在另一个源查询的结果上进行。

让我们来创建一个使用“OrderDetailsForCustomersInCountry”结果的复合查询。首先在Order_Details表下增加一个新的查询,命为“OrderDetailsForDateRange”.在查询设计器命令行把查询的数据源由Order_Details更改为“OrderDetailsForCustomersInCountry”。参考图6。

clip_image002[7]

图6:为“OrderDetailsForDateRange”查询选择数据源。

请注意,来自源查询的参数,如在这个实例中的Country,在复合查询中仍然是可用的。这些继承的参数在设计器界面是不可编辑的。但是这些参数在PreprocessQuery()方法及设计器界面,尤其是作为过滤器的右操作数仍然是可用的。

“OrderDetailsForDateRange”按照订单日期过滤出某一特定日期范围内的订单。起始和结束日期会由用户提供。在查询设计器界面,点击“+ Add Filter”。选择“Order.OrderDate”作为过滤器的左操作数。操作符选择“is between”。操作符“is between” 有两个右操作数。添加“StartDate”参数作为第一个右操作数,参数“EndDate”作为第二个右操作数。现在查询设计器看起来应该像图7中显示的那样。

clip_image004[4]

图7:带有查询“OrderDetailsForDateRange”的查询设计器

复合查询的层次结构

本节说明一个查询如何成为多个查询的一个源。它也说明了一个复合查询可以成为其他一个或多个查询的源。

销售团队需要追踪以高价售出的产品。销售团队的每个成员只对她/他自己负责的产品感兴趣。高价产品是那些单价高于$25的产品。此外,产品应该在市场上流通,即“Discontinued”字段设置为false。

Order_Details下添加一个新的查询。命名为“OrderDetailsForHighPriceProducts”。把查询的源设置为“OrderDetailsForCustomersInCountry”。在查询设计器面板上添加两个过滤器,如图8显示的那样。

clip_image006[4]

图8:带有“OrderDetailsForHighPriceProducts”查询的查询设计器

多层次结构

现在让我们在“OrderDetailsForHighPriceProducts”上创建另一层次的结构。在这些高价产品中,我们想过滤掉大量的采购。销售团队定义一个价钱为$2000及高于它的作为一个大量采购。

Order_Details下添加一个新的查询。命名为“OrderDetailsForLargePurchase”.。把查询源改为“OrderDetailsForHighPriceProducts”。设计器界面没有提供添加决定价格的逻辑支持,这是由于它是一个多域计算。因此,在PreprocessQuery()方法中添加下面的代码。

Visual Basic:

Private Sub OrderDetailsForLargePurchase_PreprocessQuery

(ByRef query As System.Linq.IQueryable

(Of LightSwitchApplication.Order_Detail))

query = From detail In query

Where (detail.UnitPrice * detail.Quantity) > 2000

End Sub

C#:

partial void OrderDetailsForLargePurchase_PreprocessQuery(ref IQueryable<Order_Detail> query)

{

query = from details in query

where (details.UnitPrice * details.Quantity) > 2000

select details;

}

“OrderDetailsForLargePurchase”是“OrderDetailsForHighPriceProducts”,的一个子部分,“OrderDetailsForHighPriceProducts”是“OrderDetailsForCustomersInCountry”的一个子部分。这表明了查询的多层次结构。

“OrderDetailsForCustomersInCountry”是“OrderDetailsForHighPriceProducts”和“OrderDetailsForDateRange”的数据源。这说明复合查询可以使用一个查询作为数据源。

你应该注意到,在查询设计器的命令栏下拉列表中只列出行有效的数据源。一个有效的数据源是父实体或在相同实体下的其他任意的查询,它不会产生一个循环引用。当查询相互变成对方的了数据源时就产生了循环引用。如果查询A是查询B的源,那么查询B就不能是查询A的源。在销售团队的查询中,“OrderDetailsForLargePurchase”不能是OrderDetailsForHighPriceProducts的源。但是,两个查询都可以使用Order_Details“OrderDetailsForCustomersInCountry”或“OrderDetailsForDateRange”作为它们的源。

标量查询

销售团队想知道他们最有价值的顾客,即那些在一个订单上消费最多的顾客。要找到这一个且只有一个客户,我们可以创建一个查询。

Order_Details下添加一个查询。把它命名为“BiggestPocketQuery”。打开查询属性窗口。将属性“Number of Results Returned:”由Many更改为One。注意“Add Sort”按钮变成了不可用的。由于此查询返回的结果数是一个单一的结果,因此对此排序是不适用的。这个查询看起来应该如图9中显示的那样。

clip_image008[4]

图9:将“BiggestPocketQuery”查询设置为标量

请注意一个标量查询不能作为另一个查询或屏幕的数据源。

PreprocessQuery()方法中添加下面的代码计算每个订单的采购价格,并返回价格最高的。

Visual Basic:

Private Sub BiggestPocketQuery_PreprocessQuery

(ByRef query As System.Linq.IQueryable

(Of LightSwitchApplication.Order_Detail))

query = From detail In query

Order By (detail.UnitPrice * detail.Quantity) Descending

Take (1)

End Sub

C#:

partial void BiggestPocketQuery_PreprocessQuery(ref IQueryable<Order_Detail> query)

{

query = query = query.OrderByDescending(od => od.UnitPrice * od.Quantity).Take(1);

}

标量查询屏幕

这部分将显示如何创建一个能展示标量查询结果的屏幕。

在设计器中打开一个非标量查询。在查询设计器命令栏,点击“Add Screen”.。在“Add New Screen”对话框中选择“Search Data Screen”模板。在界面名字中输入“MostValuableCustomer”。将屏幕数据设为“(None)”。点击OK。参考图10。

clip_image010

图10:对最有价值的客户添加新的屏幕对话框

在屏幕设计器中,点击命令栏的“Add Data Item…”按钮。见图11。

clip_image012

图 11:在“Add Data Item”对话框中,选择“NorthwindData.BiggestPocketQuery”。点击OK。查看图12。

clip_image013

图12:在屏幕上添加标量查询

在屏幕设计器上,把查询拖动到界面布局部分,如图13所示。

image

图13:向屏幕布局添加标量查询

运行(按F5键)应用程序,打开“Most Valuable Customer”屏幕。这看起来会和图 14中显示的一样。

clip_image016

图 14: 最有价值客户屏幕

总结

LightSwitch中的查询设计器使开发人员可以很简单地创建复合标量查询。PreprocessQuery()方法使开发人员能够写出额外的逻辑操作来筛选结果。屏幕设计器允许你创建能展示查询结果的屏幕。祝您在LightSwitch中创建自己的查询时用得愉快!

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

[原文发表地址]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。我们也需要追踪预约中更新的次序,以使邮件用户可以把它们关联在一起。任何时候发出去一个已更新的预约邮件,我们可以简单地增加一个数列数量 。这是预约实体的模式(点击放大)。

clip_image001

值得注意的是在本例中我还为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项目中添加一个类:

clip_image002

我给帮助类起名为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: http://tools.ietf.org/html/rfc2445
        'Abridged version here: http://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_InsertedAppointments_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。我正在扮演职员,因此我添加了我自己的微软邮件地址。现在当我创建了一个新的预约,填充图片,点击保存,在我的收件箱我收到了一个预约!不错!

clip_image003

clip_image004

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

clip_image005

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

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

用得开心!