Bulk Data Transactions Using OPENXML

The Problem

You want to insert 5000 records into the database. You normally do this by creating a separate insert statement for each record, and this is fine with your boss, but you’re a glutton for punishment and want to optimize this scenario because it occurs often enough in your application. On the other hand, you’re also lazy, have ten thousand interesting things to do, and would love to not have to spend a lot of time tackling the problem. Oh, and you’ll be using stored procedures because, as everyone knows, they’re better than dynamic SQL. So? What do you do?

Enter OPENXML

OPENXML is a nice little function that will allow us to solve the above-mentioned problem without feeling like we’re hacking our way through it. The need for a hack wouldn’t arise if we can just send any number of parameters that we want to the stored procedure; i.e., in .NET, we have the params[] array, and in Javascript, we have the args array, but in stored procedures, we don’t have any such luxury.

The code, then, of the stored procedure that bulk-inserts data into a table using OPENXML:

  1. CREATE procedure [dbo].[openxml_test]
  2.  
  3. @xmlDocument text
  4.  
  5. as
  6.  
  7. declare @handle int
  8.  
  9. exec sp_xml_preparedocument @handle output, @xmlDocument
  10.  
  11. begin
  12.  
  13. set nocount on;
  14.  
  15. insert into openxml_test_db(ID, random_data)
  16.  
  17.      select tid, tdata from openxml(@handle, N‘ROOT/Thread’, 2)
  18.  
  19.      with (tid uniqueidentifier, tdata varchar(100))
  20.  
  21. exec sp_xml_removedocument @handle
  22.  
  23. end
  24.  
  25.  
  26.  

The XML structure is going to be of the form:

  1. <ROOT>
  2.  
  3. <Thread>
  4.  
  5. <TGuid>cf69e831-4b82-43bc-bc0c-00013af696d3</TGuid>
  6.  
  7. <TData>An awful lot of data</TData>
  8.  
  9. </Thread>
  10.  
  11. </ROOT>

What We’re Doing

Line 3: Sending the variable as text is less effecient than sending it as varchar(8000). If you’re reasonably sure that your xml (including the tags, data, and all) that you send to the stored procedure isn’t going to exceed 8000 characters in length, I suggest you use that instead.

Line 9: sp_xml_preparedocument is a system stored procedure that, as its name implies, prepares the xml document. This stored procedure needs to be called before a call to openxml.

Line 17: We’re getting the data from the xml document and inserting it into the table. The @handle is the handle returned by sp_xml_preparedocument, the‘ROOT/Thread’ is the XPATH that tells OPENXML which rows to process, and the 2 describes the mapping between OPENXML’s rowset and the XML Document. This last parameter is optional. The default mapping is attribute-centric mapping, which we would use if our XML structure was like the following:

  1. …
  2. <TGuid value=”The guid goes here” />
  3. <TData value=”An awful lot of data” />
  4. …

Line 19: We’re basically telling how to format the data we get from OPENXML.

Line 21: The XML Document that SQL Server created is stored in memory. To avoid memory leaks, we close the handle.

So, What Kind of Savings Can We Expect?

Comparison of a normal insert vs. a bulk-insert

Two things to notice:
  1. The savings become more and more significant as the number of records we’re inserting becomes larger.
  2. Even when we’re inserting only 50 records, the difference is noticeable (about .34 seconds).

Input Validation Using Regular Expressions

Regular expressions are often used for user input validation. One common problem is to validate that the e-mail that the user entered is valid. There are a zillion readymade e-mail regex for your use, but I was always interested in knowing why something works, so I set about to create my own regex. That led to my making more commonly-used regular expressions, so I thought I’d share them.

Note that I’m still very much of a newbie when it comes to regular expressions, so these might not be the most efficient ways to validate the appropriate inputs. I checked that each pattern’s working using RadSoftware’s excellent RegexDesigner. It’s such a relief to not have to not have to write a test application.

Oh, the “#” you see in the regular expressions are comments.

  1. using System;
  2. using System.Text.RegularExpressions;
  3. namespace SG.Net.Utilities
  4. {
  5. public class InputValidation
  6. {
  7. /// <summary>
  8. /// A password is valid if
  9. /// 1. It’s at least [minLength] long.
  10. /// 2. It has at least one uppercase letter.
  11. /// 3. It has at least one lowercase letter.
  12. /// 4. It has at least one digit.
  13. /// 5. It has at least one non-alphanumeric character.
  14. /// </summary>
  15. /// <param name="password">The password to validate.</param>
  16. /// <param name="minLength">Minimum length of the password.</param>
  17. /// <returns>True if the password is valid, false otherwise.</returns>
  18. public static bool ValidatePassword(string password, int minLength)
  19. {
  20. Regex pattern = new Regex(@"
  21. ^ # Match the start of string
  22. (?=.*d) # Do we have at least one digit?
  23. (?=.*[A-Z]) # Do we have at least one uppercase letter?
  24. (?=.*[a-z]) # Do we have at least one lowercase letter?
  25. (?=.*[^a-zA-Z0-9nrt ]) # Do we have at least one non-alphanumeric character?
  26. .{" + minLength.ToString() + @",} # Is it at least 6 characters long?
  27. $ # Match the end of the string
  28. ", RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);
  29. return pattern.IsMatch(password);
  30. }
  31. /// <summary>
  32. /// An email is valid if
  33. /// 1. It contains an ‘@’ symbol.
  34. /// 2. It contains a ‘.’ symbol.
  35. /// 3. There are alphanumeric characters (or certain symbols) in front of the ‘@’ symbol.
  36. /// 4. There are alphanumeric characters in between ‘@’ and ‘.’ symbols.
  37. /// 5. There are alpha characters after the ‘.’ symbol.
  38. /// </summary>
  39. /// <param name="email">The email address to validate.</param>
  40. /// <returns>True if email is valid, false otherwise.</returns>
  41. /// <remarks>
  42. /// This function supports multiple subdomains.
  43. /// Check http://www.ietf.org/rfc/rfc2822.txt for generic syntax of emails
  44. /// </remarks>
  45. public static bool ValidateEmail(string email)
  46. {
  47. Regex pattern = new Regex(@"
  48. ^ # Match the beginning of the string
  49. [a-zA-Z0-9]+ # Start with an alphanumeric character
  50. [a-zA-Z0-9!`#$^&*]* # Allow for any number of approved symbols
  51. @ # Match the ‘@’ symbol
  52. [a-zA-Z0-9]+ # One or more alphanumeric characters
  53. [a-zA-Z0-9.]* # Allow for subdomains
  54. . # Match the ‘.’ symbol
  55. [a-zA-Z]+ # Match the domain
  56. $ # Match the end of the string
  57. ", RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);
  58. return pattern.IsMatch(email);
  59. }
  60. /// <summary>
  61. /// A social security number is valid if it’s 9 digits long.
  62. /// </summary>
  63. /// <param name="ssn">The social security number to validate.</param>
  64. /// <returns>True if the ssn is valid, false otherwise. </returns>
  65. public static bool ValidateSSN(string ssn)
  66. {
  67. //Strip the seperators (for example, ‘-’). It’s so much easier to code the regular
  68. //expression when we don’t have to handle the seperators (which the user may or may
  69. //not have entered.
  70. ssn.Trim().Replace("-", String.Empty).Replace(".", String.Empty).Replace(" ", String.Empty);
  71. return Regex.IsMatch(ssn, @"^d{9}$");
  72. }
  73. /// <summary>
  74. /// A phone number is valid if it is 6 or 9 digits long.
  75. /// </summary>
  76. /// <param name="phoneNumber">The phone number to validate.</param>
  77. /// <returns>True if phone number is valid, false otherwise.</returns>
  78. /// <remarks>Obviously, this doesn’t allow for international phone numbers.</remarks>
  79. public static bool ValidatePhoneNumber(string phoneNumber)
  80. {
  81. //Valid phone numbers, for example, include:
  82. //1. (999)-999-9999
  83. //2. (999)999-9999
  84. //3. 999-999-9999
  85. //4. 9999999999
  86. //So we’ll once again strip the seperators to make our lives easier.
  87. //(), ‘-’, ‘.’, and ‘ ‘ are considered valid seperators.
  88. phoneNumber.Trim().Replace("(", String.Empty).Replace(")", String.Empty).
  89. Replace("-", String.Empty).Replace(".", String.Empty).
  90. Replace(" ", String.Empty);
  91. return ((Regex.IsMatch(phoneNumber, @"^d{9}$") || Regex.IsMatch(phoneNumber, @"^d{6}$")));
  92. }
  93. /// <summary>
  94. /// Validates a date using C#-built in DateTime.TryParse()
  95. /// function.
  96. /// </summary>
  97. /// <param name="date">The date to validate</param>
  98. /// <returns>True if the date is valid, false otherwise.</returns>
  99. public static bool ValidateDate(string date)
  100. {
  101. //Dates can be entered in a couple of different formats:
  102. //1. 2006-03-04
  103. //2. 03-04-2006
  104. //3. April 3 2006
  105. //4. April 3, 2006
  106. //5. Apr 3, 2006
  107. //6. 03-APR-2006
  108. //7. 3-APR-06
  109. //You get the idea.
  110. //It’s probably doable with regular expressions, but we
  111. //might as well use C#’s awesome DateTime.Parse(string date)
  112. //method to validate the date.
  113. DateTime dateParsed = new DateTime();
  114. return DateTime.TryParse(date, out dateParsed);
  115. }
  116. /// <summary>
  117. /// An IP Address is valid if it’s 10 digits long.
  118. /// </summary>
  119. /// <param name="ipaddress">The IP Address to validate</param>
  120. /// <returns>True if IP Address is valid, false otherwise.</returns>
  121. public static bool ValidateIPAddress(string ipaddress)
  122. {
  123. //We’re only going to allow the ‘.’ for a seperator.
  124. ipaddress.Trim().Replace(".", String.Empty);
  125. //We’ll also strip the ‘http://’, ‘https://’, or ‘ftp://’
  126. ipaddress.Replace("http://", String.Empty).Replace("https://", String.Empty).
  127. Replace("ftp://", String.Empty);
  128. return Regex.IsMatch(ipaddress, @"^d{10}$");
  129. }
  130. /// <summary>
  131. /// Validates most forms of urls.
  132. /// </summary>
  133. /// <param name="url">The URL to validate</param>
  134. /// <returns>True if the URL is valid, false otherwise.</returns>
  135. /// <remarks>Check http://www.ietf.org/rfc/rfc2396.txt for generic syntax of URLs.</remarks>
  136. public static bool ValidateURL(string url)
  137. {
  138. //Valid urls include:
  139. //1. http://www.awesomesite.com/
  140. //2. http://awesomesite.com/
  141. //3. www.awesomesite.com
  142. //4. www.awesomesubdomain.awesomesite.com
  143. //5. http://www.awesomesite.com/awesomepage.aspx
  144. //6. ftp://awesomesite.com
  145. //7. 999.999.999 (IP Address)
  146. //8. https://awesomesite.com/
  147. //9. awesomesite.com
  148. //10. http://999.999.999
  149. //Substitute whatever domain name you want for the ‘com’
  150. //We’ll check for the domain case first, and the ip address case
  151. //second.
  152. Regex pattern = new Regex(@"
  153. ^ # Match start of string
  154. ( # Match one of
  155. ftp://| # these
  156. https://| # three
  157. http:// # characters
  158. )? # (optional); followed
  159. (www.)? # optionally by the string ‘www.’ followed by
  160. [A-Za-z0-9]+ # one or more alphanumeric characters
  161. [A-Za-z0-9.]* # Allow for subdomains
  162. . # Match the ‘.’ symbol
  163. [A-Za-z]+ # Followed by one or more alphanumeric characters
  164. /? # Followed by an optional (closing) slash
  165. (
  166. /[A-Za-z0-9/]+ # Allow for pages
  167. . # Match the ‘.’ symbol
  168. [A-Za-z0-9/]+ # Match the page’s extension
  169. )? # All of which is optional, of course
  170. $ # Match end of string
  171. ", RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);
  172. if(pattern.IsMatch(url))
  173. return true;
  174. //Let’s now try to check for the IP Address case
  175. return InputValidation.ValidateIPAddress(url);
  176. }
  177. }
  178. }

C# Syntax Highlighter V1.0

Regular expressions are a pain in the neck. Unfortunately, they're also very useful. So I bullied my uncooperative brain into studying regular expressions and found myself hooked — once I got out of the "Why the @&#$*! doesn't this work' phase. Anyway, I always wanted to make a Syntax Highlighter and I finally got around to doing it. Here's the code for anybody who might want to learn more about regular expressions or anybody who just wants yet another syntax highlighter in their toolbox. ####Why You Should Highlight Code

Because you're a nice person. Because it will make your code more readable. Because your readers will love you. Any more questions?

Without further ado, then, here's the code for Syntax Highlighter V1.0, highlighted and formatted, I might add, using the Syntax Highlighter:

  1. using System;
  2. using System.Text;
  3. using System.Text.RegularExpressions;
  4.  
  5. public class CsharpHighlighter
  6. {
  7. public string Highlight(string code)
  8. {
  9. StringBuilder patterns = new StringBuilder();
  10.  
  11. //Regular expression for single-line comments
  12. patterns.Append(@"(/(?!//)/[^ ]*)|");
  13.  
  14. //Regular expression for formal documentation comments
  15. patterns.Append(@"(///[^ ]*)|");
  16.  
  17. //Regular expression for matching multi-line comments
  18. patterns.Append(@"(/*.*?*/)|");
  19.  
  20. //Regular expression for matching double-quote string
  21. patterns.Append(@"((?<!@)&quot;[^ ]*?(?<!\)&quot;)|");
  22.  
  23. //Regular expression for matching hard quotes string
  24. patterns.Append(@"(@&quot;.*?(?<!\)&quot;)|");
  25.  
  26. //Regular expression for matching single-quote string
  27. patterns.Append(@"('[^ ]*?(?<!\)')|");
  28.  
  29. //Keywords
  30. patterns.Append(GetKeywords());
  31.  
  32. Regex all = new Regex(patterns.ToString(), RegexOptions.Singleline);
  33.  
  34. code = all.Replace(code, new MatchEvaluator(HandleMatch));
  35.  
  36. Regex line = new Regex(@"^.*?$", RegexOptions.Multiline);
  37.  
  38. code = line.Replace(code, new MatchEvaluator(HandleLines));
  39.  
  40. //Turn tabs and spaces into &nbsp;s
  41. Regex tabsToSpaces = new Regex(@"<li> * *", RegexOptions.Singleline);
  42.  
  43. code = tabsToSpaces.Replace(code, new MatchEvaluator(HandleTabs));
  44.  
  45. //Break multi-line comments into lines properly
  46. Regex mlcToLines = new Regex(@"/*.*?*/", RegexOptions.Singleline);
  47.  
  48. code = mlcToLines.Replace(code, new MatchEvaluator(HandleMLC));
  49.  
  50. //Break hard strings properly
  51. Regex hardStrToLines = new Regex
  52. (@"@&quot;.*?(?<!\)&quot;", RegexOptions.Singleline);
  53.  
  54. code = hardStrToLines.Replace(code, new MatchEvaluator(HandleSTR));
  55.  
  56. return "<ol class = "code"> " + code + "</ol> ";
  57. }
  58.  
  59. private string HandleMatch(Match m)
  60. {
  61. //Single-line comments
  62. if(m.Groups[1].Success)
  63. {
  64. return "<span class = "slc">" + m.Value + "</span>";
  65. }
  66.  
  67. //Formal documentation comments
  68. else if (m.Groups[2].Success)
  69. {
  70. return "<span class = "fdc">" + m.Value + "</span>";
  71. }
  72.  
  73. //Multi-line comments
  74. else if (m.Groups[3].Success)
  75. {
  76. return "<span class = "mlc">" + m.Value + "</span>";
  77. }
  78.  
  79. //String
  80. else if (m.Groups[4].Success || m.Groups[5].Success || m.Groups[6].Success)
  81. {
  82. return "<span class = "str">" + m.Value + "</span>";
  83. }
  84.  
  85. else if (m.Groups[7].Success)
  86. {
  87. return "<span class = "kwd">" + m.Value + "</span>";
  88. }
  89.  
  90. else
  91. {
  92. return String.Empty;
  93. }
  94. }
  95.  
  96. private string HandleLines(Match m)
  97. {
  98. //Add &nbsp; to empty lines so they show up
  99. if (m.Value.Trim().Length < 1)
  100. {
  101. return "<li>&nbsp;</li>";
  102. }
  103.  
  104. else
  105. {
  106. //If we don't get rid of the new line character, the <li>
  107. //ends up on a, umm, new line — the HTML source code looks
  108. //somewhat ugly.
  109. return "<li>" + m.Value.TrimEnd(‘ ', ‘ ') + "</li>";
  110. }
  111. }
  112.  
  113. private string HandleMLC(Match m)
  114. {
  115. StringBuilder value = new StringBuilder(m.Value);
  116.  
  117. value.Replace("<li>", "<li><span class = "mlc">");
  118. value.Replace("</li>", "</span></li>");
  119.  
  120. return value.ToString();
  121. }
  122.  
  123. private string HandleSTR(Match m)
  124. {
  125. StringBuilder value = new StringBuilder(m.Value);
  126.  
  127. value.Replace("<li>", "<li><span class = "str">");
  128. value.Replace("</li>", "</span></li>");
  129.  
  130. return value.ToString();
  131. }
  132.  
  133. private string HandleTabs(Match m)
  134. {
  135. StringBuilder space = new StringBuilder();
  136.  
  137. space.Append("<li>");
  138. //We're simply going to convert each tab into 4 spaces
  139. for (int i = 0; i < m.Value.Length - 4; i++)
  140. space.Append("&nbsp;&nbsp;&nbsp;&nbsp;");
  141.  
  142. return space.ToString();
  143. }
  144.  
  145. private string GetKeywords()
  146. {
  147. StringBuilder kwds = new StringBuilder(@"b(
  148. abstract|
  149. as|
  150. base|
  151. bool|
  152. boolean|
  153. break|
  154. byte|
  155. case|
  156. catch|
  157. char|
  158. checked|
  159. class|
  160. const|
  161. continue|
  162. decimal|
  163. default|
  164. delegate|
  165. do|
  166. double|
  167. else|
  168. enum|
  169. event|
  170. explicit|
  171. extern|
  172. false|
  173. finally|
  174. fixed|
  175. float|
  176. for|
  177. foreach|
  178. get|
  179. goto|
  180. if|
  181. implements|
  182. implicit|
  183. in|
  184. instanceof|
  185. int|
  186. interface|
  187. internal|
  188. is|
  189. length|
  190. lock|
  191. long|
  192. namespace|
  193. native|
  194. new|
  195. null|
  196. object|
  197. operator|
  198. out|
  199. override|
  200. package|
  201. params|
  202. private|
  203. protected|
  204. public|
  205. readonly|
  206. ref|
  207. return|
  208. sbyte|
  209. sealed|
  210. set|
  211. short|
  212. sizeof
  213. stackalloc|
  214. static|
  215. string|
  216. struct|
  217. super|
  218. switch|
  219. synchronized|
  220. this|
  221. threadsafe|
  222. throw|
  223. throws|
  224. true|
  225. try|
  226. typeof|
  227. uint|
  228. ulong|
  229. unchecked|
  230. unsafe|
  231. ushort|
  232. using|
  233. virtual|
  234. void|
  235. while
  236.         )b");
  237.  
  238. kwds.Replace(" ", "");
  239.  
  240. kwds.Replace(" ", "");
  241.  
  242. kwds.Replace(" ", "");
  243.  
  244. return kwds.ToString();
  245. }
  246. }

What's Right About Version of Syntax Highlighter

  • It uses a list rather than the
     tag, which is just plain awesome. I can't tell you how much I hate the 
     tag. I've become especially miserable with it in the recent days. I post a lot of code to this blog, but I really don't want to spend time wrestling with lines that are too long.
  • Each line is numbered, which is also very nice. For one thing, it's easier to tell the readers to "insert [new code] at line 243" than it is to say "insert [new code] after the line in the Highlight function after we make the regex pattern for matching double-quote strings.' For another thing, line numbers just make it so much easier to read the code.

Changes to be Made Still

  • Turn the string of &nbsps; into padding-lefts.
  • Right now, this is a C#-only highlighter.
  • Read the keywords from an XML file.

Look for these changes in Version 2.0!

Charts (Part II)

All right, as promised, here’s the code that gets rid of the jagged edges of the chart we created using Office Web Components. It's hackish, so if you're against that sort of thing, run away.

What we do is basically get the chart as a very large image (1500×1500, for example) and then create a high-resolution thumbnail out of it (300×300, for example) via GDI+. The over-caffeinated reader would wonder why we bothered with office web components in the first place; why didn’t we simply use GDI+ to create the chart? There’s no good reason, really, other than that I wanted to tinker with office web components, which I’ve never used before. ;-) So, without further ado, here’s the code:

//Get the picture
byte[] imageBuffer =
    (byte[])chartSpace.
    GetPicture(“gif”, 1500, 1500);

MemoryStream ms =
    new
    MemoryStream(imageBuffer);

Bitmap original =
    (Bitmap)Bitmap.FromStream
    (ms, true, true);


//Create the thumbnail
Bitmap thumbnail =
    new Bitmap(300, 300);

Graphics g = Graphics.
    FromImage(thumbnail);

g.InterpolationMode =
    InterpolationMode.HighQualityBilinear;

g.DrawImage(
    original,
    new Rectangle
        (0, 0, 300, 300),
    0,
    0,
    1500,
    1500,
    GraphicsUnit.Pixel);

g.Dispose();

//Send the image to
    the browser
Response.
    ContentType = “image/gif”;

thumbnail.Save
     (Response.OutputStream, ImageFormat.Jpeg);

Pretty self-explanatory, so I won’t bore you with an explanation.

Encrypting Web.Config

I typically store at least one sensitive item in the web.config file: namely, the connection string to my database. There are several ways to protect your web.config file from falling into the wrong hands (for example, you can explicitly forbid ASP .NET from serving .config files or you can redirect users to a “You stink” page using HttpHandlers). But imagine for a moment that the worst happened and that somehow an attacker got hold of your web.config file. Now what? Obviously, we would like it if sensitive parts of the web.config file look like complete gibberish so that the cracker feels like an utter idiot for wasting all the time trying to get the file.

Encrypting the web.config file is, apparently, not only entirely possible but also ridiculously easy. Enter — drumroll please — aspnet_regiis.

All you need to do is type in…

aspnet_regiis -prov DataProtectionConfigurationProvider -pef [the name of the configuration setting that you want to encrypt; for example, connectionStrings] [location of the folder in which your web.config resides; for example, C:Website1]

–prov — what provider to use

–pef [section name] [location of config file] — what section to encrypt (using the provider mentioned above) and the >location of the .config file

...in cmd prompt and wait a couple of seconds while your configuration file gets encrypted. Load up web.config and you should see something like:

<CipherValue>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAM85VV82faE2ocurvYx6Z4gmoreincomprehensiblegarbagehereitg6XOFiehAkVwulqACFAAAAEZTASYdjIvoIiAR3yzotHc6z8E5</CipherValue>

Accessing the connection string, however, is no different after you encrypt than before. That is, you would still get your connection string as follows:

string connStr=ConfigurationManager.ConnectionStrings["connStr"].ConnectionString;

Note that it’s also possible to encrypt web.config programmatically (go down to step5).

Creating Charts with .NET Reports

The Problem

You have a stored procedure that returns the number of products you sold each day from a given date 'til today. You want to allow the user to pick the given date. You're a nice person and figure that you'll make the users' lives easier by showing the data in a nice little graph instead of just dumping the information in an ugly table. But you don't want to spend a lot of time doing this. Oh, and you have to use .NET's Reporting Services because, umm, you have to. ####The Solution

Assuming you already have a shared data source and data set configured and the stored procedure all working:

Drop a chart onto the page from the Toolbox.

Drop the x-axis category (probably the date) into the box below the chart that says "Drop category fields here" and drop your data fields (number of products sold each day, for example) into the box above the chart that says "Drop data fields here."

And that's pretty much it for creating the chart. To allow the user to pick the date: right-click on the document ("document" as in "not the page" as in "the yellow thing and not the white thing") and select "Report Parameters" and enter the name (same as the parameter in the stored procedure), the data type, and the label and that pretty much is it. Go to the "Preview" section and see your chart.

Creating Charts in ASP .NET with Office Web Components

In this article, we’re going to learn how to create the chart shown above on-the-fly using ASP .NET and Microsoft Office Web Components.###Step 1: Add a Reference to Microsoft Office Web Components to the Project

The very first thing to do to use MS Office to create eye-catching charts is to add a reference to the Microsoft Office Web Components dll. So go ahead and create a brand new web project imaginatively called “OfficeWebComponents.” MS Office means COM, so when you go to add a reference, you need to go over to the COM tab. Scroll down until you find the right dll, select it, and click OK.

If you have Office running on the machine, then you probably have this .dll already installed. If you don’t, you will have to get it from Microsoft.

Step 2: Write the Code

Don’t forget to add using OWC11; to your code-behind.

Before I explain it, here’s the code:

public partial class _Default :
 System.Web.UI.Page
{
 protected void Page_Load(object sender,
  EventArgs e)
 {
  string[] chartCategories = new string[]
  {
   “Luke Skywalker”,
   “Mara Jade”,
   “Leia Organa Solo”,
   “Mace Windu”,
   “Han Solo”,
   “Anakin Skywalker”,
   “Depa Billaba”,
   “Yoda”
  };

  string[] chartValues = new string[]
  {
   “5″,
   “-50″,
   “3.5″,
   “30″,
   “5″,
   “-20″,
   “20″,
   “0″
  };

  //The charting component wants its categories and
  // deliminated values tab.
  string chartCatsAsString = String.Join(“t”, chartCategories);
  string chartValuesAsString = String.Join(“t”, chartValues);

  //Create the charting workspace
  OWC11.ChartSpaceClass chartSpace = new OWC11.ChartSpaceClass();

  //Create the chart itself 
  OWC11.ChChart theChart = chartSpace.Charts.Add(0);

  //Set the type of the chart
  theChart.Type = OWC11.ChartChartTypeEnum.chChartTypeColumnClustered;

  //Add the chart’s title 
  theChart.HasTitle = true;
  theChart.Title.Caption = "Indecipherable Star Wars Graph";

  //Populate the indecipherable data
  theChart.SeriesCollection.Add(0);

  //Add the categories
  theChart.SeriesCollection[0].SetData(
   OWC11.ChartDimensionsEnum.chDimCategories,
   (int)OWC11.ChartSpecial.DataSourcesEnum.chDataLiteral,
   chartCatsAsString);

  //Add the values
  theChart.SeriesCollection[0].SetData(
   OWC11.ChartDimensionsEnum.chDimValues,
   (int)OWC11.ChartSpecial.DataSourcesEnum..chDataLiteral,
   chartValuesAsString);
   //Show the chart to the client as a GIF image (400×400)

  Response.ContentType = “image/gif”;
  Response.BinaryWrite((byte[])chartSpace.GetPicture(“gif”, 400, 400));
  Response.End();
 }
}

The code is actually pretty-self explanatory (with my comments at least ;-) ). Rather than bore you by going through the same code again, here’s some information that’s not in the code:

  • You add the chart’s legend the same way you add the title: you set the HasLegend property to true and then set the legend.

  • There are a number of ways to display your chart — everything from pie-graphs to scatter-plots.

  • Your category names and values can’t have any commas in them — the charting component seems to deliminate the string at commas as well as tabs.

  • You can save the chart as an image, too; you aren’t limited to sending the image back to the browser.

  • If you have fewer or more categories than values, you don’t get an error — the chart gets rendered with the extra category/value.

Real Life Uses

In real life, we’re obviously not going to have hard-coded categories and values in your code-behind. We’re probably going to get them from the database. This works just fine. For example, suppose you want to figure out how much time you spent each day working on a certain project. These numbers might be stored in a database or an XML file. All you do is simply read the numbers into a values array, and the days into a categories array; join them into a string just like we did in the example, and then go on from there.

Getting All the Links on a Page

One thing I keep having to do again and again (why???) is extract links from a webpage. I recently created a tiny application that gives you the list of all the links on a given web page, and I thought I’d share it with everyone. Given the url of the page, the first thing to do is to get the source code of the page so that we can screen it for links. There are a couple of ways to do this, but the easiest way, as far as I’m aware of, is to use a WebClient object:

WebClient webClient = new WebClient();

//Get the HTML from the given page byte[] response_html = webClient.DownloadData(url);

UTF8Encoding utf8 = new UTF8Encoding();

string html = utf8.GetString(response_html);

Read more about the WebClient class at MSDN.

Now that we have the source code, the next thing to do is search for all the links. You can do this the hard way — i.e., use the String class’ IndexOf function and hack your way out of the predicaments that come your way (and trust me, there are quite a few of them).

The easy way is to use regular expressions.

The pattern for matching the href = "wherever.whatever" (i.e., the url) part of a link is: href\s=\s(?:"(?<1>[^"]*)"|(?<1>\S+)). Looks a little ugly, but the point is that it works in 90% of the cases. What that regular expression actually means is the subject of another article, but suffice to say, it works.

So, the code then:

private void GetLinks(string url) { //using System.Net WebClient webClient = new WebClient();

     //Get the HTML from the given page
     byte[] response_html = webClient.DownloadData(url);

     //using System.Net
     UTF8Encoding utf8 = new UTF8Encoding(); 

     string html = utf8.GetString(response_html);

//using System.Text.RegularExpressions Regex r = new Regex ("hrefs=s(?:"(?<1>[^"]*)"|(?<1>S+))", RegexOptions.IgnoreCase | RegexOptions.Compiled);

     //using System.Text.RegularExpressions
     //Get all the matches
     MatchCollection mcl = r.Matches(html);

     //using System.Collections
     ArrayList a = new ArrayList(); 

     foreach (Match m in mcl)
         a.Add(m.Value);

//A gridview object grdLinks.DataSource = a; grdLinks.DataBind(); }

16 of 17 pages « First  <  14 15 16 17 >

On the Side