Gunnar Peipman's ASP.NET blog

Filtering dropdown by value selected from another dropdown

This is the first posting of Beginners section of this blog. I will put here code samples that are not advanced enough to put them elsewhere in this blog. First example shows how to filter one dropdown list based on value selected from another.

Let's create example web form and let's name it as DropDownFiltering.aspx. We will add two dropdown lists to it and name it as ddlMain and ddlSub. ddlMain is main dropdown that provides filter value to ddlSub. The code of form follows.


<%@ Page 
    Language="C#" AutoEventWireup="true" 
CodeBehind="DropDownFiltering.aspx.cs"
    Inherits="MyApplication1.DropDownFiltering" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>My Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        Main selection: 
        <asp:DropDownList runat="server" ID="ddlMain" DataTextField="Title" 
            DataValueField="Id" AutoPostBack="true" 
            onselectedindexchanged="ddlMain_SelectedIndexChanged"> 
        </asp:DropDownList>
        Sub selection 
        <asp:DropDownList runat="server" ID="ddlSub" DataTextField="Title" 
            DataValueField="Id">
        </asp:DropDownList>
    </div>
    </form>
</body>
</html>

As you can see I defined OnSelectedIndexChanged event for ddlMain, also it has  AutoPostBacks turned on. If user selects some value from ddlMain then web page will be sent to server and OnSelectedIndexChanged event will be fired.

Let's see now the code behind this page. There are two methods for binding dropdown lists with data. These methods are separate and not part of some events because we need to call them in more than one place in our code. When Page is loaded we will check if it is post back. If it is not post back then there is no data in dropdown lists and we have to bind them.


using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
 
namespace MyApplication1
{
    public partial class DropDownFiltering : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (IsPostBack)
                return;
 
            BindMain();
            BindSub();
        }
 
        protected void BindMain()
        {
            ddlMain.DataSource = GetMainData();
            ddlMain.DataBind();
        }
 
        protected void BindSub()
        {
            DataTable table = GetSubData();
            table.DefaultView.RowFilter = "ForeignID=" + ddlMain.SelectedValue;
 
            ddlSub.DataSource = table;
            ddlSub.DataBind();
        }
 
        protected DataTable GetMainData()
        {
            DataTable table = new DataTable();
            table.Columns.Add("ID");
            table.Columns.Add("Title");
 
            DataRow dr;
 
            dr = table.NewRow();
            dr["ID"] = 1;
            dr["Title"] = "First";
            table.Rows.Add(dr);
 
            dr = table.NewRow();
            dr["ID"] = 2;
            dr["Title"] = "Second";
            table.Rows.Add(dr);
 
            dr = table.NewRow();
            dr["ID"] = 3;
            dr["Title"] = "Third";
            table.Rows.Add(dr);
 
            return table;
        }
 
        protected DataTable GetSubData()
        {
            DataTable table = new DataTable();
            table.Columns.Add("ID");
            table.Columns.Add("ForeignID");
            table.Columns.Add("Title");
 
            DataRow dr;
 
            dr = table.NewRow();
            dr["ID"] = 1;
            dr["ForeignID"] = 1;
            dr["Title"] = "1:First";
            table.Rows.Add(dr);
 
            dr = table.NewRow();
            dr["ID"] = 2;
            dr["ForeignID"] = 1;
            dr["Title"] = "1:Second";
            table.Rows.Add(dr);
 
            dr = table.NewRow();
            dr["ID"] = 3;
            dr["ForeignID"] = 2;
            dr["Title"] = "2:First";
            table.Rows.Add(dr);
 
            dr = table.NewRow();
            dr["ID"] = 4;
            dr["ForeignID"] = 2;
            dr["Title"] = "2:Second";
            table.Rows.Add(dr);
 
            return table;
        }
 
        protected void ddlMain_SelectedIndexChanged(object sender, EventArgs e)
        {
            BindSub();
        }
    }
}

The last thing is SelectedIndexChanged event handler that is attached to ddlMain. This event will be fired when the value of ddlMain is changed on client side. When ddlMain has changed then ddlSub is binded again to show the values that correspond to ddlMain value.

Methods GetData() and GetSubData() are for example purposes only and in real applications you should replace them with your own methods that provide real data to dropdowns.

Pager class

Once I wrote a class to make paging calculations. I had some data bound user controls that had no paging support. So I had to improvise. As it is was pointless to duplicate pager code to every user control where I needed paging I wrote a class to make my life easier.


/// <summary>
/// Class for data pager calculations. 
/// </summary>
public class Pager
{
    /// <summary>
    /// Number of current page.
    /// </summary>
    public int CurrentPage = 1;
 
    /// <summary>
    /// Number of previous page.
    /// </summary>
    public int PreviousPage = 1;
 
    /// <summary>
    /// Number of next page.
    /// </summary>
    public int NextPage = 1;
 
    /// <summary>
    /// Count of pages.
    /// </summary>
    public int PageCount = 1;
 
    /// <summary>
    /// Page's first row index in collection.
    /// </summary>
    public int StartRow = 0;
 
    /// <summary>
    /// Pages last row index in collection plus one.
    /// It is meant to use in for loop.
    /// </summary>
    public int StopBeforeRow = 0;
 
    /// <summary>
    /// Returns pager object with values based on given parameters.
    /// </summary>
    /// <param name="pageNo">Number of current page.</param>
    /// <param name="pageSize">Page size.</param>
    /// <param name="collectionSize">Size of rows or items collection.</param>
    /// <returns></returns>
    public static Pager GetPager(int pageNo, int pageSize, int collectionSize)
    {
        Pager pg = new Pager();
 
        pg.CurrentPage = pageNo;
        pg.PageCount = (int)Math.Ceiling((double)collectionSize / pageSize);
        if (pg.CurrentPage > pg.PageCount)
            pg.CurrentPage = pg.PageCount;
 
        if(pageNo > 1)
            pg.PreviousPage = pg.CurrentPage-1;
        else
            pg.PreviousPage = 1;
 
        if(pg.CurrentPage>=pg.PageCount)
            pg.NextPage = pg.PageCount;
        else
            pg.NextPage = pg.CurrentPage+1;
 
        pg.StartRow = (pg.CurrentPage-1)*pageSize;
        pg.StopBeforeRow = pg.CurrentPage*pageSize;
 
        if(pg.StopBeforeRow > collectionSize)
            pg.StopBeforeRow = collectionSize;
        return pg;
    }
}

As you can see this class takes also care of inconsistent parameters and handles them so your code doesn't stop working.

Posted: May 15 2008, 07:58 PM by DigiMortal | with 1 comment(s)
Filed under: ,
Getting distinct values from arrays (through .NET Framework history)

Getting distinct values from arrays is not a unique problem. Here will I show some options how to do it. We are will use array of integer in examples here. This blog entry shows you somehow the mighty evolution of .Net Framework.

Let's say we have array of integers like this:


int[] nrs = new int[10];
nrs[0] = 1;
nrs[1] = 2;
nrs[2] = 2;
nrs[3] = 3;
nrs[4] = 4;
nrs[5] = 4;
nrs[6] = 5;
nrs[7] = 5;
nrs[8] = 6;
nrs[9] = 7;

We can see that this array contains duplicate elements. Let's try now to get distinct values of out if. The result we expect is the following array:


nrs[0] = 1;
nrs[1] = 2;
nrs[2] = 3;
nrs[3] = 4;
nrs[4] = 5;
nrs[5] = 6;
nrs[6] = 7;

1. .NET Framework 1.0/1.1

The first method we will see works okay on all .Net Framework versions. Also the older versions are supported.


public int[] GetDistinctValues(int[] array)
{
    ArrayList list = new ArrayList();
 
    for (int i = 0; i < array.Length; i++)
    {
        if (list.Contains(array[i]))
            continue;
        list.Add(array[i]);
    }
    return (int[])list.ToArray(typeof(int));
}

This method works on all versions of .NET Framework. It has some bad side effects on large arrays. Because we are using ArrayList that is built to hold objects it makes some bad overhead when using value types with it. Value types will be boxed when they are assigned to objects. When casting back from objects to value types then unboxing happens.

2. .NET Framework 2.0

.NET Framework 2.0 helps us to avoid overhead of boxing and unboxing because we can use generics. The following method is more powerful because it has no side effects of boxing and unboxing.


public int[] GetDistinctValues(int[] array)
{
    List<int> list = new List<int>();
 
    for (int i = 0; i < array.Length; i++)
    {
        if (list.Contains(array[i]))
            continue;
        list.Add(array[i]);
    }
    return list.ToArray();
}

This method doesn't use all the power of generic. So let's make this method usable on all types we want to use.


public T[] GetDistinctValues<T>(T[] array)
{
    List<T> tmp = new List<T>();
 
    for (int i = 0; i < array.Length; i++)
    {
        if (tmp.Contains(array[i]))
            continue;
        tmp.Add(array[i]);
    }
    return tmp.ToArray();
 
}

Now you can use this method also with strings, doubles and objects. Example with array shown in the beginning of this blog entry:


nrs = GetDistinctValues<int>(nrs);

Now we have used all the power of .Net Framework 2.0 and as we can see we made our method very general.

3. .NET Framework 3.5

The last thing is the shortest one and wyou don't have to expect here more than one line of code.


nrs = nrs.Distinct().ToArray();

This is possible due to extension methods for arrays and lists that .NET Framework 3.5 offers to us. So when you are using Visual Studio 2008 you can solve problems like this easily.

Posted: May 15 2008, 11:00 AM by DigiMortal | with 2 comment(s)
Filed under: ,
SharePoint: Assigning values to DateTime fields

SharePoint list items have a tricky way how to assign values to DateTime fields in your code. It is possible to assign value of DateTime type to this field but it also possible to assign a string. This string is a little bit mysterious. You may get errors with one dates but some dates will work perfect. So what's the trick?

If you want to assign date as a string to SharePoint's DateTime type field then you must format this string as an invariant date. The following line of code explains better what I mean.

item["MyDate"] = DateTime.Now.ToString(DateTimeFormatInfo.InvariantInfo);

If you guarantee that dates are in invariant format then you have no problems when assigning values to DateTime fields. Remember that invariant format is:

MM/dd/yyyy HH:mm:ss

Posted: May 14 2008, 12:31 AM by DigiMortal | with no comments
Filed under:
ASP.NET Search Sitemaps - something for SEO

ASP.NET Futures introduced support for search engine sitemaps. This is content detection protocol introduced originally by Google and later accepted also by Microsoft and Yahoo! ASP.NET makes sitemaps generating easy for us and as a bonus their model is perfect for more complex web applications like e-commerce and e-services sites. Let's see now what we can do.

Couple of word about search sitemaps

If you have small site with low number of links then managing sitemaps manually is not a problem. But if your site has many links and content that changes often you may want to automate the task of generating sitemap.

Sitemap is important thing for each site because it tells to search engine spiders where to find information from your site. If you give this information there is something you will get back - Google gives you very rich set of information about your site through their Webmasters service.

ASP.NET 2.0 introduced us the new concept - sitemaps. Besides new site navigation definition format we got also providers that were used by navigation controls. ASP.NET search sitemaps technology extends this model and makes our site navigation understandable for search engine spiders.

ASP.NET Search Sitemaps

Let's take a closer look at search sitemaps now. Suppose we have a web application that uses search sitemaps. As a first thing we will look at sitemap file that defines static navigation of our application.


<?xml version="1.0" encoding="utf-8" ?>
<sitemap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0">
  <sitemapnode url="/" title="Home"  description="Home page">
    <sitemapnode url="about-us.aspx" title="About us" description="">
      <sitemapnode url="corporate.aspx" title="Corporate information"  description="" />
      <sitemapnode url="values.aspx" title="Corporate values" description="" />
    </sitemapnode>
  </sitemapnode>
</sitemap>

Now let's see section from application's web.config where dynamic part of site's navigation is defined.


<searchSiteMap enabled="true">
  <providers>
    <add name="Navigation"type="Microsoft.Web.Preview.Search.AspNetSiteMapSearchSiteMapProvider, Microsoft.Web.Preview"/>
    <add name="Product"
           type="ASPNETFuturesEnabledWebApplication1.MySitemapProvider, ASPNETFuturesEnabledWebApplication1"
            targetUrl="Products.aspx"
            targetUrlseparator="?"
            pathInfoFormat="false"
            queryStringDataFields="ProductId"
            queryStringDataFormatString="ProductId={0}"
         />

  </providers>
</searchSiteMap>

As we can see there are two providers given that provide the sitemap system with dynamic data about navigation. First provider is the default one that generates search sitemap based on site's static navigation.

The second provider was added by me. This provider returns information about my products section navigation. When asked, it will return all links that products section contains. The code is as follows.


public class ProductEntry
{
    public string ProductId;
    public string ProductName;

    public String SiteMapLastModified;
    public String SiteMapChangeFrequency;
    public String SiteMapPriority;
}

public class ProductSitemapProvider : DynamicDataSearchSiteMapProvider
{
    public override IEnumerable DataQuery()
    {
        List<ProductEntry> products = new List<ProductEntry>();
        ProductEntry entry;

        foreach (Product product in DAL.GetProducts())
        {
            entry = new ProductEntry();
            entry.ProductId = product.Id.ToString();
            entry.ProductName = product.Name;
            entry.SiteMapLastModified = product.Modified.ToString("yyyy-MM-ddThh:mm:ss.fffZ");
            products.Add(entry);
        }
        return products;
    }
}


The class called ProductSitemapEntry defines search sitemap entry that is used with products. Second class, ProductSitemapProvider, is the one used by search sitemaps engine to get information about products. You can handle ProductSitemapEntry as one of the DTO classes for Products.

Search sitemaps structure

Search sitemaps protocol allows also sitemaps hierarchy to be defined. It is specially useful for large sites because we can say on higher level sitemaps which subsitemaps are changed meanwhile. This way we can lower our spidering traffic also.

Search sitemaps HttpHandler that we can find from web.config generates hierarchical search sitemaps. Let's see what happens when we make request to this handler.


<?xml version="1.0" encoding="utf-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <sitemap>
    <loc>http://localhost:49781/SearchSiteMaps.axd?sitemap=Navigation</loc>
    <lastmod>2008-04-20T01:58:41.694Z</lastmod>
  </sitemap>
  <sitemap>
    <loc>http://localhost:49781/SearchSiteMaps.axd?sitemap=Product</loc>
    <lastmod>2008-04-20T01:58:41.694Z</lastmod>
  </sitemap>
</sitemapindex>

This is pure hierarchical search sitemap that refers to two subsitemaps. These subsitemaps are asked through same handler and name of search sitemap is given by query parameter. When you look at search sitemaps configuration section above you may notice that these query parameters have same values as search sitemaps providers names.

Let's see now what happens when we ask first sitemap. Remember that the first one corresponds to site's static navigation shown before. Just copy and paste URL from above XML to your browser.


<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>http://localhost:49781/</loc>
  </url>
  <url>
    <loc>http://localhost:49781/about-us.aspx</loc>
  </url>
  <url>
    <loc>http://localhost:49781/corporate.aspx</loc>
  </url>
  <url>
    <loc>http://localhost:49781/values.aspx</loc>
  </url>
</urlset>

Now let's see what is returned by products search sitemap provider.


<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>http://localhost:49781/Products.aspx?ProductId=100</loc>
    <lastmod>2008-04-20T02:02:02.544Z</lastmod>
    <priority>0.4</priority>
  </url>
  <url>
    <loc>http://localhost:49781/Products.aspx?ProductId=102</loc>
    <lastmod>2008-04-20T02:02:02.544Z</lastmod>
    <priority>0.4</priority>
  </url>
</urlset>

As we can see there are two products (oh, lazy me!) introduced to search engines spiders.

Conclusion

As we can see the search sitemaps technology is very powerful feature that makes our sites SEO much simpler than it was before. We can add as many search sitemaps providers to our application's configuration and let them generate search sitemaps. Besides static navigation we may have many different sections in our site. By example, we may have blog, product vatalog, support forum, FAQ pages and so on.

ASP.NET search sitemaps will provide us with common and flexible ways to make our applications support search sitemaps protocol.

Posted: May 13 2008, 02:29 AM by DigiMortal | with 4 comment(s)
Filed under: ,
Extension method for enumerators

Extensions methods are pretty powerful thing because we can extend existing types without inheriting from them. I don't know developers who want to extend string or int or other basic types offered by .Net Framework.

There was a project lately where I had to use existing code from internet. There was extremely chaotic and confusing error handling logic and try-catch blocks appeared even in places where one little if has been enough.

One place where I found good use for extension methods was paring strings to enums by their name. The original code was something like this.


FieldTypeEnum fieldType;    
try
{
    Type enumType = typeof(FieldTypeEnum);
    object parsedType = Enum.Parse(enumType, fieldName);
    fieldType = (FieldTypeEnum)parsedType;
}
catch
{
    fieldType = FieldTypeEnum.TextType;
}

Well, we can see that this code is not very nice and short. Try-catch blocks should be used carefully because in the case of exceptions they may eat resources and hit system's performance hard. It is always better to avoid try-catch as far as you can.

So, what this code does? It tries to find enum value by the name that is assigned to the value. If value doesn't exist then default value is used. To accomplish this task without try-catch I wrote the following extension method.


public static class EnumExtensions
{
    public static bool ContainsName(this Enum e, string name)
    {
        string[] namesArray = Enum.GetNames(e.GetType());
        Int32 index = Array.IndexOf(namesArray, name);
        return (index> -1);
    }
}

This methods checks if there is given name in given enumerator and returns true if name is found in enum names array. If name is not found then, of course, false is returned. Let's see now how the previous code looks like if there is no try-catch block used.


FieldTypeEnum fieldType = new FieldTypeEnum();

if(fieldType.ContainsName(fieldName))
{
    object parsedType = Enum.Parse(fieldType.GetType(), fieldName);
    fieldType = (FieldTypeEnum)parsedType;
}
else
    fieldType = FieldTypeEnum.TextType;


We can also write the body of if this way.


FieldTypeEnum fieldType = new FieldTypeEnum();

if(fieldType.ContainsName(fieldName))
   

fieldType = (FieldTypeEnum)Enum.Parse(fieldType.GetType(), fieldName);

else
    fieldType = FieldTypeEnum.TextType;


Now we have pretty short and easily readable code instead of try-catch we used before. Also we are protected against overhead that may be caused by invalid enum constant names.

Posted: May 13 2008, 01:09 AM by DigiMortal | with no comments |
Filed under: ,
SharePoint: Half-Initialized Folders

I discovered something weird when trying to detect document library folder by URL. It is possible to create folder objects that are half initialized. I tried to ask folder by URL and I used URL of documents list view. The result - folder with correct name etc but many parameters were missing.

By example folder.Item returned me null. I got exception when I wanted to ask folder's unique ID. There were also many other parameters missing. But some parameters were correct. Somewhy I got half-initialized bastard instead of correct folder object I expected.

Solution was simple - when asking folder by URL you have to give same URL you can use to open the folder in Windows Explorer. Using these URL-s you get always correct folder objects from web object.

Posted: May 04 2008, 01:24 AM by DigiMortal | with 1 comment(s) |
Filed under:
SQL Server 2008 Reporting Services - save your time!

Some time ago I tried out SQL Server 2008 Reporting Services and I was amazed. I'm serious MS Paint terrorist and nice pictures are not one of my strong skills. So, if something makes things look nice to me I'm very happy.

I downloaded last CTP of SQL Server 2008 and installed it on my Virtual PC that runs Windows Server 2008. There were no problems during installation and after installing I was able to create my sample database. I also installed Reporting Services. During installation the reporting services web site was created on my server.

One thing we got is new Report Designer. It looks like application form Office family, specially user interface. It is easy and user friendly. After setting up data source I was able to start building the reports.

SQL Server 2008 Report Designer

Now comes the most exciting part - I was able to create a nice looking report and it took only 20 minutes to get it done. You can see the result on the following screenshot. Everything was very simple and I was able to create my report fast.

My report

When you look at the picture you may notice exporting features on top of the page. It doesn't work yet but the formats list was very cool. If new SQL Server will be available on market I'm sure it is worth to buy it.

Posted: Apr 20 2008, 12:16 AM by DigiMortal | with 1 comment(s) |
Filed under:
SharePoint: Save performance when getting list items

Another day, another solved mystery. Developers have interesting lifes as long as there are problems to solve. Another surprise from SharePoint - how to bog server down using only couple of lines of code? Of course, I have solution too that eliminates the issue.

Let's say you have large document library with many documents (let's say there is a little bit more than 90.000 documents). Some of them are versioned, some of them are new and so on. Now, let's say you have to manipulate documents in your code and you have to find document with specific ID. The document, if it exists, is given to you as SPListItem:


... 
SPListItem item = list[new Guid(itemGuidString)];
...

When you try something like this the code may run fast - as long as you have few documents you don't have any performance issues. But if you have about 90.000 documents than retrieveing one document as list item may take about 1:00 or 1:30 minutes with no problems. And good server you are using is not saving you this time.

What you have to do is to ask documents by items's unique ID from SPList.


...
SPListItem item = list.GetItemByUniqueId(new Guid(itemGuidString));
...

If you ask documents this way then documents are retrieved through CAML query and you save on performance a lot.

Posted: Apr 18 2008, 02:22 PM by DigiMortal | with 5 comment(s) |
Filed under:
Why MOSS2007 Content Import Fails?
And another discovery about SharePoint 2007 content deployment. You should be aware of forbidden file types. When making mass import of content then import fails when it finds forbidden file type. All files after first forbidden file will not be imported. I don't know if this blog entry has some value or not but maybe I will save somebody's time about hours or days :)
Posted: Apr 15 2008, 01:42 AM by DigiMortal | with no comments |
Filed under:
More Posts Next page »