Moving Reporting Service in a TFS 2008 environment

On MSDN, we have documents for moving TFS database, moving Analysis Service database etc. But it’s not covered to move Reporting Service.

After I sent this question to our internal discussion group. I received a couple of replies. Especially thank Aaron Block, Bill Essary and Lakhminder Singh for their great help. Here are the steps to move RS.

  1. Follow KB 842425 to move RS DB to a new instance.
  2. Ensure that you have a login for the TFS Reports account, in case your RS databases are now going to be hosted on a different SQL instance. The login for the RS database should have RSExecRole in Report Server databases (which it should retain when you restore the RS databases.)
  3. Create new data connections for TfsReportsDS and TfsOlapReportsDS. They can be modified via http://ReportingService/Reports.
  4. Run TfsAdminUtil ConfigureConnections command to update setting /ReportsUri and /ReportServerUri.  Consider http://support.microsoft.com/kb/959126 while doing it.
  5. Copy TfsConfigWss.exe to the SharePoint server and run it there to update the reports redirector.
  6. In the installation folder of TFS, there’s a folder named “Microsoft Visual Studio 2008 Team Foundation Server - ENU”. Update the MSIProperty.ini in that folder and change the RS machine name for the VSTF_RS_SERVER property. You just need to give new machine\server name for RS & do not give the instance name. Optionally update the VSTF_RS_REPORTS_URI, & VSTF_RS_REPORTSERVER_URI properties. See Msiproperty.ini File Properties for more information.

Testing WCF web services

This post is for refining my previous post Test WCF web service – Hosted in ASP.NET Development Server. Personally, I prefer Wordpress because it is easy to maintain layout. But all blogs at wordpress.com are censored by the Great Firewall of China (I always want to say the word that has 4 characters, starts with “F” and ends with “K” when talking about it). So I decided to continue to blog in BlogSpot.

I started working in Microsoft Shanghai Global Technical Support Center on May 5th 2008. Fortunately, the company has a private line connecting to the USA thus we can bypass the Great Firewall in the office. My friends and I have dreamed this all our lives. When I wrote the previous post about testing WCF web service, I just had several weeks of experience in VSTT, so forgive me for not having made it easy to read. Since published that post, I have received many feedbacks, thank you all! It’s time to refine the post.

I remember last year the MSDN document at http://msdn.microsoft.com/en-us/library/ms243399.aspx was the same as that in http://msdn.microsoft.com/en-us/library/ms243399(VS.80).aspx. Then Microsoft has changed the terminology about unit test. It is said that hosting a web service and actually invoking it over HTTP in a test method is considered to be “integration test”, while creating an instance of the web service class in a test method and invoking it’s methods is the “unit test”. So I guess the topic of this post is about how to host WCF web services and perform integration testing.

I focus on testing WCF web services that are hosted in ASP.NET Deployment Server (Cassini).

Background about ASP.NET unit testing.

Let me explain some background about ASP.NET unit testing. ASP.NET unit tests can (not always) be executed in the same process as the web server. See Overview of ASP.NET Unit Tests for more information. At run time of executing ASP.NET unit tests, it goes through the following steps:

  1. Backup your web.config to web.config.backup.
  2. Register an HTTP module Microsoft.VisualStudio.TestTools.HostAdapter.Web.HttpModule, which is in %Program Files%\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\Microsoft.VisualStudio.QualityTools.HostAdapters.ASPNETAdapter. to the web.config.
  3. Start ASP.NET Deployment Server if it is used.
  4. Send a request to the URL pointed by UrlToTest attribute of your test method.
  5. The registered Microsoft.VisualStudio.TestTools.HostAdapter.Web.HttpModule is triggered by the web server. The module subscribes the PageLoad event of the requested page. When PageLoad event is fired, it loads the test assembly in to the web server process and execute the test method.
  6. Restore the web.config and delete web.config.backup after tests complete.

ASP.NET Deployment Server is started when running a unit test if one of the following 2 conditions is meet:

  1. AspNetDeploymentServer attribute is specified.
    This attributes tells VS to start Cassini before executing the test method. Both AspNetDeploymentServer and AspNetDeploymentServerHost expects a physical path to the web application. In a team environment, the web site is usually located in different directories in different computers. The workaround is to use environment variables. See Testing Web Sites and Web Services in a Team Environment for more information. 
  2. UrlToTest and HostType and  AspNetDeploymentServerHost attributes are all specified.
    UrlToTest attribute is required if HostType attribute is set to “ASP.NET”. The combination of the 3 attributes let VS know ASP.NET Deployment Server should be started and the test method should be executed in the same process as the web server.

When testing web services, we usually want to invoke the web services from a proxy and it’s not necessary to execute test methods in the same process as the web server. Therefore, we don’t need to UrlToTest, HostType and AspNetDeploymentServerHost attributes.

Testing WCF web services hosted in ASP.NET Deployment Server

I go through the process of creating a WCF project and add a test method to demonstrate this scenario. Several sentences here are copied from http://msdn.microsoft.com/en-us/library/ms243399(VS.80).aspx.

  1. Create a WCF project in VS via File->New->Project->Visual C#->Web->WCF Service Application.
  2. Generate unit tests against the Web service in the standard way for generating unit tests. For more information, see How to: Generate a Unit Test.
  3. Use svcutil to generate WCF web service proxy.
    1. Right click the Service1.svc in the WCF project in Solution Explorer and select View in Browser.
    2. Run svcutil http://localhost:52747/Service1.svc /config:app.config /out:Service1Proxy.cs /language:C# to generate proxy and config file. Please replace the URL of Service1.svc to that was displayed in the address bar of the browser in #1.
    3. Add Service1Proxy.cs and app.config to the test project.
  4. Remove the attribute HostType, UrlToTest and AspNetDeploymentServerHost for the generated test method.
  5. Add the AspNetDevelopmentServerAttribute attribute to the unit test. The arguments for this attribute class point to the site of the Web service and name the server. For more information, see Ensuring Access to ASP.NET Development Server.
  6. Change the test method to use the generated proxy class to invoke the web service and add the redirection logic. The test class will look like the following.
DownloadIcon sample project
I also paste some code here to ease reverences.

WcfWebServiceHelper.cs

using System;
using System.Reflection;
using System.ServiceModel.Description;
using System.ServiceModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestProject1
{
class WcfWebServiceHelper
{
public static bool TryUrlRedirection(object client, TestContext context, string identifier)
{
bool result = true;
try
{
PropertyInfo property = client.GetType().GetProperty("Endpoint");
string webServer = context.Properties[
string.Format("AspNetDevelopmentServer.{0}", identifier)].ToString();
Uri webServerUri = new Uri(webServer);
ServiceEndpoint endpoint = (ServiceEndpoint)property.GetValue(client, null);

EndpointAddressBuilder builder = new EndpointAddressBuilder(endpoint.Address);
builder.Uri = new Uri(
endpoint.Address.Uri.OriginalString.Replace(
endpoint.Address.Uri.Authority, webServerUri.Authority));

endpoint.Address = builder.ToEndpointAddress();

}
catch (Exception e)
{
context.WriteLine(e.Message);
result = false;
}
return result;
}

}
}


Test Method



        [TestMethod()]
[AspNetDevelopmentServer("WcfService1",
"C:\\Users\\Administrator\\Desktop\\TfsRoot\\BuildTest\\Main\\MyServices\\WcfService1")]
public void GetDataTest()
{
Service1Client target = new Service1Client();
Assert.IsTrue(WcfWebServiceHelper.TryUrlRedirection(target,TestContext,"WcfService1"));
int value = 0;
string expected = "You entered: 0";
string actual;
actual = target.GetData(value);
Assert.AreEqual(expected, actual);
}

How to: Initialize a WorkItem instance for the changed work item in WorkItemChangedEvent handler

Recently I’ve been asked about several questions that need to initialize a WorkItem instance in the web service handler for WorkItemChangedEvent. So I paste a sample code here to ease the future references.

One thing to note is that in order to query work items, the identity for the app pool that hosts the web service in IIS must has property permission granted. In TFS 2005 and 2008, work item permissions are based on area and iteration. See Team Foundation Server Permissions for more information.

using System;
using System.Diagnostics;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

namespace EventHandler
{
/// <summary>
/// Summary description for Service1
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
// [System.Web.Script.Services.ScriptService]
public class WorkItemChangedEventHandler : System.Web.Services.WebService
{

[SoapDocumentMethod(
Action = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03/Notify",
RequestNamespace = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03")]
[WebMethod]
public void Notify(string eventXml)
{
try
{
string tfsUrl = "http://TFS_AT:8080";
TeamFoundationServer tfs = TeamFoundationServerFactory.GetServer(tfsUrl);

var workItemId = GetWorkItemID(eventXml);

var wis = (WorkItemStore)tfs.GetService(typeof(WorkItemStore));
var wi = wis.GetWorkItem(workItemId);


}
catch (Exception e)
{
var eventSource = "My WorkItemChangedEvent Handler";
var logName = "Application";
var machineName = "."; //local computer.

if (!EventLog.SourceExists(eventSource, machineName))
{
var eventObj = new EventSourceCreationData(eventSource, logName);
EventLog.CreateEventSource(eventObj);
}

var eventLog = new EventLog(logName, machineName, eventSource);
eventLog.WriteEntry(e.Message);
}
}

private int GetWorkItemID(string eventXml)
{
var doc = new XmlDocument();
doc.LoadXml(eventXml);
var path = "/WorkItemChangedEvent/CoreFields/IntegerFields/Field[ReferenceName='System.Id']/NewValue";
var node = doc.SelectSingleNode(path);

var id = int.Parse(node.InnerText);
return id;
}

}
}