Some stuff about Web and .NET development
RSS icon Email icon Home icon
  • Custom paging in ASP.NET with ListView & DataPager

    Posted on July 23rd, 2009 Thibaut 7 comments

    1. Introduction

    In this article, we’ll see how to implement custom paging in an ASP.NET website. You most probably have already worked with some controls having native paging implemented (such as the GridView and FormView for example) but they’re not applicable in every case. First, custom paging will allow you to page everything you want the way you want, but also, if correctly implemented, it’s gonna be a lot lighter than using a heavyweight control with all its richness only to beneficiate from its native paging.

    So, to illustrate how to implement this, we’re gonna use the ListView, which enables you to define a template (much like the Repeater) for your display and the DataPager control to configure custom paging on it. And for those who read my article about implementing the Bing API (part 1 & part 2), it’s the occasion to see how I actually implemented the paging of the search results.

    2. The ASP.NET code

    <asp:ListView ID="lvwSearchResults" runat="server" ItemPlaceholderID="plhItemContainer" onitemdatabound="lvwSearchResults_ItemDataBound">

        <LayoutTemplate>

            <ol class="searchResults">

                <asp:PlaceHolder ID="plhItemContainer" runat="server" />

            </ol>

        </LayoutTemplate>

        <ItemTemplate>

            <li>

                <ul>

                    <li><asp:HyperLink ID="lnkSearchResultUrl" runat="server"></asp:HyperLink></li>

                    <li><asp:Literal ID="litSearchResultDescription" runat="server" /></li>

                </ul>

            </li>

        </ItemTemplate>

    </asp:ListView>

     

    <div id="pager">

        <asp:DataPager ID="dpgSearchResultsPager" runat="server" PagedControlID="lvwSearchResults">

            <Fields>

                <asp:NextPreviousPagerField ShowNextPageButton="False" ButtonCssClass="previousNextLink" />

                <asp:NumericPagerField ButtonCount="10" ButtonType="Link" NumericButtonCssClass="numericLink" />

                <asp:NextPreviousPagerField ShowPreviousPageButton="False" ButtonCssClass="previousNextLink" />

            </Fields>

        </asp:DataPager>

    </div>

    Let’s first have a look at the ASP.NET code. What’s noticeable here :

    • The 2 controls : ListView and DataPager, which references the ListView (PagedControlID property)
    • The LayoutTemplate (stucture) and ItemTemplate (content) regions of the ListView
    • A ol is used in the LayoutTemplate because it’s the more correct, semantically speaking, for an ordered list of search results. But you could also be using a ul
    • The reference to the PlaceHolder by the ListView (ItemPlaceholderID)
    • We’ll use here the ItemDataBound event, but of course the inline Eval would also work perfectly
    • There’s a Div surrounding the DataPager for CSS purposes
    • The DataPager is very flexible and powerful : you can set the paging to be numeric, to be next/previous/first/last, or a mix of them (the case here : previous / numeric / next, just like Bing or Google), to display it at the top or bottom, or even both. Take a look at the Smart Tag in design view and make your choice…

    3. The C# code

    protected void Page_Load(object sender, EventArgs e)

    {

        this.dpgSearchResultsPager.PageSize = PageSize;

    }

    In the Page_Load(), initialize the PageSize property of your DataPager. Here is the code from the Bing API context, where we had defined a constant for the page size. But it could also be hardcoded in your ASP.NET code.

    private void DisplayResponse(SearchResponse response)

    {

        . . .

     

        // Otherwise, perform databinding

        this.lvwSearchResults.DataSource = response.Web.Results;

        this.lvwSearchResults.DataBind();

    }

    For those who want to use paging in the Bing API context, here’s where to put the initialization code. For the others, just put these two lines in the Page_Load().

    protected void lvwSearchResults_ItemDataBound(object sender, ListViewItemEventArgs e)

    {

        if (e.Item.ItemType == ListViewItemType.DataItem)

        {

            HyperLink lnkSearchResultUrl = (HyperLink)e.Item.FindControl("lnkSearchResultUrl");

            Literal litSearchResultDescription = (Literal)e.Item.FindControl("litSearchResultDescription");

            ListViewDataItem dataItem = (ListViewDataItem)e.Item;

            WebResult searchResult = (WebResult)dataItem.DataItem;

     

            if (lnkSearchResultUrl != null && litSearchResultDescription != null && searchResult != null)

            {

                string url = searchResult.Url;

                string description = searchResult.Description;

     

                if (HighlightText)

                    description = description.Replace("\uE000", "<span class=’highlight’>").Replace("\uE001", "</span>");

                else

                    description = description.Replace("\uE000", string.Empty).Replace("\uE001", string.Empty);

     

                lnkSearchResultUrl.Text = url;

                lnkSearchResultUrl.ToolTip = url;

                lnkSearchResultUrl.NavigateUrl = url;

                litSearchResultDescription.Text = description;

            }

        }

    }

    And finally, the code from the ItemDataBound event. Again, it’s from the Bing API context but serves as a good example and is straightforward to adapt.

    4. The click-twice problem

    I’m dedicating a section to this problem as I noticed from a web search that many developers had the same problem. When you want to change the page (ie, click on a paging button), nothing happens and you’ve got to click the same button twice to get the expected result. Clicking another button results in an even more strangy behaviour. Here’s the solution :

    protected void Page_PreRender(object sender, EventArgs e)

    {

        // Workaround to prevent clicking twice on the pager to have results displayed properly

        this.lvwSearchResults.DataBind();

    }

    5. A touch of CSS to polish that

    When all this done, you can have fun styling all of that. I’m just listing here the more fundamentals styles to apply, that is, removing the list numbers and centering the pager

    ol, ul

    {

        list-style-type : none;

        margin-left : 0;

        padding-left : 0;

    }

     

    ol li, ul li

    {

        margin-left : 0;

        padding-left : 0;

    }

     

    div#pager

    {

        text-align : center;

    }

    6. The final result

    Final result

    7. Conclusion

    Implementing custom paging in ASP.NET is very easy and powerful at the same time. But as all the power resides in the DataPager, I encourage you to take a look at the functionalities it can provide (start by taking a look at the Smart Tag). Hope you enjoyed this and see you soon ;)

    Share/Save/Bookmark

  • Implementing the Bing API in a ASP.NET website (part 2)

    Posted on July 22nd, 2009 Thibaut 7 comments

    In my last post, we saw how to get started with the Bing API. But the MSDN code is lacking (intentionally) from the following things that you might find useful :

    • You want to use Bing from a website and not from a console application
    • You want to use Bing to search on a particular website and not the whole web
    • You might want to handle the different kinds of errors/informations the right way
    • Some paging would also be very useful to browse the results
    • And maybe simplifying the code, extracting constants to ease their setup

    That’s what we’re gonna see here by taking a look at my implementation. Let’s see what the code is all about, then we’ll discuss about it. The code will be splitted in several parts, so we can discuss about it step by step.

    1. Extracting hardcoded values into constants

    public partial class SearchResults : Page

    {

        private const string BingApplicationId = "YourAppIDHere";

        private const string WebsiteToSearch = "http://www.thibautvs.com";

        private const int MaxResultsCount = 50; // Max value : 50 (Bing API v2.0)

        private const int PageSize = 10;

        private const string SearchLanguage = "en-US";

        private const bool HighlightText = true;

        private const string BingApiVersion = "2.0";

    First, I extracted all the hardcoded values in the code into constants. That’s way easier to setup the values. I also added one which enables you to easily activate/deactivate text highlighting of the keywords in the search result and the PageSize constant to take into account the paging functionality. From experience, I noticed that the BingService throws a SoapException when the ResultsCount is too large. By experimentation, I noticed that the value must be max 50. You can take a look at the MSDN to get more info about this restriction.

    2. Initializing the search request

    protected void Page_Load(object sender, EventArgs e)

    {

        this.dpgSearchResultsPager.PageSize = PageSize;

        InitializeSearchResults();

    }

    In the Page_Load, we initialize the PageSize of our pager (which is a DataPager bound to a ListView in the aspx page) and call the InitializeSearchResults() method.

    private void InitializeSearchResults()

    {

        using (BingService service = new BingService())

        {

            try

            {

                SearchRequest request = BuildRequest();

                SearchResponse response = service.Search(request);

                DisplayResponse(response);

            }

            catch (System.Web.Services.Protocols.SoapException ex)

            {

                DisplayErrors(ex.Detail);

            }

            catch (System.Net.WebException ex)

            {

                Console.WriteLine(ex.Message);

            }

        }

    }

    Tthe InitializeSearchResults method makes use of the BingService, which implements the IDisposable interface (thus, used here with the using keyword) in order to :

    1. Initialize the search request
    2. Get a search response
    3. Display the response
    4. Handle a SOAP exception, if there is one
    5. Handle a network exception, if there is one

    private SearchRequest BuildRequest()

    {

        SearchRequest request = new SearchRequest();

     

        // Common request fields (required)

        request.AppId = BingApplicationId;

        request.Query = Request.QueryString["keyword"] + string.Format(" (site:\"{0}\")", WebsiteToSearch);

        request.Sources = new SourceType[] { SourceType.Web };

     

        // Common request fields (optional)

        request.Version = BingApiVersion;

        request.Market = SearchLanguage;

        request.Adult = AdultOption.Moderate;

        request.AdultSpecified = true;

        request.Options = new SearchOption[] { SearchOption.EnableHighlighting };

     

        // Web-specific request fields (optional)

        request.Web = new WebRequest();

        request.Web.Count = MaxResultsCount;

        request.Web.CountSpecified = true;

        request.Web.Offset = 0;

        request.Web.OffsetSpecified = true;

        request.Web.Options = new WebSearchOption[]

        {

            WebSearchOption.DisableHostCollapsing,

            WebSearchOption.DisableQueryAlterations

        };

     

        return request;

    }

    Again, the code shown by the BuildRequest() method is autodescriptive. Just a precision about the search query : the code here assumes that you’re passing a keyword to the page via the querystring and in order to restrict the search to a particular website, your query must have the following form :

    keywordToSearchFor (site:http://www.thesitetosearch.com)

    3. Displaying the results

    private void DisplayResponse(SearchResponse response)

    {

        // If no results were found, display message and return

        if (response.Web == null || response.Web.Results == null)

        {

            this.litInformationZone.Text = "No results found.";

            return;

        }

     

        // Otherwise, perform databinding

        this.lvwSearchResults.DataSource = response.Web.Results;

        this.lvwSearchResults.DataBind();

    }

    As a crash occured when I submitted to Bing a request having no result (response.Web.Results having a NULL value), the first thing I do here is to protect the code from that error and warn the user by a message that her search query has no result. The litInformationZone is just a simple Literal object (and thus why I prefixed this “lit”). If the search has results, we perform the databinding with our ListView.

    4. Displaying the errors

    As errors may always occur, we have to handle them properly and display them to the user in a more convenient form than a disgracious exception message. Another lesson that Steve Krug teaches us in his Don’t make me think book is always apologize for the inconvenience to the user and explain to her briefly the error she’s encountered. This is where I started creating my own ErrorPanel UserControl that I used here and can also be reused later, so I suggest you doing the same. This UserControl is reffered in my code as pnlError and has one public property : ErrorMessage.

    private void DisplayErrors(XmlNode errorDetails)

    {

        // Add the default namespace to the namespace manager.

        XmlNamespaceManager nsmgr = new XmlNamespaceManager(errorDetails.OwnerDocument.NameTable);

        nsmgr.AddNamespace("api", "http://schemas.microsoft.com/LiveSearch/2008/03/Search");

        XmlNodeList errors = errorDetails.SelectNodes("./api:Errors/api:Error", nsmgr);

     

        if (errors != null)

        {

            StringBuilder builder = new StringBuilder();

     

            foreach (XmlNode error in errors)

            {

                foreach (XmlNode detail in error.ChildNodes)

                    builder.AppendLine(detail.Name + ": " + detail.InnerText);

     

                builder.AppendLine(Environment.NewLine);

            }

     

            this.pnlError.ErrorMessage = builder.ToString();

            this.pnlError.Visible = true;

        }

    }

    5. Enjoying the final result

    Time to watch some screens about the final result of all this work :

    Search results

    You can see from this picture the paging feature (that we’ll cover more in detail in a future article) and the highlight of the search keywords in the description text. By the way, this is accomplished by replacing the delimiter characters by a span having a specific CSS class. Code to accomplish this is shown below :

    if (HighlightText)

        description = description.Replace("\uE000", "<span class=’highlight’>").Replace("\uE001", "</span>");

    else

        description = description.Replace("\uE000", string.Empty).Replace("\uE001", string.Empty);

    Of course, your website needs to be indexed by Bing in order to perform searches on it. To check if your website is indexed, type “site:yoursiteurl” into Bing (ex : site:http://thibautvs.com). If it isn’t indexed yet, it will propose you to submit the URL so it can index it.

    6. Conclusion

    And this is it ! As you can see, the Bing API is pretty cool and easy to implement (actually, I took me less than 2 hours to get a first, beta running version). And a lot more services such as searching for images, videos, instant answers, mobile web, ads, phone books, using spell checkers or translations, … are also available (more info on the MSDN). Hope you’ve enjoyed this article, don’t hesitate if you would have any questions about that.

    See you ;)

    Share/Save/Bookmark

  • Implementing the Bing API in a ASP.NET website (part 1)

    Posted on July 21st, 2009 Thibaut 11 comments

    Bing

    Foreword : as the title suggests, the Bing API was implemented here in a ASP.NET website, but as the code dealing with the API is in C#, it should be straightforward to adapt it in some other .NET projects such as Silverlight. Hope you’ll enjoy this article !

    1. Context

    On the ASP.NET website I’m currently working on, I wanted to implement a search functionality as that’s the kind of very important criterias for the users on today’s websites. Steve Krug actually lists the search engine in the top 5 needed functionalities in his Don’t make me think book. I had several possibilities to do this :

    • Use the Google API, which is the first solution I’ve thought about
    • Use Yahoo & IBM’s OmniFind
    • Implement my own search engine
    • See what other options are available

    2. Choosing the right search engine API

    Of course, I wanted a totally free solution. I don’t want to have to pay each month or year for the kind of functionality I expect being free. So as I said, I thought about using Google API at first. There’s a free version but with advertisements. You’ve got to pay about 100$ a year if you want them out. Ok we can already eliminate this one. One doesn’t really wants ads of his direct concurrents to appear next to the search results on her own website.

    I also listed the solution of developing a little search engine by myself. Although I completely agree that this kind of project could be very interesting, I needed a fast solution and why developing something that I knew a way better version would be available for free out there on the web. There was also the OmniFind option, but the installation, deployment and database configuration makes it heavier to use than a simple API. At this moment, I thought about Bing. I tested it at its release and was very impressed by the relevance of the results, especially by the image search. And they’ve got all what I need for free. Plenty of documentation on the MSDN about the API, in multiple programming languages including C# that I’m using. Perfect ! Let’s implement this !

    3. Implementing the Bing API

    3.1. Choosing the protocol

    The Bing API can be used with the 3 following protocols :

    • JSON
    • XML
    • SOAP

    So the first step is to decide which one you’ll gonna use. In our case, that is, working with the code behind in C#, the use of SOAP was according to me the preferred choice. Indeed, JSON is best used with Javascript and we’re working in C#, so we can eliminate this choice. Remains XML and SOAP. XML is nice but you don’t want to have your code filled with potentially long XPATH strings when you can work with strongly typed objects and accessing their properties. Also, we beneficiate from the intellisense support for every information we’ll want to access. (more info on the MSDN about these protocols). Let’s now see how to implement the API using SOAP.

    3.2. Obtaining a Bing AppID

    In order to gain acccess to Bing through the API, you must first obtain an AppID from the Bing Developer Center.

    Bing AppID Just click on the Create an AppID link to get one. You’ll have to fill in a little form, with information such as your company name, website and your country.

    3.3. Adding the Web Reference under Visual Studio

    Web Reference Then, in Visual Studio, right click on your web project and click on Add Web Reference from the context menu.

    Type the following in the textbox : http://api.bing.net/search.wsdl?AppID=YourAppId&Version=2.2 (replace your AppID in this url). Then you should get this :

    Method detectedYou can see that the Search method from the API was correctly found. You can still change the Web reference name, which is the one you’ll use in your code to reference the Bing service.

    3.4. Integration of the API into the code

    In the page(s) where you want to use the Bing API, first include the following using statement :

    using [YourProjectDefaultNamespace].[TheWebReferenceName];

    Ex :

    using sampleproject.net.bing.api;

    Then you can implement and adapt the code listed on the MSDN to your needs.

    4. Implementing the Bing API - Part 2

    When you’ll have a look at the code from the MSDN, you’ll see that some adaptations are necessary.

    1. First, there is little chance that you want to implement Bing as a console application.
    2. Second, you want to use it to search on a particular website and not the whole web.
    3. You may also wish to handle the different types of errors the best way possible.
    4. And maybe you would like to have some ASP.NET code illustrating the implementation.

    This is exactly what’s coming next, where we’ll have a look at my implementation of the API in a ASP.NET website. So be sure to check out the part 2.

    Coming soon…

    Share/Save/Bookmark