Here, we'll be focusing on a lot of interesting features of the .NET Framework. Just to enumerate some: web requests, web handlers, graphics, caching, section handlers etc. My purpose was to create a solution that can be easily customized and extended by others.
Looking at the prerequisites
By having such a purpose, I have decided to provide most of the settings in the "web.config" file, as follows:
allowLocation="true" allowDefinition="Everywhere"
restartOnExternalChanges="true" />
As you might have noticed I have created a custom section handler. The sample code includes the handler and is pretty much self explanatory, so we will not get into further details on this. However, I would like to explain the structure of the section. It contains a title and exception node that describes how to draw the title, and respectively the exception in case it occurs. It also contains an items node, which acts as a collection; it provides us the possibility to specify how to draw unlimited posts (if they exist) on the surface of the image. I have chosen to draw only the latest three. Note that the structure is similar with the RSS 2.0 standard.
drawColor="194, 71, 11"
drawBorder="false" />
drawColor="255, 0, 0"
drawBorder="false" />
drawFont="Verdana, 8pt"
drawColor="255, 55, 0"
drawBorder="false">
drawFont="Verdana, 6pt"
drawColor="0, 0, 0"
drawBorder="false"/>
drawFont="Verdana, 8pt"
drawColor="255, 55, 0"
drawBorder="false">
drawFont="Verdana, 6pt"
drawColor="0, 0, 0"
drawBorder="false"/>
drawFont="Verdana, 8pt"
drawColor="255, 55, 0"
drawBorder="false">
drawFont="Verdana, 6pt"
drawColor="0, 0, 0"
drawBorder="false"/>
The RSS Feed URL can also be easily modified by the following lines of code:
The basic and exception images are provided as resources in "WebResources.resx" and are linked at compile time.
Coding the task
By collecting all these prerequisites, we now have everything we need to get started. We begin by creating a new ASP.NET Web Site. The task is to respond to a request with an image (our own dynamic image). It may seem a little odd at the first glance, but it can be achieved by using a Generic Handler. The Generic Handler is built upon a basic asynchronous handler, which is standard, and we'll be focusing only on the asynchronous task.
The following code represents the header of the asynchronous procedure and the declarations within, which are going to be used throughout the procedure.
Visual C#
HttpRequest objRequest = MyContext.Request;
HttpResponse objResponse = MyContext.Response;
Cache objCache = MyContext.Cache;
NameValueCollection objAppSettings = null;
GeneralSectionHandler objConfig = null;
Bitmap objImage = null;
objResponse.ContentType = "image/png";
objResponse.Clear();
Visual Basic
Private Sub StartAsyncTask(ByVal workItemState As [Object])
Dim objRequest As HttpRequest = MyContext.Request
Dim objResponse As HttpResponse = MyContext.Response
Dim objCache As Cache = MyContext.Cache
Dim objAppSettings As NameValueCollection = Nothing
Dim objConfig As RSSGraphics.GeneralSectionHandler = Nothing
Dim objImage As Bitmap = Nothing
objResponse.ContentType = "image/png"
objResponse.Clear()
The featured line requires attention in the sense that it specifies the type of response, which is an image in PNG format.
Visual C#
try
{
if (objCache["BitmapCache"] == null)
{
Visual Basic
Try
If objCache.Item("BitmapCache") Is Nothing Then
In order to serve many simultaneous requests, I have used the cache to insert and retrieve the image, which I must say it increases the performance quite a bit. In the case that the image is not found in the cache, or it may have expired, a new request to the RSS Feed URL is made, and the xml document is loaded. Next, the basic image is loaded from the resources, and drawn on the surface of it using smooth anti-alias (just to get a nice looking response). At the end, the image is saved into the cache in order to be retrieved from it by the next request, if it happens within 15 minutes.
Visual C#
objAppSettings =
System.Web.Configuration.
WebConfigurationManager.AppSettings;
objConfig =
(GeneralSectionHandler)System.Configuration.
ConfigurationManager.GetSection("RSSGraphics/Channel");
string RSSFeedURL = objAppSettings["RSSFeedURL"];
HttpWebRequest rssFeed =
(HttpWebRequest)WebRequest.Create(RSSFeedURL);
XmlDocument rssDocument = new XmlDocument();
rssDocument.Load(rssFeed.GetResponse().GetResponseStream());
objImage = Resources.WebResources.RSS_Basic;
Graphics objGraphics = Graphics.FromImage(objImage);
objGraphics.SmoothingMode = SmoothingMode.AntiAlias;
objGraphics.TextRenderingHint =
System.Drawing.Text.TextRenderingHint.AntiAlias;
DrawChannel(rssDocument, objGraphics, objConfig);
DrawItems(rssDocument, objGraphics, objConfig);
objGraphics.Dispose();
//Insert bitmap in cache for 15 minutes
objCache.Insert("BitmapCache", objImage,
null, Cache.NoAbsoluteExpiration, new TimeSpan(0, 15, 0));
}
else
{
//Retrieve bitmap from cache
objImage = (Bitmap)objCache["BitmapCache"];
}
}
catch (Exception ex)
{
Visual Basic
objAppSettings = Web.Configuration.WebConfigurationManager.AppSettings
objConfig = System.Configuration.ConfigurationManager.GetSection("RSSGraphics/Channel")
Dim RSSFeedURL As String = objAppSettings.Item("RSSFeedURL")
Dim rssFeed As HttpWebRequest = DirectCast(WebRequest.Create(RSSFeedURL), HttpWebRequest)
Dim rssDocument As XmlDocument = New XmlDocument
rssDocument.Load(rssFeed.GetResponse.GetResponseStream())
objImage = CType(Resources.WebResources.RSS_Basic, Bitmap)
Dim objGraphics As Graphics = Graphics.FromImage(objImage)
With objGraphics
.SmoothingMode = SmoothingMode.AntiAlias
.TextRenderingHint = Text.TextRenderingHint.AntiAlias
End With
DrawChannel(rssDocument, objGraphics, objConfig)
DrawItems(rssDocument, objGraphics, objConfig)
objGraphics.Dispose()
'Insert bitmap in cache for 15 minutes
objCache.Insert("BitmapCache", objImage, Nothing, NoAbsoluteExpiration, New TimeSpan(0, 15, 0))
Else
'Retrieve bitmap from cache
objImage = CType(objCache.Item("BitmapCache"), Bitmap)
End If
Catch ex As Exception 'Exception
If an exception occurs, an image containing details about the exception is returned. The code that caches the exception is simple and similar.
Visual C#
objImage = Resources.WebResources.RSS_Exception;
Graphics objGraphics = Graphics.FromImage(objImage);
objGraphics.SmoothingMode = SmoothingMode.AntiAlias;
objGraphics.TextRenderingHint =
System.Drawing.Text.TextRenderingHint.AntiAlias;
DrawException(ex, objGraphics, objRequest, objConfig);
if (objGraphics != null)
{
objGraphics.Dispose();
}
}
Visual Basic
objImage = CType(Resources.WebResources.RSS_Exception, Bitmap)
Dim objGraphics As Graphics = Graphics.FromImage(objImage)
With objGraphics
.SmoothingMode = SmoothingMode.AntiAlias
.TextRenderingHint = Text.TextRenderingHint.AntiAlias
End With
DrawException(ex, objGraphics, objRequest, objConfig)
If Not objGraphics Is Nothing Then objGraphics.Dispose()
End Try
At this moment, no matter what, we have a response. All that is left to do is return it by writing the contents of the bitmap to a stream, in PNG format. Because PNG format cannot be written to a non-seekable stream, an intermediate memory stream object (which is seekable) is used.
Visual C#
MemoryStream objMemoryStream = new MemoryStream();
objImage.Save(objMemoryStream, ImageFormat.Png);
objMemoryStream.WriteTo(objResponse.OutputStream);
objMemoryStream.Dispose();
objResponse.Flush();
MyCompleted = true;
MyCallback(this);
Visual Basic
Dim objMemoryStream As MemoryStream = New MemoryStream
objImage.Save(objMemoryStream, ImageFormat.Png)
objMemoryStream.WriteTo(objResponse.OutputStream)
objMemoryStream.Dispose()
objResponse.Flush()
MyCompleted = True
MyCallback(Me)
End Sub 'StartAsyncTask
Drawing the task
Now we'll start painting using brushes, and we'll not get dirty; we are going to achieve this by code. Basically we have three procedures that are going to handle this task: one that is going to draw the exception details on the exception image; and two procedures that are going to draw the title and the latest posts on the basic image.
The first procedure is the one that draws the exception. Due to security issues, the exception message is drawn only if the request is made from the same computer where the solution resides; else a friendly message is displayed. A lot of conversions are made here from string to rectangle, font, color, etc. Be sure to enter them correctly in the "web.config", or else an exception is raised.
Visual C#
private void DrawException(Exception ex, Graphics drawGraphics,
HttpRequest objRequest, GeneralSectionHandler objConfig)
{
try
{
string drawString = "Exception: ";
if (objRequest.IsLocal)
{
drawString += ex.Message;
}
else
{
drawString += "We are unable to serve your request.";
}
//Rectangle
RectangleConverter drawRectangleConverter =
new RectangleConverter();
Rectangle drawRectangle = new Rectangle();
drawRectangle =
(Rectangle)drawRectangleConverter.ConvertFrom(
objConfig.Exception.drawRectangle);
//Font
FontConverter drawFontConverter = new FontConverter();
Font drawFont;
drawFont =
(Font)drawFontConverter.ConvertFromString(
objConfig.Exception.drawFont);
//Brush
ColorConverter drawColorConverter = new ColorConverter();
Color drawColor = new Color();
drawColor =
(Color)drawColorConverter.ConvertFrom(
objConfig.Exception.drawColor);
SolidBrush drawBrush = new SolidBrush(drawColor);
//String format
StringFormat drawStringFormat = new StringFormat();
drawStringFormat.Alignment = StringAlignment.Near;
drawStringFormat.LineAlignment = StringAlignment.Center;
drawStringFormat.Trimming = StringTrimming.EllipsisWord;
//Border
if (Convert.ToBoolean(objConfig.Exception.drawBorder))
{
drawGraphics.DrawRectangle(Pens.Black, drawRectangle);
}
drawGraphics.DrawString(
drawString, drawFont, drawBrush,
drawRectangle, drawStringFormat);
}
catch
{
drawGraphics = null;
}
}
Visual Basic
Private Sub DrawException(ByVal ex As Exception, ByRef drawGraphics As Graphics,_
ByVal objRequest As HttpRequest, _
ByVal objConfig As RSSGraphics.GeneralSectionHandler)
Try
Dim drawString As String = "Exception: "
If objRequest.IsLocal Then
drawString += ex.Message.ToString
Else
drawString += "We are unable to serve your request."
End If
With objConfig.Exception
'Rectangle
Dim drawRectangleConverter As New RectangleConverter
Dim drawRectangle As New Rectangle
drawRectangle = drawRectangleConverter.ConvertFrom(.drawRectangle)
'Font
Dim drawFontConverter As New FontConverter
Dim drawFont As Font
drawFont = drawFontConverter.ConvertFromString(.drawFont)
'Brush
Dim drawColorConverter As New ColorConverter
Dim drawColor As New Color
drawColor = drawColorConverter.ConvertFrom(.drawColor)
Dim drawBrush As New SolidBrush(drawColor)
'String format
Dim drawStringFormat As StringFormat = New StringFormat()
With drawStringFormat
.Alignment = StringAlignment.Near
.LineAlignment = StringAlignment.Center
.Trimming = StringTrimming.EllipsisWord
End With
'Border
If CType(.drawBorder, Boolean) Then _
drawGraphics.DrawRectangle(Pens.Black, drawRectangle)
drawGraphics.DrawString(drawString, drawFont, drawBrush,
drawRectangle, drawStringFormat)
End With
Catch ex2 As Exception
drawGraphics = Nothing
End Try
End Sub
The second procedure is the one that draws the channel title, which is read from the XML document and then painted inside the rectangle with the specified font and color; simple and can be easily understood. In the case that the text will not fit in the given rectangle, a text trimming will occur. The rectangle border is drawn only if required attribute is set to true. Also, a lot of conversions are made, as in the previous sample, so be sure to enter them correctly.
Visual C#
private void DrawChannel(XmlDocument rssDocument,
Graphics drawGraphics, GeneralSectionHandler objConfig)
{
XmlNode rssChannel =
rssDocument.SelectSingleNode("rss/channel");
string drawString = rssChannel["title"].InnerText;
//Rectangle
RectangleConverter drawRectangleConverter =
new RectangleConverter();
Rectangle drawRectangle = new Rectangle();
drawRectangle =
(Rectangle)drawRectangleConverter.ConvertFrom(
objConfig.Title.drawRectangle);
//Font
FontConverter drawFontConverter = new FontConverter();
Font drawFont;
drawFont =
(Font)drawFontConverter.ConvertFromString(
objConfig.Title.drawFont);
//Brush
ColorConverter drawColorConverter = new ColorConverter();
Color drawColor = new Color();
drawColor =
(Color)drawColorConverter.ConvertFrom(
objConfig.Title.drawColor);
SolidBrush drawBrush = new SolidBrush(drawColor);
//String format
StringFormat drawStringFormat = new StringFormat();
drawStringFormat.Alignment = StringAlignment.Near;
drawStringFormat.LineAlignment = StringAlignment.Center;
drawStringFormat.Trimming = StringTrimming.EllipsisWord;
//Border
if (Convert.ToBoolean(objConfig.Title.drawBorder))
{
drawGraphics.DrawRectangle(Pens.Black, drawRectangle);
}
drawGraphics.DrawString(
drawString, drawFont, drawBrush,
drawRectangle, drawStringFormat);
}
Visual Basic
Private Sub DrawChannel(ByVal rssDocument As XmlDocument,_
ByRef drawGraphics As Graphics,_
ByVal objConfig As RSSGraphics.GeneralSectionHandler)
Dim rssChannel As XmlNode = rssDocument.SelectSingleNode("rss/channel")
Dim drawString As String = rssChannel.Item("title").InnerText
With objConfig.Title
'Rectangle
Dim drawRectangleConverter As New RectangleConverter
Dim drawRectangle As New Rectangle
drawRectangle = drawRectangleConverter.ConvertFrom(.drawRectangle)
'Font
Dim drawFontConverter As New FontConverter
Dim drawFont As Font
drawFont = drawFontConverter.ConvertFromString(.drawFont)
'Brush
Dim drawColorConverter As New ColorConverter
Dim drawColor As New Color
drawColor = drawColorConverter.ConvertFrom(.drawColor)
Dim drawBrush As New SolidBrush(drawColor)
'String format
Dim drawStringFormat As StringFormat = New StringFormat()
With drawStringFormat
.Alignment = StringAlignment.Near
.LineAlignment = StringAlignment.Center
.Trimming = StringTrimming.EllipsisWord
End With
'Border
If CType(.drawBorder, Boolean) Then _
drawGraphics.DrawRectangle(Pens.Black, drawRectangle)
drawGraphics.DrawString(drawString, drawFont, drawBrush,_
drawRectangle, drawStringFormat)
End With
End Sub
The third procedure is the one that draws the items. The drawing procedures are very similar, but in addition to the previous sample, I have used two enumerators, one containing items from the xml document and one containing the graphic description on how to draw them. In the case one gets to the end, then both of them will stop. The code draws only the title and the publication date of a post, but can be easily extended to also draw other properties of a post.
Visual C#
private void DrawItems(XmlDocument rssDocument,
Graphics drawGraphics, GeneralSectionHandler objConfig)
{
XmlNodeList rssItems =
rssDocument.SelectNodes("rss/channel/item");
IEnumerator rssItemsEnum = rssItems.GetEnumerator();
ItemsConfigElemColl rssGraphicItems = objConfig.Items;
IEnumerator rssGraphicItemsEnum =
rssGraphicItems.GetEnumerator();
while ((rssItemsEnum.MoveNext()) &&
(rssGraphicItemsEnum.MoveNext()))
{
XmlNode rssItem = (XmlNode)rssItemsEnum.Current;
string drawString =
rssItem.SelectSingleNode("title").InnerText;
DateTime drawDate =
DateTime.Parse(rssItem.SelectSingleNode(
"pubDate").InnerText);
ItemConfigElem cItem =
(ItemConfigElem)rssGraphicItemsEnum.Current;
//Rectangle
RectangleConverter drawRectangleConverter =
new RectangleConverter();
Rectangle drawRectangle = new Rectangle();
drawRectangle =
(Rectangle)drawRectangleConverter.ConvertFrom(
cItem.drawRectangle);
Rectangle drawRectangle2 = new Rectangle();
drawRectangle2 =
(Rectangle)drawRectangleConverter.ConvertFrom(
cItem.pubDate.drawRectangle);
//Font
FontConverter drawFontConverter = new FontConverter();
Font drawFont;
drawFont =
(Font)drawFontConverter.ConvertFromString(cItem.drawFont);
Font drawFont2;
drawFont2 =
(Font)drawFontConverter.ConvertFromString(
cItem.pubDate.drawFont);
//Brush
ColorConverter drawColorConverter = new ColorConverter();
Color drawColor = new Color();
drawColor =
(Color)drawColorConverter.ConvertFrom(cItem.drawColor);
SolidBrush drawBrush = new SolidBrush(drawColor);
Color drawColor2 = new Color();
drawColor2 =
(Color)drawColorConverter.ConvertFrom(
cItem.pubDate.drawColor);
SolidBrush drawBrush2 = new SolidBrush(drawColor2);
//String format
StringFormat drawStringFormat = new StringFormat();
drawStringFormat.Alignment = StringAlignment.Near;
drawStringFormat.LineAlignment = StringAlignment.Center;
drawStringFormat.Trimming = StringTrimming.EllipsisWord;
//Border
if (Convert.ToBoolean(cItem.drawBorder))
{
drawGraphics.DrawRectangle(Pens.Black, drawRectangle);
}
if (Convert.ToBoolean(cItem.pubDate.drawBorder))
{
drawGraphics.DrawRectangle(Pens.Black, drawRectangle2);
}
drawGraphics.DrawString(
drawString, drawFont, drawBrush,
drawRectangle, drawStringFormat);
drawGraphics.DrawString(
cItem.pubDate.MoreText + drawDate.Date.ToString(),
drawFont2, drawBrush2, drawRectangle2, drawStringFormat);
}
}
Visual Basic
Private Sub DrawItems(ByVal rssDocument As XmlDocument,_
ByRef drawGraphics As Graphics,_
ByVal objConfig As RSSGraphics.GeneralSectionHandler)
Dim rssItems As XmlNodeList = rssDocument.SelectNodes("rss/channel/item")
Dim rssItemsEnum As IEnumerator = rssItems.GetEnumerator()
Dim rssGraphicItems As RSSGraphics.ItemsConfigElemColl = objConfig.Items
Dim rssGraphicItemsEnum As IEnumerator = rssGraphicItems.GetEnumerator
While rssItemsEnum.MoveNext And rssGraphicItemsEnum.MoveNext
Dim rssItem As XmlNode = CType(rssItemsEnum.Current, XmlNode)
Dim drawString As String = rssItem.SelectSingleNode("title").InnerText
Dim drawDate As DateTime = CType(rssItem.SelectSingleNode("pubDate").InnerText,_
DateTime)
Dim cItem As RSSGraphics.ItemConfigElem = rssGraphicItemsEnum.Current
With cItem
'Rectangle
Dim drawRectangleConverter As New RectangleConverter
Dim drawRectangle As New Rectangle
drawRectangle = drawRectangleConverter.ConvertFrom(.drawRectangle)
Dim drawRectangle2 As New Rectangle
drawRectangle2 = drawRectangleConverter.ConvertFrom(.pubDate.drawRectangle)
'Font
Dim drawFontConverter As New FontConverter
Dim drawFont As Font
drawFont = drawFontConverter.ConvertFromString(.drawFont)
Dim drawFont2 As Font
drawFont2 = drawFontConverter.ConvertFromString(.pubDate.drawFont)
'Brush
Dim drawColorConverter As New ColorConverter
Dim drawColor As New Color
drawColor = drawColorConverter.ConvertFrom(.drawColor)
Dim drawBrush As New SolidBrush(drawColor)
Dim drawColor2 As New Color
drawColor2 = drawColorConverter.ConvertFrom(.pubDate.drawColor)
Dim drawBrush2 As New SolidBrush(drawColor2)
'String format
Dim drawStringFormat As StringFormat = New StringFormat()
With drawStringFormat
.Alignment = StringAlignment.Near
.LineAlignment = StringAlignment.Center
.Trimming = StringTrimming.EllipsisWord
End With
'Border
If CType(.drawBorder, Boolean) Then_
drawGraphics.DrawRectangle(Pens.Black, drawRectangle)
If CType(.pubDate.drawBorder, Boolean) Then_
drawGraphics.DrawRectangle(Pens.Black, drawRectangle2)
drawGraphics.DrawString(drawString, drawFont, drawBrush,_
drawRectangle, drawStringFormat)
drawGraphics.DrawString(CType(.pubDate.MoreText, String) & drawDate.Date,_
drawFont2,drawBrush2, drawRectangle2, drawStringFormat)
End With
End While
End Sub
No comments:
Post a Comment