Blog

Categorised by 'ASP.NET'.

  • Over the last few months I have been carrying out endless amounts of research and development to find a way to create my own eCommerce styled search similar to the likes of what eBay and Amazon use. Otherwise known as “Faceted Search”, whereby the search results are filtered through a series of facets belonging to your search criteria. Each facet typically corresponds to the possible values of a property common to a set of objects.

    Sounds very difficult and complex doesn’t it! Smile Even to this very day, I am sure eBay and Amazon must use some kind of “magic” to get their search to work in a seamlessly and efficient format.

    There are numerous search solutions out there that could help you achieve in making this type of search. From my experience I couldn’t find any low cost out-of-the-box solutions that would help me in making my own search. Majority of the search vendors were not only very expensive but they also required a quote to tailor make a solution for you.

    In the early stages I tried expanding my Lucene.NET knowledge, but I couldn’t find a flexible way to introduce facets into my search. I must admit I am not exactly an expert in Lucene and this could have also had a part to play in failing miserably.

    When I thought all was lost and there was no chance in hell in being able to figure this thing out, I luckily came across a few blog and StackOverflow posts by a guy called Mauricio Scheffer. Mauricio seems to be the brains behind the .NET client version of a search platform called: SolrNet. SolrNet is a  Solr client library built for the .NET Framework. This is one of the strengths of Solr. It can be consumed within other development platforms such as Python and Ruby.

    SolrNet just happened to be an ideal solution to what I was looking for and with just over a weeks development I was able to build my own basic search, which looks something like this:

    SolrNet1 SolrNet2

    As you can see from my screenshots, you can carry out a search by report type and/or global text search. In addition, the showing and hiding of the facet objects are purely dependent on the searches returned.

    SolrNet is a very flexible package and I know just enough to implement the basics. But I was really surprised on how well the searches performed even with the most basic implementation. So I am looking forward to adding additional features as over the next few months and perfecting both my Solr search index and code.

    I won’t be posting the code that I used to create my search since its quite a big project and tailor made specific to my database architecture. But here are a few links that I found useful to get me started in the world of SolrNet:

  • Over the last few days I have been doing some research on the best way to implement search functionality for a site I am currently building. The site will consists mainly of news articles. The client wanted a search that would allow a user to search across all fields that related to a news article.

    Originally, I envisaged writing my own SQL to query a few tables within my database to return some search results. But as I delved further into designing the database architecture in the early planning stages, I found that my original (somewhat closed minded) approach wouldn't be flexible nor scalable enough to search and extract all the information I required.

    From what I have researched, the general consensus is to either use SQL Full Text Search or Lucene.NET. Many have favoured the use of Lucene due to its richer querying language and generally more flexible since you have the ability to write a search index tailored to your project. From what I gather, Lucene can work with any type of text data. For example, you not only can index rows in your database but there are also solutions to support indexing physical files in your application. Neat!

    I have written some basic code (below) with a couple methods to get started in creating a search index and carrying out a multi-query search across your whole index. You would further enhance this code to only carry out a full index once all required records have been added. Most implementations of Lucene would use incremental indexing, where documents already in the index are just updated individually, rather than deleting the whole index and building a new one every time. I plan to hook up and optimise my Lucene code into a service that would be scheduled to carry out an incremental index every midnight.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Lucene;
    using Lucene.Net;
    using Lucene.Net.Store;
    using Lucene.Net.Analysis;
    using Lucene.Net.Analysis.Standard;
    using Lucene.Net.Index;
    using Lucene.Net.Documents;
    using Lucene.Net.QueryParsers;
    using Lucene.Net.Search;
    using System.Configuration; 
    
    namespace MES.DataManager.Search
    {
        public class Lucene
        {
            public static void IndexSite()
            {           
                    //The file location of the index
                    string indexLocation = @ConfigurationManager.AppSettings["SearchIndexPath"];
    
                    Directory searchDirectory = null;
    
                    if (System.IO.Directory.Exists(indexLocation))
                        searchDirectory = FSDirectory.GetDirectory(indexLocation, false);
                    else
                        searchDirectory = FSDirectory.GetDirectory(indexLocation, true); 
    
                    //Create an analyzer to process the text
                    Analyzer searchAnalyser = new StandardAnalyzer(); 
    
                    //Create the index writer with the directory and analyzer.
                    IndexWriter indexWriter = new IndexWriter(searchDirectory, searchAnalyser, true);
    
                    //Iterate through Article table and populate the index
                    foreach (Article a in ArticleBLL.GetArticleDetails())
                    {
                        Document doc = new Document();
    
                        doc.Add(new Field("id", a.ID.ToString(), Field.Store.YES, Field.Index.UN_TOKENIZED, Field.TermVector.YES));
                        doc.Add(new Field("title", a.Title, Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.YES));
                        doc.Add(new Field("articletype", a.Type.TypeName, Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.YES)); 
    
                        if (!String.IsNullOrEmpty(a.Summary))
                            doc.Add(new Field("summary", a.Summary, Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.YES));                
    
                        if (!String.IsNullOrEmpty(a.ByLineShort))
                            doc.Add(new Field("bylineshort", a.ByLineShort, Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.YES));                    
    
                        if (!String.IsNullOrEmpty(a.ByLineLong))
                            doc.Add(new Field("bylinelong", a.ByLineLong, Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.YES));                   
    
                        if (!String.IsNullOrEmpty(a.BasicWords))
                            doc.Add(new Field("basicwords", a.BasicWords, Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.YES));                   
    
                        if (!String.IsNullOrEmpty(a.MediumWords))
                            doc.Add(new Field("mediumwords", a.MediumWords, Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.YES));                   
    
                        if (!String.IsNullOrEmpty(a.LongWords))
                            doc.Add(new Field("longwords", a.LongWords, Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.YES));  
    
                        //Write the document to the index
                        indexWriter.AddDocument(doc);
                    }
                              
    
                    //Optimize and close the writer
                    indexWriter.Optimize();
                    indexWriter.Close();         
            }
    
            public static List<CoreArticleDetail> SearchArticles(string searchTerm)
            {
                Analyzer analyzer = new StandardAnalyzer(); 
    
                //Search by multiple fields
                MultiFieldQueryParser parser = new MultiFieldQueryParser(
                                                                    new string[]
                                                                    {
                                                                        "title",
                                                                        "summary",
                                                                        "bylineshort",
                                                                        "bylinelong",
                                                                        "basicwords",
                                                                        "mediumwords",
                                                                        "longwords"
                                                                    },
                                                                    analyzer); 
    
                Query query = parser.Parse(searchTerm); 
    
                //Create an index searcher that will perform the search
                IndexSearcher searcher = new IndexSearcher(@ConfigurationManager.AppSettings["SearchIndexPath"]); 
    
                //Execute the query
                Hits hits = searcher.Search(query);
    
                List<int> articleIDs = new List<int>(); 
    
                //Iterate through index and return all article id’s
                for (int i = 0; i < hits.Length(); i++)
                {
                    Document doc = hits.Doc(i);
    
                    articleIDs.Add(int.Parse(doc.Get("id")));
                } 
    
                return ArticleBLL.GetArticleSearchInformation(articleIDs);
            }
    
        }
    }
    

    As you can see, my example allows you to carry out a search across as many of your fields as you require which I am sure you will find useful. It took a lot of research to find out how to carry out a multi query search. Majority of the examples I found over the internet showed you how to search only one field.

    The main advantage I can see straight away from using Lucene is that since the search data is held on disk, there is hardly any need to query the database. The only downside I can see is problems being caused by the possibility a corrupt index.

    For more information on using Lucene, here are a couple of links that you may find useful to get started (I know I did):

    http://www.codeproject.com/KB/library/IntroducingLucene.aspx http://ifdefined.com/blog/post/Full-Text-Search-in-ASPNET-using-LuceneNET.aspx

  • Watermarking and general image manipulation within the .NET Framework has become quite an easy thing to carry out thanks to the features provided by the System.Drawing namespace. The System.Drawing namespace contains types to help you with…well…drawing and rendering images. I will not be covering the basic use of the System.Drawing class. But feel free to carry out a Google.

    My example consists of using a .NET (aspx) page and a Generic Handler (ashx). The .NET page will allow me to select an image, add a logo to the top left and some text. The Generic Handler will contain all the magic needed to manipulate the image based on selections made within the .NET page. The screenshot (below) shows my basic program in action.

    Image Watermarking

    Firstly, let me start off by showing you the code for the Generic Handler.

    ImageRenderJpeg.ashx

    using System;
    using System.Web;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Drawing.Drawing2D;
    
    public class ImageRenderJpeg : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "image/jpeg";
    
            //Retrieve image details
            string imageUrl = context.Request.QueryString["ImageUrl"].ToString();
            string imageComment = context.Request.QueryString["ImageComment"].ToString();
            string imageIconUrl = context.Request.QueryString["Icon"].ToString();
    
            if (!String.IsNullOrEmpty(imageUrl))
            {
                //Get the location of the image
                Image imagePhoto = Image.FromFile(imageUrl);
    
                // Get dimensions of image
                int imageHeight = imagePhoto.Height;
                int imageWidth = imagePhoto.Width;
    
                //Create a new Bitmap
                Bitmap oBitmap = new Bitmap(imageWidth, imageHeight, PixelFormat.Format24bppRgb);
    
                //Load Background Graphic from Image
                Graphics oGraphics = Graphics.FromImage(oBitmap);
                oGraphics.SmoothingMode = SmoothingMode.HighQuality;
                oGraphics.DrawImage(imagePhoto, new Rectangle(0, 0, imageWidth, imageHeight), 0, 0, imageWidth, imageHeight, GraphicsUnit.Pixel);
    
                //Layer 1: Add an Image Logo to the top left
                if (!String.IsNullOrEmpty(imageIconUrl))
                {
                    Image imageIcon = Image.FromFile(imageIconUrl);
                    oGraphics.DrawImage(imageIcon, new Rectangle(5, 5, 124, 48), 0, 0, imageIcon.Width, imageIcon.Height, GraphicsUnit.Pixel);
    
                    imageIcon.Dispose();
                }
                
                //Layer 2: Add Comment
                if (!String.IsNullOrEmpty(imageComment))
                {
                    Font commentFont = new Font("Arial", 14, FontStyle.Regular); //Font Style
                    StringFormat commentFormat = new StringFormat();
                    commentFormat.Alignment = StringAlignment.Near; //Align text in left of layer
    
                    SolidBrush commentBrush = new SolidBrush(Color.Black); //Font Colour
    
                    oGraphics.FillRectangle(Brushes.Beige, 5, imageHeight - 55, imageWidth - 15, 50); //Create a rectangle with white background
                    oGraphics.DrawString(imageComment, commentFont, commentBrush, new Rectangle(5, imageHeight - 55, imageWidth - 15, 50), commentFormat); //Add comment text inside rectangle
                }
                
                //Layer 3: Add Copyright watermark
                Font watermarkFont = new Font("Arial", 40, FontStyle.Bold); //Font Style
                SolidBrush semiTransBrush = new SolidBrush(Color.LightGray); //Font Colour
                StringFormat watermarkFormat = new StringFormat();
                watermarkFormat.Alignment = StringAlignment.Center; //Align text in center of image
    
                oGraphics.DrawString("Copyright",
                    watermarkFont,
                    semiTransBrush,
                    new PointF(imageWidth / 2, imageHeight / 2), watermarkFormat);
    
                //Dispose of graphic objects
                imagePhoto.Dispose();
                oGraphics.Dispose();
    
                //Output image
                oBitmap.Save(context.Response.OutputStream, ImageFormat.Jpeg);
            }
            else
            {
            }
    
        }
    
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
    

    You can see that I am manipulating my image based on the query string parameters I pass from my .NET page into my Generic Handler. Hopefully, my code is commented well enough to explain the general overview on what is going on.

    The following code displays how my aspx page parses all the parameters needed to generate an image on the page:

    Default.aspx

    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
    
    <!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>Image Generator</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            Select an Image:
            <asp:DropDownList ID="ddlImage" runat="server">
                <asp:ListItem Text="*" Value="*">Select Image</asp:ListItem>
                <asp:ListItem Text="The Tumbler" Value="C:\Users\Surinder\Documents\Visual Studio 2010\WebSites\ImageCreator\Images\batmobile_Tumbler.jpg"></asp:ListItem>
                <asp:ListItem Text="Audi TT" Value="C:\Users\Surinder\Documents\Visual Studio 2010\WebSites\ImageCreator\Images\new-audi-tt-coupe.jpg"></asp:ListItem>
                <asp:ListItem Text="Volvo Concept" Value="C:\Users\Surinder\Documents\Visual Studio 2010\WebSites\ImageCreator\Images\volvo-s60-concept-interior1.jpg"></asp:ListItem>
            </asp:DropDownList>
            <br />
            <br />
            Add Logo: <asp:TextBox ID="txtImage" runat="server"></asp:TextBox>
            <br />
            <br />
            Add a comment:
            <asp:TextBox ID="txtComment" runat="server" TextMode="MultiLine" Width="500" Height="50"></asp:TextBox>
            <br />
            <br />
            <asp:Button ID="btnCreateImage" Text="Create Image" runat="server" onclick="btnCreateImage_Click" />
            <br />
            <br />
            <img id="imgRender" alt="Image Render" title="Image Render" runat="server" />        
        </div>
        </form>
    </body>
    </html>
    

    Default.aspx.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    
    public partial class _Default : System.Web.UI.Page 
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (ddlImage.SelectedValue == "*")
            {
                imgRender.Visible = false;
            }
            else
            {
                imgRender.Visible = true;
            }
        }
    
        protected void btnCreateImage_Click(object sender, EventArgs e)
        {
            if (ddlImage.SelectedValue != "*")
            {
                //The Image source will be pointed to our Generic Handler to display the image.
                imgRender.Src = String.Format("ImageRenderJpeg.ashx?ImageUrl={0}&ImageComment={1}&Icon={2}", ddlImage.SelectedValue, txtComment.Text, txtImage.Text);
            }
        }
    }
    
  • I am currently working on an ASP.NET 4.0  e-commerce site using Entity Framework alongside LINQ. I came across a small issue when I needed to carry out some calculations based on product pricing and the discounts that would need to be applied based on a specific customers allowance.

    You maybe thinking, what’s the issue? Well I wanted to be able to make the calculations within my LINQ query since both product pricing and customer discount amounts are stored in the database. So initially wrote the following code:

    using (MyEntities myContext = new MyEntities())
    {
        int productPrice = (from p in myContext.Products
                            where p.ProductID == 1
                            select p.Price).SingleOrDefault(); 
     
        int customerDiscount = (from cd in myContext.CustomerDiscounts
                                where cd.CustomerID == 15
                                select cd.Discount).SingleOrDefault(); 
     
        int productDiscountedPrice = productPrice - ((productPrice * customerDiscount) / 100);
    }
    

    As you can see from my code above, I had to write two separate LINQ queries in order to get the values I wanted and then base my calculations on those values. But I was determined to carry out my calculations in one query. Luckily, LINQ has has a really cool keyword that I totally missed. It’s the “let” keyword which allows you to declare a variable and assign it a calculated value.

    using (MyEntities myContext = new MyEntities())
    {
        int productDiscount = (from cd in myContext.CustomerDiscounts
                               join p in myContext.Products on cd.ProductID equals cd.ProductID
                               where p.ProductID == 1 && cd.CustomerID == 15
                               let discountAmount = p.Price - ((p.Price * cd.Discount) / 100)
                               select discountAmount).SingleOrDefault();
    }
    

    Since my database schema allowed me to join my “CustomerDiscount” and “Products” table, I was able to join the two tables and retrieve values I required through one query.

  • Over the last few months I have had the ability to mess around with a bit of jQuery. Even though I don’t have the complete understanding on how it works, I can see the benefits of writing my code in jQuery compared to bashing out lots of lines of JavaScript to do the same thing.

    One the cool features I have used is calling one of my .NET methods using the “$.ajax” jQuery command. In my example (below), I have created two aspx pages. The code-behind of my first page  (jQueryMethodTest.aspx) will only contain a public static method called “WhatIsYourName”, which returns a string value.

    [WebMethod]
    public static string WhatIsYourName(string name)
    {
        if (!String.IsNullOrEmpty(name))
        {
            return String.Concat("Hello ", name, "!");
        }
        else
        {
            return String.Empty;
        }
    }
    

    Remember, the jQueryMethodTest.aspx page only needs to contain our method nothing else! Additional methods can be added. Just don’t add any web controls.

    The second page (jQueryAjax.aspx), will contain our jQuery code and some HTML to output our result from calling the “WhatIsYourName” method.

    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
        <script type="text/javascript" language="javascript" src="javascript/jquery.js"></script>
    </head>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#btnSubmitName").click(function(event) {
                $.ajax({
                    type: "POST",
                    url: "jQueryMethodTest.aspx/WhatIsYourName",
                    data: "{'name': '" + $('#name').val() + "'}",
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    success: function(message) {
                        ShowPopup(message);
                    },
                    error: NameFailed
                });
            });
        });
    
        function ShowPopup(result) {
            if (result.d != "") {
                $("#Message").html(result.d);
            }
            else {
                $("#Message").html("I didn't get your name.");
            }
        }
    
        function NameFailed(result) {
            $("#Message").html(result.status + ' ' + result.statusText);
        }  
      </script>  
    
    <body>
        <form id="form1" runat="server">
        <div>
            <input id="name" name="name" type="text" />
            <br />
            <input id="btnSubmitName" name="btnSubmitName" type="button" value="Submit" />
            <br /><br />
            <span id="Message" style="color:Red;"></span>
        </div>
        </form>
    </body>
    </html>
    

    If all goes well, you should get the following result:

    Calling ASP Method Using jQuery

    The “$.ajax” jQuery command requires the following parameters in order to work:

    • url – links to where our .NET method is placed.
    • data – retrieves the value from some control in our page to pass to our method. Remember, the name of the parameter must be named the same as the parameter from our .NET method.
    • dataType – the response type.
    • contentType – the request content type.
    • success – the JavaScript function that gets fired on postback.
    • error – the Javascript function that gets fired if there is a failure. This is an optional parameter.

    I guess jQuery’s motto really is true: “write less, do more”.

  • I had created some .NET UserControl’s that I needed to dynamically add to a Panel control within my page. I previously thought generating my UserControl’s dynamically would be the same as dynamically generating any other .NET Control, like this:

    private void CreateControls()
    {
        //Create control
        TextBox txtUser = new TextBox();
        txtUser.ID = "txtUser";
        txtUser.Text = "Please enter a value";
     
        //Add Control to Panel already in our .NET page
        pnlControlPlaceHolder.Controls.Add(txtUser);
    }
    

    But I was wrong! :-)

    Fortunately, there is a really easy way to to add a UserControl dynamically by simply making use of the “LoadControl” method. The “LoadControl” method takes a single parameter containing the virtual path of your UserControl. For example:

    private void CreateControls()
    {
        //Create control
        Control myUserControl = LoadControl("MyUserControl.ascx") as MyUserControl;
        myUserControl.ID = "ucMyControl";
     
        //Add Control to Panel already in our .NET page
        pnlControlPlaceHolder.Controls.Add(myUserControl);
    }
    

    Easy!

  • Ok! I admit it! I posted some incorrect information from one of my previous blog posts to “Dynamically Output A List of YouTube Video’s In ASP.NET”. I stupidly said: “The RSS feed is not structured in a nice enough format to output all the information you may need with ease.” I must have been drunk when I wrote that. As you can see from a sample of their RSS feed below I was wrong:

    <entry>
      <id>http://gdata.youtube.com/feeds/api/videos/R6r2ckeIpic</id>
        <published>2009-05-31T17:01:12.000Z</published>
        <updated>2009-06-01T01:22:11.000Z</updated>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='bike'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='Podcast'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='Pedrosa'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='motorcycles'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='Honda+RC212V'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='speed'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='Suzuki+GSV-R800'/>
        <category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/categories.cat' term='Sports' label='Sport'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='Rossi'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='motorcycle+road+racing'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='motograndprix'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='Yamaha+YZR+M1'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='Mugello'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='Italy'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='Stoner'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='Ducati+Desmosedici+GP8'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='MotoGP'/>
        <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='Lorenzo'/>
        <title type='text'>MotoGP action from Mugello 2009</title>
        <content type='text'>The best of the action from the Gran Premio D&amp;#180;Italia Alice, the fifth round of the 2009 motogp World Championship.</content>
        <link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=R6r2ckeIpic'/>
        <link rel='http://gdata.youtube.com/schemas/2007#video.responses' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/R6r2ckeIpic/responses'/>
        <link rel='http://gdata.youtube.com/schemas/2007#video.related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/R6r2ckeIpic/related'/>
        <link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/users/motogp/uploads/R6r2ckeIpic'/>
        <author>
          <name>MotoGP</name>
          <uri>http://gdata.youtube.com/feeds/api/users/motogp</uri>
        </author>
        <gd:comments>
          <gd:feedLink href='http://gdata.youtube.com/feeds/api/videos/R6r2ckeIpic/comments' countHint='24'/>
        </gd:comments>
        <media:group>
          <media:category label='Sport' scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>Sports</media:category>
          <media:description type='plain'>The best of the action from the Gran Premio D&amp;#180;Italia Alice, the fifth round of the 2009 motogp World Championship.</media:description>
          <media:keywords>MotoGP, Italy, Mugello, Podcast, Stoner, Pedrosa, Rossi, Lorenzo, Yamaha+YZR+M1, Ducati+Desmosedici+GP8, Honda+RC212V, Suzuki+GSV-R800, motorcycle+road+racing, motograndprix, motorcycles, bike, speed</media:keywords>
          <media:player url='http://www.youtube.com/watch?v=R6r2ckeIpic'/>
          <media:thumbnail url='http://i.ytimg.com/vi/R6r2ckeIpic/2.jpg' height='90' width='120' time='00:00:38.500'/>
          <media:thumbnail url='http://i.ytimg.com/vi/R6r2ckeIpic/1.jpg' height='90' width='120' time='00:00:19.250'/>
          <media:thumbnail url='http://i.ytimg.com/vi/R6r2ckeIpic/3.jpg' height='90' width='120' time='00:00:57.750'/>
          <media:thumbnail url='http://i.ytimg.com/vi/R6r2ckeIpic/0.jpg' height='240' width='320' time='00:00:38.500'/>
          <media:title type='plain'>MotoGP action from Mugello 2009</media:title>
          <yt:duration seconds='77'/>
        </media:group>
        <yt:noembed/>
        <gd:rating average='4.862069' max='5' min='1' numRaters='29' rel='http://schemas.google.com/g/2005#overall'/>
        <yt:statistics favoriteCount='10' viewCount='1055'/>
    </entry>
    <entry>
        ...
    </entry>
    <entry>
        ...
    </entry>
    

    Each “entry” element within the RSS feed represents a YouTube video. You are able to extrapolate all the important information about each movie such as Average Score, View Count, Thumbnail Images, Video Description, etc. Really useful stuff!

    You may be thinking: Why should I use an RSS feed to retrieve the video information rather than using the YouTube API? Well, using a YouTube API is definitely the easier and most straight-forward method. But what you should be aware that the API only works from .NET 2.0 onwards. There isn’t a YouTube API for .NET 1.1. Unfortunately, I only found this out when I tried to implement the API into one of my .NET 1.1 client sites.

    The code I have written below, reads the YouTube RSS feed and stores the information in a DataTable.

    private void GetYouTubeData(string YouTubeUrl)
    {
        //Create DataTable to store specific YouTube information
        DataTable dtYouTubeVideoData = new DataTable();
        dtYouTubeVideoData.Columns.Add("YouTubeID");
        dtYouTubeVideoData.Columns.Add("Title");
        dtYouTubeVideoData.Columns.Add("Description");
        dtYouTubeVideoData.Columns.Add("ImageUrl");
        dtYouTubeVideoData.Columns.Add("AverageRatings");
        dtYouTubeVideoData.Columns.Add("ViewCount");
    
        DataRow drYouTubeVideoData;
    
        //Link to YouTube RSS feed
        XmlTextReader rssReader = new XmlTextReader(YouTubeUrl);
        XmlDocument xmlDoc = new XmlDocument();
    
        //Download the XML (via the XmlTextReader)
        xmlDoc.Load(rssReader);
    
        //Select all nodes starting with "entry"
        XmlNodeList xmlNodeList = xmlDoc.GetElementsByTagName("entry");
        
        //For each "entry" element found
        foreach (XmlNode node in xmlNodeList)
        {
            drYouTubeVideoData = dtYouTubeVideoData.NewRow();
    
            //Create a new document, to search through the inner contents
            XmlDocument innerXmlDocument = new XmlDocument();
            innerXmlDocument.LoadXml(node.OuterXml);
    
            // Get movie ID
            drYouTubeVideoData["YouTubeID"] = innerXmlDocument.GetElementsByTagName("id")[0].InnerText.Replace("http://gdata.youtube.com/feeds/api/videos/", "");
    
            // Get movie title
            drYouTubeVideoData["Title"] = innerXmlDocument.GetElementsByTagName("title")[0].InnerText;
    
            //Get movie description
            drYouTubeVideoData["Description"] = innerXmlDocument.GetElementsByTagName("content")[0].InnerText;
            
            //Get the thumbnails
            XmlNodeList mediaTumbnail = innerXmlDocument.GetElementsByTagName("media:thumbnail");
    
            //Iterate through each thumbnail and only get one thumbnail per <entry>.
            foreach (XmlNode thumbnailNode in mediaTumbnail)
            {
                if (thumbnailNode.Attributes["height"].Value == "90" && thumbnailNode.Attributes["url"].Value.EndsWith("1.jpg"))
                {
                    drYouTubeVideoData["ImageUrl"] = thumbnailNode.Attributes["url"].Value;
                }
            }
    
            //Get movie rating
            XmlNodeList ratings = innerXmlDocument.GetElementsByTagName("gd:rating");
    
            foreach (XmlNode ratingsNode in ratings)
            {
                drYouTubeVideoData["AverageRatings"] = ratingsNode.Attributes["average"].Value;
            }
    
            //Get Statistics
            XmlNodeList statistics = innerXmlDocument.GetElementsByTagName("yt:statistics");
    
            foreach (XmlNode statisticsNode in statistics)
            {
                drYouTubeVideoData["ViewCount"] = statisticsNode.Attributes["viewCount"].Value;
            }
    
            dtYouTubeVideoData.Rows.Add(drYouTubeVideoData);
        }
    
        rssReader.Close();
    
        //Bind YouTube data to repeater
        repVideoList.DataSource = dtYouTubeVideoData;
        repVideoList.DataBind();        
    }
    
  • I recently needed to dynamically display a list of YouTube video’s from a specific YouTube account. If you plan to output the list of video’s straight from the YouTube’s RSS feed into your .NET application don’t bother. The RSS feed is not structured in a nice enough format to output all the information you may need with ease. For example, video ratings, time, etc are all in the same XML block.

    As you can see from the screenshot below, the ASP.NET page I created outputs all the information that you will probably want to show (if it doesn’t then it should give you a good starting point).

    YouTubeReaderScreenshot

    Before creating your own custom YouTube .NET application you will need to carry out two things:

    1. Go to the YouTube API Toolswebsite and download the “Google Data API SDK”. Once you have installed this on your computer. You will need to copy three dll’s from one of the sample projects:

      • Google.GData.Client.dll
      • Google.GData.Extensions.dll
      • Google.GData.YouTube.dll

      These dll's can be copied to your new project.

    2. Register a Developer Key. This is important! Without the developer key, your custom YouTube application will not work.

    To create the page (above), copy the following code:

    ASPX.CS Page

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Xml;
    using System.Net;
    using System.IO;
    using System.Text.RegularExpressions;
    using Google.GData.Client;
    using Google.GData.Extensions;
    using Google.GData.Extensions.MediaRss;
    using Google.YouTube;
    using Google.GData.YouTube;
    using System.Text;
    using System.Data;
    
    public partial class _Default : System.Web.UI.Page
    {
        private string YouTubeChampionshipChannel;
        private string YouTubeClientID;
        private string YouTubeDeveloperKey;
        public string YouTubeMovieID;
        public DataTable dtVideoData = new DataTable();
    
        protected void Page_Load(object sender, EventArgs e)
        {
            //Pass User Name to the YouTube link
            YouTubeChampionshipChannel = "MotoGP";
    
            //Add the YouTube Developer keys.
            //Register a Developer Key at: http://code.google.com/apis/youtube/dashboard
            YouTubeClientID = "test";
            YouTubeDeveloperKey = "testabc123";
    
            CreateVideoFeed();
    
            //Assign the first video details on page load.
            if (String.IsNullOrEmpty(YouTubeMovieID))
            {
                YouTubeMovieID = dtVideoData.Rows[0]["VideoID"].ToString();
                lblDescription.Text = dtVideoData.Rows[0]["Description"].ToString();
            }
    
        }
    
        private void CreateVideoFeed()
        {
            YouTubeRequestSettings settings = new YouTubeRequestSettings("MotoGP Channel", YouTubeClientID, YouTubeDeveloperKey);
            YouTubeRequest request = new YouTubeRequest(settings);
    
            //Link to the feed we wish to read from
            string feedUrl = String.Format("http://gdata.youtube.com/feeds/api/users/{0}/uploads?orderby=published", YouTubeChampionshipChannel); ;
                    
            dtVideoData.Columns.Add("Title");
            dtVideoData.Columns.Add("Description");
            dtVideoData.Columns.Add("DateUploaded");
            dtVideoData.Columns.Add("Ratings");
            dtVideoData.Columns.Add("NoOfComments");
            dtVideoData.Columns.Add("VideoID");
            dtVideoData.Columns.Add("Duration");
    
            DataRow drVideoData;
    
            Feed<Video> videoFeed = request.Get<Video>(new Uri(feedUrl));
    
            //Iterate through each video entry and store details in DataTable
            foreach (Video videoEntry in videoFeed.Entries)
            {
                drVideoData = dtVideoData.NewRow();
    
                drVideoData["Title"] = videoEntry.Title;
                drVideoData["Description"] = videoEntry.Description;
                drVideoData["DateUploaded"] = videoEntry.Updated.ToShortDateString();
                drVideoData["Ratings"] = videoEntry.YouTubeEntry.Rating.Average.ToString();
                drVideoData["NoOfComments"] = videoEntry.CommmentCount.ToString();
                drVideoData["VideoID"] = videoEntry.YouTubeEntry.VideoId;
                drVideoData["Duration"] = videoEntry.YouTubeEntry.Duration.Seconds.ToString();
    
                dtVideoData.Rows.Add(drVideoData);            
            }
    
            repVideoList.DataSource = dtVideoData;
            repVideoList.DataBind();
        }
    
        protected void repVideoList_ItemDataBound(object sender, RepeaterItemEventArgs e)
        {
            if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
            {
                DataRowView drVideo = (DataRowView)e.Item.DataItem;
    
                LinkButton showVideo = (LinkButton)e.Item.FindControl("btnShowVideo");
                Literal title = (Literal)e.Item.FindControl("Title");
                Literal description = (Literal)e.Item.FindControl("Description");
                Literal ratings = (Literal)e.Item.FindControl("Ratings");
                Literal noOfComments = (Literal)e.Item.FindControl("NoOfComments");
                Literal duration = (Literal)e.Item.FindControl("Duration");
    
                showVideo.CommandArgument = drVideo["VideoID"].ToString();
                title.Text = drVideo["Title"].ToString();
                description.Text = drVideo["Description"].ToString();
                ratings.Text = drVideo["Ratings"].ToString();
                noOfComments.Text = drVideo["NoOfComments"].ToString();
                duration.Text = drVideo["Duration"].ToString();
    
            }
        }
        protected void repVideoList_ItemCommand(object source, RepeaterCommandEventArgs e)
        {
            // Pass the YouTube movie ID to flash
            YouTubeMovieID = e.CommandArgument.ToString();
    
            if (YouTubeMovieID == e.CommandArgument.ToString())
            {
                lblDescription.Text = ((Literal)e.Item.FindControl("Description")).Text;
            }
    
        }
    }
    

    ASPX Page

    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
    
    <!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></title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div style="margin: 10px;">
            <div style="float: left; width: 300px; height: 400px; overflow: scroll;">
                <asp:Repeater ID="repVideoList" runat="server" OnItemDataBound="repVideoList_ItemDataBound"
                    OnItemCommand="repVideoList_ItemCommand">
                    <HeaderTemplate>
                        <table>
                    </HeaderTemplate>
                    <ItemTemplate>
                        <tr>
                            <td>
                                <asp:LinkButton ID="btnShowVideo" runat="server">Show Video</asp:LinkButton>
                                <br>
                                <strong>
                                    <asp:Literal ID="Title" runat="server"></asp:Literal></strong>
                                <br />
                                <asp:Literal ID="Description" runat="server"></asp:Literal>
                                <br />
                                Rating: <asp:Literal ID="Ratings" runat="server"></asp:Literal>
                                <br />
                                Comments: <asp:Literal ID="NoOfComments" runat="server"></asp:Literal>
                                <br />
                                Duration: <asp:Literal ID="Duration" runat="server"></asp:Literal>
                                <br />
                                <br />
                            </td>
                        </tr>
                    </ItemTemplate>
                    <FooterTemplate>
                        </table>
                    </FooterTemplate>
                </asp:Repeater>
            </div>
            <div style="float: left; margin-left: 15px;width:600px;2">
                <object width="480" height="385" style="float: left; clear: both; margin-bottom: 10px;">
                    <param name="movie" value="http://www.youtube.com/v/<%=YouTubeMovieID %>&hl=en&fs=1&rel=0">
                    </param>
                    <param name="allowFullScreen" value="true"></param>
                    <param name="allowscriptaccess" value="always"></param>
                    <embed src="http://www.youtube.com/v/<%=YouTubeMovieID %>&hl=en&fs=1&rel=0" type="application/x-shockwave-flash"
                        allowscriptaccess="always" allowfullscreen="true" width="480" height="385"></embed>
                </object>
                <div style="float: left;">
                    <asp:Label ID="lblDescription" runat="server"></asp:Label>
                </div>
            </div>
        </div>
        </form>
    </body>
    </html>
    

    This is just the beginning on what the YouTube API has to offer. For more information, visit the YouTube API website (http://code.google.com/apis/youtube/2.0/developers_guide_dotnet.html).

    * Post Updated 18/06/2009 – You can output YouTube video’s via RSS feed *

  • ASP.NET Membership Provider makes implementing secure authenticating membership forms more straightforward. The ASP.NET Membership Provider contains so many useful methods. But I could not find a method within the Membership class to check whether there was an existing email address in the database even though you can state in the web.config (requiresUniqueEmail) file:

    <membership>
      <providers>
        <add
          name="SqlMembershipProvider"
          type="System.Web.Security.SqlMembershipProvider, ..."
          connectionStringName="LocalSqlServer"
          enablePasswordRetrieval="false"
          enablePasswordReset="true"
          requiresQuestionAndAnswer="true"
          applicationName="/"
          requiresUniqueEmail="true"
          passwordFormat="Hashed"
          maxInvalidPasswordAttempts="5"
          minRequiredPasswordLength="7"
          minRequiredNonalphanumericCharacters="1"
          passwordAttemptWindow="10"
          passwordStrengthRegularExpression=""
        />
      </providers>
    </membership>
    

    I created the following CustomValidator with a ServerValidate event to carry out the duplicate email check:

    protected void DuplicateEmailCheck_ServerValidate(object source, ServerValidateEventArgs args)
        {
            //Create MembershipUserCollection to collate a list of duplicate email addresses
            MembershipUserCollection memCollection = Membership.GetUserNameByEmail(args.Value.ToString());
    
            //If duplicate email addresses are found then error
            if (memCollection.Count > 0)
            {
                args.IsValid = false;
            }
            else
            {
                args.IsValid = true;
            }
        }
    
  • I came across a problem today when trying to find an effective way to validate the length of a password field within a registration form I was creating. ASP.NET already has a bunch of useful validation controls. Most of which I have already have in use within my registration form, such as the RequiredFieldValidator, CompareValidator and RangeValidator.

    Now you might be thinking. What’s your problem dude? Its not hard to validate the length of a field. Yeah, you are right. But all the validation controls I am using (above) do not create post backs. I could have easily created a CustomValidator control to solve my problem, but this only fires once a post back has occurred.

    I guess my only solution is to use a RegularExpressionVaildator which meant I had to do some research into RegEx. To use RegEx to validate the length of a string between 0 and y (some number), use the following expression:

    .{0,y}
    

    To validate the exact length, use the following expression:

    .{y}
    

    Both example’s above will accept any type of characters entered in the field.