Beth's Chinese blog
Carla Fair-Wright has a blog dedicated to the women, past and present, who have made contributions in science, technology, engineering, and math (STEM) fields. Carla is the Principal and Owner of Optimal Consulting LLC and has some interesting women featured on her blog so when she asked me for an interview I was excited to be featured. Check it out:
Thanks for featuring women in technology, Carla!
Enjoy!
A couple weeks ago my good friend and MVP, Robin Shahan (RobinDotNet), wrote up instructions on how to get a bootstrapper package for SQL Server Express 2008 R2 that you can use with ClickOnce deployments. Visual Studio 2010 comes with the bootstrapper for SQL Server Express 2008 but not R2. Robin’s post will show you how to create one:
Read it on her blog: How about a bootstrapper package for SQLServer Express 2008 R2?
Thanks Robin! Enjoy!
I’ll be presenting at the Code Project’s virtual tech summit on LightSwitch. This is a free, online “conference” with both Visual Studio developer and Agile tracks. Check out the agenda:
The Code Project Virtual Tech Summit Agenda
Here’s my session which will premiere at 3:15pm PST tomorrow and will be available on demand for 90 days.
Register here!
CoDe Magazine released it’s March/April issue a couple days ago and the LightSwitch team is featured! We’ve got some articles from the team that you should definitely check out. They are available online for free:
Inside Visual Studio LightSwitch by John Rivard and Steve Anonsen In this article John and Steve explain the architecture behind Visual Studio LightSwitch.
From Zero to Business Application in 15 Minutes by Beth Massi (that’s me! ;)) In this article I show you how to build a business application in minutes that manages photos on construction projects.
Getting the Most Out of the Save Pipeline in Visual Studio LightSwitch by Dan Seefeldt In this article Dan gives insight into how the save pipeline works and how you can add your own business logic to control how data is updated.
Ask the Doc Detective by Steve Hoag Steve shows us some cool LightSwitch tips & tricks and how to get around the LightSwitch MSDN Library documentation.
And keep on the watch for more great articles from the team in CoDe Magazine – we’ll be rolling them out thru summer!
One of the first things I always do after installing Visual Studio is to install SQL Server Management Studio (SSMS). Visual Studio 2010 installs SQL Server 2008 Express on your machine but doesn’t include SSMS. Although you can use Visual Studio to create/connect/design databases, I like having SSMS around for advanced management. I recall SSMS for SQL Server 2005 was a simple install, unfortunately they threw the kitchen sink into the SSMS 2008 installer and I’m always confused at which buttons I need to push to get it to do what I want. So I’m writing up this blog post for two reasons 1) So I remember the steps and 2) So you can be less frustrated :-) (BTW, a birdie tells me that the SQL team is looking at making this installer much simpler in the future. Hooray!)
Okay the first thing you need is to make sure you get the right version of SSMS. If you installed Visual Studio 2010 then you will need the 2008 version (not R2).
STEP 1: Download Microsoft® SQL Server® 2008 Management Studio Express and select either SQLManagementStudio_x64_ENU.exe or SQLManagementStudio_x86_ENU.exe depending on your machine’s OS bit-ness. I’m running Windows 7-64bit so I’ll be detailing the steps as it pertains to that OS.
STEP 2: Make sure you’re logged in as an administrator of the machine then right-click on the exe you downloaded and select “Run as Administrator”. If you’re on Windows 7 then you’ll get a compatibility warning. Click past it for now to continue with the install. Later you’ll need to apply SQL 2008 Service Pack 2.
STEP 3: You should now see the “SQL Server Installation Center” window. Yes it looks scary. Select the “Installation” tab.
STEP 4: Select “New SQL Server stand-alone installation or add features to an existing installation”. It will then run a rule check. Make sure there are no failures and then click OK.
STEP 5: The next step is misleading. The Setup Support Files window looks like it’s doing something and stuck on “Gathering user settings.” It’s actually waiting for you to click the Install button! Doh!
STEP 6: Another rule check. You’ll probably end up with a Windows Firewall warning this time. If you want to enable remote access to SQL Server you’ll need to configure the firewall later. Since I’m using SQL Server Express for development purposes on this machine only, I won’t need to worry about that. Click Next.
STEP 7: Okay here is the step I always mess up because it’s just not intuitive at all. On the Installation Type window you have a choice between “Perform a new installation of SQL Server 2008” OR “Add features to an existing instance of SQL Server 2008”. You need to select new installation, NOT add features. I know I know, totally weird. You would think that since you just want to add SSMS that it would be Add features to existing instance – I mean I don’t want a new instance, just the dang tools. Sigh. Click Next.
STEP 8: Next you get the Product Key window. You obviously don’t need a product key for SQL Server Express since it’s free so just click Next.
STEP 9: Accept the License Terms and click Next.
STEP 10: Okay now for the window we’ve all been waiting for - Feature Selection. Check off “Management Tools – Basic” and then click Next.
STEP 11: Verify your disk space requirements and click Next.
STEP 12: Help Microsoft improve SQL Server features and services by selecting to send usage and error reports to them (or not). Click Next.
STEP 13: Another quick rule check runs. Click Next.
STEP 14: Now it looks like we’re ready to install. Click the Install button.
The install will kick off and will take about 5 minutes to complete.
STEP 15: Once the installation completes, click the Next button again.
STEP 16: Complete! Click the Close button and you should be all set.
STEP 17: Fire up SQL Server Management Studio! You should now see it in your Programs under Microsoft SQL Server 2008. Make sure you run it as an administrator for access to all the features.
And don’t forget at some point to install the latest SQL Server 2008 Service Pack. I hope this helps people who have just installed Visual Studio 2010 but also want to install SQL Server Management Studio. I know I’ll be referring to my own post on this when I need to do it again :-)
Tonight I’m speaking in San Francisco on one of my favorite topics, OData. Here’s the info: Creating and Consuming OData Services for Business Applications. (By the way, the Open Data Protocol (OData) is a protocol for querying and updating data over the web. If you’re not familiar with OData I encourage you to check out www.odata.org and come to my talk tonight!)
Since I’ve done this talk a couple times I thought it would be a good idea to add some new demos. One that I thought would be fun would be using OData in a Windows Phone 7 application. It turns out that it’s actually pretty easy do once you have all the right tools and libraries. Here’s what you’ll need:
Windows Phone Developer Tools RTW – This includes Visual Studio 2010 Express but if you already have VS 2010 Pro or higher the Windows developer tools will just integrate into those versions. It also gives you Expression Blend, XNA Game Studio, and a nifty phone emulator and deployment tools.
Visual Basic for Windows Phone Developer Tools RTW – This allows you to develop Windows Phone 7 apps using Visual Basic. It’s a really light-weight install and includes the project templates you need to build phone apps with VB.
OData Client Library for Windows Phone 7 – You can grab just the binaries and client proxy generator in the ODataClient_BinariesAndCodeGenToolForWinPhone.zip. You’ll need to add a reference to the Windows Phone 7 OData client library in your phone projects to use OData.
First thing to do is fire up Visual Studio, File –> New Project and select Silverlight for Windows Phone –> Windows Phone Application:
This sets up the project files and opens up the designer with a design view on the left and the XAML view on the right. For this example let’s create an application that browses the public Netflix catalog here: http://odata.netflix.com/v1/Catalog/ (By the way there are a lot of OData producers and the list is growing. Check them all out here: http://www.odata.org/producers)
I’ve written a lot about OData in the past but I’ve always used clients that take advantage of the full .NET framework like Console apps, WPF apps and Excel add-ins. When you create these projects it’s easy to just add a service reference to the OData service and the client proxy and client assemblies are automatically added to your project. Unfortunately these steps are manual in a Windows Phone 7 project, but it’s not too bad. Here are the steps:
1. Extract the ODataClient_BinariesAndCodeGenToolForWinPhone.zip and unblock the files (Right-click –> Properties –> click the “Unblock” button on the General tab).
2. Use the DataSvcUtil.exe contained in here to generate the client proxy. This will generate the client-side classes based on the OData service. So for my example, I’ll open up a command prompt and generate my Netflix classes in the file “NetflixModel.vb” like so:
>datasvcutil /uri:http://odata.netflix.com/v1/Catalog/ /out:.\NetflixModel.vb /Version:2.0 /DataServiceCollection /language:VB
3. Copy the output file to your Windows Phone 7 Project. You can just copy the file from Windows explorer and paste it directly into the project in Visual Studio (I love that feature :-)).
4. In your Windows Phone 7 project, add a reference to the System.Data.Services.Client assembly also contained in the zip you extracted in step 1.
Okay now that we have all the pieces in place let’s add some basic UI and some code to call the Netflix OData service. What I’ll do is provide a simple Listbox that lists titles that fall into a genre that the user can type in a textbox. The Title class has a Name property and that’s what we’ll databind our listbox to. Here’s the XAML for my UI contained in the MainPage.xaml:
<!--ContentPanel - place additional content here--> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <ListBox Grid.ColumnSpan="2" ItemsSource="{Binding}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Name}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <TextBox Name="textBox1" Text="Adventures" Grid.Row="1" /> <Button Content="Go" Grid.Column="1" Grid.Row="1" Name="button1" /> </Grid>
Titles and Genres participate in a many-to-many relationship so we need to query the Genre based on its name and include the Titles for that Genre. When the user clicks the Go button that’s when I’m going to execute the query. A query against an OData service is just an HTTP GET so we need to construct the right URI to the service. When working with the OData client in full .NET framework, you have the the ability to write LINQ queries and the client library will translate them to http calls. Unfortunately this isn’t supported on the phone yet. (See this announcement on the team blog for details.)
Fortunately it’s pretty easy to construct the URIs. If you open up your favorite browser to http://odata.netflix.com/v1/Catalog/Genres that will execute a query that returns all the Genres in the Netflix catalog. In order to pull up a specific Genre called “Adventures” we just use http://odata.netflix.com/v1/Catalog/Genres('Adventures') and in order to get the Titles returned for this Genre we use the $expand syntax: http://odata.netflix.com/v1/Catalog/Genres('Adventures')?$expand=Titles (Take a look at the URI conventions for all the operations that are supported.)
Once you have the URI you can call the LoadAsync method to fetch the data. For my application, in order to get an ordered list of titles into the ListBox I execute an in-memory LINQ query over the results to manipulate them further. So here’s the code for the MainPage:
Imports System.Data.Services.Client Imports WindowsPhoneApplication1.NetflixCatalog.Model Partial Public Class MainPage Inherits PhoneApplicationPage Dim WithEvents ctx As New NetflixCatalog.Model.NetflixCatalog(New Uri("http://odata.netflix.com/v1/Catalog")) Dim WithEvents genres As New DataServiceCollection(Of Genre)(ctx) Dim titles As IEnumerable(Of Title) Public Sub New() InitializeComponent() End Sub Private Sub button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles button1.Click genres.Clear() Dim url = String.Format("/Genres('{0}')?$expand=Titles", Me.textBox1.Text) Dim uri = New Uri(url, UriKind.Relative) genres.LoadAsync(uri) End Sub Private Sub genres_LoadCompleted(sender As Object, e As LoadCompletedEventArgs) Handles genres.LoadCompleted If genres.Any Then titles = From g In genres From t In g.Titles Select t Distinct Order By t.Name Else titles = New List(Of Title) From {New Title With {.Name = "No titles found in that genre."}} End If Me.DataContext = titles End Sub End Class
Okay hit F5 and watch the phone emulator fire up. Type in a genre and hit the Go button to see the results loaded from the Netflix Catalog OData service. Sweet!
Here’s a couple tips for an easier time when building Windows Phone 7 apps that consume OData services.
Visualize your OData - If you don’t know the schema of the OData service you’re working with it may be kind of hard to visualize the relations between entities. I recommend installing the OData Protocol Visualizer extension and then just add a console application to your Solution in Visual Studio, add a service reference to the OData service to generate the proxy classes, and then right-click on the service reference and select “Show in Diagram”.
Using LINQ - The System.Data.Service.Client library for the full .NET framework has the ability to take your LINQ queries and translate them to http calls. If you’re more comfortable with LINQ (like me) you can use the same console application to see how the queries are translated. You can either view the http call by putting a debugger breakpoint on your LINQ queries or you can install Fiddler (which I highly recommend) to see all the http traffic.
So I hope this helps in getting you started with OData on Windows Phone 7. OData is fun and easy and it’s a great way to exchange data over the web in a standard way. Now I just need to brush up on my design “skillz” and see if I can make a prettier looking WP7 app ;-)
The Visual Studio BizApps team has joined forces with the awesome developer evangelists in the West region to bring you some expert SharePoint 2010 training in March that you can’t miss. Come learn how to effectively use Visual Studio 2010 to develop and manage solutions for SharePoint 2010 directly from the team that builds the SharePoint tools for Visual Studio! You’ll get an overview of all the tools in Visual Studio 2010 for SharePoint development as well tips & tricks on how to use them to their full potential. You’ll also see how to use Team System to manage the application lifecycle of SharePoint projects as well as some new features we’ve added in Visual Studio 2010 SP1. We’ve also got additional SharePoint experts in each location to deliver real-world SharePoint developer training.
Weather you’re a seasoned SharePoint veteran or totally new to SharePoint development, this event is for you. Check out the cities where we’ll be on tour:
Tempe, AZ - 03/15/2011 REGISTER >>
Denver, CO - 03/17/2011 REGISTER >>
Mountain View, CA - 03/29/2011 REGISTER >>
Irvine, CA - 03/31/2011 REGISTER >>
Register today! Enjoy!
Last article I wrote about how you could automate Outlook to send appointments from a button on a screen in a LightSwitch application. If you missed it:
How To Create Outlook Appointments from a LightSwitch Application
That solution automates Outlook to create an appointment from entity data on a LightSwitch screen and allows the user to interact with the appointment. In this post I want to show you how you can automate the sending of appointments using the iCalendar standard format which many email clients can read, including Outlook. I’ll also show you how you can send updates to these appointments when appointment data in the LightSwitch application changes. We will use SMTP to create and send a meeting request as a business rule. This is similar to the first HTML email example I showed a couple weeks ago. Automated emails are sent from the server (middle-tier) when data is being inserted or updated against the data source. Let’s see how we can build this feature.
Because we want to also send updated and cancelled meeting requests when appointment data is updated or deleted in the system, we need to add a couple additional properties to the Appointment entity to keep track of the messages we’re sending out. First we need a unique message ID which can be a GUID stored as a string. We also need to keep track of the sequence of any updates that are made to the appointment so that email clients can correlate them. We can simply increment a sequence number anytime we send out an updated appointment email. So here’s the schema of the Appointment entity (click to enlarge).
Notice that I also have relations to Customer and Employee in this example. We will be sending meeting requests for these two parties and we’ll make the Employee the organizer of the meeting and the Customer the attendee. In this entity I also am not showing the MsgID and MsgSequence properties on the screen. These will be used on code only. Now that we have our Appointment entity defined let’s add some business rules to set the values of these properties automatically. Drop down the “Write Code” button on the top-right of the Entity Designer and select Appointments_Inserting and Appointments_Updating. Write the following code to set these properties on the server-side before they are sent to the data store:
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
I also want to add a business rule on the StartTime and EndTime properties so that the start time is always before the end time. Select the StartTime property on the Entity and now when you drop down the “Write Code” button you will see StartTime_Validate at the top. Select that and write the following code:
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
Finally make sure you create a New Data Screen for this Appointment entity.
Now that we have our Appointment entity and New Data Screen to enter them we need to build a helper class that we can access on the server to send the automated appointment email. Just like before, we add the helper class to the Server project. Switch to File View on the Solution Explorer and add a class to the Server project:
I named the helper class SMTPMailHelper. The basic code to send an email is simple. You just need to specify the SMTP server, user id, password and port by modifying the constants at the top of the class. TIP: If you only know the user ID and password then you can try using Outlook 2010 to get the rest of the info for you automatically.
The trick to creating the meeting request is to create an iCalendar formatted attachment and add it as a text/calendar content type. And in fact, this code would work the same in any .NET application, there’s nothing specific to LightSwitch in here. I’m setting the basic properties of the meeting request but there are a lot of additional properties you can use depending on what kind of behavior you want. Take a look at the spec for more info (the iCalendar is an open spec and it’s here. There’s an abridged version that is a little easier to navigate here.)
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 FunctionEnd Class
Now that we have our helper class in the server project we can call it from the server-side business rules. Again, drop down the “Write Code” button on the top-right of the Entity Designer and now add Appointments_Inserted, Appointments_Updated and Appointments_Deleting methods to the ApplicationDataService. Call the SendAppointment method passing the Appointment entity properties. In the case of Appointment_Deleting then also pass the isCancelled flag in as True. So now the ApplicationDataService should look like this:
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
Okay let’s run this and check if it works. First I added an employee and a customer with valid email addresses. I’m playing the employee so I added my Microsoft email address. Now when I create a new Appointment, fill out the screen, and click Save, I get an appointment in my inbox. Nice!
Now update the appointment in LightSwitch by changing the time, location, subject or notes. Hit save and this will send an update to the meeting participants.
Nice! This means that anytime we change the Appointment data in LightSwitch, an updated appointment will be sent via email automatically. Keep in mind though, that if users make changes to the appointment outside of LightSwitch then those changes will not be reflected in the database. Also I’m not allowing users to change the customer and employee on the Appointment after it’s created otherwise updates after that would not be sent to the original attendees. Instead when the Appointment is deleted then a cancellation goes out. So the idea is to create a new Appointment record if the meeting participants need to change.
I think I prefer this method to automating Outlook via COM like I showed in the previous post. You do lose the ability to let the user interact with the appointment before it is sent, but this code is much better at keeping the data and the meeting requests in sync and works with any email client that supports the iCalendar format.
Check it out, I’m on dnrTV talking about LightSwitch:
Beth Massi on Visual Studio LightSwitch Beta 1
In this episode I show my version of the Vision Clinic Walkthrough and build an application from scratch that federates multiple databases and SharePoint data. I also show how to work with entity business rules, screen code, and walk through a variety of other cool features of LightSwitch Beta 1.
It’s always fun to do these shows with Carl and sometimes they don’t always go as smoothly as you’d like. In this one, my phone battery dies about 40 minutes into the show so I have to call back on a speakerphone. So you may notice my voice sounding funky at the end. It’s still me I promise ;-)
Last post I showed how to create email in a couple different ways, one of them being from the LightSwitch UI on the client side that used COM to automate Outlook to create an email. If you missed it here it is:
How To Send HTML Email from a LightSwitch Application
This generated some questions about how to automate Outlook to do other things like add appointments to someone’s calendar. So because of this interest, I’ve decided to continue my series on Office automation from LightSwitch :-). This time I’ll show how to create a client-side helper class that creates and sends appointments through Outlook.
We need a helper class on the client so we can call if from a button on our screen. You do this by selecting the “File View” on the Solution Explorer, right-click on the Client project and select Add –> New Class. I named the class “OutlookAppointmentHelper” for this example.
This helper class uses COM automation, a feature of Silverlight 4 and higher. So first we need to check if we’re running out-of-browser on a Windows machine by checking the AutomationFactory.IsAvailable property. Next we need to get a reference to Outlook, opening the application if it’s not already open. The rest of the code just creates the appointment and sends it. In the code below, you could also comment out the call to Send and instead call Display to allow the user to modify the appointment first. NOTE: The key piece of code that enables the sending of the appointment to the toAddress is to set the MeetingStatus property to 1 (olMeeting), otherwise the appointment will just stay on the user’s calendar and wont be sent to the recipient.
Imports System.Runtime.InteropServices.Automation Public Class OutlookAppointmentHelper Const olAppointmentItem As Integer = 1 Const olMeeting As Integer = 1 Shared Function CreateOutlookAppointment(ByVal toAddress As String, ByVal subject As String, ByVal body As String, ByVal location As String, ByVal startDateTime As Date, ByVal endDateTime As Date) As Boolean Dim result = False Try Dim outlook As Object = Nothing If AutomationFactory.IsAvailable Then Try 'Get the reference to the open Outlook App outlook = AutomationFactory.GetObject("Outlook.Application") Catch ex As Exception 'If Outlook isn't open, then an error will be thrown. ' Try to open the application outlook = AutomationFactory.CreateObject("Outlook.Application") End Try If outlook IsNot Nothing Then 'Create the Appointment ' Outlook object model (OM) reference: ' http://msdn.microsoft.com/en-us/library/ff870566.aspx ' Appointment Item members: ' http://msdn.microsoft.com/en-us/library/ff869026.aspx Dim appt = outlook.CreateItem(olAppointmentItem) With appt .Body = body .Subject = subject .Start = startDateTime .End = endDateTime .Location = location .MeetingStatus = olMeeting .Recipients.Add(toAddress) .Save() '.Display() .Send() result = True End With End If End If Catch ex As Exception Throw New InvalidOperationException("Failed to create Appointment.", ex) End Try Return result End Function End Class
Also note that if you want to add multiple recipients to the appointment you could pass in an array or List(Of String) and loop through that calling .Recipients.Add for each one:
Shared Function CreateOutlookAppointment(ByVal toAddress As List(Of String), ...rest of code ...Dim appt = outlook.CreateItem(olAppointmentItem) With appt .Body = body .Subject = subject .Start = startDateTime .End = endDateTime .Location = location .MeetingStatus = olMeeting For Each addr In toAddress .Recipients.Add(addr) Next .Save() '.Display() .Send() End With
You can also add required and optional attendees by setting appropriate properties. Take a look at the object model for the Appointment item for more details.
Here’s how you add a command button to a screen. In the Execute method for the command button is where we add the code to create the appointment. I also want to have the button disabled if AutomationFactory.IsAvailable is False and you check that in the CanExecute method. Here I’m working with an Appointment entity in my data model but the data to feed the appointment can come from anywhere. So here’s the code in my screen:
Private Sub CreateOutlookAppt_CanExecute(ByRef result As Boolean) result = System.Runtime.InteropServices.Automation.AutomationFactory.IsAvailable End Sub Private Sub CreateOutlookAppt_Execute() ' Schedule the appointment via Outlook With Me.Appointment If .Customer.Email <> "" Then If OutlookAppointmentHelper.CreateOutlookAppointment(.Customer.Email, .Subject, .Notes, .Location, .StartTime, .EndTime) Then Me.ShowMessageBox("Appointment has been sent.") End If Else Me.ShowMessageBox("This customer does not have an email address", "Missing Email Address", MessageBoxOption.Ok) End If End With End Sub
Run this and you will see that when the user clicks the button the appointment is sent and it appears on the user’s calendar.