Coffee break
- Using MS WebAPI to add API Support to Your Existing ASP.NET MVC Web Applications : Sumit Maitra talks about how to use the newly launched WebAPI in an existing MVC 3 application by upgrading it to ASP.NET MVC 4 and extending it to provide http services using the WebAPI
- Compare Visual Studio 11 Beta editions : Here is a quick comparison of the new Visual Studio 11 beta editions
- Visual Studio 11 Beta and .NET 4.5 Beta – Available now! : Somasegar announces the availability of Visual Studio 11 Beta and .NET 4.5 Beta versions for download. Do check the links highlighting some of the innovations that have gone in to building Visual Studio 11 Developer Preview release
- Introduction to jQuery mobile : Brian Mains discusses the jQuery Mobile framework for developing user interfaces for mobile devices
- One ASP.NET – Making JSON Web APIs with ASP.NET MVC 4 Beta and ASP.NET Web API : Scott Hanselman talks about the improvements in MVC 4, about Web API and how it fits in to One ASP.NET
Serializing a PartialView as JSON in ASP.NET MVC
[tweetmeme style=”compact”]In certain situations you might want to make an AJAX request to load a Partial View (UserControl) and display in a web page. It was quite easy to accomplish that using AJAX.NET Update Panels. Though jQuery doesn’t have any Update Panels of its own, it is quite easy to accomplish the same. In this post, I will try to make an AJAX request to fetch a UserControl as JSON and display it on the screen. Alright, so follow me…
1) Create a Partial View name PostList as follows…
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Post>>" %> <table border="0" width="100%"> <thead> <tr> <th> Post ID </th> <th> Title </th> <th> Description </th> <th> Created On </th> </tr> </thead> <tbody> <%foreach (MyMVCTrials.Models.Post p in Model) { %> <tr> <td> <%=p.PostID.ToString() %> </td> <td> <%=p.Title %> </td> <td> <%=p.Description %> </td> <td> <%=p.CreatedOn %> </td> </tr> <%} %> </tbody> </table>
2) Create a Entity class named Post as follows…
public class Post { public int PostID { get; set; } public string Title { get; set; } public string Description { get; set; } public string CreatedOn { get; set; } }
3) Create a private method in the Controller class, say PostController, to prepare a List of Posts and return it.
private IList<Post> GetPosts() { IList<Post> posts = new List<Post>(); for (int i = 1; i <= 10; i++) { Post post = new Post(); post.PostID = i; post.Title = String.Format("My Post Title #{0}", i.ToString()); post.Description = String.Format("My Description {0}", i.ToString()); post.CreatedOn = DateTime.Now.ToLongDateString(); posts.Add(post); } return posts; }
4) Create a private method in your Controller which will execute the PartialView and return a JSON string of the view.
private JsonResult SerializeControl(string controlPath, object model) { ViewPage page = new ViewPage(); ViewUserControl ctl = (ViewUserControl)page.LoadControl(controlPath); page.Controls.Add(ctl); page.ViewData.Model = model; page.ViewContext = new ViewContext(); System.IO.StringWriter writer = new System.IO.StringWriter(); System.Web.HttpContext.Current.Server.Execute(page, writer, false); string outputToReturn = writer.ToString(); writer.Close(); return this.Json(outputToReturn.Trim()); }
5) Create the following Action Methods in your Controller
//Returns the View that will display the List of Posts based on an AJAX call. public ActionResult Index() { return View(); } //Returns the serialized JSON string of the partial view, PostList public JsonResult PostList() { System.Threading.Thread.Sleep(3000); return SerializeControl("~/Views/Shared/PostList.ascx", GetPosts()); }
6) And finally, in our Index View Page, add the following…
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <script type="text/javascript"> $(document).ready(function() { $("#loadPosts").click(function() { $("#postList").html("Loading Posts...."); $.getJSON("/Home/PostList", function(data) { if (data) { $("#postList").html(data); } }); }); }); </script> <input type="button" id="loadPosts" value="Load Posts!" /> <div id="postList" /> </asp:Content>
When you run the application, here is how it will look like…
On clicking the button
Final result!
Scott Hanselman talks about MVC 1.0 and NerdDinner.com at Mix09
A really great presentation by Scott Hanselman demonstrating how he created NerdDinner.com using MVC 1.0. In this 70 minutes presentation, Scott demonstrates how we can build a real Web site with ASP.NET, ASP.NET AJAX, Authentication, Authorization, MVC, Microsoft SQL Server and jQuery. Long video, but really worth it.
CodePlex Project: http://nerddinner.codeplex.com
Upload files to UNC share using ASP.NET
[tweetmeme style=”compact”]SCENARIO 1: Your ASP.NET website should upload files to a File Server accessible via an UNC share
SOLUTION
- Create a Local User Account in the Web server with Username say “TestUser” and Password say “Secret” of your choice
- Create a Local User Account in File server with the same Username “TestUser” and Password “Secret” as the one created in the Web server.
- In Web.config set the impersonation to true for the above Local User Account as follows…
And your upload script will be something like this…
if ((FileUpload1.PostedFile != null) && (FileUpload1.PostedFile.ContentLength > 0)) { string fileName = System.IO.Path.GetFileName(FileUpload1.PostedFile.FileName); string folderPath = @"\\MyUNCShare\MyFolder\"; string locationToSave = folderPath + "\\" + fileName ; try { FileUpload1.PostedFile.SaveAs(locationToSave ); Response.Write("The file has been uploaded."); } catch (Exception ex) { Response.Write("Error: " + ex.Message); } } else { Response.Write("Please select a file to upload."); }
Run the code and test the upload and it should work.
SCENARIO 2:
Now this is fine for a demo. But what if I want to setup Windows authentication for my application but restrict the authenticated users of the application from directly accessing the UNC share to copy files? Any uploads to the UNC share should be done only using the Local User Account that I created earlier.To solve this issue do the following…
1) Change the identity impersonate tag in Web.config to
<identity impersonate="true" />
, assuming that you have enabled Windows authentication in IIS as well.
2) Change the impersonation at runtime to the Local User Account, upload the file and then undo the impersonation. To do this use the following code…
using System.Security.Principal; using System.Runtime.InteropServices; namespace FileUploadUNCShare { public partial class _Default : System.Web.UI.Page { public const int LOGON32_LOGON_INTERACTIVE = 2; public const int LOGON32_PROVIDER_DEFAULT = 0; WindowsImpersonationContext impersonationContext; [DllImport("advapi32.dll")] public static extern int LogonUserA(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool RevertToSelf(); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern bool CloseHandle(IntPtr handle); private bool ImpersonateUser(String userName, String domain, String password) { WindowsIdentity tempWindowsIdentity; IntPtr token = IntPtr.Zero; IntPtr tokenDuplicate = IntPtr.Zero; if (RevertToSelf()) { if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) != 0) { if (DuplicateToken(token, 2, ref tokenDuplicate) != 0) { tempWindowsIdentity = new WindowsIdentity(tokenDuplicate); impersonationContext = tempWindowsIdentity.Impersonate(); if (impersonationContext != null) { CloseHandle(token); CloseHandle(tokenDuplicate); return true; } } } } if (token != IntPtr.Zero) CloseHandle(token); if (tokenDuplicate != IntPtr.Zero) CloseHandle(tokenDuplicate); return false; } private void UndoImpersonation() { impersonationContext.Undo(); } protected void Page_Load(object sender, EventArgs e) { } protected void Button1_Click(object sender, EventArgs e) { if (ImpersonateUser("Test_Share", "", "@Dell123")) { if ((FileUpload1.PostedFile != null) && (FileUpload1.PostedFile.ContentLength > 0)) { string fileName = System.IO.Path.GetFileName(FileUpload1.PostedFile.FileName); string folderPath = @"\\MyUNCShare\MyFolder\"; string locationToSave = folderPath + "\\" + fileName; try { FileUpload1.PostedFile.SaveAs(locationToSave); Response.Write("The file has been uploaded."); } catch (Exception ex) { Response.Write("Error: " + ex.Message); } } else { Response.Write("Please select a file to upload."); } UndoImpersonation(); } else { Response.Write("Failed"); } } } }
This will ensure that the impersonation you need to upload the file does not interfere with the Application level impersonation you may want to use.
Hope it is useful!
Cheers!
ref: http://support.microsoft.com/kb/306158
Container-Managed Persistence
[tweetmeme style=”compact”]The most fascinating part in developing ASP.NET applications for me is how you can leverage different design models relatively easily, compared to other platforms. Coming from a ASP background, I was excited when I created my first 3-tier application in ASP.NET. I’ve been interested in architectures ever since. The n-tier architecture adopted in most of the Microsoft’s sample applications such as IBuySpy, is one of the commonly used models and I’ve used it successfully in my applications. It is what we call a Component-Managed Persistence model, where the component needs to know how to make calls to the persistence layer such as SQL Server and also what are the parameters required for a transaction with the persistence layer. So in a sense the business layer is tightly coupled with the under lying persistence layer.
I came across this book “ASP.NET E-commerce Programming: Problem – Design – Solution” by Kevin Hoffman a long time back. Though I bought the book, i really didn’t understand the Container-Managed Persistence model described in the book. I had tough time trying to download the code associated with the book as the book was no longer supported on www.wrox.com. Anyway, I managed to download the code after several hours of surfing and googling the web. I spent a lot od time trying to understand how the architecture works and I finally managed to understand how brilliant it is. I was however surprised to find that there ain’t much reviews or talk about this book anywhere. Anyway, I would like to thank Kevin Hoffman for this wonderful book.
So what is Container-Managed Persistence?
Quoting from the book: “Container-Managed Persistence is a design pattern whereby business objects have no direct knowledge of where their data came from and how it will be persisted” The business objects are pure business objects and are never tighly coupled with any persistence layer.
After trying several sample applications with the CMPServices library and the associated libraries that formed the part of the architecture, there was one issue that I wasn’t comfortable with in the CMPServices library. The PersistableObjectSet.cs class uses an internal DataSet for handling the resultset from the database. Now, I being a strong advocate of using DataReaders in ASP.NET application was not really comfortable with it. So, I decided to try my hands on tweaking the CMPServices library to make DataReader as the default data handling object and also provide the developer with an option to choose the data object – DataSet, DataTable or DataReader he/she wants to use in the application.
I made few additions to the CMPServices library to achieve what I said. Now to start off I created an Enum CMPDataObjectType as follows
public enum CMPDataObjectType { /// /// DataSet /// DataSet = 1, /// /// DataTable /// DataTable = 2, /// /// DataReader /// DataReader = 3 }
The enum has three fields, representing the Data object types that we are going to support in the CMPServices library. The actual PerisistableObjectSet class in the CMPServices library is as follows
public class PersistableObjectSet : PersistableObject { protected DataSet internalData; public PersistableObjectSet() { internalData = new DataSet(); } public virtual void FinalizeData() { PersistableObjectSet.FinalizeData( internalData ); } public static void FinalizeData(DataSet scratchData ) { } public DataSet ResultSet { get { return internalData; } set { internalData = value; } } }
As you can see it has an internal DataSet which is used to hold the records returned from the database. Now we need to add support for DataReader and DataTable in this class. So I modified the class as follows
public class PersistableObjectSet : PersistableObject { /// /// The internal DataSet of the PersistableObjectSet /// protected DataSet objectDataSet; /// /// The internal DataReader of the PersistableObjectSet /// protected IDataReader objectDataReader; /// /// The internal DataTable of the PersistableObjectSet /// protected DataTable objectDataTable; /// /// CMPDataObjectType /// protected CMPDataObjectType cmpDataObjectType; /// /// Default constructor /// public PersistableObjectSet() { } /// /// Gets/Sets the value of the internal DataSet of the PersistableObjectSet /// public DataSet DataSet { get { return objectDataSet; } set { objectDataSet = value; } } /// /// Gets/Sets the value of the internal DataReader of the PersistableObjectSet /// public IDataReader DataReader { get { return objectDataReader; } set { objectDataReader = value; } } /// /// Gets/Sets the value of the internal DataTable of the PersistableObjectSet /// public DataTable DataTable { get { return objectDataTable; } set { objectDataTable = value; } } /// /// The CMPDataObjectType refers to the Datatype that will be used for Data retrieval. /// There are 3 CMPDataObjectTypes - DataSet, DataTable and DataReader. /// DataReader is the default one and it is recommended to use it for any kind of data retrieval. /// DataTable and DataSet must be used only for special data maniuplations. /// DataSet should be used only for special requirements like serialization/deserialization, caching etc. /// public CMPDataObjectType DataObjectType { get { if( cmpDataObjectType == 0 ) { cmpDataObjectType = CMPDataObjectType.DataReader; } return cmpDataObjectType; } set { cmpDataObjectType = value; } } /// /// This method provides a format for child classes to implement data manipulation methods. /// If any additional work needs to be done on the retrieved data, this method can be /// implemented as a standard place for data finalisation to take place. /// /// If you are using a DataReader you SHOULD call this method in your business logic to /// close the Connection object. /// public virtual void FinaliseData() { if( cmpDataObjectType == CMPDataObjectType.DataSet ) { PersistableObjectSet.FinaliseData( objectDataSet ); } else if( cmpDataObjectType == CMPDataObjectType.DataReader ) { PersistableObjectSet.FinaliseData( objectDataReader ); } else if( cmpDataObjectType == CMPDataObjectType.DataTable ) { PersistableObjectSet.FinaliseData( objectDataTable ); } } /// /// This method is called by the FinaliseData() overload in the business logic to do /// any additional work with the DataSet. /// /// DataSet public static void FinaliseData( DataSet tempData ) { tempData.Dispose(); } /// /// This method is called by the FinaliseData() overload in the business logic to do /// any additional work with the DataTable. /// /// DataTable public static void FinaliseData( DataTable tempData ) { tempData.Dispose(); } /// /// This method is called by the FinaliseData() overload in the business logic to do /// any additional work with the DataReader. /// /// IDataReader/SqlDataReader public static void FinaliseData( IDataReader tempData ) { tempData.Close(); tempData.Dispose(); } }
So basically I have added a property DataObjectType which is used to set the type of data object we are going to use. By default it is DataReader. Each data types will have their own FinaliseData method. For the DataReader we make sure that the object is closed in order to close its connection object.
We also need to do couple of changes to the SqlPersistenceContainer class, as we now have three types of data object types to handle.
Since we use the data object only while retrieving the data, we modify the Select method as follows
/// /// This method performs the Select operation. It executes a stored procedure and places any return or output values /// back onto the instance of the PersistableObject that was provided. If the object instance can be cast to a PersistableObjectSet, then the /// container will attempt to assign the output or return value to a Data object within the PersistableObjectSet. /// /// Persistable object public override void Select( PersistableObject selectObject ) { try { CommandMapping commandMap = containerMap.SelectCommand; SqlCommand selectCommand = BuildCommandFromMapping( commandMap ); AssignValuesToParameters( commandMap, ref selectCommand, selectObject ); if(selectCommand.Connection.State == ConnectionState.Closed) selectCommand.Connection.Open(); if( selectObject is PersistableObjectSet ) { PersistableObjectSet objectSet = (PersistableObjectSet)selectObject; AssignResultSetToObjectSet( commandMap, selectCommand, ref selectObject ); AssignOutputValuesToInstance( commandMap, selectCommand, ref selectObject ); //selectCommand.Connection.Close(); } else { selectCommand.ExecuteNonQuery(); selectCommand.Connection.Close(); AssignOutputValuesToInstance( commandMap, selectCommand, ref selectObject ); } //selectCommand.Connection.Dispose(); //selectCommand.Dispose(); } catch (Exception dbException) { throw new Exception("Persistance (Select) Failed for PersistableObject", dbException ); } }
We have remaned AssignResultSetToDataSet to AssignResultSetToObjectSet to give it a more generic name. The AssignResultSetToObjectSet method is as follows
private void AssignResultSetToObjectSet( CommandMapping commandMap, SqlCommand sqlCommand, ref PersistableObject persistObject ) { SqlDataAdapter sqlDa = null; PersistableObjectSet objectSet = (PersistableObjectSet)persistObject; if( objectSet.DataObjectType == CMPDataObjectType.DataSet ) { sqlDa = new SqlDataAdapter( sqlCommand ); objectSet.DataSet = new DataSet(); sqlDa.Fill ( objectSet.DataSet ); sqlCommand.Connection.Close(); sqlCommand.Connection.Dispose(); sqlCommand.Dispose(); } else if( objectSet.DataObjectType == CMPDataObjectType.DataReader ) { SqlDataReader objReader = sqlCommand.ExecuteReader( CommandBehavior.CloseConnection ); objectSet.DataReader = objReader; } else if( objectSet.DataObjectType == CMPDataObjectType.DataTable ) { sqlDa = new SqlDataAdapter( sqlCommand ); objectSet.DataTable = new DataTable(); sqlDa.Fill ( objectSet.DataTable ); sqlCommand.Connection.Close(); sqlCommand.Connection.Dispose(); sqlCommand.Dispose(); } }
We create the instance of the select data object type and fetch the data using it. So, thats all the changes we do to the CMPServices library.
Now lets see how we use it. I created a simple contacts management application and here is a sample of a function in the Business layer. Since I am sure I am not going to change the underlying datasource, I populate the data to a strongly-typed collection object which I will then use in my presentation layer. I have a function called GetContacts and I have three versions of the function to support the data object type under consideration.
For DataReader (default)
public static Contacts GetContacts() { SqlPersistenceContainer spc = new SqlPersistenceContainer( CMPConfigurationHandler.ContainerMaps["SherstonContacts"] ); ContactSet contactSet = new ContactSet(); spc.Select( contactSet ); Contacts contactList = new Contacts(); while( contactSet.DataReader.Read() ) { IDataReader row = contactSet.DataReader; Contact contact = new Contact(); contact.ContactId = Convert.ToInt32( row["ContactId"] ); contact.ContactName = row["ContactName"].ToString(); contact.Email = row["Email"].ToString(); contactList.Add( contact ); } ///It is imperative that you call the FinaliseData method for DataReader as this will ///automatically close it’s connection object. Note that it is called after the DataReader's values ///are retrieved as DataReader's require the connection object to be open unlike DataSet/DataTable. contactSet.FinaliseData(); return contactList; }
For DataSet
public static Contacts GetContacts() { SqlPersistenceContainer spc = new SqlPersistenceContainer( CMPConfigurationHandler.ContainerMaps["SherstonContacts"] ); ContactSet contactSet = new ContactSet(); //Setting the DataObjectType of the contactSet object to DataSet contactSet.DataObjectType = CMPDataObjectType.DataSet; spc.Select( contactSet ); Contacts contactList = new Contacts(); ///For DataSet and DataTable the FinaliseData method should be called before extracting the values ///as the manipulation with the data would have occured if there is an overriden FinaliseData method ///in the ContactSet class contactSet.FinaliseData(); foreach( DataRow row in contactSet.DataSet.Tables[0].Rows ) { Contact contact = new Contact(); contact.ContactId = Convert.ToInt32( row["ContactId"] ); contact.ContactName = row["ContactName"].ToString(); contact.Email = row["Email"].ToString(); contactList.Add( contact ); } return contactList; }
For DataTable
public static Contacts GetContacts() { SqlPersistenceContainer spc = new SqlPersistenceContainer( CMPConfigurationHandler.ContainerMaps["SherstonContacts"] ); ContactSet contactSet = new ContactSet(); //Setting the DataObjectType of the contactSet object to DataTable contactSet.DataObjectType = CMPDataObjectType.DataTable; spc.Select( contactSet ); Contacts contactList = new Contacts(); ///For DataSet and DataTable the FinaliseData method should be called before extracting the values ///as the manipulation with the data would have occured if there is an overriden FinaliseData method ///in the ContactSet class contactSet.FinaliseData(); foreach( DataRow row in contactSet.DataTable.Rows ) { Contact contact = new Contact(); contact.ContactId = Convert.ToInt32( row["ContactId"] ); contact.ContactName = row["ContactName"].ToString(); contact.Email = row["Email"].ToString(); contactList.Add( contact ); } return contactList; }
So here it is a modified CMPServices library with support for DataReader and DataTable 🙂
Displaying RSS feeds in ASP.NET
[tweetmeme style=”compact”]The XML control is one of the least used controls in ASP.NET. To be honest I haven’t used it much myself. I combined my little knowledge in XSL with the XML control to create a simple RSS reader. Here is the code for the WebForm
.rssContainer { border: 1px dotted #000000; padding: 3px; font-family: Arial, Verdana, Geneva; font-size: 11px; width: 100%; } #header { background-color: #f7f7f7; border: 1px solid #000000; } #header h2 { color: #333333; } #header .description { font-size: 14px; font-weight: bold; } void Page_Load(object source, System.EventArgs e) { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load("https://2leggedspider.wordpress.com/feed/"); rssFeed.Document = xmlDoc; rssFeed.TransformSource = rss.xsl; }
The code as you can see is very simple. We create a XmlDocument object and open the feed using it. Then we pass the document to the XML control. You would also notice that we are passing an XSL source to the XML control through it’s TransformSource property. The XSL does all the formatting of the feed data. Lets look at the XSL code now.
<table class="rssContainer"> <tr> <td> <h2> <a href="{link}" target="_blank"> </a> </h2> <div class="description"> </div> </td> </tr> <tr> <td> </td> </tr> </table> <a href="{link}" target="_blank"></a> - <br /><i> </i> <hr />
The XSL code is pretty simple as you can see. Well I definetely will consider this option when I need to display any kind of XML content again.
Edit Web.config at run-time
[tweetmeme style=”compact”]I had an argument with one of my friends recently on his statement that Web.config cannot be edited at run-time. My counter statement was if it is a XML file and if you have write permission on it then you can do it. Here is the code to do the same…
public class ConfigManager { //The file path of Web.config static string configFilePath = HttpContext.Current.Server.MapPath("~/web.config"); /// Returns the Web.config as XmlDocument private static XmlDocument GetWebConfig() { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load( configFilePath ); return xmlDoc; } /// Checks for the appSettings node in Web.config /// Updates the value for the given key if the key /// already exists or creates a new node for the /// given key and value combination if it is not present public static void CreateAppSetting( string key, string val ) { XmlDocument xmlDoc = GetWebConfig(); XmlNode xmlNode = xmlDoc.SelectSingleNode("//appSettings"); if( xmlNode == null ) { throw new Exception("appSettings node not found!"); } string nodeFormat = string.Format("//add[@key='{0}']", key) ); XmlElement xmlElement = ( (XmlElement) xmlNode.SelectSingleNode( nodeFormat ); try { if( xmlElement != null ) { xmlElement.SetAttribute( "value", val ); } else { xmlElement = xmlDoc.CreateElement( "add" ); xmlElement.SetAttribute( "key", key ); xmlElement.SetAttribute( "value", val ); xmlNode.AppendChild( xmlElement ); } SaveWebConfig( xmlDoc ); } catch( Exception ex ) { throw new Exception( ex.Message ); } } /// Saves the changes to the Web.config file private static void SaveWebConfig( XmlDocument xmlDoc ) { try { XmlTextWriter writer = new XmlTextWriter( configFilePath, null ); writer.Formatting = Formatting.Indented; xmlDoc.WriteTo( writer ); writer.Flush(); writer.Close(); } catch( Exception ex ) { throw new Exception( ex.Message ); } } }//end of class
The application should have required permissions on Web.config to edit it, otherwise the code will throw an access denied error.
DataSet or DataReader?
I’ve been developing websites in ASP.NET for quite a while now. During my initial projects in ASP.NET I like many others liberally used DataSets whenever possible and hardly went for DataReaders. Now the reason I didn’t prefer DataReaders was because i often forget to close it’s connection object and then I receive a “all pooled connections were in use and max pool size was reached…” error from the SQL Server after some trial runs. Silly reason uh! Now, I know why the error occured but the lazy me wanted a option where I don’t have to worry about these issues.
Then one fine day I felt an invisible slap on my head and I began to seriously consider best practices, performance, robust architecure etc.. seriously. I guess my old programming habits in ASP were reluctant to let me go. But something unusual happened, I won!
Now back to the real issue. DataSets are really powerful and easy to use. But when it comes to developing performant websites, DataSets should never be an option. DataReaders are fast and I mean really fast when compared to a DataSet or for that matter a DataTable. Eventhough DataReaders are fast, there was one fact which worried me. DataReaders need a open connection object while retrieving data. Now would it be a problem if I try to read a large number of records, say more than 10,000 using a DataReader as it needs the open connection to execute? Well, yes if you don’t close the connection object as soon as you are done with it.
I came across this link A Speed Freak’s Guide to Retrieving Data in ADO.NET by Craig Davis and it clearly explains the advantage of using DataReader over DataSet or DataTable. But are DataSets a complete no no when it comes to ASP.NET? Well not really. If you have checked the IBuySpy portal’s architecure, the use of strongly-typed DataSet makes a lot of sense. Since the data is cached most of the time and rarely updated, the performance issue is not really a huge threat. More over it is more easier to handle the large XML file that defines the portals structure using the strongly-typed DataSet.
So does that mean that as long as you cache the DataSet it is fine to use them? Well I would still go for DataReaders. You cannot cache a DataReader object but you can cache the data you receive from DataReader. We can create a class with public properties that mirrors the table structure, iterate through the records in the DataReader, populate the relevant data to the object instance of the custom class and then add it preferably to a strongly-type collection or an ArrayList, which can be cached. Obviously we need to enter few extra lines of code to do this but its worth the effort as we have performance in mind.
DataReaders should therefore be the first choice when it comes to data access in ASP.NET. DataSets could be used if you don’t worry about the performance factor or if there is situation where DataSet is the only option, which in my opinion is very unlikely.
Generating a dynamic bar graph using ASP.NET and C#
[tweetmeme style=”compact”]Back in the days of ASP, web developers had to rely on third party components to dynamically create images on a web page. In ASP, we would use colored tables to display a bar graph. But wouldn’t it be great if we could generate a dynamic graph by ourselves instead of relying on some third-party components. Well, the .NET framework allows us to do just that and more.
Consider a scenario where we had to display a graph representing the sales of a company from say 2000-2004. We need to generate a dynamic bar graph using the data provided to us. Let me take you through the step-by-step process to accomplish the task.
I am using Notepad and .NET framework 1.0. Why notepad? Well, I believe it’s the best way to learn .NET or for that matter any programming language and also the code is very clean.
The two classes, which we need to generate images on the fly, are System.Drawing.Bitmap and System.Drawing.Graphics. The Bitmap class is used to represent an instance of an image and the Graphics class can be used to draw lines, curves and other geometric shapes.
Be sure to check about these two classes and their methods from http://msdn.microsoft.com
So lets get started. Open Notepad and type the following two lines
We will be using these two namespaces in our application.
The method we will write to generate the graph is called ‘GenerateBarGraph’
The initial structure of the code will look like this
Bar graph generated using ASP.NET and C# protected void GenerateBarGraph( string graphTitle, ArrayList xValues, ArrayList yValues, int barWidth, int barSpaceWidth, int graphHeight) { //Our code to generate bar graph will come here } private void Page_Load(object sender, System.EventArgs e) { } <img src="" />
The parameters passed to the GenerateBarGraph method are as follows
graphTitle : The title of the graph
xValues : The values in the x-axis
yValues : The values in the y-axis
barWidth : The width of each bar
barSpaceWidth : The space between each bars
graphHeight : The height of the graph(excluding the title and x-values)
Change the GenerateBarGraph method as follows
protected void GenerateBarGraph( string graphTitle, ArrayList xValues, ArrayList yValues, int barWidth, int barSpaceWidth, int graphHeight) { int graphTitleHeight=20; // Height in pixels utilized by the title in the graph int itemsHeight=35; // Height in pixels utilized by the items in the x-axis /* The Graph’s width is calculated by adding the width of a bar and the space between two bars multiplied by the total values in the x-axis plus the space between two bars */ int graphWidth= (barWidth + barSpaceWidth) * xValues.Count + barSpaceWidth; /* The maximum height that a bar can attain needs to be found from the y-values passed as parameter */ int maxBarHeight=0; //Total height of the image is calculated int totalGraphHeight = graphHeight + graphTitleHeight + itemsHeight; //Create an instance of Bitmap class with the given width and height Bitmap barBitmap=new Bitmap(graphWidth, totalGraphHeight); /* Graphics class does not have a constructor and hence we call its static method FromImage and pass the Bitmap object to it */ Graphics barGraphics= Graphics.FromImage(barBitmap); /* Using the Graphics object we fill the image of given dimensions with light gray color */ barGraphics.FillRectangle( new SolidBrush(Color.WhiteSmoke), 0, 0, graphWidth, totalGraphHeight); /* We create an instance of Font class available in System.Drawing. We will be using this to display the title of the graph. */ Font titleFont=new Font("Verdana",14, FontStyle.Bold); /* Use the Graphics object’s DrawString method to draw the title at the specified location */ barGraphics.DrawString( graphTitle, titleFont, new SolidBrush(Color.Red), (graphWidth / 2) - graphTitle.Length * 5, totalGraphHeight - itemsHeight); //////////////Code to generate bars will come here///////////////// /* Save the image to the web server’s D: drive. We use the PNG format to make it look crisp. */ barBitmap.Save("D:\\bargraph.png",ImageFormat.Png); //Dispose off the Graphics and Bitmap objects barGraphics.Dispose(); barBitmap.Dispose(); }
Change the Page_Load method as follows
private void Page_Load(object sender, System.EventArgs e) { ArrayList _years=new ArrayList(); _years.Add("2000"); _years.Add("2001"); _years.Add("2002"); _years.Add("2003"); _years.Add("2004"); ArrayList _sales=new ArrayList(); _sales.Add(270500); _sales.Add(211930); _sales.Add(223300); _sales.Add(343000); _sales.Add(424750); GenerateBarGraph("ABC Ltd. Sales (2000-2004)",_years, _sales, 50, 25, 400); }
Change the source of the IMG tag used in our aspx page as follows
<img />
Now, if you run this page you will see an image displayed on the browser with the title “ABC Ltd. Sales (2000-2004)” displayed in red at the bottom
If you notice in the Page_Load method, we have passed two ArrayLists to the GenerateBarGraph method and also the other previously mentioned parameters. We will be using these two ArrayLists to display the bars.
Now replace //////////////Code to generate bars will come here///////////////// in GenerateBarGraph method with the following code.
/* Find the highest value in the yValues ArrayList and set it as the maximum height of the bar */ foreach(int _value in yValues) if(_value > maxBarHeight) maxBarHeight=_value; //barXPos will store the x position of a bar int barXPos = barSpaceWidth; int barHeight; Font itemsFont=new Font("Verdana",9, FontStyle.Bold); Font valuesFont=new Font("Verdana", 7, FontStyle.Italic); Random rnd=new Random(); for(int i=0;i Gives the bar height in percentage with respect to the maximum bar height set by us ((((int)yValues[i]* 100 / maxBarHeight) )* graphHeight)/100 will give the bar height in pixels */ barHeight=((((int)yValues[i]* 100 / maxBarHeight) )* graphHeight)/100; //Draw the bar with the set brush, x and y positions, width and height barGraphics.FillRectangle( barBrush, barXPos, graphHeight-barHeight, barWidth, barHeight); //Draw the x-value along the x-axis barGraphics.DrawString( xValues[i].ToString(), itemsFont, barBrush, barXPos, graphHeight); //Draw the respective y value on top of the bar barGraphics.DrawString( yValues[i].ToString(), valuesFont, barBrush, barXPos, (graphHeight-barHeight)-itemsHeight); //Change the x position of the next bar barXPos += (barWidth + barSpaceWidth); }
If you now run the aspx page you will notice the bar graph representing the sales of a company from 2000-2004. If you refresh the page you will see that each bar will have a new color.
The Graphics API provided by the .NET framework is very extensive and what we have seen gives us a useful startup. Do take time to understand the classes and its methods used in this article.
leave a comment