Friday, August 16, 2013

Test Explorer is not updated properly when a test fails inside an async task

I am working on Windows Phone 8 Mobile App and when I am running a Unit Test case ,It does not showing the test status for a failed test. Instead it  is displaying the below message in Output/Tests window

"The active Test Run was aborted because the execution process exited unexpectedly.".

When I re run my Test case using Resharper, it is saying that "The Test wasn't run".

After some research , I came to know is When ever we are testing Silverlight UI Controls, We need to run the test cases, in a Separate Thread, otherwise the debugger will throw an Access denied to Main thread exception.

But When we run our test cases on a separate thread, We should not write any Assert statements in that thread , instead we need to write Assertions in the main thread. This is because of a known bug in the Visual Studio. https://connect.microsoft.com/VisualStudio/feedback/details/764583/test-explorer-is-not-updated-properly-when-a-test-fails-inside-an-async-task#details

Visual studio will end the debug process when ever a failed Assertion occurs inside of the Child thread and there will be no intimation of this status to the Main thread. So what we need to do is create a variable in the main thread and save the Assertion status in that   variable in Child thread and use that variable to Raise Assertions in the main thread..!

Below is the sample code.

[TestMethod]

public void ApplicationBarWrapperTest_MenuItems_Add_New()
{
 
var waitHandle = new AutoResetEvent(false);

ApplicationBarWrapper wrapper = null;

//main thread variable to save the status.
bool isMenuItemAdded;

//child thread
Deployment.Current.Dispatcher.BeginInvoke(async () =>

{
 
wrapper = new ApplicationBarWrapper();

var newItem = new ApplicationBarWrapperMenuItem();

int menuItemsCount = wrapper.ApplicationBar.MenuItems.Count;

newItem.Text = "New Item";


newItem.AddToApplicationBar(wrapper.ApplicationBar);

//saving the status
isMenuItemAdded=wrapper.ApplicationBar.MenuItems.Count==(menuItemsCount + 1);

waitHandle.Set();

});
 
waitHandle.WaitOne(TimeSpan.FromSeconds(10));
//back to main thread ,use that variable to throw assertion.
Assert.IsTrue(isMenuItemAdded, "Adding New Menu Item Not working");

}

Tuesday, August 06, 2013

Creating and Sending an Excel Work book with more than one Work sheet as a Response.


 I came across a task that I need to send an Excel work book with more than one sheet as a response. Here My Excel work book is having a table structured data in all sheets, so each sheet uses same set of styles defined globally. The number of sheets are determined dynamically at run time based on the data. So I followed the below approach and able to complete the task. May be it will help those who are searching to accomplish the same task.

Steps:

1) Save the given Excel work book Template (or the given sample one) as XML document using Save As command of Excel.

2) Open the XML file in an Editor (you can use Visual Studio)

3) If we observe XML of the Excel document, we can see, the inner structure of Our Excel work book in XML.

<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:o="urn:schemas-microsoft-com:office:office"
 xmlns:x="urn:schemas-microsoft-com:office:excel"
 xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
 xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:html="http://www.w3.org/TR/REC-html40">
 <DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">
  <Author></Author>
  <LastAuthor></LastAuthor>
  <Created></Created>
  <LastSaved></LastSaved>
  <Version>14.00</Version>
 </DocumentProperties>
 <CustomDocumentProperties xmlns="urn:schemas-microsoft-com:office:office">
  <ContentTypeId dt:dt="string">0x01010051E63A8B93CA8F4F99468AE0B2BDD026</ContentTypeId>
 </CustomDocumentProperties>
 <OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office">
  <AllowPNG/>
 </OfficeDocumentSettings>
 <ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
  <WindowHeight>9345</WindowHeight>
  <WindowWidth>20730</WindowWidth>
  <WindowTopX>0</WindowTopX>
  <WindowTopY>60</WindowTopY>
  <ActiveSheet>1</ActiveSheet>
  <ProtectStructure>False</ProtectStructure>
  <ProtectWindows>False</ProtectWindows>
 </ExcelWorkbook>
 <Styles>
  <Style ss:ID="Default" ss:Name="Normal">
   <Alignment ss:Vertical="Bottom"/>
   <Borders/>
   <Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="11" ss:Color="#000000"/>
   <Interior/>
   <NumberFormat/>
   <Protection/>
  </Style>
  <Style ss:ID="s59">
   <Alignment ss:Horizontal="Center" ss:Vertical="Center" ss:WrapText="1"/>
   <Borders>
    <Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="2"
     ss:Color="#EBEBEB"/>
   
   </Borders>
   <Font ss:FontName="Segoe UI" x:Family="Swiss" ss:Color="#BEBEBE" ss:Bold="1"/>
   <Interior ss:Color="#4E4E4F" ss:Pattern="Solid"/>
  </Style>
  <Style ss:ID="s60">
   <Font ss:FontName="Segoe UI" x:Family="Swiss" ss:Color="#000000"/>
  </Style>

  Styles…follows here…! I have removed some of the styles, for space concern.

 </Styles>
[WorkSheets]
</Workbook>

Here [WorkSheets] is a place holder to place Excel work sheets. I have extracted this XML into a separate xml file, like this I have created four xml templates for Excel table row and Excel content row as well. In each xml file I have added place holders (Ex:[MetricGroup] ) You better try to Save an Existing Excel sheet into XML format and check how the sheets are placed in between.

4) After that I have created an HTTP Handler (We need this on a SharePoint site), or else you can place your logic in an aspx file or elsewhere ever you need it.

Below is my code. I wrote it according to my needs, you can modify the below logic according to your requirements, but the basic functionality is same.


  void IHttpHandler.ProcessRequest(HttpContext context)
    {
        string qsKpiID = context.Request.QueryString["k_id"];
        int kpiID;
        if (string.IsNullOrEmpty(qsKpiID))
            return;

        if (!int.TryParse(qsKpiID, out kpiID))
            return;

        context.Response.ContentType = "application/ms-excel";
        context.Response.AddHeader("content-disposition", string.Format("attachment;filename=ExcelExport{0}.xls", DateTime.Now.ToString("MM_dd_yyyy_hh_mm")));

        string workSheetTemplate = File.ReadAllText(context.Server.MapPath("~/_controltemplates/15/Export.BI.Controls/ExcelSheet.xml"));
        string workBookTemplate = File.ReadAllText(context.Server.MapPath("~/_controltemplates/15/Export.BI.Controls/ExcelWorkloadPage.xml"));
        string headRow = File.ReadAllText(context.Server.MapPath("~/_controltemplates/15/Export.BI.Controls/ExcelHeadRow.xml"));
        string contentRow = File.ReadAllText(context.Server.MapPath("~/_controltemplates/15/Export.BI.Controls/ExcelContentRow.xml"));

        MetricManager mgr = new MetricManager();
        var metricSummaryData = mgr.GetTotalSummary(kpiID);
        var metricGroups = mgr.GetMetricGroups(kpiID);
        StringBuilder excelSheetsStr = new StringBuilder();
        string newCellTemplate = "<Cell ss:StyleID=\"s57\"><Data ss:Type=\"Number\">{0}</Data></Cell>";
        string newDateCellTemplate = "<Cell ss:StyleID=\"s58\"><Data ss:Type=\"DateTime\">{0:yyyy-MM-dd}T00:00:00.000</Data></Cell>";
        string emptyCell = "<Cell ss:StyleID=\"s57\"/>";
        foreach (var metricGroup in metricGroups)
        {
             var metricGroupData = metricSummaryData.Where(m => m.MetricGroupID == metricGroup.ID).ToList();
             var metricTypes= mgr.GetMetricTypes();
             string excelSheetStr = workSheetTemplate.Replace("[MetricGroup]", metricGroup.Name);
            StringBuilder excelRows = new StringBuilder();
            int expandedRowCount = 2;
            int expandedColumnCount = 9;
             foreach (var metricType in metricTypes)
             {
                 excelRows.Append(headRow.Replace("[Metric Type]",metricType.Name));
                 expandedRowCount++;
                 foreach (var item in metricGroupData.Where(m => m.MetricTypeID == metricType.ID))
                 {

                     string newRow = contentRow.Replace("[MetricName]", item.Name);
                     newRow = newRow.Replace("[Target]", item.Target != null ?string.Format(newCellTemplate,item.Target) : emptyCell);
                     newRow = newRow.Replace("[Baseline]", item.BaseLine != null ? string.Format(newCellTemplate, item.BaseLine) : emptyCell);
                     newRow = newRow.Replace("[Target Date]", item.TargetDate != null ? string.Format(newDateCellTemplate, item.TargetDate) : emptyCell);
                     newRow = newRow.Replace("[Previous Month]", item.Previous != null ? string.Format(newCellTemplate, item.Previous) : emptyCell);
                     double itemActual = 0;
                     newRow = newRow.Replace("[Current]", item.Actual != null ? item.Actual : "");

                     if (double.TryParse(item.Actual, out itemActual))
                     {
                         newRow = newRow.Replace("[CurrentDataType]", "Number");
                     }
                     else
                     {
                         newRow = newRow.Replace("[CurrentDataType]", "String");
                     }
                     if(item.Result!=null && item.Result !="G")
                        newRow = newRow.Replace("s66", item.Result=="R"?"s76":"s75");
                     newRow = newRow.Replace("[Target YTD]", item.TotalTarget != null ? string.Format(newCellTemplate, item.TotalTarget) : emptyCell);
                     newRow = newRow.Replace("[Actual YTD]", item.TotalActual != null ? string.Format(newCellTemplate, item.TotalActual) : emptyCell);
                     excelRows.Append(newRow);
                     expandedRowCount++;
                     //break;
                 }
                 //break;
             }
             excelSheetStr = excelSheetStr.Replace("[ContentRows]", excelRows.ToString());
             excelSheetStr = excelSheetStr.Replace("[LastUpdated]", string.Format("last update {0:MM/dd}", (from d in metricGroupData select d.DateCreated).Max().Value));
             excelSheetStr = excelSheetStr.Replace("[ExpandedRowCount]", expandedRowCount.ToString());
             excelSheetStr = excelSheetStr.Replace("[ExpandedColumnCount]", expandedColumnCount.ToString());
           
            excelSheetsStr.Append(excelSheetStr);
        }
        workBookTemplate = workBookTemplate.Replace("[WorkSheets]", excelSheetsStr.ToString());

        context.Response.Write(workBookTemplate);
        context.Response.End();
       
    }