Dynamic Images The ImageLabelHandler Example


Dynamic Images ”The ImageLabelHandler Example

In this section, we will show you how to use an HTTP handler to generate binary data in the form of a dynamic image generated by parameters passed in via a query string. The traditional approach of generating dynamic images involves temporary files, which need to be periodically deleted. HTTP handlers provide a clean way to serve these dynamic images without requiring on-disk persistence. A variety of Web application scenarios involve dynamic images ”stock charts , e-card previews, image watermarking, renderings of product catalog images that are stored in a database, and so on.

The ImageLabelHandler is a simple example of dynamic image generation. This handler generates a JPEG image containing the specified text rendered in the specified formatting by using GDI+ functionality present in the .NET Framework. You could use such a component in a font gallery application that allows a user to experiment with fonts and make selections before downloading them onto his or her machine. In addition to the HTTP handler, this example implements an associated custom control that enhances the developer experience of using the handler from an .aspx page. Figure 19-4 illustrates the handler and its associated control in a test page.

Figure 19-4. ImageLabelHandler being used within ImageLabel ­Test.aspx

graphics/f19hn04.jpg

This sample demonstrates a third mechanism for implementing HTTP handlers. Here the code for the handler is compiled into an assembly that is placed in the application's bin directory, and the .ashx file simply contains a reference to the class in the assembly. This is particularly useful when you need to deploy your HTTP handler in a compiled assembly without distributing the source code.

Here is the .ashx file used to reference the implementation of the HTTP handler:

 <%@WebHandlerLanguage="C#" Class="MSPress.ServerComponents.ImageLabelHandler"%> 

Listing 19-6 shows the code for the ImageLabelHandler , which is compiled into the MSPress.ServerComponents assembly.

Listing 19-6 ImageLabelHandler.cs
 usingSystem; usingSystem.ComponentModel; usingSystem.Collections; usingSystem.Drawing; usingSystem.Drawing.Drawing2D; usingSystem.Drawing.Imaging; usingSystem.Web; 
 //ImageLabelHandlergeneratesanimagewithtextbasedon //argumentspassedintoit. publicclassImageLabelHandler:IHttpHandler{ privateint_width; privateint_height; privateint_fontSize; privatebool_bold; privatebool_italic; privatebool_underline; privatestring_fontName; privateColor_foreColor; privateColor_backColor; privateStringAlignment_horizontalAlign; privateStringAlignment_verticalAlign; privatestring_text; //Initializetherenderinginformation. publicImageLabelHandler(){ _width=100; _height=100; _fontName="Verdana"; _fontSize=10; _foreColor=Color.Black; _backColor=Color.White; _horizontalAlign=StringAlignment.Near; _verticalAlign=StringAlignment.Near; _text=String.Empty; } publicboolIsReusable{ get{ //Thehandlercontainsmemberdataspecifictoasingle //request;thesameinstancecan'tbeusedtohandle //multiplerequests. returnfalse; } } //Initializestheparametersusedtorenderthelabelfrom //argumentsspecifiedintherequestquerystringorfrom //postdata. privatevoidInitializeParameters(HttpRequestrequest){ strings; //Textparameter s=request["text"]; 
 if((s!=null)&&(s.Length!=0)){ _text=s; } //SimilarlyinitializefontName.  s=request["fontSize"]; if((s!=null)&&(s.Length!=0)){ _fontSize=Int32.Parse(s); } //Similarlyinitializewidth,height.  s=request["bold"]; if((s!=null)&&(s.Length!=0)){ _bold=Boolean.Parse(s); } //Similarlyinitializeitalic,underline.  //Colorinformation(optional) s=request["foreColor"]; if((s!=null)&&(s.Length!=0)){ _foreColor=(Color)TypeDescriptor.GetConverter(typeof(Color)).ConvertFromInvariantString(s); } //SimilarlyinitializebackColor.  //Alignmentinformation s=request["horizAlign"]; if((s!=null)&&(s.Length!=0)){ if(String.Compare(s,"left",true)==0){ _horizontalAlign=StringAlignment.Near; } elseif(String.Compare(s,"center",true)==0){ _horizontalAlign=StringAlignment.Center; } elseif(String.Compare(s,"right",true)==0){ _horizontalAlign=StringAlignment.Far; } 
 else{ thrownewArgumentOutOfRangeException("horizAlign", "Unsupportedhorizontalalignment"); } } //SimilarlyinitializeverticalAlign.  } } //Handlestherequestandgeneratestheresultingresponse. publicvoidProcessRequest(HttpContextcontext){ //Thestandardrequest/responseobjects HttpRequestrequest=context.Request; HttpResponseresponse=context.Response; Imageimage=null; Graphicsg=null; StringFormattextFormat=null; FonttextFont=null; BrushtextBrush=null; BrushbackBrush=null; try{ //Extracttherequestparameters. InitializeParameters(request); //Createtheimageandtherenderingsurface. image=new Bitmap(_width,_height,PixelFormat.Format32bppArgb); g=Graphics.FromImage(image); //Filltherenderingsurface. backBrush=newSolidBrush(_backColor); g.FillRectangle(backBrush,0,0,_width,_height); if(_text.Length!=0){ //Createtheassociatedrenderingobjects. textFormat= newStringFormat(StringFormatFlags.NoWrap); textFormat.Alignment=horizontalAlign; textFormat.LineAlignment=verticalAlign; 
 FontStylestyle=FontStyle.Regular; if(_bold){ style=FontStyle.Bold; } if(_italic){ style=FontStyle.Italic; } if(_underline){ style=FontStyle.Underline; } textFont= newFont(_fontName,(float)_fontSize,style); textBrush=newSolidBrush(_foreColor); //Renderthetextbasedonalltheinputparameters. g.DrawString(_text,textFont,textBrush, newRectangleF(0,0,_width,_height), textFormat); } //Writeouttheimagetotheoutputstream. response.ContentType="image/jpeg"; image.Save(response.OutputStream,ImageFormat.Jpeg); } catch(Exception){ } finally{ if(backBrush!=null) backBrush.Dispose(); if(textBrush!=null) textBrush.Dispose(); if(textFormat!=null) textFormat.Dispose(); if(textFont!=null) textFont.Dispose(); if(image!=null) image.Dispose(); if(g!=null) g.Dispose(); } } } 

The ImageLabelHandler returns false for its implementation of the IsReusable property. The handler implementation maintains the request parameters as instance data. Therefore, an existing instance of the handler cannot be reused to handle subsequent requests.

The ImageLabelHandler supports a number of parameters in the request URL, such as the text to be shown and its font characteristics, colors, and alignment. ImageLabelHandler also provides default values for all these parameters, so that the user can specify them only if needed. The constructor of the handler initializes all the parameters to their default values. As a result, the Initialize ­Parameters helper method is used to extract the parameters that were specified by using the QueryString name /value pairs available on the request object to override specific default values. A well-designed HTTP handler should be able to handle incoming requests with missing parameters as appropriate, typically by falling back on a predictable default behavior.

Like all handlers, ImageLabelHandler implements its primary functionality in its ProcessRequest method. The implementation can be divided into four parts : initialization based on request parameters, rendering of the text, writing or saving the in-memory rendered image into the response stream, and disposing resources and objects. The ImageLabelHandler uses the rich drawing functionality provided by GDI+ in the System.Drawing namespace and its subnamespaces. GDI+ provides a number of objects representing fonts, colors, brushes, and string formatting, making it simple to render the specified text in a rich manner. Note that these objects must be disposed of at the end of each request so that they can free their underlying resources as soon as possible. If these objects are not disposed of, they can cause scalability problems. The ImageLabelHandler always ensures disposal of these objects by using a try/catch/finally construct.

Once the image is rendered, GDI+ provides the functionality to save the objects into an arbitrary stream in a specified format. In this example, the handler saves the rendered image in JPEG format. Because GDI+ can write to any stream, the image is saved directly into the response's output stream, thereby eliminating the need to copy bytes of data from one location to another. Note that the ImageLabelHandler sets the ContentType of the response appropriately to "image/jpeg" .

The ImageLabel Control and Designer

It is typical for HTTP handlers such as ImageLabelHandler to accept a number of request parameters to enable customization of their output. By default, the programming model associated with an HTTP handler is simply a string URL that must be generated by the page developer. A server control can enhance the developer experience associated with an HTTP handler by encapsulating the logic of generating the URL with the correct parameters based on the control's property values. The control allows the developer to access the HTTP handler without having full knowledge of the correct URL syntax needed to invoke the handler.

Listing 19-7 contains the code for the ImageLabel control, which simplifies the use of the ImageLabelHandler shown in Listing 19-6.

Listing 19-7 ImageLabel.cs
 usingSystem; usingSystem.ComponentModel; usingSystem.ComponentModel.Design; usingSystem.Drawing; usingSystem.Drawing.Design; usingSystem.Text; usingSystem.Web.UI; usingSystem.Web.UI.Design; usingSystem.Web.UI.WebControls; usingImage=System.Web.UI.WebControls.Image; namespaceMSPress.ServerComponents{ [ DefaultProperty("Text"), Designer(typeof(MSPress.ServerControls.Design.ImageLabelDesigner), typeof(IDesigner)) ] publicclassImageLabel:Image{ publicoverrideUnitHeight{ get{ returnbase.Height; } set{ if(value.IsEmpty(value.Type==UnitType.Pixel)){ base.Height=value; } else{ thrownewArgumentException("Onlypixelunits"+ "aresupportedforHeight","value"); } } } [ Category("Appearance"), DefaultValue(HorizontalAlign.NotSet), Description("Thehorizontalalignmentofthetextwithintheimage") ] 
 publicHorizontalAlignHorizontalAlign{ get{ objecto=ViewState["HorizontalAlign"]; if(o==null){ returnHorizontalAlign.NotSet; } return(HorizontalAlign)o; } set{ if((value<HorizontalAlign.NotSet) (value>HorizontalAlign.Right)){ thrownewArgumentOutOfRangeException("value"); } ViewState["HorizontalAlign"]=value; } } [ Category("Behavior"), DefaultValue(""), Description("TheURLtoImageLabelHandler.ashx"), Editor(typeof(UrlEditor), typeof(UITypeEditor)) ] publicstringImageLabelHandlerUrl{ get{ objecto=ViewState["ImageLabelHandlerUrl"]; if(o==null){ returnString.Empty; } return(string)o; } set{ ViewState["ImageLabelHandlerUrl"]=value; } } [ Bindable(false), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), EditorBrowsable(EditorBrowsableState.Never) ] publicoverridestringImageUrl{ get{ returnCreateImageLabelUrl(); } 
 set{ thrownewNotSupportedException("Youshouldsetthe"+ "propertiesofthecontrolinstead."); } } [ Category("Appearance"), DefaultValue(""), Description("Thetexttobeshowninthecontrol") ] publicstringText{ get{ objecto=ViewState["Text"]; if(o==null){ returnString.Empty; } return(string)o; } set{ ViewState["Text"]=value; } } [ Category("Appearance"), DefaultValue(VerticalAlign.NotSet), Description("Theverticalalignmentofthetextwithin"+ "theimage") ] publicVerticalAlignVerticalAlign{ get{ objecto=ViewState["VerticalAlign"]; if(o==null){ returnVerticalAlign.NotSet; } return(VerticalAlign)o; } set{ if((value<VerticalAlign.NotSet) (value>VerticalAlign.Bottom)){ thrownewArgumentOutOfRangeException("value"); } ViewState["VerticalAlign"]=value; } } 
 publicoverrideUnitWidth{ get{ returnbase.Width; } set{ if(value.IsEmpty(value.Type==UnitType.Pixel)){ base.Width=value; } else{ thrownewArgumentException("Onlypixelunits"+ "aresupportedforWidth","value"); } } } privatestringCreateImageLabelUrl(){ stringhandlerUrl=ImageLabelHandlerUrl; if(handlerUrl.Length==0){ thrownewApplicationException("ImageHandlerUrlhas"+ "notbeensettoavalidvalue."); } StringBuilderurl=newStringBuilder(1024); boolargAppended=false; url.Append(handlerUrl); url.Append('?'); stringtext=Text; if(text.Length!=0){ url.Append("text="); url.Append(text); argAppended=true; } stringfontName=Font.Name; if(fontName.Length!=0){ if(argAppended) url.Append('&'); url.Append("fontName="); url.Append(fontName); argAppended=true; } 
 FontUnitfontSize=Font.Size; if((fontSize.IsEmpty==false)&& (fontSize.Type==FontSize.AsUnit)&& (fontSize.Unit.Type==UnitType.Point)){ if(argAppended) url.Append('&'); url.Append("fontSize="); url.Append(((int)fontSize.Unit.Value).ToString()); argAppended=true; } if(Font.Bold){ if(argAppended) url.Append('&'); url.Append("bold=true"); argAppended=true; } if(Font.Italic){ if(argAppended) url.Append('&'); url.Append("italic=true"); argAppended=true; } if(Font.Underline){ if(argAppended) url.Append('&'); url.Append("underline=true"); argAppended=true; } ColorforeColor=ForeColor; if(foreColor.IsEmpty==false){ if(argAppended) url.Append('&'); url.Append("foreColor="); url.Append(TypeDescriptor.GetConverter(typeof(Color)).ConvertToInvariantString(foreColor)); argAppended=true; } ColorbackColor=BackColor; if(backColor.IsEmpty==false){ if(argAppended) url.Append('&'); 
 url.Append("backColor="); url.Append(TypeDescriptor.GetConverter(typeof(Color)).ConvertToInvariantString(backColor)); argAppended=true; } Unitwidth=Width; if(width.IsEmpty==false){ if(argAppended) url.Append('&'); url.Append("width="); url.Append(((int)width.Value).ToString()); argAppended=true; } Unitheight=Height; if(height.IsEmpty==false){ if(argAppended) url.Append('&'); url.Append("height="); url.Append(((int)height.Value).ToString()); argAppended=true; } HorizontalAlignhorizAlign=HorizontalAlign; if(horizAlign!=HorizontalAlign.NotSet){ if(argAppended) url.Append('&'); url.Append("horizAlign="); url.Append(horizAlign.ToString()); argAppended=true; } VerticalAlignvertAlign=VerticalAlign; if(vertAlign!=VerticalAlign.NotSet){ if(argAppended) url.Append('&'); url.Append("vertAlign="); url.Append(vertAlign.ToString()); argAppended=true; } returnurl.ToString(); } } } 

The ImageLabel control uses several server control authoring concepts that we covered in Part III of this book, "Server Controls ”Nuts and Bolts."

ImageLabel derives from Image (and not Label ) because all the standard Image properties (such as AlternateText ) apply to dynamically generated images as well. This control adds a Text property, which is used to set the text rendered in the dynamic image. The control overrides the Width and Height properties to restrict their range to pixel units because the handler implementation can work only by using pixel dimensions.

The ImageLabel control generates the URL to the ImageLabelHandler with the appropriate request string by using its current property values. Because the control is associated with an HTTP handler, it offers the ImageLabelHandlerUrl property, which allows the developer to specify the location of the handler itself. This is the property that ties the control to its associated handler. Because the control is responsible for automatically computing the source URL of the image, it overrides the ImageUrl property so that it cannot be set by the page developer.

As specified in the metadata for the ImageLabel class, the control is associated with its own designer, ImageLabelDesigner . Chapter 15, "Design-Time Functionality," covered the basics of implementing control designers. All controls that are associated with an HTTP handler should provide a custom designer that approximates a reasonable design-time appearance without having to invoke the handler at design time. This is primarily because the designer updates design-time appearance on every property change and because invoking a remote URL often can result in a sluggish user experience while generating spurious requests that the server will need to handle.

Listing 19-8 contains the code for the ImageLabelDesigner class.

Listing 19-8 ImageLabelDesigner.cs
 usingSystem; usingSystem.ComponentModel; usingSystem.ComponentModel.Design; usingSystem.Diagnostics; usingSystem.IO; usingSystem.Text; usingSystem.Web.UI; usingSystem.Web.UI.Design; usingSystem.Web.UI.WebControls; usingMSPress.ServerComponents; namespaceMSPress.ServerComponents.Design{ 
 publicclassImageLabelDesigner:ControlDesigner{ publicoverridestringGetDesignTimeHtml(){ ImageLabelimageLabel=(ImageLabel)Component; TabledesignerTable=newTable(); TableRowrow=newTableRow(); TableCellcell=newTableCell(); designerTable.Rows.Add(row); row.Cells.Add(cell); cell.Text=imageLabel.Text; if(cell.Text.Length==0){ cell.Text="["+imageLabel.ID+"]"; } cell.ApplyStyle(imageLabel.ControlStyle); cell.HorizontalAlign=imageLabel.HorizontalAlign; cell.VerticalAlign=imageLabel.VerticalAlign; StringBuildersb=newStringBuilder(512); HtmlTextWriterwriter= newHtmlTextWriter(newStringWriter(sb)); designerTable.RenderControl(writer); returnsb.ToString(); } publicoverridevoidInitialize(IComponentcomponent){ if(!(componentisImageLabel)){ thrownewArgumentException("ComponentmustbeanImageLabel", "component"); } base.Initialize(component); } } } 

The designer overrides the GetDesignTimeHtml method, which is called by the designer framework to create a design-time representation of the control. In its implementation, the ImageLabelDesigner creates instances of the Table , TableRow , and TableCell controls to approximate the runtime image characteristics. The designer then copies over the text and relevant style properties (such as colors and fonts) from its associated ImageLabel control onto the table controls. The designer then renders the table and uses the resulting HTML as its design-time representation. Thus, the designer provides a reasonable approximation of the rendering that will be generated by the HTTP handler at runtime.

Listing 19-9 shows the ImageLabel control being used declaratively and programmatically in the ImageLabelTest.aspx sample page.

Listing 19-9 ImageLabelTest.aspx
 <%@PageLanguage="c#"%> <%@RegisterTagPrefix="mspsc"Namespace="MSPress.ServerComponents" Assembly="MSPress.ServerComponents"%> <%@ImportNamespace="System.Drawing"%> <scriptrunat="server"> privatevoidPage_Load(objectsender,EventArgse){ imageLabel1.Text=textTextBox.Text; imageLabel1.Font.Name=fontNameDropDown.SelectedItem.Text; imageLabel1.Font.Size=newFontUnit(fontSizeTextBox.Text.Trim()); imageLabel1.Font.Bold=boldCheckBox.Checked; imageLabel1.Font.Italic=italicCheckBox.Checked; imageLabel1.Font.Underline=underLineCheckBox.Checked; imageLabel1.Width= Unit.Pixel(Int32.Parse(widthTextBox.Text.Trim())); imageLabel1.Height= Unit.Pixel(Int32.Parse(heightTextBox.Text.Trim())); imageLabel1.ForeColor= Color.FromName(foreColorDropDown.SelectedItem.Text); imageLabel1.BackColor= Color.FromName(backColorDropDown.SelectedItem.Text); imageLabel1.HorizontalAlign= (HorizontalAlign)Int32.Parse(horizAlignDropDown.SelectedItem.Value); imageLabel1.VerticalAlign= (VerticalAlign)Int32.Parse(vertAlignDropDown.SelectedItem.Value); strings=imageLabel1.ImageUrl; imageHyperLink.Text=s; imageHyperLink.NavigateUrl=s; } </script> <html> <body> <formid="ImageLabelTest"method="post"runat="server"> <p> ThispageusestheImageLabelcontrolandImageLabelHandlerto generatedynamicimagesbasedonyourselectionsbelow. 
 </p> <tablewidth="100%"border="0"> <!--HTMLandservercontrolsusedtogenerateUItocustomizethe ImageLabelare not shown here--> <tr> <tdnowrapcolspan="2"> <mspsc:ImageLabelid="imageLabel1"runat="server" EnableViewState="False"BorderStyle="Solid" BorderWidth="1px"BorderColor="LightSkyBlue" ImageLabelHandlerUrl="ImageLabel.ashx"/> </td> </tr> </table> <asp:LinkButtonid="generateButton"runat="server" Text="Generate!"/> </form> </body> </html> 

HTTP Handlers and Session State

Some HTTP handlers might require access to objects stored in ASP.NET session state. For example, a page requires session state because script within the page could use the Session object to store data across requests. By default, an HTTP handler does not have access to session data.

An HTTP handler can indicate its need for session state by implementing one of two interfaces. If the handler requires read-only access to the objects stored in session state, it can implement System.Web.SessionState.IReadOnly ­SessionState . If the handler requires read-write access, it needs to implement System.Web.SessionState.IRequiresSessionState . Both of these interfaces are marker interfaces ”in other words, they do not contain any methods . They simply help the HTTP runtime identify which handlers require session state. Listing 19-10 shows an implementation of a handler that requires access to session state.

Listing 19-10 Outline of an HTTP handler that requires access to session state
 <%@WebHandlerLanguage="C#"Class="StatefulHandler"%> usingSystem; usingSystem.Web; usingSystem.Web.SessionState; 
 publicclassStatefulHandler:IHttpHandler,IRequiresSessionState{ publicboolIsReusable{ get{...} } publicvoidProcessRequest(HttpContextcontext){ HttpSessionStatesession=context.Session;  } } 

Summary

HTTP handlers provide a simple but powerful model that allows you to customize the request-handling functionality built into ASP.NET. This model also allows you to provide logic to handle new extensions without having to learn the details of implementing an ISAPI for IIS. Furthermore, HTTP handlers allow you to cleanly implement processing logic that does not generate HTML markup. Finally, you can associate HTTP handlers with a type-safe programming model and a rich design-time experience by providing an associated server control that encapsulates the logic of generating the URL used to access the HTTP handler.



Developing Microsoft ASP. NET Server Controls and Components
Developing Microsoft ASP.NET Server Controls and Components (Pro-Developer)
ISBN: 0735615829
EAN: 2147483647
Year: 2005
Pages: 183

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net