Andrew Coates ::: MSFT

It's all about community!

  • Andrew Coates ::: MSFT

    Document Creation and Conversion with the OpenXML SDK and SharePoint 2010 Word Automation Services – Part 1

    • 0 Comments

    Update 2011-04-06 I Finally got around to writing Part 2 of this post.

    I had the pleasure this week of presenting at the Australian SharePoint Conference – much kudos to NZ SharePoint MVPs Debbie Ireland and Mark Orange, and local SharePoint MVPs/community folk James Milne, Brendan Law and Kathy Hughes and the team for putting on such a great event.

    My session was entitled "Creating and manipulating documents with Word Services and Open XML" and covered the use of the OpenXML SDK and a new feature in SharePoint 2010 (Standard and above) called Word Automation Services. I've uploaded the deck to my SkyDrive, and you can grab it from there.

    Disclaimer

    I stole a bunch of the code I showed (or at least the concepts for it) from Eric White's excellent blog, several posts from the Word Team and from MSDN:

    Development Environment

    I ran these demos on a HP Envy 15 with 8 GB of RAM, a Core i7 processor running Windows 7 x64 and with both SharePoint 2010 and Visual Studio 2010 installed.

    See the MSDN Article "Setting Up the Development Environment for SharePoint 2010 on Windows Vista, Windows 7, and Windows Server 2008"for information on how to set up your development environment for SharePoint development and then read Beth Massi's notes on that article to actually get it working – "Setting up Windows 7 for Office & SharePoint 2010 Beta Development".

    I installed the OpenXML SDK 2.0 – download from here, documentation here.

    I also installed the Visual Studio 2010 Pro Power Tools. If you don't have this wonderful set of tools installed on your machine stop reading this now and go and install them. I mean it. Right now!

    Demo Code

    Set up a Windows Form to call your code from

    I built a Winforms app to demonstrate the document creation concepts and if you want to follow along, you can do the same. You know the drill, File|New Project, or just New Project from the VS2010 Start Page

    Create a new WinForms Project

    Notice I've used a C# project for this. A later step, reflecting the template document, results in C# code that you'll put into a class in this project so that's why I used C#. I guess you could put the reflected code into a separate class library project and do the rest in VB.NET if you like. I'll leave that as a exercise for the reader.

    Drop 4 Buttons and a NumericUpDown on the form and set some properties as follows

    ControlNameText
    button1 CreateOneDocumentLocallyButton Create One Document Locally
    button2 CreateManyDocumentsLocallyButton Create Many Documents Locally
    button3 CreateOneSharePointDocumentButton Create One Document on SharePoint
    button4 CreateManySharePointDocumentsButton Create Many Documents on SharePoint

    and

    ControlNameIncrementMaximumMinimumValue
    numericUpDown1 NumberOfDocuments 100 1000 0 100

    You should have a form that looks a bit like this:

    Form layout

    Generate a Document using the OpenXML SDK

    Now it's time to actually generate the document using the OpenXML SDK.

    The first thing to do is separate your generation code from your UI, so double-click on the CreateOneDocumentLocallyButton button to generate a click event handler and then in the event.

    In the Form1 class definition, before the constructor, instantiate a new DocGenerator class (don't worry that you haven't created a class definition yet, you will in a sec):

    DocGenerator gen = new DocGenerator();

    In the button click event, call the DocGenerator's CreateOneDocumentLocally method and then show a MessageBox to confirm that something's happened. Your form code should look like this:

    1. using System;
    2. using System.Collections.Generic;
    3. using System.ComponentModel;
    4. using System.Data;
    5. using System.Drawing;
    6. using System.Linq;
    7. using System.Text;
    8. using System.Windows.Forms;
    9.  
    10. namespace OpenXMLDocumentGeneration
    11. {
    12.     public partial class Form1 : Form
    13.     {
    14.         DocGenerator gen = new DocGenerator();
    15.  
    16.         public Form1()
    17.         {
    18.             InitializeComponent();
    19.         }
    20.  
    21.         private void CreateOneDocumentLocallyButton_Click(object sender, EventArgs e)
    22.         {
    23.             gen.CreateOneDocumentLocally();
    24.             MessageBox.Show("Done");
    25.         }
    26.     }
    27. }

    Now it's time for the Visual Studio Magic™. Position your cursor somewhere on the word DocGenerator (in Line 14 above) and press Ctrl+. (that's hold the Control key down and press the full stop, or period button). A Smart Tag will pop up offering to either generate a new class for Doc Generator or a new type.

    Generate new class or type smart tag

    If you choose Generate New Type, you'll get a dialog offering to generate one of four different types, with a number of access  options and decisions to be made about where the type definition should be stored:

    The Generate New Type dialog gives you lots of options

    If you choose the default Generate class option though, Visual Studio will generate a new class file with the same name as the class being defined (in this case, DocGenerator) and a class definition for that class in the default namespace.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    namespace OpenXMLDocumentGeneration
    {
        class DocGenerator
        {
        }
    }

    This is a great time-saving feature that allows you to work from the inside-out with your creation of types.

    The same technique works for defining the methods on the class. Putting  the cursor somewhere in the gen.CreateOneDocumentLocally() text (in line 23 above) and pressing Ctrl+. will prompt you to create a method stub for the method you've yet to define.

    Generate a method stub for the hereto undefined method.

    Now you need to add the code that will generate the document.

    The first thing to do is add a couple of references to the project. Right-click on your project in the Solution Explorer and choose Add Reference. If you've done what I told you you should above and installed the Visual Studio 2010 Pro Power Tools, you'll get a dialog that looks like this (note that assemblies that are already referenced in the project have a green "check mark" icon next to them):

    The new Add Reference dialog

    Remember that the OpenXML SDK is a set of wrappers around the System.IO.Packaging namespace, which is in the Windowsbase assembly, so you need to add references to something with OpenXML in the name, and to Windowsbase. Click in the Search Assemblies box in the top-right corner of the dialog and type "OpenXML" (without the quotes). The huge list of assemblies is filtered and just the one you want remains:

    Add Reference dialog filtered

    Double-clicking on the assembly in the list adds the reference and the little green icon appears next to it.

    Do the same for the Windowsbase assembly.

    Now you're going to create your first programmatically-generated OpenXML document.

    Switch to the DocGenerator class and add a couple of using directives:

    using DocumentFormat.OpenXml.Packaging;
    using DocumentFormat.OpenXml.Wordprocessing;

    A very common pattern when creating OpenXML documents (and one used by the OpenXML SDK document reflector tool we'll see in a minute) is to create an OpenXML package with a using statement.

    In the CreateOneDocumentLocally method, delete the exception line (triple-click to select the entire line) and add the following:

    using (WordprocessingDocument package =
        WordprocessingDocument.Create("MyFirstDocument.docx",
        DocumentFormat.OpenXml.WordprocessingDocumentType.Document))
    {
        
    }

    This has now created the outer container, or package (I'll examine some of the overloads of the Create method a bit later). Now you need to add some content to the package.

    The first thing to do is add a main document part – this is the bit that holds all of the text for the document. Inside the using statement's braces type

     

    package.AddMainDocumentPart();

     

    The MainDocumentPart has a document property which holds a reference to a Document class. Create one of those now:

    Document doc = new Document();

    The hierarchy of the Document object looks like this:

    Hierarchy of a document object

    So you'll new up one of each from the top down, and then append them back in the reverse order to build the hierarchy:

    Your method should look something like this:

     

    internal void CreateOneDocumentLocally()
    {
        using (WordprocessingDocument package =
            WordprocessingDocument.Create("MyFirstDocument.docx",
            DocumentFormat.OpenXml.WordprocessingDocumentType.Document))
        {
            package.AddMainDocumentPart();

            // instantiate the members of the hierarchy
            Document doc = new Document();
            Body body = new Body();
            Paragraph para = new Paragraph();
            Run run = new Run();
            Text text = new Text() { Text = "The OpenXML SDK rocks!" };

            // put the hierarchy together
            run.Append(text);
            para.Append(run);
            body.Append(para);
            doc.Append(body);

            // Assign the document object to the MainDocumentPart's Document property

            package.MainDocumentPart.Document = doc;

        }
    }

     

    Moment of truth time – run the project and click the Create One Document Locally button. You should be rewarded with a MessageBox saying "Done".

    Close the form.

    Right-click on the project in Solution Explorer and choose "Open Folder in Windows Explorer".

    Navigate down into Bin/Debug.

    Double-click on MyFirstDocument.docx to open it in Word. With any luck, you should have something like this:

    Your first progromattically-generated OpenXML document

    Document Reflection Using the OpenXML SDK 2.0 Productivity Tool

    You'll have noticed that it still seemed to take a lot of code just to create a single sentence in a plain, un-themed document (although, I assure you, it's a lot less than you'd need to use if you called System.IO.Packaging directly). Fortunately, the OpenXML SDK ships with an all-purpose, Swiss Army Knife of a tool called the OpenXML SDK 2.0 Productivity Tool for Microsoft Office (I'm going to call it the OSDKT from now on). If you installed the SDK in the default location, the tool lives at C:\Program Files (x86)\Open XML SDK\V2.0\tool\OpenXmlSdkTool.exe. Take a moment now to fire it up. When it starts, it look like this:

    OpenXML SDK 2.0 Productivity Tool

    It's definitely worth checking out the documentation of this multi-purpose tool, but for now you'll just be using what I think is the most powerful feature, the Document Reflector. The Document reflector examines an OpenXML document and generates the code required to create the document programmatically. This means that you can start with a complex, beautifully themed document as a template, generate the code for that document and then adapt the code to include whatever makes sense (data from a database, web data, calculated information and so on).

    To reflect a document, click the Open File button in the OSDKT's toolbar and choose any OpenXML document. In this case, choose a Word document that doesn't support templates (usually with the extension docx). If you don't have one handy, I've uploaded one here for you to use.

    Notice that the hierarchy of the document is represented in the left side of the tool. In this screenshot I've expanded the hierarchy to show the MainDocumentPart, the Document, the Body, a Paragraph, a Run and a Text class.

    Open XML Hierarchy

    Note that there are lots of other nodes in the hierarchy that you didn't add in the first, hand-coded example you tried above.

    Now it's time to generate the code to create this document. Select the root node of the hierarchy in the left pane of the OSDKT and then click the Reflect Code button. After a few moments, the right pane fills up with what's known in the business as a shedload of code.

    Click somewhere in that code and then press Ctrl+A and then Ctrl+C to copy it all to the clipboard.

    Switch back to Visual Studio.

    Right-click on the project in the Solution Explorer and choose Add Class.

    Call the class DocumentCreator.

    Press Ctrl-A and Ctrl-V to replace the default code for the class with the code you copied from the OSDKT

    Change the NameSpace to OpenXMLDocumentGeneration and the class name to DocumentCreator. Your class definition should look something like this:

     

    using DocumentFormat.OpenXml.Packaging;
    using Ap = DocumentFormat.OpenXml.ExtendedProperties;
    using Vt = DocumentFormat.OpenXml.VariantTypes;
    using DocumentFormat.OpenXml;
    using DocumentFormat.OpenXml.Wordprocessing;
    using A = DocumentFormat.OpenXml.Drawing;
    using M = DocumentFormat.OpenXml.Math;
    using Ovml = DocumentFormat.OpenXml.Vml.Office;
    using V = DocumentFormat.OpenXml.Vml;
    using W14 = DocumentFormat.OpenXml.Office2010.Word;

    namespace OpenXMLDocumentGeneration
    {
        public class DocumentCreator
        {
            // Creates a WordprocessingDocument.
            public void CreatePackage(string filePath)
            {
                using (WordprocessingDocument package =
                    WordprocessingDocument.Create(filePath,
                    WordprocessingDocumentType.Document))
                {
                    CreateParts(package);
                }
            }

     

    Now all that needs to happen is to create and call this class instead of our hand-crafted code.

    In the DocGenerator class, add a field to hold a reference to an instance of our new DocumentCreator class as well as a base folder for output:

    Next, replace the contents of the CreateOneDocumentLocally method with a call to the DocumentCreator's CreatePackage method:

    Now remove the using directives that reference the OpenXML SDK namespaces as you don't need them in this class any more. The class should now look like this:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    using DocumentFormat.OpenXml.Packaging;
    using DocumentFormat.OpenXml.Wordprocessing;

    namespace OpenXMLDocumentGeneration
    {
        class DocGenerator
        {
            DocumentCreator gen = new DocumentCreator();
            string OutputFolder = @"c:\temp\";

            internal void CreateOneDocumentLocally()
            {
                gen.CreatePackage(OutputFolder + "MyNextOpenXMLDocument.docx");
            }
        }
    }

    The last thing you'll do is add some code to time the creation of the document (so you can compare the different method calls later). In the Form1 code, instantiate a Stopwatch class and assign it to a field called sw then add code to reset and start the stopwatch before the document is created and to stop it and report the results when the method returns. Form1.cs should now look like this:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Diagnostics;

    namespace OpenXMLDocumentGeneration
    {
        public partial class Form1 : Form
        {
            DocGenerator gen = new DocGenerator();
            Stopwatch sw = new Stopwatch();

            public Form1()
            {
                InitializeComponent();
            }

            private void CreateOneDocumentLocallyButton_Click(object sender, EventArgs e)
            {
                sw.Reset();
                sw.Start();
                gen.CreateOneDocumentLocally();
                sw.Stop();
                MessageBox.Show(String.Format("Created one document locally in {0} ms",
                    sw.ElapsedMilliseconds));
            }
        }
    }

    Run this project up and click the Create One Document Locally button. In my case, this resulted in a MessageBox like this:
    Results of the first document creation run - 347ms

    Clicking it a couple more times gave interesting results:

    14ms 13ms

    Showing that time to actually create a document is pretty small – most of the time in the first instance is in instantiating the document creation classes.

    To show this even more vividly, add an event handler to the CreateManyDocumentsLocallyButton's click event that passes the value of the NumberOfDocs numericUpDown control to a method:

     

    private void CreateManyDocumentsLocallyButton_Click(object sender, EventArgs e)
    {
        sw.Reset();
        sw.Start();
        gen.CreateManyDocumentsLocally((int)NumberOfDocuments.Value);
        sw.Stop();
        MessageBox.Show(String.Format("Created {1} documents locally in {0} ms",
            sw.ElapsedMilliseconds, (int)NumberOfDocuments.Value));
    }

     

    CreateManyDocumentsLocally just loops through and creates that may documents:

     

    internal void CreateManyDocumentsLocally(int NumberOfDocs)
    {
        for (int i = 0; i < NumberOfDocs; i++)
        {
            gen.CreatePackage(string.Format("{0}MyNextOpenXMLDocument{1:D5}.docx",
                OutputFolder, i));
        }

    }

     

    Running this with 100 documents gave me this:

    100 documents too 1828 ms

    and with 1000 documents

    Creating 1000 documents locally took 14935 ms

    Finally, try using the new Parallel Task Library:

     

    internal void CreateManyDocumentsLocallyInParallel(int NumberOfDocs)
    {
        System.Threading.Tasks.Parallel.For(0, NumberOfDocs, i =>
        {
            gen.CreatePackage(string.Format("{0}MyNextOpenXMLDocument{1:D5}.docx",
                OutputFolder, i));
        });

    }

     

    and update the Form1 code that calls it to look like:

     

    private void CreateManyDocumentsLocallyButton_Click(object sender, EventArgs e)
    {
        sw.Reset();
        sw.Start();
        gen.CreateManyDocumentsLocallyInParallel((int)NumberOfDocuments.Value);
        sw.Stop();
        MessageBox.Show(String.Format("Created {1} documents locally with Parallel Processing in {0} ms",
            sw.ElapsedMilliseconds, (int)NumberOfDocuments.Value));
    }

     

    Calls to 100 and 1000 documents with Parallel processing on my quad core box only gave me about a 50% increase in performance:

    Creating 100 docs in parallel took 1481 ms Creating 1000 documents in parallel took 8992 ms

    Tune in next time

    This concludes Part 1. In Part 2, I'll explore writing the documents to a SharePoint library instead of to a local file system and then converting the documents from docx to pdf files with no user intervention at all!

    Additional Resources

    Download the SharePoint 2010 Developer Training Kit

     

  • Andrew Coates ::: MSFT

    Linking Word 2007 Content Controls to Custom XML

    • 4 Comments

    One of the demos I showed in my session at OzFox was inserting some new Custom XML into an existing template file and having that bound to some content controls. This makes mail merges REALLY easy. It also allows you to pick up changes to customer details made in the document itself and propagate them back to the server, because the binding between the Content Controls and the Custom XML is dynamic and bi-directional.

    However, doing the binding in the first place is not all that intuitive. Here is a series of steps that worked for me - note that I'm not saying it's the only, or even the best way.

    BTW, there's a great video up on Channel 9 where Matthew Scott discusses the Word 2007 Content Control Toolkit.

    Step 1: Download and install the Word 2007 Content Control Toolkit from CodePlex.

    Grab the installer, the documentation and (optionally) the source code from here.

    Run the MSI file and accept the defaults.

    Step 2: Create a Word 2007 document to use as a template

    It doesn't really matter what's in it, but you can add some placeholder text using the =rand() function if you like.

    Step 3: Add Content Controls to the document

    You'll need to enable the developer tab on the ribbon, then just drag controls from the controls group into the document:

    I used text controls (second from the left in the top row), but you can get fancier with Rich Text, images, combo boxes, datetime pickers etc.

    Highlight each control and click the properties button in the controls group of the developer toolbar.

    Properties button

    Give the control a sensible name.

    Note that there are a number of other options for the control's properties. If you want the bi-directional data binding to work, make sure you don't check the "Remove content control when contents are edited" box.

    Do this for as many fields as you want to bind to.

    Save and close the document

    Step 4: Add some custom XML to the document

    Open the document using the Word 2007 Content Control Toolkit.

    Word document open in the content control toolkit

    Note that at this stage there's no custom XML in the document. Click the Create a new Custom XML Part hyperlink and change to the Edit View tab and add some XML. You don't need a schema, it just needs to be well-formed XML. Here's the XML I used:

    <root>
    <name>
    <givenName>Charles</givenName>
    <familyName>Sterling</familyName>
    <salutation>Chuck</salutation>
    </name>
    <address>
    <location>Level 9</location>
    <street>1 Eagle St</street>
    <suburb>BRISBANE</suburb>
    <state>QLD</state>
    <postalCode>4000</postalCode>
    <country>AUSTRALIA</country>
    </address>
    </root>

    Custom XML inserted 

    Step 5: Bind the Content Controls to custom XML nodes

    Go back to Bind View and drag nodes from the tree on the right to the appropriate control on the left. Note that you need to select the node before you drag it.

    Binding the

    Save the document and open it in Word.

    Document with data from Custom XML

    Now change something (for example change the salutation from "Chuck" to "Charles"). Save and close the file and open it using the Content Control Toolkit. Notice that the XML has been changed.

  • Andrew Coates ::: MSFT

    Enabling the Word 2007 Developer Tab

    • 2 Comments

    One of the things that took me the longest to do when I was prepping for my OzFox Session was also one of the simplest - enabling the Developer tab in the Word 2007 ribbon. The Developer tab looks like this:

    Word 2007 Developer Ribbon

    and it allows you to do things that a Word developer might need to do, including adding content controls. By default though, it's not enabled. To switch it on, choose Word Options from the Word Menu:

    Word Menu

    Make sure you've got the Popular group highlighted, and check the box that says "Show Developer Tab in the Ribbon".

    Word Options Dialog

    Yeah, I guess I should have found that sooner.

  • Andrew Coates ::: MSFT

    Turning off Proofing and Autocorrect in Word 2007

    • 1 Comments

    This is the second time this question has come up in the last week or so, so I thought I’d answer it here.

    Sometimes, you want to turn off proofing (spelling and grammar checking) and autocorrect in Word, either globally or for some parts of your document (for me, that’s often in code blocks). The easiest way to do this is to update the style on which the relevant section of the document is based.

    By default, most styles in Word inherit from Normal, so if you want to switch it off globally, switching it off for the Normal style will switch it of for any child styles in which the option has not been explicitly overridden. Here’s how to do it for Normal in six four easy steps:

    1. In the Styles group on the Home Tab, Expand the styles list by clicking the More button in the bottom right-hand corner of the of the list.

    Styles1

    2. Choose Apply Styles from the menu at the bottom of this drop-down

    Styles2

    3. Make sure Normal is selected in the drop-down and then click the Modify ... button

     Styles3

    UPDATE – my son has just told me that you can concatenate the first three steps into one by right-clicking on Normal in the style group on the home tab and choosing Modify from the pop-up menu.

    4 (2). Choose Format in the Modify Style dialog

     Styles4

    5 (3). Pick Language from the pop-up menu

    Styles5

    6 (4). Check the box that says Do Not Check Spelling or Grammar

    Styles6

    Click OK and now any style that inherits from Normal (that doesn’t have a explicit setting for this) will have proofing and autocorrect turned off.

  • Andrew Coates ::: MSFT

    Using the AdRotator Class + Dynamic Image Generation in ASP.NET 2.0

    • 1 Comments

    As part of my study group homework, I promised to bone up on the AdRotator class in ASP.NET2.0 and report back to the group. Turns out this lead to another cool thing that I needed to report back on - dynamic image generation.

    The source code I’ll refer to throughout this post is available for download. Of course, it’s intended as a sample only. Use it at your own risk, no warranty expressed or implied, contents may be hot. Don’t say you weren’t warned. The source code demonstrates the 3 modes of populating an AdRotator control and a technique for dynamically displaying images stored as binary data in fields of a database.

    AdRotator Class

    The AdRotator class basically allows you to display a random image (more on where images come from and how "random" works in a bit) with an associated bit of ALT text and (optionally) a hyperlink from the image to somewhere else. You’ve seen this kind of thing on web pages before, in fact it goes back to ASP.NET 1.0 and has been implemented in any number of other web frameworks as well. Since we’ve been studying for the ASP.NET 2.0 exam, that’s what I’ll cover here.

    3 Modes

    AdRotator populates its image and navigation properties in 3 different ways, which I’ll call “Database”, “XML” and “Programmatic” for reasons that should become obvious, if they’re not already.

    Database

    In database mode, an AdRotator is linked to a DataSource that needs, at the very least, a column for the URL of the image, a column for the URL to navigate to when the imaged is clicked and a column for the string to be displayed in the ALT tag of the image. In the MSDN documentation (online and offline), the full allowable schema is listed and includes some compulsory fields:

    • An int primary key, which can be called anything you like
    • an nvarchar field called “ImageURL” that has a relative or absolute URL pointing to the image to be displayed
    • an nvarchar field called “NavigateURL” that has a relative or absolute URL that the image is hyperlinked to. If you leave the contents of this field blank, there’s no hyperlink from the image.
    • an nvarchar field called “AlternateText” containing a string that will be inserted into the <IMG> tag’s ALT attribute (and will, therefore, depending on the viewing browser’s capabilities or preferences, be displayed as a tooltip, or instead of the image or be read aloud to the user etc). By the way, is it just me, or should this be called "AlternativeText"?

    Note that the name of these fields can be overridden in the AdRotator by setting the ImageURLField, NavigateURLField and AlternateTextField properties to the appropriate field name.

    In addition, the following fields are optional.

    • an int field called “Impressions”. This is the relative weighting of each field and determines the likelihood of a particular image being displayed. You can use this field to do absolute weighting (where the impression values don’t change), or to smooth the curve somewhat by decrementing the value in the source data whenever an image is displayed (or when the hyperlink is clicked, or whatever). I can imagine putting something in this field like the amount of money each advertiser has given me to display their add. The more you give me, the better the chance your ad comes up.
    • an nvarchar field called “Keyword”, which allows you to keep all your ads for the entire site (or perhaps many sites) in the one table and the instance of the AdRotator Class on a particular page can filter to just get the relevant ads for that page by setting the KeywordFilter property.
    • int fields called “Width” and “Height”, which allow the AdRotator to include height and width hints as attributes of the generated <IMG> tag.

    Of course, with the cool data handling functionality of data sources in ASP.NET 2.0, you don’t need a table anything like this, you can pull the data into the appropriate structure with a SQL statement in the DataSource itself. So, if you were dynamically displaying the thumbnail image from the ProductPhoto table in the AdventureWorks sample database, allowing a click to redirect to the LargePhoto image in the same table (again, dynamically rendered) and taking a description from the Product table for the Alternative text (OK, from now on I’ll call it Alternate text and grind my teeth), you could use something like

    <asp:SqlDataSource ID="ProductPhotos"
     
    runat="server"
      ConnectionString="<%$ ConnectionStrings:AdventureWorksConnectionString %>"
      SelectCommand="SELECT Production.ProductPhoto.ProductPhotoID AS ID,
       
    './ProductThumbNail.ashx?ID=' + LTRIM(STR(Production.ProductPhoto.ProductPhotoID)) AS ImageUrl,
        './ProductPhoto.ashx?ID=' + LTRIM(STR(Production.ProductPhoto.ProductPhotoID)) AS NavigateUrl,
        Production.Product.Name AS AlternateText
        FROM Production.ProductPhoto
        INNER JOIN Production.ProductProductPhoto
          ON Production.ProductPhoto.ProductPhotoID = Production.ProductProductPhoto.ProductPhotoID
        INNER JOIN Production.Product
          ON Production.ProductProductPhoto.ProductID = Production.Product.ProductID">
    </asp:SqlDataSource>

    Which produces a resultset with the 4 required columns. Of course, writing SQL statements like that is much easier when you do it like this:

    QueryBuilder

    The AdRotator control itself then looks like this:

    <asp:AdRotator ID="AdRotator1" runat="server" DataSourceID="ProductPhotos" />

    All of the properties (except the DataSourceID and the name, of course) are left at their default values because I've used the default names for the fields in the DataSource definition.

    XML

    The next way to populate the fields of an AdRotator is to use a static XML file. It has a very similar structure to the schema of the table required for the Database method. All of the details of the schema are available in MSDN (online and offline). There’s an example file included in the accompanying downloads. Here's an extract (of a file called Advertisments.xml):

    
    

    <Ad>
     
    <ImageUrl>~/ProductThumbNail.ashx?ID=70</ImageUrl>
      <
    NavigateUrl>~/ProductPhoto.ashx?ID=70</NavigateUrl>
      <
    AlternateText>Product Number 70</AlternateText>
      <
    Impressions>100</Impressions>
    </
    Ad>

     And the AdRotator looks like this

    
    

    <asp:AdRotator ID="AdRotator2" runat="server" AdvertisementFile="~/Advertisments.xml" />

    Again, the default values work here because I’ve followed the naming conventions.

    Programmatic

    The third (and potentially most interesting from a developers’ point of view, although the database method is pretty cool too) is to populate the properties programmatically whenever the AdRotator AdCreated event fires (occurs once per round trip to the server after the creation of the control, but before the page is rendered). The event handler is passed an AdCreatedEventArgs object. You set the 3 properties, ImageURL, NavigateURL and AlternateText (familiar looking names aren’t they) and they are used to render the control.

    The AdRotator control looks like this:

    
    

    <asp:AdRotator ID="AdRotator3" runat="server" OnAdCreated="AdRotator3_AdCreated" />

    And in the code-behind file (mine's in C#, but VB.NET would work just as well), I just wired up an event handler (via the properties grid in my case, because I'm lazy)

    
    

    protected void AdRotator3_AdCreated(object sender, AdCreatedEventArgs e)
    {
      // Just for this example - see the download for a different way of using ProductID
      int ProductID = 1;
      
    e.ImageUrl =
    "~/ProductThumbNail.ashx?ID=" + ProductID.ToString();
      e.NavigateUrl =
    "~/ProductPhoto.ashx?ID=" + ProductID.ToString();
      e.AlternateText =
    "Programatically generated text for product ID " + ProductID.ToString();
    }

    This is potentially very powerful. You could do real-time image targeting based on the navigation history of this user, based on personalisation they’ve done or any number of other criteria.

    Dynamic Image Generation

    Up until now, I’ve glossed over the fact that the AdRotator class expects URLs pointing to images, not byte streams or arrays of binary data, or anything else that might represent an image. This potentially presents a problem when (as we have in our example), the images are stored as binary data in a database file. You don’t want to have to write them out to disk, where they’ll take up as much space again and need to be kept in sync with the database version. Up until Beta 1 of VS2005, it looked like this was going to be very easy, just use the Dynamic Image Control. Alas, it was dropped between Beta 1 and Beta 2 (see http://msdn.microsoft.com/asp.net/beta2/beta2changes.aspx). Fortunately, that very same page lists some code that can be adapted to do exactly what we need.

    Essentially, we need to create an HTTP Handler class that intercepts calls to a particular URL and returns the image in a format that the browser expects. There are docs on the IHTTPHandler interface (which the specialised dynamic image generation classes in the example implement) in the MSDN docs (online and offline). HTTP handlers give you a means of interacting with the low-level request and response services of the IIS Web server and provide functionality much like ISAPI extensions but with a simpler programming model.

    Here’s the code for one of the Handler classes (ProductThumbNail.ashx). The other one is identical, except a different TableAdaptor is used to retrieve the bigger photo.

    
    

    <%@ WebHandler Language="C#" Class="ProductThumbNail" %>

    using System;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;
    using System.Web;
    using System.Web.Caching;
    using System.Data;
    using System.Data.Sql;
    using System.Web.UI.WebControls;
    using System.Web.Configuration;
    using System.Configuration;
    using System.Collections;

    public class ProductThumbNail : IHttpHandler
    {
     
    public void ProcessRequest (HttpContext context) {

        // Get the image ID from querystring, and use it to generate a cache key.
       
    String imageID = context.Request.QueryString["ID"];
       
    String cacheKey = context.Request.CurrentExecutionFilePath + ":" + imageID;

       
    Byte[] imageBytes;
       
       
    // Check if the cache contains the image.
       
    Object cachedImageBytes = context.Cache.Get(cacheKey);
       
       
    if (cachedImageBytes != null)
        {
          imageBytes = (
    Byte[])cachedImageBytes;
        }
       
    else
       
    {
          GetImageTableAdapters.
    ThumbNailTableAdapter oTA = new GetImageTableAdapters.ThumbNailTableAdapter();
         
    GetImage.ThumbNailDataTable oDT = oTA.GetData(int.Parse(imageID));
          
         
    if (oDT.Rows.Count == 0)
          {
           
    // MAGIC NUMBER!
           
    // Photo ID 1 displays "No Image Available" image
           
    oDT = oTA.GetData(1);
          }
          
         
    imageBytes = (
    byte[])oDT.Rows[0]["ThumbNailPhoto"];
          
         
    // Store it in the cache (to be expired after 2 hours).
         
    context.Cache.Add(cacheKey, imageBytes, null
           
    DateTime.MaxValue, new TimeSpan(2, 0, 0), 
           
    CacheItemPriority.Normal, null);
          
        
    }
         
        
    // Send back image.
        
    context.Response.ContentType = "image/jpeg";
        context.Response.Cache.SetCacheability(
    HttpCacheability.Public);
        context.Response.BufferOutput =
    false;
        context.Response.OutputStream.Write(imageBytes, 0, imageBytes.Length);
      }
      
      
    public bool IsReusable {
       
    get {
         
    return false;
        }
      }
    }

    The important method to implement in the IHTTPHandler interface is ProcessRequest(). This gives you low-level access to the context object which, in turn, gives to access to the Request and Response objects so you can get the ID passed as a query string in the URL (using Request) and shove stuff back out with the Response object. The stuff to shove back out is just a byte array grabbed from the binary column in the table. Notice, I’ve just grabbed the contents of the ThumbNailPhoto column in the first row and cast it to a byte array.

    To send the image back, just set the content type (note that this could come from the table as well, or could be dynamically determined from the header in the binary if required), tell the Response object just to stream the bits back, don't wait for the stream to close, and then just write the byte array to the output stream.

    A couple of other things to note here (and I’m not claiming they’re best practice – comments always welcome):

    Using a DataSet to retrieve data from the server

    Notice that instead of programatically creating connections and commands, I’ve opted to create a dataset (GetImage.xsd in the App_Code folder) with a couple of table adaptors. The naming convention adopted by ASP.NET gets me every time I use these. The TableAdaptors are in the <DataSetName>TableAdaptors namespace and aren’t available to intellisense until you’ve built the project after the xsd is created. The data tables are classes of the <DataSetName> class. So in this case The GetImageTableAdaptors namespace contains the ThumbNailTableAdaptor and the ProductPhotoTableAdaptor classes. The GetImage class has two properties – instances of the ThumbNail and ProductPhoto classes.

    Configuring these datasets is heaps easier than remembering how to use them. Just add a DataSet to the project and the first TableAdaptor is added automatically. I added a very simple SQL statement with a parameter (the ProductPhotoID) to return a single row. To add the other, just right-click on the xsd design surface and choose Add TableAdaptor.

    Note that I’ve opted for the very simplest Table Adaptors – no INSERT, UPDATE or DELETE clauses. These are only ever designed to be read-only.

    Caching Images

    This bit was already in the code on the MSDN site about the DynamicImageControl being pulled, but I left it in there because it seemed like such a good idea. The cache engine takes a key to see whether it has already looked up the image and stored it for future use. In this case I’ve left the cache duration at 2 hours. Obviously, you’d massage this based on how often images were likely to change.

  • Andrew Coates ::: MSFT

    TechEd ANZ Call for Content Update

    • 13 Comments

    We’ve had a great response to the TechEd ANZ Call For Content we announced a couple of weeks ago. Here’s the submission count as of this morning:

    Track Breakout BoF Total
    ARC 4 0 4
    AZU 2 1 3
    DEV 25 4 29
    DYN 3 0 3
    MOB 4 0 4
    OFC 31 4 35
    SEC 2 0 2
    SOA 2 1 3
    DAT 12 1 13
    UC 7 0 7
    VIR 2 0 2
    WEB 11 1 12
    WCS 10 1 11
    Totals 115 13 128

    I’ve had a couple of questions around this and while I’ve answered them directly, I’ll also post the answers here.

    When do submissions close?

    The answer is "it depends". Track owners will close off their tracks for submissions when they feel like they’ve got enough submissions. Having said that, we’ll leave all tracks open until at least the 1st of June, so you’ve still got a week or so as a minimum.

    What’s the difference between a Breakout Session and a Birds of a Feather Session?

    A breakout session is one of the main sessions that are held during the conference. There are 9 rooms available in each of 15 time slots across the 3 days. You can expect anywhere between 20 and 1000 people at a breakout session. The content and presenters for these sessions are determined by the track owners.

    Birds of a Feather sessions are designed to be informal gatherings of like-minded people. This is a community-driven session held outside the timeslots for breakouts. It's likely that there will be perhaps as many as 10 BoF sessions and they’ll be scheduled for lunchtimes and perhaps on Wednesday evening. We're planning to make the BoF selection something that attendees can contribute to. The current proposal (subject to change, no promises etc) is to publish a list of the submissions and have people vote.

    How Many Submissions Will You Accept?

    There are only 135 slots for TechEd this year and so there are obviously going to be many more public submissions than available slots. There's no hard number we're aiming for, but please don’t be too disappointed if your session isn't selected this time. One of the things I’d love to do though is publicise the sessions that weren't selected with a view to local User Groups picking them up as presentations through the year. I won’t do that without the submitter's permission though.

    Are You Paying Presenters?

    No – there's been some confusion because the link from my original post to the rules for submitting content was a generic link that triggered some "smart" stuff server-side to determine where you were submitting from and displayed the rules appropriate to that region. A number of people got the TechEd North America rules (it explicitly said that at the top of the page) and they do have a stipend for speakers. We don't have the budget for that.

    So there's no more confusion, here are the rules for submitting content for TechEd Australia and New Zealand.

  • Andrew Coates ::: MSFT

    Document Creation and Conversion with the OpenXML SDK and SharePoint 2010 Word Automation Services – Part 2

    • 0 Comments

    A long time ago, I wrote Part 1 of this post, based on the presentation I did at the 2010 SharePoint Conference in Sydney. If you're following along with the code, you may want to review that post so you can set up your development environment to match mine.

    To quickly recap, the last post showed how to create Word (OpenXML, docx) documents programmatically and write them to disk using the OpenXML SDK (and therefore without the requirement for Word/Office on the machine creating the documents).

    In this part, I'll extend the solution to write the documents to a document library in SharePoint and then use Word Automation Services to automatically convert the docx files to PDF format.

    Setup

    To follow along with this walkthrough without changes, setup your SharePoint instance (at http://localhost) as follows:

    • Create a document library called "Created Docs" in the root site
    • Create a document library called "Converted Docs" in the root site

    If you use a remote server, and/or a different site or library names, you'll need to adjust some of the URI and path strings in the code below to make it work.

    Writing to a SharePoint Library

    Carrying on from last time, wire up the click event of the CreateOneSharePointDocumentButton:

    Click event handler
    1. private void CreateOneDocumentOnSharePointButton_Click(object sender, EventArgs e)
    2. {
    3.     gen.CreateOneDocumentOnSharePoint();
    4. }

    Generate a stub for the CreateOneDocumentOnSharePoint() method in the DocGenerator class using the Ctrl+. technique made possible by the Visual Studio 2010 Productivity Power Tools.

    Switch to and add a using statement to the DocGenerator class to give you access to the SharePoint Client Libraries - giving it an alias will help disambiguate the File class later:

    1. using SPC = Microsoft.SharePoint.Client;

    Using the SharePoint Client Libraries, it's very easy to write documents to a document library, and there's no need to write a document to a local drive. This means we'll use a different overload of the OpenXML SDK's WordprocessingDocument.Create() method that writes, not to a file, but to a MemoryStream.

    CreateOneDocumentOnSharePoint
    1. internal void CreateOneDocumentOnSharePoint()
    2. {
    3.   SPC.ClientContext clientContext = new SPC.ClientContext("http://localhost");
    4.   string fileUrl = "/Created Docs/MyVeryVeryCoolDoc.docx";
    5.  
    6.   sw.Reset();
    7.   sw.Start();
    8.  
    9.   using (MemoryStream ms = gen.CreatePackage())
    10.   {
    11.     ms.Seek(0, SeekOrigin.Begin);
    12.     SPC.File.SaveBinaryDirect(clientContext, fileUrl, ms, true);
    13.   }

    In this code, you create a new SharePoint.Client.ClientContext that gives access to the site (in this case at http://localhost, but if you've got things set up differently, change it here).

    Create an overload of the CreatePackage() method in the DocumentCreator class that creates and populates a MemoryStream:

    CreatPackage Overload
    1. internal MemoryStream CreatePackage()
    2. {
    3.  
    4.   MemoryStream ms = new MemoryStream();
    5.   using (WordprocessingDocument package =
    6.     WordprocessingDocument.Create
    7.     (ms, WordprocessingDocumentType.Document))
    8.   {
    9.     CreateParts(package);
    10.   }
    11.  
    12.   return ms;
    13.  
    14. }

    Move the pointer to the start of the MemoryStream and call the File.SaveBinaryDirect() method passing in the ClientContext, a string indicating where the file should be written, the stream and a boolean that tells SharePoint whether or not to overwrite an existing file with the same name.

    Running the app and clicking the One document in SharePoint button shows that it's very fast - in my case 102ms

    Writing 1 document to SharePoint took 0.1 seconds

    Writing lots of documents is fast too - add an event handler to the CreateOneSharePointDocumentButton:

    Click Event Handler
    1. private void CreateManyDocumentsOnSharePointButton_Click(object sender, EventArgs e)
    2. {
    3.   gen.CreateManyDocumentsOnSharePointInParallel((int)NumberOfDocumentsToCreate.Value);
    4.  
    5. }

    And add a CreateManyDocumentsOnSharePointInParallel() method that uses a Parallel.For() loop to call CreatePackage() and File.SaveBinaryDirect() for as many files as you create:

    Create lots of SharePoint Docs
    1. internal void CreateManyDocumentsOnSharePointInParallel(int NumberOfDocs)
    2. {
    3.   SPC.ClientContext clientContext = new SPC.ClientContext("http://localhost");
    4.   string fileUrl = "/Created Docs/MyEvenCoolerDoc{0:D5}.docx";
    5.  
    6.   sw.Reset();
    7.   sw.Start();
    8.  
    9.   Parallel.For(0, NumberOfDocs, i =>
    10.   {
    11.  
    12.     using (MemoryStream ms = gen.CreatePackage())
    13.     {
    14.       ms.Seek(0, SeekOrigin.Begin);
    15.       SPC.File.SaveBinaryDirect(clientContext, string.Format(fileUrl, i), ms, true);
    16.     }
    17.  
    18.   });
    19.  
    20.   sw.Stop();
    21.  
    22.   System.Windows.Forms.MessageBox.Show(string.Format(
    23.       "Wrote {3} documents to SharePoint ({1}{2}) in {0} ms (using parallel processing)",
    24.       sw.ElapsedMilliseconds,
    25.       clientContext.Url,
    26.       fileUrl,
    27.       NumberOfDocs));
    28.  
    29. }

    This is also pretty fast - in my case 40ms per document.

    Writing 100 documents to SharePoint (in parallel) took 4 seconds

    Navigating to the document library shows all those documents sitting just where you'd expect to see them:

    Cool documents created en-masse

    Converting Word Documents to a Fixed Format (PDF or XPS)

    PDFUp until now, we've not had to use Word (or any other Office client) as all we've been doing is generating documents, not rendering them. Just like you can create an HTML document without requiring a browser, it's perfectly valid to create a Word document (or any other OpenXML format document) without using Word.

    However, to view the document, or to create a fixed version of it like PDF or XPS, it's necessary to render it. Up until the release of SharePoint 2010, the highest fidelity way to do this was to open the document in Word. Of course, doing that on the server was fraught with difficulty. Word is not designed to be a server-side tool - it throws (sometimes modal) dialogs, it spends a lot of resources on updating the screen and it's not optimised for multi-processor, large memory scenarios. When there is a user interacting with Word though, the bottleneck is rarely the computer.

    The SharePoint team addressed this problem with the Word Automation Services feature in SharePoint 2010 (standard edition and higher). Word Automation Services is the client code from Word with the UI bits stripped out and optimised to run as a server process. All of the rendering engine is available for SharePoint to use without any of the issues (both technical and from a licensing point of view) of using Word on a server. There's lots of great info on Word Automation Services on MSDN and elsewhere. Here's the list of resources I provided in the first post in this series:

    Word Automation Services (WAS) document conversion jobs run as as an asynchronous server-side job that can either be scheduled automatically (for example, when a document is placed in a folder) or programmatically. Either way, the job won't start immediately, just the next time the WAS scheduler runs. The frequency of the scheduler running is set in Central Administration - see the links above for details on how to set it up. I set it to the minimum interval - one minute.

    Interacting programmatically with the service is pretty straightforward, but there are two gotchas:

    1. the .NET libraries are 3.5 only, so the project you create must be a .NET 3.5 project, and
    2. the calls will fail (with cryptic exceptions) if it's not a 64-bit call, so you must target either x64 or Any processor type, not x86.

    Create a new console application and make sure that the target framework is 3.5.

    Create a new console application targetting Framework 3.5

    Open the Visual Studio Configuration Manager dialog by dropping down the Solution Configurations drop-down on the Visual Studio Standard toolbar (or choosing Configuration Manager from the Build menu):

    Choose Configuration Manager

    Next, add a Solution Platform:

    image

    to target Any CPU (or x64)

    image

    Now you're ready to start building.

    Converting a single document to PDF

    Add references to the Microsoft.SharePoint and Microsoft.Office.Word.Server assemblies.

    Add using statements for those assemblies:

    1. using Microsoft.SharePoint;
    2. using Microsoft.Office.Word.Server.Conversions;

    Add a couple of static string properties to the class that you can adjust to suit the way you've got your SharePoint setup configured:

    1. // If you manually installed Word Automation Services, then replace the name
    2. // in the following line with the name that you assigned to the service when
    3. // you installed it.
    4. static string cWordServicesName = "Word Automation Services";
    5. static string siteUrl = "http://localhost";

    Now you can initiate the conversion of a single document:

    Convert a single document
    1. private static void SingleConv()
    2. {
    3.   using (SPSite spSite = new SPSite(siteUrl))
    4.   {
    5.     ConversionJob job = new ConversionJob(cWordServicesName);
    6.     job.UserToken = spSite.UserToken;
    7.     job.Settings.UpdateFields = true;
    8.     job.Settings.OutputFormat = SaveFormat.PDF;
    9.     job.AddFile(siteUrl + "/Created%20Docs/MyAwesomeDoc.docx",
    10.         siteUrl + "/Converted%20Docs/MyAwesomeDoc.pdf");
    11.     job.Start();
    12.     Console.WriteLine("Job ID: {0} started", job.JobId);
    13.     Console.WriteLine("Press the any key ...");
    14.     Console.ReadKey();
    15.   }
    16. }

    There are a few things to note here.

    Firstly, you get a reference to the Site using the SharePoint libraries, not the SharePoint Client libraries that we used to write the Word docs to the list in the first place.

    Next, you need to pass a user token to the new ConversionJob, and you get that from the SPSite user token.

    Third, you specify the output format using the SaveFormat enumeration.

    Finally, remember the service is performed asynchronously and so although you get a Job ID back, you don't get any more information about the job status (more on that when we do bulk conversions)

    Converting documents to PDF en-masse

    Converting whole libraries at once is also very easy. The ConversionJob class has an AddLibrary() method that takes as parameters a source and destination SPList object.

    Converting whole libraries
    1. private static void BulkConv()
    2. {
    3.   using (SPSite spSite = new SPSite(siteUrl))
    4.   {
    5.     Console.WriteLine("Starting conversion job");
    6.     ConversionJob job = new ConversionJob(cWordServicesName);
    7.     job.UserToken = spSite.UserToken;
    8.     job.Settings.UpdateFields = true;
    9.     job.Settings.OutputFormat = SaveFormat.PDF;
    10.     job.Settings.OutputSaveBehavior = SaveBehavior.AlwaysOverwrite;
    11.     SPList listToConvert = spSite.RootWeb.Lists["Created Docs"];
    12.     SPList listToPopulate = spSite.RootWeb.Lists["Converted Docs"];
    13.     job.AddLibrary(listToConvert, listToPopulate);
    14.     job.Start();
    15.     Console.WriteLine("Bulk conversion job {0} started", job.JobId);
    16.     ConversionJobStatus status = new ConversionJobStatus(cWordServicesName,
    17.         job.JobId, null);
    18.     Console.WriteLine("Number of documents in conversion job: {0}", status.Count);
    19.     while (true)
    20.     {
    21.       System.Threading.Thread.Sleep(5000);
    22.  
    23.       status.Refresh();
    24.       if (status.Count == status.Succeeded + status.Failed)
    25.       {
    26.         Console.WriteLine("{2} Completed, Successful: {0}, Failed: {1}",
    27.             status.Succeeded, status.Failed, DateTime.Now);
    28.         break;
    29.       }
    30.       Console.WriteLine("{2} In progress, Successful: {0}, Failed: {1}",
    31.           status.Succeeded, status.Failed, DateTime.Now);
    32.     }
    33.  
    34.     Console.ReadKey();
    35.   }
    36. }

    Checking the status of the job is straightforward (as long as you have the JobId - a GUID uniquely identifying this conversion job). The ConversionJobStatus object holds information about the conversion job including how many documents are to be converted, how many have been converted successfully and how many have failed. Calling the Refresh() method gets the most up-to-date status and you can use that to poll for completion. Remember that jobs only start every <n> minutes, where n is a setting in SharePoint Central Administration

    Converting documents is an asynchronous process

    The result is a SharePoint list full of PDF files, created without ever needing to open Word.

    A library full of converted PDFs

    A converted PDF in Adobe Reader

    Conclusion

    The combination of the OpenXML SDK and Word Automation Services makes server-side document creation simple, scalable and efficient. This is definitely a tool worth adding to your arsenal.

    Source Code

    I've zipped up the two solutions - the document creation (.NET 4.0) WinForms project and the document conversion (.NET3.5) project for you to download and play with. Notice that they are NOT production ready - they're illustrative only. Use them at your peril, your mileage may vary, contents may be hot no guarantees etc … you know the drill.

    Document Creation Solution Download (241kB)
    Document Conversion Solution Download (115kB)

  • Andrew Coates ::: MSFT

    VB6 Runtime Supported in Windows 7

    • 2 Comments

    Just got this note from Paul Yuknewicz. There’s an update to the VB6 support page on MSDN. In summary:

    Since the initial release of this support statement, the Windows 7 operating system has been announced.  This document has been updated to clarify Microsoft’s support for VB6 on Windows 7.

    VB6 runtime will ship and will be supported in Windows 7 for the lifetime of the OS.  Developers can think of the support story for Vista being the same as it is for Windows 7.  However there are no plans to include VB6 runtime in future versions of Windows.

  • Andrew Coates ::: MSFT

    Windows 7 on my Mac Mini

    • 5 Comments

    Back in the day, Dave and I bought (intel) Mac Minis (there was a retail outlet which had apparently decided to stop selling them and they were going pretty cheap). It’s been my main Vista machine at home for 18 months or so now (running bootcamp), I never boot into OSX, the machine was a member of my SBS domain and all was well with the world. Of course, I just can’t leave well enough alone (in addition to the fact that I’d “wasted” 40 of the 80GB HD on an OSX partition that I never use). I decided to take the plunge and see what the Win7 experience was like.

    First I tried an upgrade – running setup.exe from the DVD and choosing the upgrade option. Initial results were poor because of a lack of drive space on the Win partition,  but even after getting the available space up to 10GB (lose VS, Office and some games), the setup would get most of the way through and then just roll back. By the way, the rollback experience is great – there’s nothing left of the install afterwards except a note on the desktop as to what’s gone wrong – Vista is as it’s always been.

    Today I took the plunge and started a clean install.

    I inserted a Win7 x86 beta DVD, shutdown Vista, held down the alt key and turned the Mini on. After some time (after I released the alt key), the mac boot screen came up offering to boot from either of the partitions on the hard drive or the DVD. I chose the DVD and the rest was exactly like any other Win7 install experience I’ve had to date, that is to say seamless. Within 30 min I was at a Win7 desktop starring the Beta fish and was informed that Windows was already 65% of the way through downloading 4 updates. One more restart and I was in business.

    There are three devices showing up as problems in Device Manager, one USB device, the TPM and “Performance Counters” in the Other Devices node.

    No mucking around with bootcamp drivers, none of the insidious Apple updates.

    Next, I downloaded and installed Windows Live Essentials, and then I’ll do the rest of my standard install stuff – Office2007, VS2008, Acrobat Reader etc.

    Win7 Beta on Mac Mini – Big Thumbs-Up from me.

  • Andrew Coates ::: MSFT

    Virtual Earth - Now available for Commercial Use

    • 10 Comments

    Steve Lombardi’s VE Session has just finished and contained some cool announcements.

    Now available for commercial use.

    First and foremost, VE is now available for commercial use. The nitty-gritty details are available from the virtual earth developer site (www.viavirtualearth.com) but, in essence, if you leave the What and Where fields visible, you can use the control provided for a commercial site for free. In Jan 2006, there will be an option to pay for use and remove those controls.

    Steve’s working on a demo site for this technology called www.MyFavouritePlaceOnEarth.com – catchy huh? It’s not live yet, but when it is, it’ll allow you to mark a spot anywhere on the earth (or, more accurately at the moment, anywhere in the continental US) as a “significant site” and decorate it with the metadata to say why.

    More info on current features

    Steve explained how the Locate Me feature works. It finds all of the WiFi hot spots it can (whether or not it can connect to them) and their signal strength. It looks up their MAC address on a database they have server-side and does a triangulation to work out your location to within “50 to 100 feet”.

    There was another feature of the current release that I hadn’t twigged to – the ability to email or blog from your scratch pad. Check it out. It’s pretty neat.

    Beta 2 – Coming Soon

    I mentioned above that the data are only available for the US. This one of the things that will be remedied in the next release (called beta 2 and due out “this fall”). The new features of VE in this release that Steve disclosed today are:

    • Eagle Eye image view – photos taken from fairly low altitude at a 45° angle (as opposed to the 90° that the imagery currently uses). This makes for a really neat perspective view.
    • User pushpins and collections
    • Improved aerial and satellite imagery (of the 90° kind)
    • Driving directions
    • New content types – Traffic, Movies etc
    • Mobile – send to phone. This looks like you’ll be able to send details of a location or locations to your mobile (or someone else’s). Imagine being able to send a “meet me here …” SMS.
    • The big one (for me) is the geographic expansion – currently planned to be England and perhaps some more of Europe. More and more data will come on line as the team source and massage the data.

     VE and MapPoint Web Services – better together

    Today, VE and MWS are a great team (geolocate and address with the MWS API and chuck it up on VE, for example). In January, it will be even easier to combine their strengths as they will pretty much be merging around the time of MWS 4.0.

    Useful Sites

    www.virtualearth.com

    www.viavirtualearth.com – check out the gallery of VE applications

    http://www.viavirtualearth.com/MyVirtualEarth/MapControlTest.html – this is a testbed where you can play with the whole VE API. Well worth a look.

     

     PDC2005

Page 1 of 47 (469 items) 12345»