Friday, November 21, 2014

JSON Viewer. Part 2

JSON Viewer extension has now a few new features:
  • ability to print formatted data
  • ability to format input data keeping JSON markup

  • ability to compare 2 JSON data

These features are available in release 1.0.2. Source code and binaries to download are here: https://jsonviewervsextension.codeplex.com.

After installation the viewer appears in the main menu: Tools -> JSON Viewer. Applicable to VS 2012 and 2013.

Monday, November 3, 2014

JSON Viewer

Made a simple extension to Visual Studio to view JSON data in a more user-friendly format.

Source code and binaries to download are here: https://jsonviewervsextension.codeplex.com. After installation the viewer is available in the main menu: Tools -> JSON Viewer.

Applicable to VS 2012 and 2013. Please note that custom VS extensions cannot be installed into Express editions of Visual Studio due to a Microsoft policy.

Sunday, October 19, 2014

Not So Primitive Types

A few nuances about the primitive types in .Net which are not so obvious:
  • If you add two shorts you will get int. The same with bytes. Arithmetic operators for these types are not implemented in .Net therefore the values are implicitly converted to int before calculation.
        short x = 2, y = 2;
        var z = x + y;
        Console.WriteLine(z.GetType()); //output: System.Int32 
    
  • Chuck Norris is not the only person who can divide by zero. Every .Net developer can do this. Just use floating point numbers.
        var z = 1.0 / 0;
        Console.WriteLine(z); //output: Infinity 
    

Sunday, June 15, 2014

Bulk Generation of Tasks in TFS


It usually took me hours to add all tasks for my team for the next sprint in TFS. Existing UIs for this purpose were too sl-o-o-o-w, both built-in Team Explorer and Web Access (I mean Web Access for TFS 2010; 2012 is better in this context). The most annoying thing is that I usually need to specify just title and estimate for tasks but have to wait until the whole complex task entry form is loaded and then closed.

I also found bulk work items generation by means of Excel is not that convenient for addition of hierarchical (parent-child) data.

So I made a small utility for this purpose. Details and source code are here:

How to use

The main form is divided in 3 parts:
  • main menu: you can select a TFS project here, specify an area and/or an iteration to filter displayed work items and select a template for tasks creation. The default template containing all required fields is created automatically for each project. You can add more templates in case of need.
  • left panel: the work items tree is displayed here; it can be filtered by area and/or iteration. You also can load a selected item specifying its ID.
  • right panel: tasks template is displayed here; fields that should be the same for all new tasks are listed in the top part (e.g. State); items that should be unique for each item (e.g. Title) are listed in the grid below. The grid supports pasting data copied from Excel.
Use Task Template -> Manage Templates menu item to create/edit/delete tasks templates.

Tuesday, April 20, 2010

Two facts you may not know about storing ASP.Net Session in SQL DB

  1.   If saving of large objects in the Session in InProc mode is bad practice then doing the same in SQLServer mode is real evil. The main problem in this case is serialization/deserialization data on every request to the server. Each user's session is a single record in ASPStateTempSessions table so all objects need to be deserialized even if you request something which is rather small. Symptoms of this problem: you notice using a profiler that usual int userId = Session["UserId"] takes a few seconds!!! to execute.
  2.     If you noticed that ASPStateTempSessions table is rather large and permanently grows and grows then check if the SQL Server agent is running. It is responsible for deleting of expired sessions. It should have a job to execute the DeleteExpiredSessions stored procedure every minute (by default).

Friday, March 20, 2009

How to merge cells with equal values in the GridView


My solution is not the first; however, I think, it is rather universal and very short - less than 20 lines of the code.

The algorithm is simple: to bypass all the rows, starting from the second at the bottom, to the top. If a cell value is the same as a value in the previous (lower) row, then increase RowSpan and make the lower cell invisible, and so forth.

The code that merges the cells is very short:
public class GridDecorator
{
    public static void MergeRows(GridView gridView)
    {
        for (int rowIndex = gridView.Rows.Count - 2; rowIndex >= 0; rowIndex--)
        {
            GridViewRow row = gridView.Rows[rowIndex];
            GridViewRow previousRow = gridView.Rows[rowIndex + 1];

            for (int i = 0; i < row.Cells.Count; i++)
            {
                if (row.Cells[i].Text == previousRow.Cells[i].Text)
                {
                    row.Cells[i].RowSpan = previousRow.Cells[i].RowSpan < 2 ? 2 : 
                                           previousRow.Cells[i].RowSpan + 1;
                    previousRow.Cells[i].Visible = false;
                }
            }
        }
    }
}

The last action is to add an OnPreRender event handler for the GridView:
protected void gridView_PreRender(object sender, EventArgs e)
{
    GridDecorator.MergeRows(gridView);
}

Wednesday, June 4, 2008

Charts: let Google work for you

I can't class myself as an developer who is always hip to the latest news in the software industry, partly through laziness and partly through necessity to write code, and not merely to surf the Internet :) That is why I missed appearance of a very cute and handy service from Google, which allowed to integrate charts into the web pages.

For the time being it is only half a year old as far as I can judge by the date of the first record in the discussion group, therefore I think this information can be interesting for someone else besides me.

The service allows to dynamically generate practically all essential types of charts such as bar charts, pie charts and so on. The only thing you have to do to get the chart is forming the url in concordance with some rules and the service will return the PNG-format image.

As an example I decide to use data from another Google service, namely Google Analytics, which together with all its merits has one lack: the absence of public access to the analytic data that does not allow to boast :) of the nice designed statistics of your site unlike, for example, ClustrMaps.

At the beginning I save the data about visits to my site for the last three monthes (Mar 1, 2008 - May 31, 2008) in the XML format. I want to build two graphs - the map overlay and the pie chart - based on these data.
//analytic data file path
string xmlFilePath = Server.MapPath("~/map-overlay.xml");

XmlDocument doc = new XmlDocument();
doc.Load(xmlFilePath);

XmlNodeList nodes = doc.SelectNodes("/AnalyticsReport/Report/GeoMap/Region");

//the world map overlay
Image1.ImageUrl = GetGeoMapChartUrl(nodes);
//the pie chart
Image2.ImageUrl = GetPieChartUrl(nodes);

Chart #1. The site visits overlay on the world map

The site visits overlay on the world map

Google Charts is one of the few services that has the clear and comprehensible documentation, therefore this is realy pleasant to use it :)
//maximal number of visits from a country
private int max;

private string GetGeoMapChartUrl(XmlNodeList nodes)
{
//list of countries names
StringBuilder sbCountries = new StringBuilder();
//list of values for each country to be colored
StringBuilder sbColors = new StringBuilder();

if (nodes.Count > 0)
{
//maximal number of visits from a country (data in the file are sorted in descending order)

max = int.Parse(nodes[0]["Value"].InnerText, NumberStyles.Number);

foreach (XmlNode node in nodes)
{
//NumberStyles.Number has to be set because of the presence of whitespaces in the numbers spelling, for example, "1 329"
int value = int.Parse(node["Value"].InnerText, NumberStyles.Number);
//country code in concordance with ISO 3166
sbCountries.Append(node["Id"].InnerText);
//country color which depends on the number of visits - the more visits the more saturated color
sbColors.Append(GetColorCode(value));
}
}

//http://chart.apis.google.com/chart - service url
//?chs=440x220 - image size (440х220 is the maximum available for all maps)
//&cht=t - chart type
//&chtm=world - geographical area (there are also available separate continents and the territory of USA)
//&chd=s:{0} - codes of colors
//&chld={1} - codes of countries
//&chco=ffffff,ffebcc,ff9900 - ffffff (default country color), ffebcc ... ff9900 (color gradient for painted countries)
//&chf=bg,s,99ccff - color of the image background (seas, oceans)

return string.Format("http://chart.apis.google.com/chart?chs=440x220&chd=s:{0}&cht=t&chtm=world&chld={1}&chco=ffffff,ffebcc,ff9900&chf=bg,s,99ccff",
sbColors,
sbCountries
);
}
The country color depends on the number of visits and has to be one of the symbols from A...Za...z0...9 array, where A is the least value and 9 is the greatest one.
private char GetColorCode(int value)
{
int res = value*61/max;
if (res < 26) //A...Z
return (char)(65 + res);
else if (res < 51) //a...z
return (char)(71 + res);
else //0...9
return (char)(res - 4);
}

Chart #2. Pie chart

Pie chart

Unfortunately, if you simply pass an array of data (for example: 4, 6, 10) the pie chart is shown incorrectly, all secrors are of the same size. You have to pass percentage instead of absolute values (for the above example it should be 20, 30, 50). Through percentage calculation next code looks more complicated then the previous chart code.
private string GetPieChartUrl(XmlNodeList nodes)
{
//the total number of visits
int total = 0;
//the number of visits from each country
List<int> values = new List<int>(nodes.Count);
//list of countries names
List<string> names = new List<string>(nodes.Count);

foreach (XmlNode node in nodes)
{
//NumberStyles.Number has to be set because of the presence of whitespaces in the numbers spelling, for example, "1 329"

int value = int.Parse(node["Value"].InnerText, NumberStyles.Number);
total += value;
values.Add(value);
names.Add(string.Format("{0} ({1})", node["Name"].InnerText, value));
}

//data have to be in percents
List<string> data = new List<string>();

int sumPercents = 0;
int sumValues = 0;

for (int i = 0; i < values.Count; i++)
{
int percent = values[i] * 100 / total;
if (percent > 1)
{
sumPercents += percent;
sumValues += values[i];
data.Add(percent.ToString());
}
else
{
//visits from the countries that take 1% or less are shown together
names.RemoveRange(i, names.Count - i);
names.Add(string.Format("Other Countries ({0})", total - sumValues));
data.Add((100 - sumPercents).ToString());
break;
}
}


//http://chart.apis.google.com/chart - service url
//?cht=p3 - chart type
//&chd=t:{0} - data
//&chs=600x150 - image size (limited to 300000 pixels that is, for example, 300х1000)
//&chl={1} - labels

return string.Format("http://chart.apis.google.com/chart?cht=p3&chd=t:{0}&chs=600x150&chl={1}",
string.Join(",", data.ToArray()),
string.Join("|", names.ToArray()));
}
P.S. One thing that you can't find in the documentation: how to pass non-latin characters?
Is this case you have to convert characters to UTF8 encoding: for example, cyrilic character "B" has to be "%D0%92".
string value = ...//text that contains non-latin characters 
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(value);

StringBuilder sb = new StringBuilder();
foreach (byte b in bytes)
sb.AppendFormat("%{0}", b.ToString("X"));

string encodedValue = sb.ToString();