Adventures in Developer Land

Making this stuff less hard one demo at a time

Dealing with Related Data in Silverlight When the Data Comes From a Service

clock August 20, 2010 00:42 by author Robert Green

My wife and I like good wine. Washington has about 600 wineries, all somewhere between 10 minutes and 4 and 1/2 hours driving distance from our house. We own somewhere north of 100 bottles of wine, most of them purchased at wineries in Washington. Fifty or so of the bottles are in a wine fridge. Sixty or so are in a wine cabinet. A handful of them are in a wooden wine rack in the kitchen. Some are in the garage.

This is by no means a big or expensive collection of wine, but it is more than we keep track of in our heads. Clearly, we need some kind of database application to manage our wines. We need to add wines after we buy them and delete wines after we drink them. We need to be able to see what wines we own and where each bottle is located.

I am writing a Silverlight application to keep track of our wine. The data is in a SQL Server database. I have two primary tables: Wines and Wineries. I have two lookup tables: Varietals and Appellations. The varietal is the type of wine (Cabernet Sauvignon, Merlot, Chardonnay, etc) and the appellation is the region where the grapes are grown.

The application gets its data from a WCF service. The service models the data using the Entity Framework. Each method of the service will use LINQ to Entities to retrieve or modify data.

Display all wines in a DataGrid

Here is what I want for the page that displays all wines. (I am focusing on working with data at this point and have not spent much time on the UI. That will come later.)

Figure1

The DataGrid will include all wines. That way, we can easily scroll through our wines and see what we have. Wines are grouped by location. The top group is Cabinet, Fridge, Kitchen, Garage, Other. The second grouping level groups wine by shelf if they are in the cabinet or the wine fridge.

My first thought was to just return a list of wines from the WCF service. So I created this method:

    <OperationContract()>

    Public Function GetWines() As List(Of Wine)

      _winesEntities = New WinesEntities

      Dim winesQuery =

        From w In _winesEntities.Wines

      Return winesQuery.ToList

    End Function

In the page Load, I call the WCF service, asynchronously of course.

    AddHandler winesService.GetWinesCompleted, AddressOf WinesWCFService_GetWinesCompleted

    winesService.GetWinesAsync()

The WinesWCFService_GetWinesCompleted method runs when the service returns. This code sets up a view, binds the view to the list of wines returned by the service, sorts the wines by location and winery, and then groups the wines by location.

    Private Sub WinesWCFService_GetWinesCompleted(ByVal sender As Object, ByVal e As WinesWCFService.GetWinesCompletedEventArgs)

      winesViewSource = CType(Me.Resources("winesViewSource"), CollectionViewSource)

      winesViewSource.Source = e.Result

      winesView = CType(winesViewSource.View, ICollectionView)

      winesView.SortDescriptions.Add(New SortDescription("Location1", ListSortDirection.Ascending))

      winesView.SortDescriptions.Add(New SortDescription("Location2", ListSortDirection.Ascending))

      winesView.SortDescriptions.Add(New SortDescription("WineryName", ListSortDirection.Ascending))

      winesView.GroupDescriptions.Add(New PropertyGroupDescription("Location1"))

      winesView.GroupDescriptions.Add(New PropertyGroupDescription("Location2"))

    End Sub

When I ran the application, this is what I got.

Figure2

Well, of course! The Wine entity contains only the ids for the winery, varietal and appellation. The actual names are in related entities. So I modified the GetWines method to return the missing data.

    Public Function GetWines() As List(Of Wine)

      _winesEntities = New WinesEntities

      Dim winesQuery =

        From w In _winesEntities.Wines

        Select w.WineId, w.Winery.WineryName, w.Vintage, w.Designation,

               w.Varietal.VarietalName, w.Appellation.AppellationName,

               w.Price, w.Location1, w.Location2, w.Acquired, w.Notes

      Return winesQuery.ToList

    End Function

This won’t compile because a value of type 'System.Collections.Generic.List(Of <anonymous type>)' cannot be converted to 'System.Collections.Generic.List(Of WineManager.Web.Wine)'.

When the query was Dim winesQuery = From w In _winesEntities.Wines, the field winesQuery was IQueryable(Of Wine). The ToList method of that returns a generic List(Of Wine).

However, when you perform a projection, you get an anonymous class. There is no class in my entity data model that includes the names of the winery, varietal and appellation. Therefore, winesQuery is IQueryable(Of <anonymous type>) and you can’t call ToList to convert it to a generic List (Of Wine).

I needed some way of getting the missing data to the client, so I created a WineSummary class, attributed it as a DataContract, and modified the GetWines method to return a generic List(Of WinerSummary).

    Public Function GetWines() As List(Of WineSummary)

      _winesEntities = New WinesEntities

      Dim winesQuery =

        From w In _winesEntities.Wines

        Select w.WineId, w.Winery.WineryName, w.Vintage, w.Designation,

               w.Varietal.VarietalName, w.Appellation.AppellationName,

               w.Price, w.Location1, w.Location2, w.Acquired, w.Notes

      Dim wineSummaries = New List(Of WineSummary)

      For Each _wine In winesQuery

        Dim _wineSummary = New WineSummary

        _wineSummary.WineId = _wine.WineId

        _wineSummary.WineryName = _wine.WineryName

        _wineSummary.Vintage = _wine.Vintage

        _wineSummary.Designation = _wine.Designation

        _wineSummary.VarietalName = _wine.VarietalName

        _wineSummary.AppellationName = _wine.AppellationName

        _wineSummary.Price = _wine.Price

        _wineSummary.Location1 = _wine.Location1

        _wineSummary.Location2 = _wine.Location2

        _wineSummary.Acquired = _wine.Acquired

        _wineSummary.Notes = _wine.Notes

        wineSummaries.Add(_wineSummary)

      Next

      Return wineSummaries

    End Function

When I ran the application, all of the wine information appeared. Hooray!

Delete a wine

If we drink a wine, I want to delete it from the database. The WCF service’s DeleteWine method takes a wine’s id and then deletes it. It returns true if the wine was deleted and false if it wasn’t.

    Public Function DeleteWine(ByVal wineId As Integer) As Boolean

      _winesEntities = New WinesEntities

      Try

        Dim _wine =

          _winesEntities.Wines.First(

          Function(w) w.WineId = wineId)

        _winesEntities.DeleteObject(_wine)

        If _winesEntities.SaveChanges() > 0 Then

          Return True

        Else

          Return False

        End If

      Catch ex As Exception

        Throw New DataException("There is no wine with that Id")

      End Try

    End Function

When the user clicks the Delete button, the following code executes:

    If MessageBox.Show("Do you want to delete this wine?", "Wines",MessageBoxButton.OKCancel) = MessageBoxResult.OK Then

      winesService = New WinesServiceClient

      AddHandler winesService.DeleteWineCompleted, AddressOf WinesWCFService_DeleteWineCompleted

      winesService.DeleteWineAsync(CType(winesDataGrid.SelectedItem, WineSummary).WineId)

    End If

The DataGrid is populated with WineSummary objects. To find the id of the selected wine, I get the grid’s SelectedItem, which is an object in code, and cast it to WineSummary. I then get the WineId and pass it to DeleteWine.

Update a wine’s information

To save a wine’s information, the client calls the WCF service’s UpdateWine method.

    Public Function UpdateWine(ByVal _wine As Wine) As Boolean

      _winesEntities = New WinesEntities

      Dim originalWine =

        _winesEntities.Wines.First(

        Function(w) w.WineId = _wine.WineId)

      originalWine.Acquired = _wine.Acquired

      originalWine.AppellationId = _wine.AppellationId

      originalWine.Designation = _wine.Designation

      originalWine.Location1 = _wine.Location1

      originalWine.Location2 = _wine.Location2

      originalWine.Notes = _wine.Notes

      originalWine.Price = _wine.Price

      originalWine.VarietalId = _wine.VarietalId

      originalWine.Vintage = _wine.Vintage

      originalWine.WineryId = _wine.WineryId

      If _winesEntities.SaveChanges() > 0 Then

        Return True

      Else

        Return False

      End If

    End Function

To save a wine’s information, the client sends a Wine object to the service. This object contains the updated information for the wine. The code above retrieves the wine’s information from the database and updates it with the wine object sent by the client. I could have checked each property to see if it changed, but I just chose to update the server wine object with the client wine object’s information and then save the server wine object.

Now it gets fun again, assuming your idea of fun is solving coding puzzles! I want to be able to update the information for one or more wines. Perhaps I left out some information when I added the wine. Maybe I mistyped the price. Maybe we are rearranging wines and are going to change the location of ten bottles. I want to be able to make changes like that in place in the grid. I don’t want to go to a different page and view wines one at a time to make these types of changes.

I also want to be able to make changes quickly. So rather than add a Save button to the page, I am using the RowEditEnded event of the DataGrid. That way, as I move off a wine in the DataGrid, the changes will be saved immediately. Here is the code I used in my first attempt.

    winesService = New WinesServiceClient

    AddHandler winesService.UpdateWineCompleted, AddressOf WinesWCFService_UpdateWineCompleted

    winesService.UpdateWineAsync(CType(winesDataGrid.SelectedItem, Wine))

This won’t work. UpdateWine takes an instance of Wine, but the DataGrid is now populated with instances of WineSummary.

So I modified UpdateWine to take a WineSummary object from the client rather than a Wine object.

    Public Function UpdateWine(ByVal _wine As WineSummary) As Boolean

To do that, I had to add WineryId, VarietalId and AppellationId properties to the WineSummary class. And then I had to update GetWines, adding the code below in bold.

    Public Function GetWines() As List(Of WineSummary)

      _winesEntities = New WinesEntities

      Dim winesQuery =

        From w In _winesEntities.Wines

        Select w.WineId, w.WineryId, w.Winery.WineryName, w.Vintage, w.Designation,

             w.VarietalId, w.Varietal.VarietalName,

             w.AppellationId, w.Appellation.AppellationName,

               w.Price, w.Location1, w.Location2, w.Acquired, w.Notes

      Dim wineSummaries = New List(Of WineSummary)

      For Each _wine In winesQuery

        Dim _wineSummary = New WineSummary

        _wineSummary.WineId = _wine.WineId

        _wineSummary.WineryId = _wine.WineryId

        _wineSummary.WineryName = _wine.WineryName

        _wineSummary.Vintage = _wine.Vintage

        _wineSummary.Designation = _wine.Designation

        _wineSummary.VarietalId = _wine.VarietalId

        _wineSummary.VarietalName = _wine.VarietalName

        _wineSummary.AppellationId = _wine.AppellationId

        _wineSummary.AppellationName = _wine.AppellationName

        _wineSummary.Price = _wine.Price

        _wineSummary.Location1 = _wine.Location1

        _wineSummary.Location2 = _wine.Location2

        _wineSummary.Acquired = _wine.Acquired

        _wineSummary.Notes = _wine.Notes

        wineSummaries.Add(_wineSummary)

      Next

      Return wineSummaries

    End Function

Finally, I went back to the client and made the following change to the DataGrid’s RowEditEnded event handler:

    winesService = New WinesServiceClient

    AddHandler winesService.UpdateWineCompleted, AddressOf WinesWCFService_UpdateWineCompleted

    winesService.UpdateWineAsync(CType(winesDataGrid.SelectedItem, WineSummary))

Accounting for nullables

The first time I ran this, I got the following exception in the GetWines method.

Figure3

Not all wines have an appellation. In that case, _wine.AppellationId is Nothing. But _wineSummary.AppellationId is an integer and defaults to 0. So I made AppellationId and VarietalId nullable in the WineSummary class. And while I was at it, I made Price nullable as well.

    <DataContract()>

    Public Class WineSummary

    <DataMember()>

    Public WineId As Integer

    <DataMember()>

    Public WineryId As Integer

    <DataMember()>

    Public WineryName As String

    <DataMember()>

    Public Vintage As String

    <DataMember()>

    Public Designation As String

    <DataMember()>

    Public VarietalId As Integer?

    <DataMember()>

    Public VarietalName As String

    <DataMember()>

    Public AppellationId As Integer?

    <DataMember()>

    Public AppellationName As String

    <DataMember()>

    Public Price As Decimal?

    <DataMember()>

    Public Location1 As String

    <DataMember()>

    Public Location2 As String

    <DataMember()>

    Public Acquired As String

    <DataMember()>

    Public Notes As String

    End Class

WineId and WineryId are required. If I don’t know who made the wine, I am not putting it the database. I may still drink it of course! I don’t want VarietalId, AppellationId and Price to default to 0. I want them empty if the data is missing.

Summary

At this point, the application works as expected. I can see all of our wines in the DataGrid. I can delete a wine by clicking the Delete button. I can edit non-related information in the DataGrid and save my changes by moving off the current record.

I can’t modify the winery, varietal or appellation in the grid. I will have to think about that. I can use combo boxes instead of text boxes to display this information. Then to make a change, I could just select a different value. But in general, I don’t like displaying data that rarely changes in combo boxes. I will play around with this and follow up in a later post.



Configure an Out of Browser Silverlight Application to Run with Elevated Trust

clock August 16, 2010 11:15 by author Robert Green

Silverlight applications that run in the browser run with partial trust. They run in a security sandbox and have limited access to system resources. By default, they can’t read from or write to the file system, access Windows APIs, and interact with local applications such as Outlook, Word, or Excel.

For many of the applications you create, this will not impact your ability to create the application. However, there will be applications where you want access to local system resources. Prior to Silverlight 4, your only option would be to rewrite the application using Windows Presentation Foundation. Fortunately, Silverlight 4 adds the ability to run out-of-browser applications with elevated trust. These applications are known as trusted applications.

To configure a Silverlight application so that users can install it and then run it outside the browser, go to the Silverlight tab of the Project Designer and check Enable running application out of the browser.

To specify that an out-of-browser application should run with elevated permissions, click Out of Browser Settings. In the Out of Browser Settings dialog box, check Require elevated trust when running outside the browser.

Figure1

You can also specify the appearance of the title bar and border for the application window. You can select Default, No Border, or Borderless Round Corners from the Windows Style drop-down list. Note that this drop-down list is only enabled if you configure the application to run with elevated trust.

The OutOfBrowserTrustedApplication project is a simple out of browser application that displays customers and orders. The application uses a Silverlight-enabled WCF service to retrieve data from the Northwind database. When the application loads, it calls the GetCustomers method of the service to retrieve the customer Id, company name, city and country of each customer. The application displays the customers in a list box.

When you select a customer, the application calls the GetCustomer method of the service to retrieve all of the information for that customer. The application displays these in text boxes. The application also calls the GetOrders method of the service to retrieve the order id, ordered date, shipped date and dollar amount of each order the customer placed. The application displays these in a DataGrid.

Figure2

To install the application, right-click and select Install Elevated Trust Demo onto this computer. Silverlight displays a security warning because this application has requested elevated permissions and it is not signed with a trusted certificate.

Figure3

Click Install to install the application. The out-of-browser version of the application loads.

Figure4

Notice that the out of browser application has three buttons that do not appear when you run the application in the browser.

You can use the HasElevatedPermissions property of the Application.Current object to determine whether or not an out-of-browser application is trusted. The following code executes when the application starts:

    ' Visual Basic

    If Not Application.Current.HasElevatedPermissions Then

      saveToFileButton.Visibility = Visibility.Collapsed

      createLetterButton.Visibility = Visibility.Collapsed

      creteReportButton.Visibility = Visibility.Collapsed

    Else

      myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)

    End If

    // C#

    if (!Application.Current.HasElevatedPermissions)

    {

      saveToFileButton.Visibility = Visibility.Collapsed;

      createLetterButton.Visibility = Visibility.Collapsed;

      createReportButton.Visibility = Visibility.Collapsed;

    }

    else

    {

    myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

    }

This code hides the three buttons if the application is running in the browser and therefore does not have elevated trust. If the application has elevated trust, the code stores the location of the My Documents folder to the myDocuments field. Each of the three buttons writes a file to My Documents. Trusted Silverlight application only has access to user folders, specifically My Documents, My Music, My Pictures, and My Videos.

Write to a Text File

When you click the Save to file button, the following code executes:

    ' Visual Basic

    Using writeStream = New StreamWriter(customerFile & ".txt", False)

      writeStream.WriteLine(_customer.CompanyName)

      writeStream.WriteLine()

      writeStream.WriteLine(String.Format("Contact: {0}", _customer.ContactName))

      writeStream.WriteLine(String.Format(" {0}", _customer.ContactTitle))

      writeStream.WriteLine()

      writeStream.WriteLine(String.Format("Address: {0}", _customer.Address)) 

      writeStream.WriteLine(String.Format(" {0}, {1}", _customer.City, _customer.Region))

      writeStream.WriteLine(String.Format(" {0}, {1}", _customer.PostalCode, _customer.Country))

      writeStream.WriteLine()

      writeStream.WriteLine(String.Format("Phone: {0}", _customer.Phone))

      writeStream.WriteLine(String.Format("Fax: {0}", _customer.Fax))

      MessageBox.Show("The customer information has been saved to My Documents")

    End Using

    // C#

    using (var writeStream = new StreamWriter(customerFile + ".txt", false))

    {

      writeStream.WriteLine(customer.CompanyName);

      writeStream.WriteLine();

      writeStream.WriteLine(string.Format("Contact: {0}", customer.ContactName));

      writeStream.WriteLine(string.Format(" {0}", customer.ContactTitle));

      writeStream.WriteLine();

      writeStream.WriteLine(string.Format("Address: {0}", customer.Address));

      writeStream.WriteLine(string.Format(" {0}, {1}", customer.City, customer.Region));

      writeStream.WriteLine(string.Format(" {0}, {1}", customer.PostalCode, customer.Country));

      writeStream.WriteLine();

      writeStream.WriteLine(string.Format("Phone: {0}", customer.Phone));

      writeStream.WriteLine(string.Format("Fax: {0}", customer.Fax));

      MessageBox.Show("The customer information has been saved to My Documents");

    }

This code uses the StreamWriter class to create the text file. This class is in the System.IO namespace and is available in Silverlight applications. In other applications, you may have used the StringBuilder class to build a string and then used the WriteAllText method of the File class to create a file. You cannot take this approach in a Silverlight class. The StringBuilder class is in the System.Text namespace and that assembly is not available in Silverlight.

Write to a Word Document

When you click the Create letter button, the following code executes:

    ' Visual Basic

    If AutomationFactory.IsAvailable Then

      Try

        word = AutomationFactory.GetObject("Word.Application")

      Catch ex1 As Exception

        Try

          word = AutomationFactory.CreateObject("Word.Application")

        Catch ex2 As Exception

          MessageBox.Show("This application can't save customer letters")

          Exit Sub

        End Try

      End Try

      word.Documents.Add()

      word.Selection.TypeText(DateTime.Today.ToShortDateString)

      word.Selection.TypeParagraph()

      word.Selection.TypeParagraph()

      word.Selection.TypeText(

        _customer.ContactName & ChrW(11) &

        _customer.ContactTitle & ChrW(11) &

        _customer.CompanyName & ChrW(11) &

        _customer.Address & ChrW(11) &

        _customer.City & " " & _customer.Region & ChrW(11) &

        _customer.PostalCode & " " & _customer.Country)

      word.Selection.TypeParagraph()

      word.Selection.TypeParagraph()

      word.Selection.TypeText("Dear valued customer:")

      word.Selection.TypeParagraph()

      word.Selection.TypeParagraph()

      word.Selection.TypeText(

        "Now is the perfect time to train your staff on the latest technologies, including Silverlight.")

      word.Selection.TypeParagraph()

      word.Selection.TypeText("MCW Technologies offers a number of training options.")

      word.Selection.TypeParagraph()

      word.Selection.TypeText("I will call you next week and we can discuss this.")

      word.Selection.TypeParagraph()

      word.Selection.TypeParagraph()

      word.Selection.TypeText("Sincerely")

      word.Selection.TypeParagraph()

      word.Selection.TypeParagraph()

      word.Selection.TypeText("Robert Green" & ChrW(11) &

        "Sr. Consultant" & ChrW(11) &

        "MCW Technologies")

      word.Selection.TypeParagraph()

      word.ActiveDocument.SaveAs(customerFile)

      word.ActiveDocument.Close()

      MessageBox.Show("The customer letter has been saved to My Documents")

    Else

      MessageBox.Show("This application can't save customer letters")

    End If

    // C#

    if (AutomationFactory.IsAvailable)

    {

      try

      {

        word = AutomationFactory.GetObject("Word.Application");

      }

      catch (Exception ex1)

      {

        try

        {

          word = AutomationFactory.CreateObject("Word.Application");

        }

        catch (Exception ex2)

        {

          MessageBox.Show("This application can't save customer letters");

          return;

        }

      }

      word.Documents.Add();

      word.Selection.TypeText(DateTime.Today.ToShortDateString());

      word.Selection.TypeParagraph();

      word.Selection.TypeParagraph();

      word.Selection.TypeText(customer.ContactName +

          '\v' + customer.ContactTitle + '\v' + customer.CompanyName +

          '\v' + customer.Address + '\v' + customer.City + " " +

        customer.Region + '\v' + customer.PostalCode + " " +

        customer.Country);

      word.Selection.TypeParagraph();

      word.Selection.TypeParagraph();

      word.Selection.TypeText("Dear valued customer:");

      word.Selection.TypeParagraph();

      word.Selection.TypeParagraph();

      word.Selection.TypeText(

        "Now is the perfect time to train your staff on the latest technologies, including Silverlight.");

      word.Selection.TypeParagraph();

      word.Selection.TypeText("MCW Technologies offers a number of training options.");

      word.Selection.TypeParagraph();

      word.Selection.TypeText("I will call you next week and we can discuss this.");

      word.Selection.TypeParagraph();

      word.Selection.TypeParagraph();

      word.Selection.TypeText("Sincerely");

      word.Selection.TypeParagraph();

      word.Selection.TypeParagraph();

      word.Selection.TypeText("Robert Green" + "\v" +

        "Sr. Consultant" + "\v" + "MCW Technologies");

      word.Selection.TypeParagraph();

      word.ActiveDocument.SaveAs(customerFile);

      word.ActiveDocument.Close();

      MessageBox.Show("The customer letter has been saved to My Documents");

    }

    else

    {

      MessageBox.Show("This application can't save customer letters");

    }

The AutomationFactory class is in the System.Runtime.InteropServices.Automation namespace and provides access to Automation servers such as Word, Excel or Outlook. The IsAvailable property of this class specifies whether or not the Silverlight application is capable of calling an Automation server. If this property returns true, the code attempts to get a reference to the Word Automation server.

The GetObject method of the AutomationFactory class returns a reference to an activated and running Automation server. The code above first tries to get a reference to Word. The first time this code runs GetObject will throw an exception because it has not been activated yet. The code then calls the CreateObject method to activate the Word Automation server and return a reference to it. If Word is not available as an Automation server then this call will throw and exception and the code cannot create a Word document.

It may seem odd to try and reference an existing Automation server before creating it. However, this code makes sense. The second time it executes, and each time after that, the code calls GetObject to retrieve a reference to the already activated Automation server.

If the code succeeds in retrieving a reference to Word, it uses familiar code to write the letter.

Write to an Excel Workbook

When you click the Create report button, the following code executes:

    ' Visual Basic

    If AutomationFactory.IsAvailable Then

      Try

        excel = AutomationFactory.GetObject("Excel.Application")

      Catch ex1 As Exception

        Try

          excel = AutomationFactory.CreateObject("Excel.Application")

        Catch ex2 As Exception

          MessageBox.Show("This application can't save orders reports")

          Exit Sub

        End Try

      End Try

      excel.Workbooks.Add()

      excel.Range("A1").Value = "Orders placed by " & _customer.CompanyName

      excel.Range("A1").Font.Size = 14

      excel.Range("A1").Font.Bold = True

      excel.Range("A3").Value = "Order"

      excel.Range("A3").Font.Bold = True

      excel.Range("A3").HorizontalAlignment = 4

      excel.Range("B3").Value = "Ordered"

      excel.Range("B3").Font.Bold = True

      excel.Range("B3").HorizontalAlignment = 4

      excel.Range("C3").Value = "Shipped"

      excel.Range("C3").Font.Bold = True

      excel.Range("C3").HorizontalAlignment = 4

      excel.Range("D3").Value = "Amount"

      excel.Range("D3").Font.Bold = True

      excel.Range("D3").HorizontalAlignment = 4

      excel.Columns("A:A").ColumnWidth = 10

      excel.Columns("B:D").ColumnWidth = 12

      Dim row As Integer = 4

      For Each ord As OrderSummary In orders

        excel.Range(String.Format("A{0}", row)).Value = ord.OrderId

        excel.Range(String.Format("B{0}", row)).Value = ord.OrderDate

        If ord.ShippedDate <> #12:00:00 AM# Then

          excel.Range(String.Format("C{0}", row)).Value = ord.ShippedDate

        End If

        excel.Range(String.Format("D{0}", row)).Value = ord.Amount

        excel.Range(String.Format("D{0}", row)).NumberFormat = "$#,##0.00"   

        row += 1 

      Next

        excel.ActiveWorkbook.SaveAs(customerFile)

        excel.ActiveWorkbook.Close()

        MessageBox.Show("The orders report has been saved to My Documents")

    Else

      MessageBox.Show("This application can't save orders reports")

    End If

    // C#

    if (AutomationFactory.IsAvailable)

    {

      try

      { 

        excel = AutomationFactory.GetObject("Excel.Application");

      }

      catch (Exception ex1)

      {

        try

        {

          excel = AutomationFactory.CreateObject("Excel.Application");

        }

        catch (Exception ex2)

        {

          MessageBox.Show("This application can't save orders reports");

          return;

        }

      }

      excel.Workbooks.Add();

      excel.Range["A1"].Value = "Orders placed by " + customer.CompanyName;

      excel.Range["A1"].Font.Size = 14;

      excel.Range["A1"].Font.Bold = true;

      excel.Range["A3"].Value = "Order";

      excel.Range["A3"].Font.Bold = true;

      excel.Range["A3"].HorizontalAlignment = 4;

      excel.Range["B3"].Value = "Ordered";

      excel.Range["B3"].Font.Bold = true;

      excel.Range["B3"].HorizontalAlignment = 4;

      excel.Range["C3"].Value = "Shipped";

      excel.Range["C3"].Font.Bold = true;

      excel.Range["C3"].HorizontalAlignment = 4;

      excel.Range["D3"].Value = "Amount";

      excel.Range["D3"].Font.Bold = true;

      excel.Range["D3"].HorizontalAlignment = 4;

      excel.Columns["A:A"].ColumnWidth = 10;

      excel.Columns["B:D"].ColumnWidth = 12;

      int row = 4;

      foreach (OrderSummary ord in orders)

      {

        excel.Range(string.Format("A{0}", row)).Value = ord.OrderId;

        excel.Range(string.Format("B{0}", row)).Value = ord.OrderDate;

        if (ord.ShippedDate != DateTime.Parse("12:00:00 AM"))

        {

          excel.Range(string.Format("C{0}", row)).Value = ord.ShippedDate;

        }

        excel.Range(string.Format("D{0}", row)).Value = ord.Amount;

        excel.Range(string.Format("D{0}", row)).NumberFormat = "$#,##0.00";

        row += 1;

      }

      excel.ActiveWorkbook.SaveAs(customerFile);

      excel.ActiveWorkbook.Close();

      MessageBox.Show("The orders report has been saved to My Documents");

    }

    else

    {

      MessageBox.Show("This application can't save orders reports");

    }

This code follows the same pattern as the previous code. If it succeeds in retrieving a reference to Excel, it uses familiar code to create a report listing the orders for the selected customer.



About the author

Robert lives in Redmond, WA. He has written or co-authored AppDev’s Visual Studio, Silveright, WPF, LINQ, WCF and Workflow courseware, and appears in the video training for these courses, as well. Robert is a Microsoft MVP, is a member of the INETA Speaker Bureau and has been a frequent speaker at technology conferences. Before joining MCW, Robert worked at Microsoft for 8 years.

Tag cloud

Month List

Page List

Sign in