•  
    March 7th, 2012stuartwhiteforddbFit, FitNesse, TeamCity

    Introduction

    A slight change in tack for this post. I’ve been getting more involved in continuous integration and testing recently (which I’ve decided is a good thing) and two of the tools we’ve been using are TeamCity (which I’ve also decided is a good thing) and FitNesse, more specifically dbFit (which I’m still undecided on). The relative pros and cons of each are, thankfully, out with the scope of this post.

    What I will be showing is how you can run a FitNesse test suite from a TeamCity build configuration, with the individual test results available in the Tests tab of the build and the FitNesse test report in HTML as a build artifact. I’d like to show more screenshots than I’m about to but our TeamCity installation contains a fair amount of client names, and I can’t be bothered to blur them all out in Fireworks. Also there’s more red than there should be on the Overview page.

    Assumptions and Pre-requisites

    • You’ve been working with both TeamCity and FitNesse or at least know how they work.
    • You have an existing TeamCity installation with a project and a build configuration that you can modify.
    • You have an existing FitNesse installation that’s callable from your TeamCity server.
    • You have the TestRunner.exe for FitNesse, found in the dotnet folder, provided you have it.
    • You’re using TeamCity 6.5.6 or can figure out the equivalent steps for your version.

    Instructions

    First up, download the junit.xslt file and place it in your FitNesse dotnet folder.

    Open up your TeamCity home page. Click on the relevant project, click the Edit Project Settings link and then select the Parameters tab. We’re going to add some environment variables specific to the FitNesse environment. Add four Environment Variables with the following names and values.

    Name Value
    env.fitnesse.lib The path to the directory containing the TestRunner.exe. e.g. C:\Fitnesse\lib\dotnet2
    env.fitnesse.port The port number that your FitNesse server is on. e.g. 8085
    env.fitnesse.server The name of your FitNesse server. e.g. fitvm01
    env.fitnesse.suite The full name of the test suite you want to execute. e.g. FitNesse.StuartWhiteford.TestSuite

    Now, select the General tab and then click the Edit link next to your chosen build configuration. On the General Settings page add the following line to the Artifacts path field.

    %system.teamcity.build.checkoutDir%\dbfit.results.html

    Our build step will ensure that the FitNesse results are saved as dbfit.results.html to the checkout directory.

    Next, click on the Build Step(s) menu item for the build configuration and click the Add build step link. In the New build step page select Command Line as the runner type and enter a sensible name for the step (Run FitNesse Tests). In the working directory enter

    %env.fitnesse.lib%

    , select Custom script in the Run field and finally enter the following lines in the Custom script field.

    TestRunner.exe -results %system.teamcity.build.checkoutDir%\dbfit.results %env.fitnesse.server% %env.fitnesse.port% %env.fitnesse.suite%
    java -cp ..\fitnesse.jar fitnesse.runner.FormattingOption %system.teamcity.build.checkoutDir%\dbfit.results xml %system.teamcity.build.checkoutDir%\dbfit.results.xml %env.fitnesse.server% %env.fitnesse.port% %env.fitnesse.suite%
    java -cp ..\fitnesse.jar fitnesse.runner.FormattingOption %system.teamcity.build.checkoutDir%\dbfit.results html %system.teamcity.build.checkoutDir%\dbfit.results.html %env.fitnesse.server% %env.fitnesse.port% %env.fitnesse.suite%
    java com.sun.org.apache.xalan.internal.xsltc.cmdline.Compile %env.fitnesse.lib%\junit.xslt
    java com.sun.org.apache.xalan.internal.xsltc.cmdline.Transform %system.teamcity.build.checkoutDir%\dbfit.results.xml junit > %system.teamcity.build.checkoutDir%\dbfit.results.junit.xml

    Line by line, this script will perform the following actions:

    1. Call the TestRunner executable outputting the results to a file called dbfit.results in the checkout directory, passing in the FitNesse server, port and test suite to run. This is the command that actually runs the tests.
    2. Format the dbfit.results output as XML. This will allow us to see the status of each of the tests in the build.
    3. Format the dbfit.results output as HTML. This will become the FitNesse test report artifact for the build.
    4. Compile an XSLT file that we will use to transform the FitNesse XML to JUnit XML (a format that TeamCity understands).
    5. Perform the transform, saving the results as dbfit.results.junit.xml in the checkout folder.

    Your build step page should look something like the following.

    Build Step

    Click the Save button to save the new build step. Back on the build steps page click the Add build feature link. In the dialogue select XML report processing as the feature type and Ant JUnit as the report type. In the Monitoring rules fields enter

    %system.teamcity.build.checkoutDir%\dbfit.results.junit.xml

    and lastly check the Verbose output field. Click the Save button.

    If your build is that’s one that can be run manually go ahead and run it, then watch the progress on the Overview page.

    Running Build

    Once the build has completed, click on the Tests passed (hopefully) link and then on the Tests tab to view the status of the individual tests within the FitNesse suite.

    Tests

    To view the FitNesse test ouput page, click on the Artifacts tab. You should then see a link to dbfit.results.html. You can also get to here from the Artifacts context menu in the Overview page.

    Artifacts
    FitNesse

    That’s all there is to it. Note that you’re not limited to a single build step and test suite with this method. You could run a single test with one build step; have multiple build steps running a single test or an entire suite.

    Conclusion

    I’m still unconvinced with FitNesse and dbFit as a testing framework, perhaps because I’ve also spent much time recently with WatiN and Selenium, but at least now it’s part of our big happy CI family. Just.

    Tags: , , , ,
  •  
    June 16th, 2011stuartwhiteford.NET, C#, SharePoint

    It’s been about 15 months since my last post, the main reason for the delay would be my 14 month old son :-).

    I’ve been working primarily with event and feature receivers in MOSS 2007 for the last few weeks and this post will describe some of the issues that I encountered and their solutions (or workarounds) if they exist mainly for my own benefit, as my memory doesn’t seem to be what it used to, but I wouldn’t be unhappy if it helps someone else out.

    VseWSS Project Template Grumps

    These are generally fairly good. I use them as much as possible as it cuts down on the amount of bolierplate code you need to type and on the amount of faffing getting the right GUIDs in the feature.xml, element manifest, class attributes, etc.. However they’re not perfect by any means, and for me it’s becasue of the following reasons:

    • You get item and list event receivers but not an e-mail receiver. Now I don’t suppose they’re all that common but the option to add one in after the project has been created would be nice, we can at least do that with feature receivers albeit with a small amount of faffing (see next point).
    • To me the point of adding a feature receiver to a list definition project would be to have it’s code run when you activate/deactivate the list or just as likely an instance of the list, but when you add one you need to manually add the ReceiverClass and ReceiverAssembly attributes to the feature.xml of the feature you want the code to run against (including getting the public key token of the assembly).
    • In the WSP View, after you’ve made some changes to the project the reference to the Receiver class file goes AWOL and never comes back. You can obviously still get to it in the Solution Explorer but it’s just a bit annoying, particularly when it used to be there.
    • In the list definiton schema.xml file you add your custom fields, set the ShowInNewForm and ShowInEditForm attributes to TRUE, package and deploy the solution and when you create a new item from the list your field doesn’t display. The way round this is remove the element from the schema (as the built-in Item content type doesn’t have your custom fields). This one isn’t that big a deal but I still spent about half an hour on Google finding out why the fields were not displaying.

    E-mail Event Receiver and the Windows SharePoint Services Timer Service

    After implementing the e-mail event receiver, packaging and deploying you notice there’s a problem with the execution, so you deactivate and uninstall the feature, retract and delete the solution, modify the code, re-package and re-deploy. Make the receiver fire again and the same problem exists. The reason for this is that the receiver code is executed by the OWSTIMER process (the Windows SharePoint Services Timer Service) and it doesn’t use the shiny new DLL you just deployed to the GAC, it creates it own local copy and won’t use the updated assembly until the service is restarted, so at the command line:
    net stop “Windows SharePoint Services Timer”
    net start “Windows SharePoint Services Timer”
    and you should see that the the receiver uses the updated DLL.

    Enable Incoming E-mail on Document Library on Feature Activation

    I’ve saved the worst for last, this one caused me no end of pain. I have a list definition project that sets up a library based on a document library, the project has the list definition, list instance, item and list event receivers. I subsequently added a feature receiver for the purpose of enabling incoming e-mail on the library and setting the e-mail alias. Simple enough looking code to do this (if you need it you can find it here). However when I deployed the solution (using STSADM) I noticed an error message in the command window: “Error in the application”. Wow Microsoft, you’ve really excelled yourself with the verbosity of that one! Looking at the log file there was an exception:
    {xxxx.xxxx.SharePoint.Lists.xxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.FeatureActivated} : Exception Message : Error in the application., Exception Stack Trace : at Microsoft.SharePoint.SPList.UpdateDirectoryManagementService(String oldAlias, String newAlias) at Microsoft.SharePoint.SPList.Update(Boolean bFromMigration) at Microsoft.SharePoint.SPList.Update()….

    UpdateDirectoryManagementService? So it failing when trying to set the e-mail alias. OK so a bit of Googling and I’ve found this thread, which says that unless you run the code using an Application Pool account it plain won’t work. So we need to ask clients to log in using an Application Pool account to be able to install the feature. I think not…

    Enter the RunAsSystem method.

    If you use the SharePoint object model a lot you’ll be familiar with the RunWithElevatedPrivileges method, the RunAsSystem uses the same principal but in my opinion in a cleaner way. It’s been about 4 or 5 months since I originally started this admittedly short post so I have forgotten where I found this class, but here it is, and if I find out from whence it came I’ll get round to editing this post. Anyway, to use this create a method that taks an SPSite object as a parameter that you want to run using an elevated account and call it thusly: site.RunAsSystem(MyMethod);

     
        public static class SPSiteExtensions
        {
     
            public static SPUserToken GetSystemToken(this SPSite site)
            {
                SPUserToken token = null;
                bool tempCADE = site.CatchAccessDeniedException;
                try
                {
                    site.CatchAccessDeniedException = false;
                    token = site.SystemAccount.UserToken;
                }
                catch (UnauthorizedAccessException)
                {
                    SPSecurity.RunWithElevatedPrivileges(() =>
                    {
                        using (SPSite elevSite = new SPSite(site.ID))
                            token = elevSite.SystemAccount.UserToken;
                    });
                }
                finally
                {
                    site.CatchAccessDeniedException = tempCADE;
                }
                return token;
            }
     
            public static void RunAsSystem(this SPSite site, Action<SPSite> action)
            {
                using (SPSite elevSite = new SPSite(site.ID, site.GetSystemToken()))
                    action(elevSite);
            }
     
            public static T SelectAsSystem<T>(this SPSite site, Func<SPSite, T> selector)
            {
                using (SPSite elevSite = new SPSite(site.ID, site.GetSystemToken()))
                    return selector(elevSite);
            }
     
            public static void RunAsSystem(this SPSite site, Guid webId, Action<SPWeb> action)
            {
                site.RunAsSystem(s => action(s.OpenWeb(webId)));
            }
     
            public static void RunAsSystem(this SPSite site, string url, Action<SPWeb> action)
            {
                site.RunAsSystem(s => action(s.OpenWeb(url)));
            }
     
            public static void RunAsSystem(this SPWeb web, Action<SPWeb> action)
            {
                web.Site.RunAsSystem(web.ID, action);
            }
     
            public static T SelectAsSystem<T>(this SPSite site, Guid webId, Func<SPWeb, T> selector)
            {
                return site.SelectAsSystem(s => selector(s.OpenWeb(webId)));
            }
     
            public static T SelectAsSystem<T>(this SPSite site, string url, Func<SPWeb, T> selector)
            {
                return site.SelectAsSystem(s => selector(s.OpenWeb(url)));
            }
     
            public static T SelectAsSystem<T>(this SPWeb web, Func<SPWeb, T> selector)
            {
                return web.Site.SelectAsSystem(web.ID, selector);
            }
     
        }
    Tags: ,
  •  
    March 22nd, 2010stuartwhiteford.NET, C#, JavaScript, SharePoint, Silverlight

    Introduction

    In Part 1 we created a Silverlight control that enabled us to add pushpins to a Bing Map using JavaScript. In this part we’ll use that ability to create a web part that will connect to a SharePoint list of location data and render that data as pushpins on the map.

    The SharePoint Web Part

    I developed the Silverlight control on my local machine so will be switching to a VM with SharePoint and the VSeWSS 1.3 extensions installed for this part. Open up Visual Studio and create a new SharePoint Web Part project. I’ve called mine BingMapsWebPart because it’s late at night and I can’t think of anything else. I’ve also renamed the web part class to BingMap.

    To render the Silverlight control within the web part we need to emit the same markup that we had in the Test Page created for us in Part 1. for cleanliness I’ve stored most of the HTML tags, attribute strings and their values in a Constants class (you don’t have to of course). The Constants class has been added to the project and looks like the following: -

    public class Constants
    {
     
        public const string Id = "id";
        public const string Data = "data";
        public const string Type = "type";
        public const string Name = "name";
        public const string None = "none";
        public const string Value = "value";
        public const string Width = "width";
        public const string Height = "height";
        public const string Border = "border";
        public const string Hidden = "hidden";
        public const string Visibility = "visibility";
     
        public const string HtmlDiv = "div";
        public const string HtmlObject = "object";
        public const string HtmlParam = "param";
        public const string HtmlIFrame = "iframe";
     
        public const string DivId = "silverlightControlHost";
     
        public const string ObjectData = "data:application/x-silverlight-2,";
        public const string ObjectType = "application/x-silverlight-2";
     
        public const string IFrameId = "_sl_historyFrame";
     
        public const string ParamSourceName = "source";
        public const string ParamSourceValue = "_layouts/BingMaps/BingMapWebPart.xap";
        public const string ParamOnErrorName = "onError";
        public const string ParamOnErrorValue = "onSilverlightError";
        public const string ParamOnLoadName = "onLoad";
        public const string ParamOnLoadValue = "onSilverlightLoad";
        public const string ParamBackgroundName = "background";
        public const string ParamBackgroundValue = "white";
        public const string ParamWindowlessName = "windowless";
        public const string ParamWindowlessValue = "true";
        public const string ParamMinRuntimeName = "minRuntimeVersion";
        public const string ParamMinRuntimeValue = "3.0.40624.0";
        public const string ParamAutoUpgradeName = "autoUpgrade";
        public const string ParamAutoUpgradeValue = "true";
     
        public const string GetSilverlightLink = "http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0";
        public const string GetSilverlightImage = "http://go.microsoft.com/fwlink/?LinkId=108181";
        public const string GetSilverlightAltText = "Get Microsoft Silverlight";
     
        public const string CssTextDecoration = "text-decoration";
        public const string CssBorderStyle = "border-style";
     
        public const string ScriptKeySilverlight = "silverlight_js";
        public const string ScriptKeySilverlightOnLoad = "silverlightOnLoad_js";
     
        public const string SilverlightScriptSource = "_layouts/BingMaps/Silverlight.js";
     
        public const string HtmlKeyMapLocation = "MapLocation";
     
    }

    The only two you really need to care about in here are ParamSourceValue and SilverlightScriptSource. The former is the relative path to the Silverlight .xap file we created in Part 1 while the latter is the path to the standard Silverlight.js file. In this example I’ve copied both files to the LAYOUTS folder under the 12 hive on the SharePoint box. If you planning to put the files somewhere else be sure to update the values in the Constants class.

    Back in our BingMap class set up some private and public properties: -

    private Panel _controlHost;
    private string _latitudeColumn = "Latitude";
    private string _longitudeColumn = "Longitude";
    private string _titleColumn = "LinkTitle";
    private IWebPartTable _provider;
    private ICollection _tableData;
     
    [WebDisplayName("Latitude Column"),
     WebBrowsable(true),
     Personalizable(PersonalizationScope.Shared),
     WebDescription("The column from the list that stores the Latitude."),
     Category("Map Settings")]
    public string LatitudeColumn
    {
        get { return _latitudeColumn; }
        set { _latitudeColumn = value; }
    }
     
    [WebDisplayName("Longitude Column"),
     WebBrowsable(true),
     Personalizable(PersonalizationScope.Shared),
     WebDescription("The column from the list that stores the Longitude."),
     Category("Map Settings")]
    public string LongitudeColumn
    {
        get { return _longitudeColumn; }
        set { _longitudeColumn = value; }
    }
     
    [WebDisplayName("Title Column"),
     WebBrowsable(true),
     Personalizable(PersonalizationScope.Shared),
     WebDescription("The column from the list that stores the title for the information window."),
     Category("Map Settings")]
    public string TitleColumn
    {
        get { return _titleColumn; }
        set { _titleColumn = value; }
    }

    Above we have private properties for an ASP panel control (we’ll have this run at the server side to enable us to check if the control has already been created), three private properties and their public accessors for setting the columns in the SharePoint list that we want to use for Latitude, Longitude and Title information and two private properties (_provider and _tableData) to allow us to consume the data from the SharePoint list.

    The next stage is to create the markup for the Silverlight control. We’ll do this inside a CreateMapControl method as follows: -

    private void CreateMapControl()
    {
        if (_controlHost == null)
        {
            if (!this.Page.ClientScript.IsClientScriptIncludeRegistered(Constants.ScriptKeySilverlight))
            {
                this.Page.ClientScript.RegisterClientScriptInclude(this.GetType(), Constants.ScriptKeySilverlight, Constants.SilverlightScriptSource);
            }
            _controlHost = new Panel();
            _controlHost.ID = Constants.DivId;
            HtmlGenericControl obj = new HtmlGenericControl(Constants.HtmlObject);
            obj.Attributes.Add(Constants.Data, Constants.ObjectData);
            obj.Attributes.Add(Constants.Type, Constants.ObjectType);
            obj.Attributes.Add(Constants.Width, Unit.Percentage(100).ToString());
            obj.Attributes.Add(Constants.Height, Unit.Percentage(100).ToString());
            HtmlGenericControl paramSource = new HtmlGenericControl(Constants.HtmlParam);
            paramSource.Attributes.Add(Constants.Name, Constants.ParamSourceName);
            paramSource.Attributes.Add(Constants.Value, Constants.ParamSourceValue);
            HtmlGenericControl paramOnError = new HtmlGenericControl(Constants.HtmlParam);
            paramOnError.Attributes.Add(Constants.Name, Constants.ParamOnErrorName);
            paramOnError.Attributes.Add(Constants.Value, Constants.ParamOnErrorValue);
            HtmlGenericControl paramOnLoad = new HtmlGenericControl(Constants.HtmlParam);
            paramOnLoad.Attributes.Add(Constants.Name, Constants.ParamOnLoadName);
            paramOnLoad.Attributes.Add(Constants.Value, Constants.ParamOnLoadValue);
            HtmlGenericControl paramBackground = new HtmlGenericControl(Constants.HtmlParam);
            paramBackground.Attributes.Add(Constants.Name, Constants.ParamBackgroundName);
            paramBackground.Attributes.Add(Constants.Value, Constants.ParamBackgroundValue);
            HtmlGenericControl paramWindowless = new HtmlGenericControl(Constants.HtmlParam);
            paramWindowless.Attributes.Add(Constants.Name, Constants.ParamWindowlessName);
            paramWindowless.Attributes.Add(Constants.Value, Constants.ParamWindowlessValue);
            HtmlGenericControl paramMinRuntime = new HtmlGenericControl(Constants.HtmlParam);
            paramMinRuntime.Attributes.Add(Constants.Name, Constants.ParamMinRuntimeName);
            paramMinRuntime.Attributes.Add(Constants.Value, Constants.ParamMinRuntimeValue);
            HtmlGenericControl paramAutoUpgrade = new HtmlGenericControl(Constants.HtmlParam);
            paramAutoUpgrade.Attributes.Add(Constants.Name, Constants.ParamAutoUpgradeName);
            paramAutoUpgrade.Attributes.Add(Constants.Value, Constants.ParamAutoUpgradeValue);
            HtmlAnchor a = new HtmlAnchor();
            a.HRef = Constants.GetSilverlightLink;
            a.Style.Add(Constants.CssTextDecoration, Constants.None);
            HtmlImage img = new HtmlImage();
            img.Src = Constants.GetSilverlightImage;
            img.Alt = Constants.GetSilverlightAltText;
            img.Style.Add(Constants.CssBorderStyle, Constants.None);
            HtmlGenericControl iframe = new HtmlGenericControl(Constants.HtmlIFrame);
            iframe.Attributes.Add(Constants.Id, Constants.IFrameId);
            iframe.Style.Add(Constants.Visibility, Constants.Hidden);
            iframe.Style.Add(Constants.Height, Unit.Pixel(0).ToString());
            iframe.Style.Add(Constants.Width, Unit.Pixel(0).ToString());
            iframe.Style.Add(Constants.Border, Unit.Pixel(0).ToString());
            a.Controls.Add(img);
            obj.Controls.Add(paramSource);
            obj.Controls.Add(paramOnLoad);
            obj.Controls.Add(paramBackground);
            obj.Controls.Add(paramWindowless);
            obj.Controls.Add(paramMinRuntime);
            obj.Controls.Add(paramAutoUpgrade);
            obj.Controls.Add(a);
            _controlHost.Controls.Add(obj);
            _controlHost.Controls.Add(iframe);
            this.Controls.Add(_controlHost);
        }
    }

    Now that’s a fair amount of code, but essentially all it does is build a control tree that has the same markup that we had in our Test Page. Now we need to craft our JavaScript function that we want to call when the Silverlight control has loaded: -

    private void RegisterSilverlightOnLoadFunction()
    {
        try
        {
            CreateMapControl();
            if (_tableData != null)
            {
                if (!this.Page.ClientScript.IsClientScriptBlockRegistered(Constants.ScriptKeySilverlightOnLoad))
                {
                    StringBuilder sb = new StringBuilder();
                    sb.Append("function ");
                    sb.Append(Constants.ParamOnLoadValue);
                    sb.Append("(sender, args) {");
                    sb.Append("\r\n\t");
                    sb.Append("var bingMapsControl = sender.getHost();");
                    sb.Append("\r\n\t");
                    foreach (DataRowView rowView in _tableData)
                    {
                        string title = rowView.Row[this._titleColumn].ToString();
                        double latitude = double.Parse(rowView.Row[_latitudeColumn].ToString());
                        double longitude = double.Parse(rowView.Row[_longitudeColumn].ToString());
                        sb.Append("var l = bingMapsControl.content.services.createObject('");
                        sb.Append(Constants.HtmlKeyMapLocation);
                        sb.Append("');");
                        sb.Append("\r\n\t");
                        sb.Append("l.Title = '");
                        sb.Append(title);
                        sb.Append("';");
                        sb.Append("\r\n\t");
                        sb.Append("l.Latitude = ");
                        sb.Append(latitude);
                        sb.Append(";");
                        sb.Append("\r\n\t");
                        sb.Append("l.Longitude = ");
                        sb.Append(longitude);
                        sb.Append(";");
                        sb.Append("\r\n\t");
                        sb.Append("bingMapsControl.content.Communicator.AddLocation(l);");
                        sb.Append("\r\n\t");
                    }
                    sb.Append("\r\n");
                    sb.Append("}");
                    sb.Append("\r\n");
                    this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), Constants.ScriptKeySilverlightOnLoad, sb.ToString(), true);
                }
            }
        }
        catch
        { }
    }

    This will emit the same JavaScript that we had in our test page. The major difference here is that we’re looping through each row in our SharePoint list and calling the AddLocation() method. In the overridden CreateChildControls() method add a call to our CreateMapControl function: -

    protected override void CreateChildControls()
    {
        base.CreateChildControls();
        CreateMapControl();
    }

    The only thing left to code now is the web part connection so add the following two methods to the web part: -

    [ConnectionConsumer("Location Map Data")]
    public void GetConnectionInterface(IWebPartTable provider)
    {
        TableCallback callback = new TableCallback(ReceiveTable);
        _provider = provider;
        provider.GetTableData(callback);
    }
     
    public void ReceiveTable(object providerTable)
    {
        _tableData = providerTable as ICollection;
        RegisterSilverlightOnLoadFunction();
    }

    Because the data connection works asynchronously our GetConnectionInterface method defines a callback method that will be executed once the data has been returned from the list. Once we have the data the callback function, ReceiveTable, can write the required JavaScript.

    Now that’s all we need to do to get the web part to function. This version (for brevity’s sake) has zero error handling so before you put this thing anywhere near a non-development SharePoint machine make sure that you include some exception handling.

    If you have the URL of a SharePoint site in the “Start browser with URL” field in Project Properties > Debug then you can hopefullt just right-click the project in Visual Studio and select Deploy. Again, if you’re going to a production environment you’ll want to package the web part code (plus the .xap and .js files from the Silverlight project) as a .wsp file.

    What we need now is a SharePoint site (hopefully you’ve already got one of these lying around). If you’ve already got a list with location data then great. If not, then just create a new custom list with columns for Latitude and Longitude (both numbers).

    Once you’ve got your Locations list add your web part and a list view web part to a page (new or otherwise). You’ll need to set the height of the Bing Map web part in pixels to get it to show. What you should have now is a blank map of the world plus your empty Locations list. Go ahead and add some data into your list.

    Bing Map Web Part Connections

    Once you’ve done that, change the settings on the Bing Map web part by clicking the menu arrow at the top right hand corner of the control and selecting Modify Shared Web Part. Once the page reloads in edit mode, check that the column names we’ve defined in the map web part match those in the locations list. In the right-hand panel in edit mode expand the Map Settings section and ensure the values in the Latitude, Longitude and Title Columns fields are valid column names in your list. Then, click the edit menu arrow at the top right of the control and select Connections > Get Location Map Data From > Locations (or whatever your list happens to be called). The page will reload again and this time you should see pushpins on the map at the latitude and longitudes specified in your list. Click Exit Edit Mode in the top right to view the page.

    The final result should resemble something like the following (I’ve zoomed in so that we can distinguish between the Glasgow and Edinburgh pushpins.

    Bing Map Web Part

    Tags: , , , ,
  •  
    March 21st, 2010stuartwhiteford.NET, C#, JavaScript, SharePoint, Silverlight

    Introduction

    OK, another post in response to a forum question. This time on SharePoint Overflow. I was looking for a way to display pushpins on a Bing Map Silverlight control using data from a SharePoint list. I’ve split this post into two parts mainly because there’s a fair amount of code involved to just to render the Silverlight control in the web part, never mind the building the required JavaScript in managed code, so we’ll leave that for another day (or night).

    The answer to the question in this case is to use the HTML Bridge that enables interaction between HTML and Managed Code, specifically it allows us expose methods in managed code to JavaScript, but we’ll get that part later. First up let’s create the Silverlight control.

    The Silverlight Control

    This part is fairly straightforward so I won’t go into the details here. This article will give you all you need to get started. One thing to note is that to test the Silverlight control I’ve made use of the web project that Visual Studio can create for you when you create a new Silverlight project so if you want to follow along with that particular section be sure to let Visual Studio create the web project for you. You can, however, skip the “Change the Map Mode using C#” step if you want. Before we move on to the code we need to add a layer to the map so that we have somewhere to put the pushpins so add the following fragment to the <m:Map> element in the MainPage.xaml file: -

    <m:Map.Children>
    	<m:MapLayer x:Name="Pushpins">
    	</m:MapLayer>
    </m:Map.Children>

    Now we have somewhere to put them we’ll write the code that places the pushpins on the map, so open up the MainPage.xaml.cs file. To be able to use the HTML Bridge and Bing Map Control we need to add some references so add the following lines to the top of the file: -

    using System.Windows.Browser;
    using Microsoft.Maps;
    using Microsoft.Maps.MapControl;
    using Microsoft.Maps.MapControl.Core;

    Now that we have the reference we can add a custom location class that will contain the latitude and longitude coordinates enabling us to position the pin on the map. Add the following class to the MainPage.xaml.cs file within your namespace: -

    [ScriptableType]
    public class MapLocation
    {
        [ScriptableMember]
        public string Title { get; set; }
        [ScriptableMember]
        public double Latitude { get; set; }
        [ScriptableMember]
        public double Longitude { get; set; }
    }

    The ScriptableType attribute allows us to make use of the public properties, methods and events of a managed code object in JavaScript. ScriptableMember indicates that the specific property is available to JavaScript callers. Technically this isn’t required in this scenario as we’ve decorated the entire class with the ScriptableType attribute but it won’t break it either.

    Next we can add our method to add a pushpin to the map that will be called by JavaScript. Add the following lines to the MainPage class in your MainPage.xaml.cs file: -

    [ScriptableMember]
    public void AddLocation(MapLocation location)
    {
        Pushpin pin = new Pushpin();
        pin.Location = new Location(location.Latitude, location.Longitude);
        pin.Name = location.Title;
        pin.Tag = location.Title;
        Pushpins.Children.Add(pin);
    }

    The ScriptableMember attribute indicates that this method is available to us from JavaScript. To complete the MainPage class changes add the following lines to the constructor after the InitializeComponent(); call: -

    HtmlPage.RegisterCreateableType("MapLocation", typeof(MapLocation));
    HtmlPage.RegisterScriptableObject("Communicator", this);

    This first line registers our MapLocation class for use in JavaScript with the key of “MapLocation” (why complicate things?). The second line registers the MainPage class for the same purpose. This is all we need to do to the Silverlight control but before we dive into creating the SharePoint web part lets test that we can indeed call the method from JavaScript by using the test page in the web project that Visual Studio has created for us.

    The JavaScript

    I’ve called my solution BingMapWebPart so I’m opening the BingMapWebPartTestPage.aspx file (yours might be named more succinctly). There should already be an onSilverlightError function in the head of the page. Below that we can add our JavaScript function: -

    function onSilverlightLoad(sender, args) {
        var bingMapsControl = sender.getHost();
        var l = bingMapsControl.content.services.createObject("MapLocation");
        l.Title = "API Software - Glasgow";
        l.Latitude = 55.864438;
        l.Longitude = -4.262776;
        bingMapsControl.content.Communicator.AddLocation(l);
    }

    This function should add a pushpin to the map at the location of API Software’s offices in Bath Street, Glasgow. Note the use of the keys we defined in the Silverlight control. To create a new MapLocation object on the client we’ve called the content.services.createObject method of the control, passing in the “MapLocation” key, then to call the AddLocation() method of the MainPage class we’ve called the AddLocation() method of the content.Communicator object. The last thing to do is ensure that this function is called once the Silverlight control has loaded, so add the following line to the existing list of params in the object tag for the control: -

    <param name="onLoad" value="onSilverlightLoad" />

    So if this all works, when you press F5 you’ll see a pushpin pointing to Glasgow on a map of the world. In Part 2 we’ll turn this into a SharePoint web part.

    References

    Tags: , , , ,
  •  
    November 17th, 2009stuartwhitefordWindows 7, Windows Server 2008

    Working for a Microsoft shop, you get used to only using Microsoft products, it’s only when one of doesn’t work you go and look at the alternatives. Getting ready for the imminent public beta release of SharePoint 2010 I decided to set up a new virtual machine with Windows Server 2008 and SQL Server 2008 on my 64-bit Windows 7 machine. I downloaded Windows Virtual PC, created a new virtual machine and pointed the DVD Drive to the Windows Server 2008 ISO. I started the VM and…

    Windows Boot Manager Fail

    Windows Boot Manager Fail

    “CPU not compatible with 64-bit mode.” Yes it is. I even flashed the BIOS to make sure that I could enable AMD-V for the chip. Hmmm, what now? Google!

    Amongst the Virtual PC and Hyper-V results I stumbled across VirtualBox. Seemed to do what I needed it to and while it didn’t specifically mention Windows Server 2008 in the list of guest operating systems I took the chance and downloaded it. It’s a neat little program, nice interface and on creating a new virtual machine I discovered that it does indeed support Windows Server 2008 (64-bit). Result!

    Create New Virtual Machine

    Create New Virtual Machine

    I ran through the rest of the setup process without a hitch and then adjusted the settings to mount the Windows Server 2008 ISO and set the Network Adapter.

    Settings - CD/DVD-ROM

    Settings - CD/DVD-ROM

    Next, I started the VM and ran through the normal Windows setup procedure, again without a hitch, and voila!

    Running Virtual Machine

    Running Virtual Machine

    I decided to do a bit of tinkering while waiting for the SharePoint beta release, so after installing IIS and SQL Server 2008 and all the requisite updates I wondered if I could host a website on the VM and access it externally. Turns out that’s quite easy to achieve. A quick browse through the user manual and I found Configuring port forwarding with NAT. I’ve configured the guest to use the Intel PRO/1000 NIC rather than the PCNet one so have changed pcnet to e1000, so the following script will forward TCP connections on port 4321 on the host to port 1234 on the guest.


    cd "C:\Program Files\Sun\VirtualBox"
    VBoxManage setextradata "SP2010" "VBoxInternal/Devices/e1000/0/LUN#0/Config/guestssh/Protocol" TCP
    VBoxManage setextradata "SP2010" "VBoxInternal/Devices/e1000/0/LUN#0/Config/guestssh/GuestPort" 1234
    VBoxManage setextradata "SP2010" "VBoxInternal/Devices/e1000/0/LUN#0/Config/guestssh/HostPort" 4321

    All that remained was to enable port 4321 on my firewall and forward connections to the host machine, the host machine then took care of forwarding to the VM, and I could access the website residing at port 1234 on the virtual machine!

    Note: These were not the actual port numbers I used, and even if you did guess correctly all you’d get is a one word HTML test page!

    Tags: , , , ,
  • « Older Entries