Friday, November 8, 2013

MsBuild CodeTaskFactory Custom Task to Warming Up a Web App

I remember reading about the MSBuild CodeTaskFactory a couple years ago and hadn't run into a situation where I needed it until now. CodeTaskFactory is a great way to extend MSBuild's capabilities without having to go through the hassle of creating a custom MSBuild Task and having to make sure everyone has the dll.
<UsingTask TaskName="TestWebAppAvailable" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
  <ParameterGroup>
    <hostname ParameterType="System.String" Required="true" />
    <servers ParameterType="System.String[]" Required="true" />
    <Result ParameterType="System.String" Output="true" />
    <!--<TestSuccess ParameterType="System.Boolean" Output="true" />-->
  </ParameterGroup>
  <Task>
    <!--<Reference Include="System.Xml"/>-->
    <Reference Include="System.IO"/>
    <Reference Include="System.Net"/>
    <Using Namespace="System"/>
    <Using Namespace="System.IO"/>
    <Using Namespace="System.Net"/>
    <Code Type="Fragment" Language="cs">
      <![CDATA[
string requestUrl = "http://" + hostname + "/Default.aspx";
Result = "success";
foreach (string server in servers)
{
    Log.LogMessage("Server: " + server, MessageImportance.Normal);
    if (string.IsNullOrEmpty(server))
    {
        continue;
    }
    Log.LogMessage("Warming up: " + requestUrl, MessageImportance.Normal);
    System.Net.HttpWebResponse res = null;
    try
    {
        System.Net.WebRequest wr = System.Net.WebRequest.Create(requestUrl);
        wr.Timeout = 20000;
        wr.Proxy = new System.Net.WebProxy("http://" + server, false);
        res = (HttpWebResponse)wr.GetResponse();
        Log.LogMessage(server + " - " + res.StatusCode, MessageImportance.Normal);
    }
    catch (System.Net.WebException ex)
    {
        res = (HttpWebResponse)ex.Response;
        Log.LogMessage("EXCEPTION: " + ex.ToString(), MessageImportance.High);
        Log.LogMessage("res == null: " + (res == null).ToString(), MessageImportance.High);
        Log.LogMessage("Server: " + server, MessageImportance.High);
        Log.LogMessage("res.StatusCode: " + (res != null ? res.StatusCode.ToString() : "Res was null"), MessageImportance.High);
    }
    catch (System.Exception ex) {
        throw;
    }
    Log.LogMessage("res == null: " + (res == null).ToString(), MessageImportance.High);
    if (res.StatusCode != System.Net.HttpStatusCode.OK)
    {
        string message = server + " Web request failed with code: " + res.StatusCode.ToString();
        System.Net.WebException exception = new System.Net.WebException(message);
        if (res != null)
        {
            res.Close();
        }
        Result = server;
        Log.LogMessage("EXCEPTION: " + message, MessageImportance.High);
        break;
    }
    
    if (res != null)
    {
        res.Close();
    }
}
]]>
    </Code>
  </Task>
</UsingTask>
Basically this takes in a host header for the hostname and a list of servers. It then iterates over the servers and requests the Default.aspx page. A successful request returns "success". A request that returns a status code that is not 200 returns the server name that failed.

Then using the new custom task is as easy as the following:
<ItemGroup>
  <Servers Include="$(DeploymentWebServer1)"></Servers>
  <Servers Include="$(DeploymentWebServer2)"></Servers>
</ItemGroup>
<TestWebAppAvailable hostname="$(DeploymentHostname)" servers="@(Servers)" ContinueOnError="True">
  <Output TaskParameter="Result" PropertyName="TestResult" />
</TestWebAppAvailable>
<Error Text="Server $(TestResult) returned a bad response.  Recopy web application files." Condition="'$(TestResult)' != 'success'" />

No comments: