Create a Bing Widget for the CISF Security Portal

[As part of CISF we will ship an ASP.NET AJAX web portal in which you can host your custom security management applications. The portal allows users to personalize certain pages by selecting various widgets made available to them. The information in this blog post is informational only at this stage. We would suggest you wait until the code drop before creating any widgets. Also note that in a future release we plan to build a full web sandbox for widgets and so R1 widgets will need to be re-written. ]

 

Hi, Syam Pinnaka here, I am a developer on the Information Security Tools team.

In this blog post let’s see how to create a simple Bing widget for CISF Portal. CISF portal widget is an asp.net web user control which derives from and implements CISF IWidget interface. For a quick look about Widget framework, IWidgetHost and IWidget, please see our teams previous blog posts here.

The Bing API enables submitting search queries programmatically and retrieving the results from Bing Engine. Complete reference about Bing API can be found here

Let’s try creating a simple Bing widget for CISF Portal using CISF widget framework and Bing API.

Step 1: Creating a user control.

Open devenv.exe. Open CISF Portal application. Right click on Widgets folder, Choose Add New item. Select Web User Control in templates. Name the control as Bing.ascx. Click Add.

image

Step 2: Add UI mark up.

Open Bing.ascx and add the following mark up.

<asp:Panel ID="SettingsPanel" runat="Server" Visible="False" >

Show

<asp:DropDownList ID="ResultCountDropDownList" runat="Server">

<asp:ListItem>1</asp:ListItem>

<asp:ListItem>2</asp:ListItem>

<asp:ListItem>3</asp:ListItem>

<asp:ListItem>4</asp:ListItem>

<asp:ListItem>5</asp:ListItem>

<asp:ListItem>6</asp:ListItem>

<asp:ListItem>7</asp:ListItem>

<asp:ListItem>8</asp:ListItem>

<asp:ListItem>9</asp:ListItem>

<asp:ListItem>10</asp:ListItem>

</asp:DropDownList>

items

<asp:Button ID="SaveSettings" runat="Server" OnClick="SaveSettings_Click" Text="Save" />

</asp:Panel>

 

<div style="overflow:hidden;padding-top:10px;padding-left:10px" >

<asp:TextBox ID="SearchText" Text="" runat="server" MaxLength="2000" Columns="40" /><asp:Button

    ID="Search" runat="server" Text="Search..." OnClick="Search_Click" /><br />

<asp:MultiView ID="BingMultiview" runat="server" ActiveViewIndex="1">

    <asp:View runat="server" ID="BingProgressView">

        <asp:Label runat="Server" ID="labelProgress" Text="Loading..." Font-Size="smaller" ForeColor="DimGray" />

    </asp:View>

    <asp:View runat="server" ID="BingFeedView">

        <asp:Label runat="Server" ID="labelHeader" Text="" Font-Size="smaller" ForeColor="DimGray"/>

        <asp:DataList ID="ResultList" runat="Server" EnableViewState="False" CssClass="feedlist">

        <ItemTemplate>

        <asp:HyperLink ID="FeedLink" runat="server" Target="_blank" CssClass="feed_item_link" NavigateUrl='<%# Eval("url") %>' ToolTip='<%# Eval("description") %>'>

        <%# Eval("title") %>

        </asp:HyperLink>

        </ItemTemplate>

        </asp:DataList>

    </asp:View>

</asp:MultiView>

</div>

<div style="text-align:center;">

<br />

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

</div>

We are using an asp.net panel control to capture user settings. For this “Bing” widget there is only one setting. Count of items to see in the result set. The mark up following the settings panel is to place controls for results header, results themselves and an error label.

Step 3: Add code behind.

Add a reference to net.bing.api. You will need to register your application with Bing development centre and get an AppId to be able to do this. For detailed information about Bing API and AppID and a web reference, please refer to the Bing documentation here. In addition, to start referring the types in just added service reference, add the following to using section.

 using net.bing.api;

Add the event handlers for Search, SaveSetting buttons like this.

     protected void Search_Click(object sender, EventArgs e)
    {
    }
     protected void SaveSettings_Click(object sender, EventArgs e)
    {
        this._Host.HideSettings();
    }

Write the code to query Bing API and get the results.

Declare a class level cost to hold AppId required to query Bing engine.

 const string AppId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; //Replace this with the App Id you get from Bing developer centre.

Add the code to Search_Click even handler like this.

     protected void Search_Click(object sender, EventArgs e)
    {

        if (string.IsNullOrEmpty(SearchText.Text.ToString()))
        {
            //Clear previous results if any...
            ResultList.DataSource = null;
            ResultList.DataBind();
            labelHeader.Text = string.Empty;  
            return;
        }

        // BingService implements IDisposable.
        using (BingService service = new BingService())
        {
            try
            {
                this.BingMultiview.ActiveViewIndex = 0;

                SearchRequest request = BuildRequest();

                // Send the request; display the response.
                SearchResponse response = service.Search(request);
                var DataSource = GetResponse(response);//CreateDataSource();
                ResultList.DataSource = DataSource;
                ResultList.DataBind();

                this.BingMultiview.ActiveViewIndex = 1;
                  
                //Clear previous error if any...
                lblResults.Text = string.Empty;
            }
            catch (System.Web.Services.Protocols.SoapException ex)
            {
                // A SOAP Exception was thrown. Display error details.
                DisplayErrors(ex.Detail);
            }
            catch (System.Net.WebException ex)
            {
                // An exception occurred while accessing the network.
                lblResults.Text = ex.Message;
            }
        }
    }

We will also need supporting methods as below.

     private SearchRequest BuildRequest()
    {
        SearchRequest request = new SearchRequest();

        // Common request fields (required)
        request.AppId = AppId;
        request.Query = SearchText.Text.ToString();
        request.Sources = new SourceType[] { SourceType.Web };

        // Common request fields (optional)
        request.Version = "2.0";
        request.Market = "en-us";
        request.Adult = AdultOption.Moderate;
        request.AdultSpecified = true;
        request.Options = new net.bing.api.SearchOption[]
        {
            net.bing.api.SearchOption.EnableHighlighting
        };

        // Web-specific request fields (optional)
        request.Web = new WebRequest();
        request.Web.Count = 10;
        request.Web.CountSpecified = true;
        request.Web.Offset = 0;
        request.Web.OffsetSpecified = true;
        request.Web.Options = new WebSearchOption[]
        {
            WebSearchOption.DisableHostCollapsing,
            WebSearchOption.DisableQueryAlterations
        };

        return request;
    }
    private DataView GetResponse(SearchResponse response)
    {
        StringBuilder  txtHeader = new StringBuilder(string.Empty);

        // Display the results header.
        txtHeader.AppendLine("Bing API Version " + response.Version);
        txtHeader.AppendLine("Web results for " + response.Query.SearchTerms);
        txtHeader.AppendLine(string.Format("Displaying {0} to {1} of {2} results",
            response.Web.Offset + 1,
            response.Web.Offset + this.Count,  //response.Web.Results.Length,
            response.Web.Total));  
        txtHeader.AppendLine();

        //labelHeader.Visible = true;
        labelHeader.Text = txtHeader.ToString();

        DataTable dt = new DataTable();
        DataRow dr;

        dt.Columns.Add(new DataColumn("title", typeof(string)));
        dt.Columns.Add(new DataColumn("url", typeof(string)));
        dt.Columns.Add(new DataColumn("description", typeof(string)));
        dt.Columns.Add(new DataColumn("LastCrawled", typeof(string)));

        // Display the Web results.
        foreach (WebResult result in response.Web.Results)
        {
            dr = dt.NewRow();

            dr[0] = GetTextWithoutHighlighting(result.Title);
            dr[1] = GetTextWithoutHighlighting(result.Url);
            dr[2] = GetTextWithoutHighlighting(result.Description);
            
            dt.Rows.Add(dr);
        }

        IEnumerable<DataRow> FilteredRecords = dt.AsEnumerable().Take(this.Count);
        //EnumerableRowCollection<DataRow> query = (from d in dt.AsEnumerable()
        //             select d);   

        //DataView dv = new DataView(dt);
        DataView dv = new DataView(DataTableExtensions.CopyToDataTable<DataRow>(FilteredRecords));
        return dv;
    }
    private string GetTextWithoutHighlighting(string text)
    {
        StringBuilder retText = new  StringBuilder(string.Empty);
  
        // Write text to the standard output stream, changing the console
        // foreground color as highlighting characters are encountered.
        foreach (char c in text.ToCharArray())
        {
            if (c == '\uE000')
            {
                // If the current character is the begin highlighting
                // character (U+E000), change the console foreground color
                // to green.
                //Console.ForegroundColor = ConsoleColor.Green;
            }
            else if (c == '\uE001')
            {
                // If the current character is the end highlighting
                // character (U+E001), revert the console foreground color
                // to gray.
                //Console.ForegroundColor = ConsoleColor.Gray;
            }
            else
            {
                //Console.Write(c);
                retText.Append(c); 
            }
        }
        return retText.ToString();   
    }

    private void DisplayErrors(XmlNode errorDetails)
    {
        System.Text.StringBuilder txtErrors = new StringBuilder(string.Empty);
    
        // Add the default namespace to the namespace manager.
        XmlNamespaceManager nsmgr = new XmlNamespaceManager(
            errorDetails.OwnerDocument.NameTable);
        nsmgr.AddNamespace(
            "api",
            "https://schemas.microsoft.com/LiveSearch/2008/03/Search");

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

        if (errors != null)
        {
            // Iterate over the list of errors and display error details.
            txtErrors.AppendLine("Errors:");
            txtErrors.AppendLine();
            foreach (XmlNode error in errors)
            {
                foreach (XmlNode detail in error.ChildNodes)
                {
                    txtErrors.AppendLine(detail.Name + ": " + detail.InnerText);
                }

                txtErrors.AppendLine();
            }
        }
        lblResults.Text = txtErrors.ToString();
    }

Add a reference to System.Data.DataSetExtensions.dll by right clicking on the project folder, Choose Add reference, Choose System.Data.DataExtensions.dll as shown in the below images.

image

image

We will need to add a using System.Data.DataSetExtensions; to start referring the types.

Other namespaces that we need to add are as below.

 using System.Xml;
using System.Xml.Linq; 
using System.Data;
using System.Collections;
using System.Text;
using System.IO;

Step 4: Derive from IWIdget.

Add a reference to widget framework by adding the following using statement.

 using Microsoft.InformationSecurity.CISF.Portal.Business.WidgetFramework;

Add IWidget as the base class to Widgets_Bing.

public partial class Widgets_Bing : System.Web.UI.UserControl, IWidget

{

}

Step 5: Implement IWidget

Add a reference to WidgetHost by declaring a private class variable as below.

     private IWidgetHost _Host;

Initialize _Host in IWidget.Init as below.

     void IWidget.Init(IWidgetHost host)
    {
        this._Host = host;
    }

Add an accessor method to manage state.

     private XElement _State;
    private XElement State
    {
        get
        {
            if (_State == null)
            {
                string tmpStr = string.Empty;
                tmpStr = this._Host.GetState();
                if (!tmpStr.Equals(string.Empty))
                    _State = XElement.Parse(tmpStr);
            }
            return _State;
        }
    }

Add a method to retrieve count from above state.

     public int Count
    {
        get { return State.Element("count") == null ? 3 : int.Parse(State.Element("count").Value); }
        set
        {
            if (State.Element("count") == null)
                State.Add(new XElement("count", value));
            else
                State.Element("count").Value = value.ToString();
        }
    }

Add a using to System.Xml.Linq to reference XElement without any errors.

 using System.Linq;

Implement IWidget.ShowSettings, IWidget.HideSettings as shown below.

     void IWidget.ShowSettings()
    {
        SettingsPanel.Visible = true;
        ResultCountDropDownList.SelectedIndex = -1;
        ResultCountDropDownList.Items.FindByText(this.Count.ToString()).Selected = true;
    }
    void IWidget.HideSettings()
    {
        SettingsPanel.Visible = false;
        this.Count = int.Parse(ResultCountDropDownList.SelectedValue);
        this.SaveState();
        //this.ShowResults();
    }
    private void SaveState()
    {
        if (this.State == null)
            return;

        StringBuilder builder = new StringBuilder();
        XmlTextWriter writer = new XmlTextWriter(new StringWriter(builder));
        this.State.WriteTo(writer);

        var xml = builder.ToString();
        this._Host.SaveState(xml);
    }

Add a using to System.Text and System.IO to reference StringBuilder without any errors.

Just add place holder to other IWidget methods like below.

     void IWidget.Minimize()
    {
    }
    void IWidget.Maximize()
    {
    }
    void IWidget.Closed()
    {
    }
    void IWidget.FullScreen()
    {
    }

Minimize, Maximize and Closed are handled in WidgetHost. So here we just need to add custom functionality if we have any.

Step 6: Add database configurations.

Add a new row to Widget table in the CISFPortal database as below.

WidgetId   Name   Url                            Description CreatedDate                  LastUpdate                    VersionNo IsDefault DefaultState                                    Icon                     OrderNo

13              Bing     Widgets\Bing.ascx   Bing             2009-07-22 00:00:00.000 2009-07-22 00:00:00.000 1               1              <state><count>5</count></state> Widgets\Bing.gif  4

Build and deploy the web application with new “Bing” widget. We will see new Bing widget working as below.

 

image

Good to go. However we can improve this widget in many ways like add data paging support, provide rich UX, localize the search etc...

This is just to demonstrate how to create a new CISF Portal widget. Please feel to ping us if you have any questions or need a working copy of the above example.