Let’s hookup with ASP.NET Webhooks Preview

Webhook – A simple HTTP POST based pub/sub mechanism between web applications or services. This is a very effective that most of the modern web applications use this in order to handle event based pub/sub.

ASP.NET WebHooks is a framework which is in the preview release; this eases the task of incorporating webhooks in ASP.NET applications. It provides some predefined clients to subscribe events from, like Instagram, GitHub and others. It also provides a way to setup our custom ASP.NET web applications to send webhook notifications to the subscribed clients and prepare the clients to receive the webhook notifications. See the below URL for the more information.


How WebHooks work and the structure of the ASP.NET WebHook Framework

Webhooks are simple HTTP POST based pub/sub.

Webhooks has the following structure.

  • The web application/service which publishes the events should provide an interface for the subscribers to register for the webhooks.
  • Subscribers select the events they want to subscribe to, submit the callback URL to be notified along with other optional parameters. Security keys are most common among the other optional parameters.
  • The publisher will persist the subscriber details.
  • When the event occurs publisher notifies all the eligible subscribers by triggering POST request to the callback back along with the event data.

The above 4 steps are the most vital steps of a working webhook. Let’s see how ASP.NET Webhooks implement this.

As of this writing ASP.NET Webhooks are in preview and nugget packages also in the preview release.

The support for sending WebHooks is provided by the following Nuget packages:

  • Microsoft.AspNet.WebHooks.Custom: This package provides the core functionality for adding WebHook support to your ASP.NET project. The functionality enables users to register WebHooks using a simple pub/sub model and for your code to send WebHooks to receivers with matching WebHook registrations.
  • Microsoft.AspNet.WebHooks.Custom.AzureStorage This package provides optional support for persisting WebHook registrations in Microsoft Azure Table Storage.
  • Microsoft.AspNet.WebHooks.Custom.Mvc This package exposes optional helpers for accessing WebHooks functionality from within ASP.NET MVC Controllers. The helpers assist in providing WebHook registration through MVC controllers as well as creating event notification to be sent to WebHook registrants.
  • Microsoft.AspNet.WebHooks.Custom.Api This package contains an  optional set of ASP.NET Web API Controllers for managing filters and registrations through a REST-style interface.

ASP.NET Webhooks works well in Web API and it has the Azure Table Storage provider for persisting the publisher metadata and event data.

Please go through this article for the detailed information

What is missing

According to the article, webhooks are delivered to the users based on authorization. So ASP.NET webhooks store the information of the subscriber along with the user information. This helps the ASP.NET webhooks to publish the event to the right subscriber.

For example, 2 users have subscribed to the same event.

User Id Event Callback URL
Thuru PhotoAdded https://thuru.net/api/webhooks/in/content
Bob PhotoAdded http://bob.net/api/hooks/incoming/photo

ASP.NET Webhooks requires the subscribers to login to the application or the REST service in order to subscribe the events.

So when the event is triggered based on the logged in user the POST request is made to the right client. ASP.NET webhooks use user based notifications. Is there any limitation in this?

Yes, consider a scenario – where you have an application with multiple customers. Each customer has many users. And the admin of one customer wants subscribe to the PhotoAdded event as above. Her intention is to be notified whenever any of her users add a photo. So if she registers for a webhook by logging in using her credentials, she will get the notifications only when she adds a photo, because ASP.NET webhooks by default provide user based notifications. Also we can’t register this event in the global level with no authentication, the she will be notified when users of the other customers add a photo.

I hope ASP.NET webhook will provide a way to customize the notification. As of now NotifyAsync is a static extension method to which the overriding is not possible.


The requested content appears to be script and will not be served by the static file handler.

If you get the above message from IIS while working with ASP.NET 4.5 or above, the most likely cause is that you’re missing the ASP.NET 4.5 in your development IIS.

Here’s the resolution

To install or uninstall ASP.NET 4.5 on Windows 8 or Windows Server 2012, use one of the following options:

  • Run the following command from an administrative command prompt: dism /online /enable-feature /featurename:IIS-ASPNET45
  • For Windows 8 client computers, turn on “IIS-ASPNET45” in “Turn Windows Features On/Off” under “Internet Information Services-> World Wide Web Services -> Application Development Features -> ASP.NET 4.5”.
  • For Windows Server 2012 computers, enable “IIS-ASPNET45” using Server Manager, under “Web Server (IIS) -> Web Server ->Application Development -> ASP.NET 4.5”.


In Windows 8 Turn Windows Features On/Off



Facebook like link sharing

When you paste a URL in the FB share, FB pulls out some information about the URL along with any available images.

I created a similar feature in ASP.NET few years ago and I got the project file while clearing out  one of my disk partitions and thought of sharing it.

This project uses HTMLAgilitPack (http://htmlagilitypack.codeplex.com/) and XPATH for processing the HTML.

A working preview.








You can download the code here (http://sdrv.ms/MM5zA1) from SkyDrive.

CRUD operations in Infragistics WebDataGrid

DataGrids are vital controls in many user interfaces. Like it or not still 2D tables are one of the most comfortable user interface elements in not only viewing data but also in performing rapid inserts and updates.

CRUD operations of DataGrid is not a big innovation, but there are plenty of ways to do that. This post explains how to perform CRUD operations using Infragistics WebDataGrid control in ASP.NET.

This code is written using Infragistics version 12.2

Create a simple WebDataGrid.

   1: <ig:WebDataGrid ID="WebGridCarton" runat="server" Height="350px" Width="100%" AutoGenerateColumns="False" 

   2:    DataKeyFields="CartonViewModelGuid" EnableDataViewState="True" 

   3:    OnRowAdded="WebGridCarton_RowAdded" OnRowAdding="WebGridCarton_RowAdding" 

   4:    OnRowDeleted="WebGridCarton_RowDeleted" OnRowsDeleting="WebGridCarton_RowsDeleting" 

   5:    OnRowUpdated="WebGridCarton_RowUpdated" OnRowUpdating="WebGridCarton_RowUpdating">

   6:    <Columns>

   7:        <ig:BoundDataField DataFieldName="CartonViewModelGuid" Key="CartonViewModelGuid" Hidden="true" HtmlEncode="true">

   8:        <Header Text="Guid">

   9:        </Header>

  10:        </ig:BoundDataField>

  11:        <ig:BoundDataField DataFieldName="CartonInfoId" Key="CartonInfoId" Hidden="true" HtmlEncode="true">

  12:            <Header Text="Id">

  13:            </Header>

  14:        </ig:BoundDataField>

  15:        <ig:BoundDataField DataFieldName="SelectionColumn" Key="SelectionColumn" HtmlEncode="true" Width="15">

  16:        </ig:BoundDataField>

  17:        <ig:BoundDataField DataFieldName="Size" Key="Size" HtmlEncode="true">

  18:            <Header Text="Size">

  19:            </Header>

  20:        </ig:BoundDataField>

  21:        <ig:BoundDataField DataFieldName="Length" Key="Length" HtmlEncode="true">

  22:            <Header Text="Length (cm)">

  23:            </Header>

  24:        </ig:BoundDataField>

  25:        <ig:BoundDataField DataFieldName="Width" Key="Width" HtmlEncode="true">

  26:            <Header Text="Width (cm)">

  27:            </Header>

  28:        </ig:BoundDataField>

  29:        <ig:BoundDataField DataFieldName="Height" Key="Height" HtmlEncode="true">

  30:            <Header Text="Height (cm)">

  31:            </Header>

  32:        </ig:BoundDataField>

  33:        <ig:BoundDataField DataFieldName="Volume" Key="Volume" HtmlEncode="true">

  34:            <Header Text="Volume (cbm)">

  35:            </Header>

  36:        </ig:BoundDataField>

  37:        <ig:BoundDataField DataFieldName="Weight" Key="Weight" HtmlEncode="true">

  38:            <Header Text="Weight (kg)">

  39:            </Header>

  40:        </ig:BoundDataField>

  41:        <ig:BoundDataField DataFieldName="WeightLimit" Key="WeightLimit" HtmlEncode="true">

  42:            <Header Text="Weight Limit (kg)">

  43:            </Header>

  44:        </ig:BoundDataField>

  45:    </Columns>

  46:    <Behaviors>

  47:        <ig:Activation Enabled="true"></ig:Activation>

  48:        <ig:Selection CellClickAction="Row" RowSelectType="Single" Enabled="true"></ig:Selection>

  49:        <ig:EditingCore AutoCRUD="false">

  50:            <Behaviors>

  51:                <ig:CellEditing EditModeActions-EnableF2="true" EditModeActions-MouseClick="Single" Enabled="true">

  52:                    <ColumnSettings>

  53:                        <ig:EditingColumnSetting ColumnKey="CartonInfoId" ReadOnly="true" />

  54:                        <ig:EditingColumnSetting ColumnKey="SelectionColumn" ReadOnly="true" />

  55:                        <ig:EditingColumnSetting ColumnKey="CartonViewModelGuid" ReadOnly="true" />

  56:                        <ig:EditingColumnSetting ColumnKey="Size" EditorID="DropDownProvider_Size" ValidatorID="RequiredFieldValidator1" />

  57:                        <ig:EditingColumnSetting ColumnKey="Length" EditorID="WebTextEditor1" ValidatorID="DecimalValidator" />

  58:                        <ig:EditingColumnSetting ColumnKey="Width" EditorID="WebTextEditor1" ValidatorID="DecimalValidator" />

  59:                        <ig:EditingColumnSetting ColumnKey="Height" EditorID="WebTextEditor1" ValidatorID="DecimalValidator" />

  60:                        <ig:EditingColumnSetting ColumnKey="Volume" EditorID="WebTextEditor1" ReadOnly="true" ValidatorID="DecimalValidator" />

  61:                        <ig:EditingColumnSetting ColumnKey="Weight" EditorID="WebTextEditor1" ValidatorID="DecimalValidator" />

  62:                        <ig:EditingColumnSetting ColumnKey="WeightLimit" EditorID="WebTextEditor1" ValidatorID="DecimalValidator" />

  63:                    </ColumnSettings>

  64:                    <EditModeActions MouseClick="Single" />

  65:                </ig:CellEditing>

  66:                <ig:RowAdding Alignment="Top" EditModeActions-EnableF2="true" EditModeActions-EnableOnActive="true"

  67:                    EditModeActions-MouseClick="Single" Enabled="true">

  68:                    <EditModeActions EnableOnActive="True" MouseClick="Single" />

  69:                    <ColumnSettings>

  70:                        <ig:RowAddingColumnSetting ColumnKey="SelectionColumn" ReadOnly="true" />

  71:                        <ig:RowAddingColumnSetting ColumnKey="Size" EditorID="DropDownProvider_Size" ValidatorID="RequiredFieldValidator1" />

  72:                        <ig:RowAddingColumnSetting ColumnKey="Length" EditorID="WebTextEditor1" ValidatorID="DecimalValidator" />

  73:                        <ig:RowAddingColumnSetting ColumnKey="Width" EditorID="WebTextEditor1" ValidatorID="DecimalValidator" />

  74:                        <ig:RowAddingColumnSetting ColumnKey="Height" EditorID="WebTextEditor1" ValidatorID="DecimalValidator" />

  75:                        <ig:RowAddingColumnSetting ColumnKey="Volume" EditorID="WebTextEditor1" ReadOnly="true" ValidatorID="DecimalValidator" />

  76:                        <ig:RowAddingColumnSetting ColumnKey="Weight" EditorID="WebTextEditor1" ValidatorID="DecimalValidator" />

  77:                        <ig:RowAddingColumnSetting ColumnKey="WeightLimit" EditorID="WebTextEditor1" ValidatorID="DecimalValidator" />

  78:                    </ColumnSettings>

  79:                </ig:RowAdding>

  80:                <ig:RowDeleting Enabled="true" />

  81:            </Behaviors>

  82:        </ig:EditingCore>

  83:    </Behaviors>

  84:    <EditorProviders>

  85:        <ig:TextBoxProvider ID="WebTextEditor1">

  86:            <EditorControl ID="EditorControl1" ClientIDMode="Static" runat="server"></EditorControl>

  87:        </ig:TextBoxProvider>

  88:        <ig:DropDownProvider ID="DropDownProvider_Size">

  89:            <EditorControl ClientIDMode="Predictable" DropDownContainerMaxHeight="100px" EnableAnimations="False"

  90:                EnableDropDownAsChild="False" ID="CartonSizeDrdEditor" DropDownContainerWidth="185px" AutoPostBack="true" 

  91:                EnableAjaxViewState="true">

  92:            </EditorControl>

  93:        </ig:DropDownProvider>

  94:    </EditorProviders>

  95: </ig:WebDataGrid>

This grid has one drop down editor and others fields are text box editors. And it supports CRUD. The grid control has 6 CRUD events associated with it. They’re RowAdding, RowAdded, RowUpdating, RowUpdated, RowDeleting and RowDeleted.

This is the ViewModel class used for data binding.

   1: public class CartonInfoGridViewModel

   2: {

   3:     public string CartonViewModelGuid { get; private set; }


   5:     public int CartonInfoId { get; set; }

   6:     public string Size { get; set; }

   7:     public string Length { get; set; }

   8:     public string Width { get; set; }

   9:     public string Height { get; set; }

  10:     public string Volume { get; set; }

  11:     public string Weight { get; set; }

  12:     public string WeightLimit { get; set; }


  14:     public CartonInfoGridViewModel()

  15:     {

  16:         this.CartonViewModelGuid = Guid.NewGuid().ToString();

  17:     }

  18: }

CartonViewModelGuid property is used identify the objects in the Session collection.


Adding Rows

In the RowAdding event we create a new ViewModel object with the values entered in the grid. And add it to the Session collection then we bind the Session collection to the grid. Please note these property names are same as the ones in the ASPX file.

   1: protected void WebGridCarton_RowAdding(object sender, Infragistics.Web.UI.GridControls.RowAddingEventArgs e)

   2: {

   3:     var cartonRow = new CartonInfoGridViewModel();


   5:     cartonRow.CartonInfoId = -1;

   6:     cartonRow.Size = e.Values["Size"].ToString();

   7:     cartonRow.Length = e.Values["Length"].ToString();

   8:     cartonRow.Width = e.Values["Width"].ToString();

   9:     cartonRow.Height = e.Values["Height"].ToString();

  10:     cartonRow.Volume = e.Values["Volume"].ToString();

  11:     cartonRow.Weight = e.Values["Weight"].ToString();

  12:     cartonRow.WeightLimit = e.Values["WeightLimit"].ToString();


  14:     if (this.Session["GridGridCartonsInfo"] != null && this.Session["GridCartonsInfo"] is List<CartonInfoGridViewModel>)

  15:     {

  16:         var collection = Session["GridCartonsInfo"] as List<CartonInfoGridViewModel>;

  17:         collection.Add(cartonRow);

  18:     }

  19:     else

  20:     {

  21:         var collection = new List<CartonInfoGridViewModel>();

  22:         collection.Add(cartonRow);

  23:     }


  25:     this.DataBindCaronInfoGrid();

  26: }

DataBindCaronInfoGrid method binds the Session data to the Grid.

Just to prevent any Javascript errors popping up during this process in the RowAdded event we suppress the exceptions.

   1: protected void WebGridCarton_RowAdded(object sender, Infragistics.Web.UI.GridControls.RowAddedEventArgs e)

   2: {

   3:     e.ExceptionHandled = true;

   4: }


Deleting Rows

To delete a row we get the Id of the object and remove it from the Session collection. And bind the Session data to the grid.

   1: protected void WebGridCarton_RowsDeleting(object sender, Infragistics.Web.UI.GridControls.RowDeletingEventArgs e)

   2: {

   3:     string id = e.Row.DataKey.First().ToString();


   5:     if (this.Session["GridGridCartonsInfo"] != null && this.Session["GridCartonsInfo"] is List<CartonInfoGridViewModel>)

   6:     {

   7:         var collecion = this.Session["GridGridCartonsInfo"] as List<CartonInfoGridViewModel>;


   9:         var removeItem = collecion.FirstOrDefault(c => c.CartonViewModelGuid == id);


  11:         if (removeItem != null)

  12:             collecion.Remove(removeItem);

  13:     }


  15:     this.DataBindCaronInfoGrid();

  16: }


  18: protected void WebGridCarton_RowDeleted(object sender, Infragistics.Web.UI.GridControls.RowDeletedEventArgs e)

  19: {

  20:     e.ExceptionHandled = true;

  21: }


Updating Rows

Updating the rows happens the same way, we identify the object via the Id and update the values.

   1: protected void WebGridCarton_RowUpdating(object sender, Infragistics.Web.UI.GridControls.RowUpdatingEventArgs e)

   2: {

   3:     if (Session["GridGridCartonsInfo"] != null && Session["GridGridCartonsInfo"] is List<CartonInfoGridViewModel>)

   4:     {

   5:         var collection = Session["GridGridCartonsInfo"] as List<CartonInfoGridViewModel>;


   7:         if (e.OldValues["CartonViewModelGuid"].ToString() == e.Values["CartonViewModelGuid"].ToString())

   8:         {

   9:             string id = e.Values["CartonViewModelGuid"].ToString();


  11:             var editItem = collection.FirstOrDefault(c => c.CartonViewModelGuid == id);


  13:             if (editItem != null)

  14:             {

  15:                 editItem.CartonInfoId = Convert.ToInt32(e.Values["CartonInfoId"]);

  16:                 editItem.Size = e.Values["Size"].ToString();

  17:                 editItem.Length = e.Values["Length"].ToString();

  18:                 editItem.Width = e.Values["Width"].ToString();

  19:                 editItem.Height = e.Values["Height"].ToString();

  20:                 editItem.Volume = e.Values["Volume"].ToString();

  21:                 editItem.Weight = e.Values["Weight"].ToString();

  22:                 editItem.WeightLimit = e.Values["WeightLimit"].ToString();

  23:             }

  24:         }


  26:         this.DataBindCaronInfoGrid();

  27:     }          

  28: }


  30: protected void WebGridCarton_RowUpdated(object sender, Infragistics.Web.UI.GridControls.RowUpdatedEventArgs e)

  31: {

  32:     e.ExceptionHandled = true;

  33: }



In the ViewModel class CartonViewModelGuid property is used as the object identifier in the collection. (In this case collection is persisted in the ASP.NET Session) For the new rows we set the CartonInfoId to –1, because this is an identity column in the database.

When we update the objects which are stored in the database already we can’t change the CartonInfoId that’s the reason why we use a separate Id property for the ViewModels.


The code for the DataBindCaronInfoGrid method

   1: private void DataBindCaronInfoGrid()

   2: {

   3:     if (this.Session["GridCartonsInfo"] != null && this.Session["GridCartonsInfo"] is List<CartonInfoGridViewModel>)

   4:     {

   5:         this.WebGridCarton.DataSource = Session["GridCartonsInfo"] as List<CartonInfoGridViewModel>;

   6:         this.WebGridCarton.DataBind();

   7:     }

   8:     else

   9:         this.LblStatus.Text = "Session expired, Please reload the page.";

  10: }

HTTP Headers

This is a quick and a small post on reading the HTTP headers in ASP.NET. Code gets the HTTP header key – value pairs and write it to a text file.

   1: protected void Page_Load(object sender, EventArgs e)

   2: {

   3:     var headers = Request.Headers;

   4:     StreamWriter writer = new StreamWriter(Server.MapPath("~/data.txt"));


   6:     foreach (var item in headers.AllKeys)

   7:     {

   8:         string data = item + "---" + headers[item];              

   9:         writer.WriteLine(data);

  10:     }


  12:     writer.Flush();

  13:     writer.Close();

  14: }

Output text file as follows.

   1: Connection---Keep-Alive

   2: Accept---text/html, application/xhtml+xml, */*

   3: Accept-Encoding---gzip, deflate

   4: Accept-Language---en-US

   5: Host---localhost:24859

   6: User-Agent---Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)

UI Updates – Part 2


ASP.NET UI updates are handles using Ajax. In this example it is described how to use the ASP.NET Ajax for the updates.

In ASP.NET Ajax we can have update panel and make the updates via Ajax calls. Prepare your ASPX markup like below.

   1: <body>

   2:     <form id="form1" runat="server">

   3:         <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>


   5:         <div>

   6:             <asp:Button ID="BtnPostback" runat="server" Text="PostBack" OnClick="BtnPostback_Click" />

   7:         </div>


   9:         <div>

  10:             <asp:UpdateProgress ID="UpdateProgress1" runat="server">

  11:                 <ProgressTemplate>

  12:                     <asp:Label Text="Working..." ID="LblBusyIndicator" runat="server"></asp:Label>

  13:                 </ProgressTemplate>

  14:             </asp:UpdateProgress>

  15:         </div>


  17:         <div>

  18:             <asp:UpdatePanel ID="UpdatePanel1" runat="server">

  19:                 <ContentTemplate>

  20:                     <asp:Label ID="Label1" runat="server" Text=""></asp:Label>

  21:                 </ContentTemplate>

  22:                 <Triggers>

  23:                     <asp:AsyncPostBackTrigger ControlID="BtnPostback" />

  24:                 </Triggers>

  25:             </asp:UpdatePanel>

  26:         </div>


  28:     </form>

  29: </body>

Code behind..

   1: private void PerformTask()

   2: {

   3:     // job time 4 - 10 seconds

   4:     int time = new Random().Next(4, 11);

   5:     Thread.Sleep(1000 * time);

   6:     Label1.Text = String.Format("Hello from worker - ran for {0} seconds", time);

   7: }



  10: protected void BtnPostback_Click(object sender, EventArgs e)

  11: {

  12:     PerformTask();

  13: }



Silverlight the platform itself is asynchronous. And we can have the same mechanism we used in WPF for the UI updates, but little different.

Read the Silverlight Dispatcher for the Silverlight UI updates

Put watermarks on uploaded images–ASP.NET

Here’s the simples piece of code in C# which can write some text on an image, eventually making it as a watermark.

StreamReader reader = new StreamReader(Server.MapPath("sample.jpg")); Bitmap bi = new Bitmap(reader.BaseStream); // base image Graphics gr = Graphics.FromImage(bi); string customString = "your string goes here"; gr.DrawString(s, new Font("Cooper Black", 25), Brushes.ForestGreen, new PointF(140, 115)); StreamWriter writer = new StreamWriter(Server.MapPath("edited.jpg")); writer.AutoFlush = true; bi.Save(writer.BaseStream, ImageFormat.Jpeg); // saving the edited image reader.Close(); writer.Close();

Charting with DotNet HighCharts

This post explains how to use the DotNet HighCharts wrapper for the HighCharts Javascript library. You can download the wrapper from Codeplex.

First create an ASP,NET file and add the following Javascript references.

   1: <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>

   2: <script src="http://code.highcharts.com/highcharts.js">

   1: </script>

   2: <script src="http://code.highcharts.com/modules/exporting.js">


Download the wrapper and add the single .dll reference to the project and add the following using statements to the code.

   1: using DotNet.Highcharts;

   2: using DotNet.Highcharts.Options;

   3: using DotNet.Highcharts.Helpers;

   4: using DotNet.Highcharts.Enums;

The wrapper provides pure C# methods for generating the charts and has a method ToHtmlString() which returns the JavaScript version of the chart. This can be set to a ASP.NET Literal control for rendering.

Then here’s a sample for creating a column chart with two series.

   1: DotNet.Highcharts.Highcharts chart = new DotNet.Highcharts.Highcharts("chart").SetXAxis(new XAxis

   2:             {

   3:                 Categories = sales.GetSales().Select(list => list.ProductName).ToArray<string>()

   4:             })

   5:             .SetSeries(new Series []

   6:             {

   7:                 new Series()

   8:                 {

   9:                     Type = ChartTypes.Column,

  10:                     Data = new Data(new object [] {125,824,122}),

  11:                     Name = "Sales"

  12:                 },

  13:                 new Series()

  14:                 {

  15:                     Type = ChartTypes.Column,

  16:                     Data = new Data(new object [] { 57,234,67 }),

  17:                     Name = "Freight"

  18:                 }

  19:             });

‘sales’ is the object data source, and we get the product name from it and the values are hard coded. All charts has a name, here the column chart’s name is ‘chart’ itself. Names should be unique for the page. If you have another chart with same in the same page only the last chart will be displayed.

Then chart has SetXAxis method where set the X-Axis properties.  And the chart has number of series. We feed the data to the series through its Data property which takes an object array.

Finally we serialize the chart as HTML content to the Literal control (ltrChart)

Example of a pie chart generation.

   1: DotNet.Highcharts.Highcharts piechart = new DotNet.Highcharts.Highcharts("pie").SetXAxis(new XAxis

   2:             {

   3:                 Categories = sales.GetSales().Select(list => list.ProductName).ToArray<string>()

   4:             })

   5:             .SetSeries(new Series[]

   6:             {

   7:                 new Series()

   8:                 {

   9:                     Type = ChartTypes.Pie,

  10:                     Data = new Data(new object [] {125,824,122}),

  11:                     Name = "Sales"

  12:                 }

  13:             });


  15:             piechartLiteral.Text = piechart.ToHtmlString();