Digital Colony!

Return HTML Page Source From Web URL in C#

Below is a snippet of code showing how to retrieve the page source of a web page. This code will return the HTML source from the home page on this site. It will then load that HTML into the string myPageSource.
using System.Net;
using System.Xml;
using System.IO;
string url = "http://digitalcolony.com";

HttpWebRequest myWebRequest = (HttpWebRequest)HttpWebRequest.Create(url);
myWebRequest.Method = "GET";
// make request for web page
HttpWebResponse myWebResponse = (HttpWebResponse)myWebRequest.GetResponse();
StreamReader myWebSource = new StreamReader(myWebResponse.GetResponseStream());
string myPageSource = myWebSource.ReadToEnd();
myWebResponse.Close();

Labels: , ,

 

Single Line If Statement in C#

Here is a standard if ... then statement in C# followed by a single line example. Using a single line if statement will reduce the number of lines of code.
if (dayOfTheWeek == "Tuesday")
{
    lunchLocation = "Fuddruckers";
}
else
{
    lunchLocation = "Food Court";
}
And the same example as a single line if ... then statement.
lunchLocation = (dayOfTheWeek == "Tuesday") ? "Fuddruckers" : "Food Court";

Labels:

 

Sending Email in ASP.NET 2.0 (C#)

Here is the C# version of Sending Email in ASP.NET 2.0 (VB.NET).
using System.Net.Mail;
The code snippet demonstrates Larry King emailing Oprah.
MailMessage Message = new MailMessage();
SmtpClient Smtp = new SmtpClient();
// Build message
Message.From = new MailAddress("larryking@cnn.com", "Larry King");
Message.To.Add(new MailAddress("oprah@oprah.com", "Oprah"));
Message.IsBodyHtml = false;
Message.Subject = "Come on My Show Soon";
Message.Body = "Please be a guest on my show. - Larry";
// Send Message
// each web host is different 
// (adjust next 2 lines accordingly)
Smtp.Host = "localhost";
Smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
Smtp.Send(Message);

Labels: , ,

 

Consuming a Web Service in ASP.NET Tutorial

My fitness site DeepFitness.com has almost two thousand articles on fitness and nutrition topics. The other day I wrote an API that exposed a few WebMethods. Below I'm going to walk through setting up a page that consumes those Web Services in an ASP.NET page developed in Visual Studio.

Add Web Reference

The first step is to Add Web Reference. Either right-mouse click the web site name in the Solution Explorer or from the tool bar select Website. Add the URL http://deepfitness.com/i/api/DeepFitnessService.asmx and click the Go button. A Web reference name of com.deepfitness will be found. Click the Add Reference button.

Web Reference

Web Namespace

Create a new web page. Add the namespace com.deepfitness to the code-behind page.
using com.deepfitness;

ASP.NET Code

The lab demo displays three sections which demonstrate three web methods. The drop-down list displays a list of tags used to label the articles on the site. This drop-down is populated using the GetTags call. Once a tag is selected, a datalist is populated with articles for that given tag using the GetArticlesByTagName call. Beside each article title is an articleID. Clicking on that articleID, will call the GetArticle method and return the article title and text below.
<h4><em>GetTags</em></h4>
<asp:Label ID="lblTag" AssociatedControlID="ddlTags" runat="server" Text="Tag:" />
<asp:DropDownList ID="ddlTags" runat="server" AutoPostBack="true" />
  
<h4><em>GetArticlesByTagName</em></h4>
<p><asp:Label ID="lblSelectedTagName" runat="server" /></p>
<div style="height:150px; overflow:scroll; background-color:#ffffcc;">
<asp:DataList ID="dlArticles" runat="server">
<HeaderTemplate></HeaderTemplate>
    <ItemTemplate>
    <asp:LinkButton ID="lbtnArticleID" runat="server" 
        CommandArgument='<%# Eval("articleID") %>'
        CommandName="GetArticle"
        OnCommand="LinkButton_Command">
        <%# Eval("articleID") %>
        </asp:LinkButton>
        <%# Eval("title") %><br />
    </ItemTemplate>
    <FooterTemplate></FooterTemplate>
</asp:DataList>
</div>

<h4><em>GetArticle</em></h4>
<p><asp:Label ID="lblTitle" runat="server" /></p>
<div id="dvArticle" runat="server" style="height:150px; overflow:scroll; background-color:#ffffcc;"/>

The C Sharp Code

Both the calls to GetTags and GetArticlesByTagName return collections and can be data bound with a single line of code each. The GetArticle call returns the WebArticleDetails class which holds the article title (Title) and article text (Page).
public DeepFitnessService dfs = new DeepFitnessService();

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {            
        ddlTags.DataSource = dfs.GetTags();
        ddlTags.DataValueField = "tagName";
        ddlTags.DataBind();
    }
    GetArticlesByTagName();
}

protected void GetArticlesByTagName()
{
    string tagName = ddlTags.SelectedValue;
    dvArticle.InnerHtml = "";
    lblTitle.Text = "";
    lblSelectedTagName.Text = tagName;
    dlArticles.DataSource = dfs.GetArticlesByTagName(tagName);
    dlArticles.DataBind();
}

protected void LinkButton_Command(Object sender, CommandEventArgs e)
{
    int articleID;
    articleID = Convert.ToInt16(e.CommandArgument);
    // get article
    WebArticleDetails article = dfs.GetArticle(articleID);
    lblTitle.Text = article.Title;
    dvArticle.InnerHtml = article.Page;
}

Lab Demo

Consuming the DeepFitness Web Service API

Labels: , ,

 

Replacing The Extended ASCII Dash in C# and SQL

Not all dashes are created equal. That's what I learned today. If you look at the ASCII Character Codes CheatSheet you will see 3 different dashes. The first is the normal one. The other two are considered extended ASCII. Extended ASCII is a polite way of saying it doesn't appear on your keyboard.
45  -
150 –
151 —
Over on DeepFitness.com, I try to create a friendly URL to each article. When you perform an HttpUtility.UrlEncode against a regular dash, it returns a dash. When you perform it against the 2nd dash it returns %e2%80%93. The 3rd dash will return %e2%80%94. Not exactly a search engine friendly URL.

I'm sure there are 10 ways to replace the bad dash with the good dash in C#. Here is the method I used.
string titleLink;
// assign titleLink a value - database perhaps
titleLink = HttpUtility.UrlEncode(titleLink);
// remove Extended ASCII dash with ASCII dash
titleLink = titleLink.Replace("%e2%80%93", "-");
A better way is to clean it up at the database level. Here is the SQL that will replace the extended dash with the normal dash.
UPDATE Article
SET title = REPLACE(title,CHAR(150),CHAR(45))

Labels: , ,

 

Capitalize the First Letter of a Word in C#

I noticed that a few of my labels in the right column were in lower-case. Since I wrote my own label control, I decided to add a line of code to make sure the first letter in each tag was capitalized.
string labelName;
labelName = char.ToUpper(labelName[0]) + labelName.Substring(1);

Labels: ,

 

Using Recursion To Return a List of all Files on a Web Site

When I decided to build a sitemap for INeedCoffee, I was able to pull a list of URLs from the database. One simple query provided me with all the URLS on the web site. But what if you don't have a database table holding all the URLs for your site? Maybe the only way to get a full list of URLs is to go through each folder on the site and make a list. Sounds like a job for code.

Recursion To the Rescue

The job we want the code to perform is to start in the root folder and build a list of files with the .ASPX extension. In this example .ASPX files are the content files. You might add .HTML or .ASP files depending on how you setup your web site. Once you have a list of files for the root folder, the code is to go inside each subfolder, add to the list of files and repeat the process until it's exhausted the entire tree structure of your web site.

The nist.gov site defines recursion as:
An algorithmic technique where a function, in order to accomplish a task, calls itself with some part of the task.

The Code

The following code when executed will build a list of every .ASPX file on the web server. Note that I add an underscore to the beginning of files that I don't wish to include on the list of indexed URLs.
public ArrayList urlList;

public void ScanWebsiteForFiles(DirectoryInfo directory)
{           
   // look for .ASPX files in current folder
   foreach (FileInfo file in directory.GetFiles("*.aspx"))
   {
       if (!file.Name.StartsWith("_"))
       {
           string thisURL = file.FullName.ToString();
           urlList.Add(thisURL.ToLower());
       }
   }

   DirectoryInfo[] subDirectories = directory.GetDirectories();
   foreach (DirectoryInfo subDirectory in subDirectories)
   {               
       ScanWebsiteForFiles(subDirectory);
   }
}
In order to run this code, pass it the root directory of your web site.
string dirName = HttpContext.Current.Server.MapPath("~/");
DirectoryInfo rootDirectory = new DirectoryInfo(dirName);
ScanWebsiteForFiles(rootDirectory);

Labels: , , ,

 

Creating an HttpHandler to Build a Search Engine Site Map

My previous post Build a Search Engine SiteMap in C# covered how to create a sitemap.xml file using the File System. It also provided guidelines on how to go about validating the sitemap as well as submitting it to the major search engines. If you came here for background on search engine sitemaps, go read that post first. If all you care about is the HttpHandler, you may proceed.

An Overview of the HttpHandler

Scenario: There is a page you wish to create that will be generated dynamically with code, but you want to use a file extension that isn't dynamic. Like XML. RSS feeds and a sitemap are two examples of xml files that would be ideal for an HttpHandler. There are other uses for HttpHandlers, but in this post we are only interested in creating a dynamic sitemap.

Here is a brief overview of how the HttpHandler will work. A request will be made for the sitemap.xml, probably by a search engine like Google. Instead of looking for it on the file system, your web application will intercept that request and pass it off to a class which which generate and deliver an XML document.

Step 1: Create SitemapHandler.cs

Inside the App_Code folder create SitemapHandler.cs. This class will implement the IHttpHandler interface. Before we deliver an XML document, let's create a simple HTML test to make sure the HttpHandler is working.
namespace HttpExtensions
{
    public class SitemapHandler : IHttpHandler
    {
        public SitemapHandler()
        { }

        #region IHttpHandler Members

        public bool IsReusable
        {
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            response = context.Response;
            response.Write("<html><body><h1>HTTP Handler is Working!</h1></body></html>");
        }
        
        #endregion
    }
}

Update the Web.Config

Add the following section inside system.web. The sitemap.aspx line is for debugging purposes only and will be removed once everything is working.
<httpHandlers>
<add verb="*" path="sitemap.xml" type="HttpExtensions.SitemapHandler"/>
<add verb="*" path="sitemap.aspx" type="HttpExtensions.SitemapHandler"/>
</httpHandlers>
From Visual Studio 2005, test the site. Now type in sitemap.xml in the path of the URL. You should see your HTTP Handler is Working! message. And if you type in sitemap.aspx, you should also see the message. As long as you view your site through Visual Studio 2005, you handler will work fine for both cases. Once you hand that job back to IIS, you'll need to do one more step.

Map *.XML to the aspnet_isapi.dll

If you run your code without doing this step, you will see your HTTP Handler is Working! message only on the sitemap.aspx request. Any request to sitemap.xml will return a 404 Page Not Found error. Instead of the HTTP Handler intercepting the request, IIS sees that the file extension is not a .NET file extension so it takes command. It looks on the file server and doesn't see a sitemap.xml and returns the 404.

The solution is map the *.xml file extension to the ASP.NET DLL (aspnet_isapi.dll). Once this is done and the server is restarted, the handler should work. For more information on how to do the IIS mapping go to Protecting Files with ASP.NET and scroll down to Protecting .mdb Files. Replace .xml for .mdb when following those directions.

Back to the SitemapHandler

Now that we have a working HttpHandler, we can go back and replace the HTTP Handler is Working! message with a real sitemap XML file. I've commented out the loop to add pages. Here is where you would add the code to pull the urls from some data store, be it a component or database.
public void ProcessRequest(HttpContext context)
{
   response = context.Response;
   response.ContentType = "text/xml";       
   using (TextWriter textWriter = new StreamWriter(response.OutputStream, System.Text.Encoding.UTF8))
   {
       XmlTextWriter writer = new XmlTextWriter(textWriter);
       writer.Formatting = Formatting.Indented;
       writer.WriteStartDocument();
       writer.WriteStartElement("urlset");
       writer.WriteAttributeString("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
       writer.WriteAttributeString("xsi:schemaLocation", "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd");
       writer.WriteAttributeString("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9");

       // Add Home Page
       writer.WriteStartElement("url");
       writer.WriteElementString("loc", "http://example.com");
       writer.WriteElementString("changefreq", "daily");
       writer.WriteEndElement(); // url

       // Add code Loop here for page nodes
       /*
       {
           writer.WriteStartElement("url");
           writer.WriteElementString("loc", url);
           writer.WriteElementString("changefreq", "monthly");
           writer.WriteEndElement(); // url
       }
       */
       writer.WriteEndElement(); // urlset
   }                      
}

Validate and Submit

Details on how to validate and submit your sitemap can be found on Build a Search Engine SiteMap in C#. Don't forget to remove the sitemap.aspx directive inside the web.config file. That was just for debugging.

A Word of Warning

After writing this and patting myself on the back for being so clever, I discovered that other XML files on my site were not displaying. They were throwing errors. Whereas IIS can natively display XML files, the ASP.NET DLL can't.

This leaves you with 2 possibilities. ONE: Use a different file extension such as .MAP instead of .XML. TWO: Write a second HTTP Handler to catch all other XML file requests. That handler would open the XML and stream it back to the browser with a contentType of "text/xml".
<add verb="*" path="sitemap.xml" type="HttpExtensions.SitemapHandler"/>
<add verb="*" path="*.xml" type="HttpExtensions.XMLHandler"/>
public void ProcessRequest(HttpContext context)
{
  HttpResponse response; 
  response = context.Response;
  string thisURL = context.Request.RawUrl.ToString();
  string thisXMLFile = HttpContext.Current.Server.MapPath(thisURL);
           
  StreamReader xmlStream = File.OpenText(thisXMLFile);
  string xmlOutput = xmlStream.ReadToEnd();
  response.ContentType = "text/xml";
  response.Write(xmlOutput);
}

Labels: , , , ,

 

Build a Search Engine SiteMap in C#

Sitemaps are XML files that web masters can create to let search engines know what what pages to index and how frequently to check for changes on each page. The XML format of the sitemap file is detailed on sitemaps.org. Here is a sample of a sitemap with a single url.
<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" 
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>http://example.com</loc>
    <changefreq>daily</changefreq>
  </url>
</urlset>
My sample differs from the one on sitemaps.org. The more defined namespace in my example will validate on Google, Yahoo! and with Ask.com. At the time of this writing, theirs doesn't. The loc and changefreq are required, priority and lastmod are optional.

My advice with the search engines is treat them like a passport agent. Say only what is required and nothing else or you could find yourself sent to the end of the line. Once you've determined what URLs will be on the sitemap, the only decision is defining the changefreq of each page. One strategy might be to set the home page to daily, section pages to weekly and content pages to monthly. If your home page has stock tickers or sports scores, you could set the changefreq to always or hourly.

Google prefers the sitemap file to be in the root folder and every example on their site names the file sitemap.xml. What Google wants, Google gets.

Additional Namespaces

using System.IO;
using System.Xml;

Sample Code

You will need write access to the sitemap.xml file. Since you don't want to give your entire root folder write access, my advice is to create a dummy sitemap.xml, place it into the root folder and then set write access to that file. Adding a try...catch to the code below will alert you if that write access is not there.
string SiteMapFile = @"~/sitemap.xml";
string xmlFile = Server.MapPath(SiteMapFile);

XmlTextWriter writer = new XmlTextWriter(xmlFile, System.Text.Encoding.UTF8);
writer.Formatting = Formatting.Indented;
writer.WriteStartDocument();
writer.WriteStartElement("urlset");
writer.WriteAttributeString("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
writer.WriteAttributeString("xsi:schemaLocation", "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd");
writer.WriteAttributeString("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9");

// Add Home Page
writer.WriteStartElement("url");
writer.WriteElementString("loc", "http://example.com");
writer.WriteElementString("changefreq", "daily");
writer.WriteEndElement(); // url

// Add Sections and Articles
SqlConnection con = new SqlConnection(connectionString);
string sql = @"SELECT url, 'weekly' as changefreq FROM Section 
UNION SELECT url, 'monthly' as changefreq FROM Articles ";
SqlCommand cmd = new SqlCommand(sql, con);
cmd.CommandType = CommandType.Text;

try
{
    con.Open();
    SqlDataReader reader = cmd.ExecuteReader();
    while (reader.Read())
    {
        string loc = "http://example.com" + reader["URL"].ToString();
        string changefreq = reader["changefreq"].ToString();
        writer.WriteStartElement("url");
        writer.WriteElementString("loc", loc);
        writer.WriteElementString("changefreq", changefreq);
        writer.WriteEndElement(); // url
    }
    reader.Close();
}
catch (SqlException err)
{
    throw new ApplicationException("Data Error (Sections):" + err.Message);
}
finally
{
    con.Close();
}

writer.WriteEndElement();// urlset        
writer.Close();

Validate Your Sitemap

Once you've confirmed you have a good looking sitemap.xml file in the root folder of your web site and it contains all the pages you want indexed by the search engines, it is now time to validate it. XML-Sitemaps.com has a sitemap validator that you can test out your new sitemap. Once it's fine, move to the next step.

Update Your robots.txt File

Add the location of your sitemap in your robots.txt file.

Sitemap: http://example.com/sitemap.xml

Google Webmaster

Google has a suite of tools for managing your relationship between your web sites and them. They call this suite Google Webmaster Central. It is here that you will register your web sites with validation files. Once they've established you are the webmaster of your site, they will present you will a screen to submit your sitemap. You will need a Google Account for this process. If you don't have one, follow the link to Create a Google Account.

Yahoo! Site Explorer

Yahoo! has a similar setup which is called Yahoo! Site Explorer. Using your Yahoo! ID, you will go through the same process of registering your web sites. And once that process has been completed, you can then submit your sitemap.xml file. Don't have a Yahoo ID? Get one.

Ask.com

Ask.com doesn't require any accounts or site validation. Just ping their server with the location of your sitemap.xml file modeled after the URL example below. Of course replace example.com with your domain name.

http://submissions.ask.com/ping?sitemap=http%3A//example.com/sitemap.xml

Monitoring the Sitemap Crawl

Both the Yahoo! Site Explorer and the Google Webmaster Tools have reports that provide updated status on the success and failure of the sitemap crawl. Ask.com to my knowledge doesn't have any such tools. And I couldn't locate a sitemap submission tool at all for MSN.com.

Using an HTTP Handler

This example uses the File System. Another option is to use an HTTP Handler to deliver the sitemap.xml.

Final Word

A friend of mine with a low traffic site saw his page views double after adding a sitemap. If one page of code can potentially double your page views, then it is worth pursuing.

Labels: , , , , ,

 

Override ReturnURL in ASP.NET Security

When using FormsAuthentication and a logged out user tries to enter a secured page that page name is appended to the ReturnUrl. After the user has been authenticated, the user is redirected to that page.

I had no problem with this feature until I timed out and hit my LogOff page. I wasn't authenticated to see the LogOff page, so it appended that page URL to the ReturnURL and sent me the LogOn page. Once I logged in, it redirected me back to the LogOff page, which promptly logged me out.

I decided it would be easier to pick the start page for the user, regardless of what the ReturnUrl parameter was. Instead of using FormsAuthentication.RedirectFromLoginPage, use FormsAuthentication.SetAuthCookie and handle the Redirect yourself.
if (FormsAuthentication.Authenticate(txtName.Text, txtPassword.Text))
{
    FormsAuthentication.SetAuthCookie(txtName.Text, true);
    Response.Redirect("MySecuredStartPage.aspx", true);              
}

Labels: , ,

 

Roll Your own Drudge Report in ASP.NET

Matt Drudge is my hero. He started The Drudge Report while working the graveyard shift at 7-11. He was at the store when the newspapers first arrived and most of the world was asleep. He was able to parlay that first access to the news into creating one of the most popular sites on the internet. Read his book Drudge Manifesto if you wish to learn more about his rise to fame.

Unlike Matt Drudge, I have no desire to get up super early and go through endless newspapers to create a hand-edited HTML page of my favorite links. Fortunately for me, we now have RSS feeds. This article will mix the ASP.NET article of the day RSS feed with the Drudge look and feel to create the ASP.NET Report.

Reverse Engineer the Drudge Report

The Drudge Report is basically a 3 column layout with an image logo and a top story link. The layout will vary over time, but that is the usual template. For the 3 column news look, we will use the asp:DataList control.

Drudge Font and Image

The logo was created using the Impact font at 100 points and Italic. In PhotoShop, under Blending Options (right mouse click Text layer), add a Drop Shadow. Play with the color, width and direction of the shadow.

Drudge uses a large upper-case Arial font for the top story and Courier for the 3-column stories. All links are black.
body 
{
    font-family: "Courier New", Courier, monospaced;
    font-size: 10pt; 
    font-weight: bold;
    color: #000;
    background-color: #fff;        
}
#headerDIV
{
    text-align: center;
}
#topstoryDIV
{
    font-size:32pt;
    font-weight:bold;
    font-family: Arial, Verdana, Helvetica;
}    
a:link, a:active, a:visited {
    color:#000;
    text-decoration:underline;
    line-height:1.2;
}

The Code

The RSS feed is loaded into an XmlDocument. The first story in the RSS feed will be designated the top story. The rest of the stories will be loaded into a DataTable which is binded to the asp:DataList.

Since ASP.NET code tends not to be photogenic, I've hard-coded links to some of my vacation photos for the top image. To mix it up a little, it will change depending upon the day of the week.
DataTable dt = new DataTable();
dt.Columns.Add("title", Type.GetType("System.String"));
dt.Columns.Add("link", Type.GetType("System.String"));

// ASP.NET Article of the Day
string rssURL = "http://asp.net/community/articles/rss.ashx";

XmlDocument doc = new XmlDocument();
doc.XmlResolver = null;
doc.Load(rssURL);

// display a different image based upon day of week
int dayOfWeek = (int)DateTime.Now.DayOfWeek;

switch (dayOfWeek)
{
    case 0:
        imgHeader.ImageUrl = "http://criticalmas.smugmug.com/photos/117793421-S.jpg";
        break;
    case 1:
        imgHeader.ImageUrl = "http://criticalmas.smugmug.com/photos/118708221-S.jpg";
        break;
    case 2:
        imgHeader.ImageUrl = "http://criticalmas.smugmug.com/photos/95113678-S.jpg";
        break;
    case 3:
        imgHeader.ImageUrl = "http://criticalmas.smugmug.com/photos/91872258-S.jpg";
        break;
    case 4:
        imgHeader.ImageUrl = "http://criticalmas.smugmug.com/photos/91842649-S.jpg";
        break;
    case 5:
        imgHeader.ImageUrl = "http://criticalmas.smugmug.com/photos/79938653-S.jpg";
        break;
    default:
        imgHeader.ImageUrl = "http://criticalmas.smugmug.com/photos/92103181-S.jpg";
        break;
}        

XmlNode oNode = doc.DocumentElement;
XmlNodeList oNodeList = oNode.SelectNodes("channel/item");

for (int itemCount = 0; itemCount < oNodeList.Count; itemCount++)
{
    string title = oNodeList[itemCount].SelectSingleNode("title").InnerText;
    string link = oNodeList[itemCount].SelectSingleNode("link").InnerText;

    if (itemCount == 0)
    {
        // top story
        hypTopStory.Text = title.ToUpper();
        hypTopStory.NavigateUrl = link;
    }
    else
    {
        DataRow dr = dt.NewRow();
        dr["title"] = title;
        dr["link"] = link;
        dt.Rows.Add(dr);
    }
}
dlStories.DataSource = dt;
dlStories.DataBind();

The ASP.NET Page

<form id="form1" runat="server">
<div id="parentDIV">
<p><a href="/">Return to Digital Colony</a></p>
    <div id="headerDIV" >
        <asp:Image ID="imgHeader" runat="server" Width="400" Height="300" BorderWidth="1" />
        <div id="topstoryDIV"><asp:HyperLink ID="hypTopStory" runat="server" /></div>
        <img src="aspnet-report.gif" width="716" height="137" alt="ASP.NET Report" />
    </div>
    <div id="storiesDIV">
    <asp:DataList ID="dlStories" runat="server" RepeatColumns="3" Width="100%" CellPadding="5">
        <ItemTemplate>
          <a href="<%# Eval("link") %>"><%# Eval("title") %></a>
          <hr />
        </ItemTemplate>            
    </asp:DataList>
    </div>        
</div>
</form>

Caching

When pulling RSS feeds, it is wise to be considerate of the bandwidth of your host site. Don't pull a fresh feed with every Page Load. Add an OutputCache directive to your page. For the ASP.NET Report example, I know that this feed updates daily, so I can set the Duration to 86400 which is the number of seconds in one day.
<%@ OutputCache Duration="86400" VaryByParam="none" %>

Working Demo

Visit my ASP.NET Report web page.

Better Than Drudge

Unlike The Drudge Report which uses awful HTML, our ASP.NET version is both XHTML and CSS compliant. It also validates under Section 508.

Your Drudge Report

If you create your own version using a different RSS feed, share it by adding a comment with a link to your page.

UPDATE JULY 2007: Microsoft moved the URL to the ASP.NET RSS feed and now restricts it to 10 articles. This makes it look silly in the Drudge format, but at least it working again. An ideal number of posts in an RSS feed for the Drudge look would be around 40.

Labels: , , , ,

 

Converting HTML to XHTML Programmatically in .NET

The Pro Version of FreeTextBox has a neat feature that will clean up poorly written HTML to a cleaner XHTML.
FreeTextBoxControls.FreeTextBox ftb = new FreeTextBoxControls.FreeTextBox();
string html = "<P><img src=joe.gif width=200 height=100 vspace=10>";
ftb.Text = html;
string xhtml = ftb.Xhtml;

Trace.Write("HTML", html);
Trace.Write("XHTML", xhtml);

Before and After

<P><img src=joe.gif width=200 height=100 vspace=10>
<p>
<img src="joe.gif" width="200" height="100" vspace="10" />
</p>
The FreeTextBox closed the paragraph tag, made the paragraph tag lower-case, added quotes to all the attributes and closed the img tag to make the entire string XML compliant. What this control didn't do was replace the deprecated vspace attribute with it's CSS equivalent.

Labels: , , ,

 

Displaying a SmugMug Gallery with ASP.NET

Back in the day I used to host all my own image galleries on my site. It's a tedious process and you can quickly use up your allocated disk space with today's multi-mega pixel cameras. Fortunately we have companies like SmugMug and Flickr that will host, manage and back-up all our images.

The problem with not hosting photo galleries is you send your audience away from your site over to their server. And your photo galleries develop their own audience which knows nothing about the parent site.

I discovered that using the XmlDataSource and DataList ASP.NET controls you can build a photo gallery on your site while the images stay over on SmugMug using a simple RSS feed.

The XmlDataSource

ASP.NET 2.0 introduced the asp:XmlDataSource control which we will use to connect to the SmugMug RSS feed. The DataFile parameter is the path to the RSS file for that photo gallery. In the snippet below, I hard-coded that value. In the example and lab that value is populated in the code behind. Also important is the XPath parameter. This is the address inside the XML Document that holds the information about each photo.
<asp:XmlDataSource ID="xmlDS" runat="server" XPath="rss/channel/item" 
    DataFile= "http://www.smugmug.com/hack/feed.mg?Type=gallery&Data=1838622&format=rss200" />

The RSS Feed (XML Document)

Here is a snippet of how a single photo is represented inside the XML Document. I've removed the portion which deals with the gallery name, as it is not used in this example. In this example the 2 values that are used when rendering the gallery inside the DataList will be link and guid.
<item>
    <title>Image Title</title>
    <link>http://criticalmas.smugmug.com/gallery/1838622/1/92083732</link>
    <description>Image description</description>
    <category>Vacation</category>
    <comments>http://criticalmas.smugmug.com/comment.mg...</comments>
    <exif:DateTimeOriginal>2006-08-24 19:07:19</exif:DateTimeOriginal>
    <pubDate>Thu, 31 Aug 2006 19:02:05 -0700</pubDate>
    <author>feeds-nobody@smugmug.com (criticalmas)</author>
    <guid isPermaLink="true">http://criticalmas.smugmug.com/photos/92083732-Th.jpg</guid>
    <enclosure url="http://criticalmas.smugmug.com/photos/92083732-Th.jpg" length="7741" type="image/jpeg"/>
</item>

The DataList Control

For this gallery, I set the RepeatColumns to 6 and just displayed the thumbnail image with a link to the full-sized image back on the SmugMug web site.
<asp:DataList ID="dlPhotos" runat="server" RepeatColumns="6">
<ItemTemplate>
  <a href="<%# XPath("link").ToString() %>">
     <asp:Image ID="img" ImageUrl='<%# XPath("guid") %>' runat="server"  /></a>
</ItemTemplate>
</asp:DataList>

Sample ASPX Using a Gallery Dropdown

The GalleryID is pulled from the URL of the photo gallery over on SmugMug. This is covered in detail in the sample lab.
<asp:XmlDataSource ID="xmlDS" runat="server" XPath="rss/channel/item" />
<h4>Select Gallery</h4>
<asp:DropDownList ID="ddlGallery" runat="server" AutoPostBack="true">
    <asp:ListItem Text="1838622: Uruguay - Colonia" Value="1838622" />
    <asp:ListItem Text="2473610: Lower Hellhole Canyon Desert Hike" Value="2473610" />
    <asp:ListItem Text="1887671: New Zealand - Whakarewarewa Thermal Village" Value="1887671" />
</asp:DropDownList>
<br /><br />
<asp:DataList ID="dlPhotos" runat="server" RepeatColumns="6">
<ItemTemplate>
    <a href="<%# XPath("link").ToString() %>">
        <asp:Image ID="img" ImageUrl='<%# XPath("guid") %>' runat="server" /></a>
</ItemTemplate>
</asp:DataList>
<asp:Label ID="lblError" Visible="false" runat="server" />

And the Code Behind (C#)

protected void Page_Load(object sender, EventArgs e)
{
   string rssURL;
   string galleryID;
   galleryID = ddlGallery.SelectedValue.ToString();
   rssURL = "http://www.smugmug.com/hack/feed.mg?Type=gallery&Data=" + galleryID + "&format=rss200";

   try
   {
       xmlDS.DataFile = rssURL;
       dlPhotos.DataSource = xmlDS;
       dlPhotos.DataBind();
   }
   catch (XmlException err)
   {
       lblError.Text = "Oops, looks like an error occured with this gallery: " + err.Message;
       lblError.Visible = true;            
   }   
}

Lab Demo

SmugMug Image Gallery in ASP.NET

The gallery uses a simple 6 column layout. Once you have the RSS feed, you can be as creative as you like in designing the layout for your photo gallery.

Other Gallery options

This demo is for the standard galleries. SmugMug has other Feed options described here.

My SmugMug referral code is: IzodUqeQndZYc
It will save you $5 on any new account.

Labels: , , , , , ,

 

Mask Email ASCII Control for ASP.NET

In the article Masking Your Email Address, we went over why you would want to hide your email address inside the source code of an HTML document, but still make it visible to the human readers of that page.

Encapsulating the code into a single .NET user control is ideal for protecting email addresses for ASP.NET sites.

Step 1 - Create a Web User Control

Add an asp:Literal control to that page.
<asp:Literal ID="ltEmail" runat="server" />

Step 2 - Jump to the Code Behind

The EmailMask code follows. Note the name of the class lab_maskemail_EmailMask was created by Visual Studio for me. Use whatever name you like or Visual Studio recommends here. The rest of the code should be the same. This code is for ASP.NET 2.0.
using System;
using System.Text;
using System.Web;

public partial class lab_maskemail_EmailMask : System.Web.UI.UserControl
{
    private string emailAddress;
    public string EmailAddress
    {
        get { return emailAddress; }
        set { emailAddress = value; } 
    }

    private string visibleAddress;
    public string VisibleAddress
    {
        get 
        {
            // if unassigned return emailAddress
            if (visibleAddress==null || visibleAddress.Length == 0)
            {
                return emailAddress;
            }
            else
            {
                return visibleAddress; 
            }
            
       }
        set { visibleAddress = value; }
    }

    private string mouseoverTag;
    public string MouseoverTag
    {
        get { return mouseoverTag; }
        set { mouseoverTag = value; }
    }

    private string subject;
    public string Subject
    {
        get { return subject; }
        set { subject = value; }
    }

    private string cssClass;
    public string CssClass
    {
        get { return cssClass;}
        set { cssClass = value; }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        // The MIN required for control is an email address, confirm it exists
        if (emailAddress == null || emailAddress.Length == 0)
        {
            ltEmail.Text = "Assign EmailAddress to Control";
            return;
        }

        // Build ASCII encoded Link
        StringBuilder asciiLink = new StringBuilder();
        asciiLink.Append("<a href=\"m&#97;ilto:");
        asciiLink.Append(ASCIIEncode(EmailAddress));
        if(subject != null && subject.Length > 0 )
        {
            asciiLink.Append("?subject=" + subject);
        }
        asciiLink.Append("\"");
        if (mouseoverTag != null && mouseoverTag.Length > 0)
        {
            asciiLink.Append(" title=\"" + mouseoverTag + "\"");
        }
        if (cssClass !=null && cssClass.Length > 0)
        {
            asciiLink.Append(" class=\"" + cssClass + "\"");
        }
        asciiLink.Append(">");
        asciiLink.Append(ASCIIEncode(VisibleAddress));
        asciiLink.Append("</a>");

        ltEmail.Text = asciiLink.ToString();      
    }

    protected string ASCIIEncode(string regularText)
    {
        regularText = regularText.Trim();

        StringBuilder encodeSB = new StringBuilder();
        char regularLetter;

        for (int j = 0; j < regularText.Length; j++)
        {
            // peel off 1 character at a time
            regularLetter = regularText[j];
            encodeSB.Append("&#" + Convert.ToInt32(regularLetter).ToString() + ";");
        }

        return encodeSB.ToString();
    }

Step 3 - Create a Test Page

Register the control at the top using whatever path and naming convention you've chosen.
<%@ Register Src="~/lab/maskemail/EmailMask.ascx" TagName="EmailMask" TagPrefix="dc" %>
Inside the ASP.NET page and control is used like below.
<dc:EmailMask ID="eMask" 
 CssClass="Summer" 
 EmailAddress="larryKing@cnn.com" 
 VisibleAddress="Email Larry King"   
 MouseoverTag="Send feedback" 
 Subject="Tonight's Show" 
 runat="server" />

Labels: , ,

 

Delete Files using C#

When I first wrote the original version of the Mask Email Image Generator (Email Obfuscator), I didn't bother to add any code to periodically remove the images from the server. Yesterday I discovered it had almost 50,000 images in that folder. Not to repeat the same mistake, I wrote a function to delete image files that are older than 3 minutes. Sample code follows.
protected void CleanImageFolder()
{
    string imgFolder = Server.MapPath("~/lab/maskemail/img/");
    string[] imgList = Directory.GetFiles(imgFolder, "*.jpg");
    foreach (string img in imgList)
    {
        FileInfo imgInfo = new FileInfo(img);
        if (imgInfo.LastWriteTime < DateTime.Now.AddMinutes(-3))
        {
            imgInfo.Delete();
        }
    }
}

Labels: , , ,

 

Convert string to Color

You will get the following error when trying to assigning a string as a Color in .NET.
Cannot implicitly convert type 'string' to 'System.Drawing.Color'

Import System.Drawing and use Color.FromName to convert a string to a color.
using System.Drawing;
Color.FromName("Blue");

Labels:

 

Digital Colony Copyright © 1999-2008 XHTML   508
This site uses Blogger, which is not 100% XHTML compliant.
Try...Catch Disclaimer: For brevity many examples do not include error handling. That is your responsibility.