Static Constructors

The Problem

Suppose you have a static readonly string whose value you don’t know until runtime. Maybe you have to read it from FileAbcdefgh.txt when certain condition is true and read it from FileXyz.txt when it’s not. But once set, the value should never be modified again.

The Solution

I’m assuming you guessed that the solution to the problem is static constructors. You guessed right. ;) Static constructors, like the null coalescing operator, are a C# feature I was not aware of until fairly recently.

Static constructors look fairly similar to normal (default) constructors:

  1. public class StaticConstructorTestClass
  2. {
  3. private static readonly string someStaticString = string.Empty;
  4.  
  5. static StaticConstructor()
  6. {
  7. someStaticString = GetFromDatabaseOrWhatever();
  8. }
  9.  
  10. private static string GetFromDatabaseOrWhatever()
  11. {
  12. // Do the processing here
  13.  
  14. return "This is a string I got from database";
  15. }
  16. }

The Rules:

When creating static constructors, keep these rules in mind:

  1. Only one static constructor per class.
  2. The constructor can’t have any parameters.
  3. The constructor can’t have an access modifier (public/private/etc).
  4. The constructor executes before any instance constructor.
  5. The constructor executes only one time per class.

The ?? Operator

The Problem

Database access has to be my least favorite activity to code, right up there with coding public data access (to private fields). The worst thing about coding database access is — easily — the need to check for null values. I recently found a nugget in .NET that makes the process a bit less painless, and I’m putting it out here in the hopes that it will help somebody avoid this:

  1. public void Read()
  2. {
  3. // …
  4.  
  5. System.Data.SqlClient.SqlDataReader rdr = new System.Data.SqlClient.SqlDataReader();
  6.  
  7. if (!rdr.IsDBNull(0))
  8. {
  9. // Yay! It’s not null.
  10. }
  11.  
  12. if (!rdr.IsDBNull(1))
  13. {
  14. // Wow, the second field is not null either.
  15. }
  16.  
  17. if (!rdr.IsDBNull(3))
  18. {
  19. // And neither is this one; I’m so excited.
  20. }
  21.  
  22. if (!rdr.IsDBNull(4))
  23. {
  24. // This one isn’t either? Really?
  25. }
  26.  
  27. // …
  28.  
  29. if (!rdr.IsDBNull(723498234))
  30. {
  31. // And this one’s not null either. Oh, what joy.
  32. }
  33. }

The Solution

Enter ??. Wait, what? ?? is an actual operator in C# and not a spelling mistake on your part? I’ve never heard of it in my entire life (okay, so maybe you did hear of it before, but I haven’t, not ’til yesterday!).

You’d code the ?? like this:

  1. AwesomeObject someAwesomeObject = new AwesomeObject();
  2.  
  3. someAwesomeObject.AwesomeField = rdr.GetString(10) ?? string.Empty;

To read more on the ?? operator, try this MSDN article.

I finally found out that the operator is called null coalescing operator and is available in C# 2005 only. Another reason C# rocks. ; -) Any questions?

Render CheckBoxList as an Unordered List

Introduction

The CheckBoxList, like most other web controls, makes my life a whole lot easier by automating what used to be a lot of drudge work. The CheckBoxList also, like most other web controls, spits out ugly, ugly markup that makes me incredibly pissed off. But the good thing in ASP .NET is that if you don’t like the way something works, you can almost always inherit from the class and override the behavior, which is what we’re going to be doing with CheckBoxList.

Web Custom Controls vs. User Controls

Web custom controls have several benefits over user controls. To use a user control, you need to copy the .ascx file to each and every project that you you want to use it in, whereas web controls are compiled into .dll files, which means that to use them you just add a reference to the .dll file. There’s also the fact that user controls don’t have designer support (admittedly not a big deal for me, since I work almost exclusively in the “Source View,” but I know lots of other people swear by the “Design View”); web custom controls, on the other hand, do.

Web Custom Controls

Creating web custom controls can look like an untamable beast, but actually it’s quite easy. Start by clicking File » New » Project » Visual C# » Windows » Web Control Library. You can just as easily create a web custom control in whatever web project you happen to be working on, but because we’re interested in easy reuse, we’ll put our extended CheckBoxList file in a web control library. Name it whatever you want.

Visual Studio will automatically create a file called WebCustomControl1.cs for you. Delete everything inside the class — we don’t need it. Rename the file to something more appropriate, like UlCheckBoxList, and change all instances of WebCustomControl1 in the file to UlCheckBoxList (Visual Studio will probably automatically change the class name for you, but you also need to change the ToolboxData).

Base Class

All web custom controls need to inherit from the WebControl class either directly or indirectly. The CheckBoxList control already inherits from this class. Since we’re interested in only the rendering part of the CheckBoxList we’ll inherit from this one.

The Code

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Text;
  5. using System.Web;
  6. using System.Web.UI;
  7. using System.Web.UI.WebControls;
  8.  
  9. namespace XhtmlWebControls
  10. {
  11. [ToolboxData("<{0}:UlCheckBoxList runat=server></{0}:UlCheckBoxList>")]
  12. public class UlCheckBoxList : CheckBoxList
  13. {
  14. protected override void Render(HtmlTextWriter writer)
  15. {
  16. Controls.Clear();
  17.  
  18. string input = "<input id={0}{1}{0} name={0}{2}{0} type={0}checkbox{0} value={0}{3}{0}{4} />";
  19. string label = "<label for={0}{1}{0}>{2}</label>";
  20.  
  21. writer.WriteLine("<ul>");
  22.  
  23. for (int index = 0; index < Items.Count; index++)
  24. {
  25. writer.Indent++;
  26. writer.Indent++;
  27.  
  28. writer.WriteLine("<li>");
  29.  
  30. writer.Indent++;
  31.  
  32. StringBuilder sbInput = new StringBuilder();
  33. StringBuilder sbLabel = new StringBuilder();
  34.  
  35. sbInput.AppendFormat(
  36. input,
  37. """,
  38. base.ClientID + "_" + index.ToString(),
  39. base.ClientID + "$" + index.ToString(),
  40. Items[index].Value,
  41. (Items[index].Selected ? " checked" : ""));
  42.  
  43. sbLabel.AppendFormat(
  44. label,
  45. """,
  46. base.ClientID + "_" + i.ToString(),
  47. Items[index].Text);
  48.  
  49.  
  50. writer.WriteLine(sbInput.ToString());
  51. writer.WriteLine(sbLabel.ToString());
  52.  
  53. writer.Indent–;
  54.  
  55. writer.WriteLine("</li>");
  56.  
  57. writer.WriteLine();
  58.  
  59. writer.Indent–;
  60. writer.Indent–;
  61. }
  62.  
  63. writer.WriteLine("</ul>");
  64. }
  65. }
  66. }
  67.  

Explained

Line 11: [ToolboxData("<{0}:UlCheckBoxList runat=server></{0}:UlCheckBoxList>")]

This tells Visual Studio what to write in the “Source View” of the .aspx page in which we’re going to use the control. That is, if you drag and drop the control from the Toolbox onto the .aspx page.

Line 16: Controls.Clear(); The CheckBoxList has a single child control of type CheckBox, which is used to render all the checkboxes in the list. But as we’re going to be writing the HTML ourselves, we don’t care about this control. So I cleared it, just in case having it around screws up something.

Line 18: string input = “<input id={0}{1}{0} name={0}{2}{0} type={0}checkbox{0} value={0}{3}{0}{4} />”;

Line 19: string label = “<label for={0}{1}{0}>{2}</label>”;

The html that’s going to be outputted with some “string format variables” thrown in. The formats are going to be replaced with the actual values later on (lines 35 - 47).

Line 25: writer.Indent++;

Makes the <li> tags and everything underneath appear one tab to the right (in the HTML source code) of the <ul> tag.

Line 38: base.ClientID + “_” + i.ToString(),

Line 39: base.ClientID + “$” + i.ToString(),

If your CheckBoxList’s name was “CheckBoxList1,” then the id and the name you see in the HTML source code would be “CheckBoxList1_x” and “CheckBoxList1$x,” where x is the number of the checkbox in the list. I saw no reason to change this.

Adding Our Class to the Toolbox

Compile the Web Control Library you just created. Go to the Toolbox, right-click on “General” (or whichever tab you want to put the class under) and select “Choose Items” — In “Choose Toolbox Items,” under “.NET Framework Components,” select “Browse” and point to the .dll file of the class (should be located in the “Debug” folder in the “Bin” folder of your Web Control Library folder). Click OK. And you’re good to go.

C# Syntax Highlighter 2.0

As anybody who looked at the source code for the previous post might have noticed that I have indeed succeeded in cleaning up my syntax highlighter to produce the UI in a more pleasant manner. I’ve also included stylings for more language-specific elements (such as namespaces, classes, and operators). The line-handler is also much simpler and much more efficient. And finally, the spaces and the tabs (i.e., the presentation) is no longer present in the actual code (which is how it should be; at any rate, a list of ten spaces doesn’t even show up, so we’re better off getting rid of it).

The Code:

  1. using System;
  2. using System.Text;
  3. using System.Text.RegularExpressions;
  4.  
  5. namespace SG.Net.Utilities.SyntaxHighlighter
  6. {
  7. public class CsharpSyntaxHighlighter
  8. {
  9. #region "Data and Data Access"
  10.  
  11. private bool useRegions = false;
  12.  
  13. private bool useLineNumbers = true;
  14.  
  15. public bool UseRegions
  16. {
  17. get { return useRegions; }
  18.  
  19. set { useRegions = value; }
  20. }
  21.  
  22. public bool UseLineNumbers
  23. {
  24. get { return useLineNumbers; }
  25.  
  26. set { useLineNumbers = value; }
  27. }
  28.  
  29. #endregion
  30.  
  31. public string Highlight(string code)
  32. {
  33. Regex all = new Regex(MakePatterns(), RegexOptions.Singleline);
  34.  
  35. code = all.Replace(code, new MatchEvaluator(HandleMatch));
  36.  
  37. //Handle Lines
  38. StringBuilder line = new StringBuilder();
  39.  
  40. //Matches line with tabs
  41. line.Append(@"(^ +.*?$)|");
  42.  
  43. //Matches line with multiple spaces at the start
  44. line.Append(@"(^ {4,}.*?$)|");
  45.  
  46. //Matches all other lines
  47. line.Append(@"(^.*?$)");
  48.  
  49. Regex lines = new Regex(line.ToString(), RegexOptions.Multiline);
  50.  
  51. code = lines.Replace(code, new MatchEvaluator(HandleLine));
  52.  
  53. //Break multi-line comments properly
  54. Regex mlcToLines = new Regex(@"/*.*?*/", RegexOptions.Singleline);
  55.  
  56. code = mlcToLines.Replace(code, new MatchEvaluator(HandleMLC));
  57.  
  58. //Break hard strings properly
  59. Regex hardStrToLines = new Regex(@"@&quot;.*?(?<!\)&quot;", RegexOptions.Singleline);
  60.  
  61. code = hardStrToLines.Replace(code, new MatchEvaluator(HandleHardStrings));
  62.  
  63. //Trim all extra white space
  64. code = Regex.Replace(code, " {2,}", String.Empty);
  65.  
  66. return " <ol class = "code"> " + code + " </ol> ";
  67. }
  68.  
  69. #region "Helper Methods"
  70.  
  71. private string MakePatterns()
  72. {
  73. StringBuilder patterns = new StringBuilder();
  74.  
  75. //Regular expression for single-quote strings
  76. patterns.Append(@"(’[^ ]*?(?<!\)’)|");
  77.  
  78. //Regular expression for double-quote strings
  79. patterns.Append(@"((?<!@)&quot;[^ ]*?(?<!\)&quot;)|");
  80.  
  81. //Regular expression for hard strings
  82. patterns.Append(@"(@&quot;.*?(?<!\)&quot;)|");
  83.  
  84. //Regular expression for single-line comments
  85. patterns.Append(@"(/(?!//)/[^ ]*)|");
  86.  
  87. //Regular expression for formal documentation comments
  88. patterns.Append(@"(///[^ ]*)|");
  89.  
  90. //Regular expression for multi-line comments
  91. patterns.Append(@"(/*.*?*/)|");
  92.  
  93. //Regular expression for language-specific syntax
  94. //such as keywords, operators, namespaces, classes,
  95. //and functions.
  96. patterns.Append(GetSpecialSyntax());
  97.  
  98. return patterns.ToString();
  99. }
  100.  
  101. private string GetSpecialSyntax()
  102. {
  103. StringBuilder specialSyntax = new StringBuilder();
  104.  
  105. specialSyntax.Append(GetOperators() + "|");
  106.  
  107. specialSyntax.Append(GetKeywords() + "|");
  108.  
  109. specialSyntax.Append(GetNamespaces() + "|");
  110.  
  111. specialSyntax.Append(GetClasses() + "|");
  112.  
  113. specialSyntax.Append(GetPreprocessorDirectives());
  114.  
  115. return specialSyntax.ToString();
  116. }
  117.  
  118. private string GetOperators()
  119. {
  120. StringBuilder ops = new StringBuilder(@"/s+(
  121.  
  122. !|
  123. !=|
  124. %|
  125. %=|
  126. &|
  127. &&|
  128. &=|
  129. (|
  130. )|
  131. *|
  132. *=|
  133. +|
  134. ++|
  135. +=|
  136. -|
  137. –|
  138. -=|
  139. -&gt;|
  140. .|
  141. /|
  142. /=|
  143. :|
  144. &lt;|
  145. &lt;&lt;|
  146. &lt;&lt;=|
  147. &lt;=|
  148. =|
  149. ==|
  150. &gt;|
  151. &gt;=|
  152. &gt;&gt;|
  153. &gt;&gt;=|
  154. ?|
  155. [|
  156. ]|
  157. ^|
  158. ^=|
  159. {|
  160. ||
  161. |=|
  162. |||
  163. }|
  164. ~
  165.  
  166. )/s+");
  167.  
  168. ops.Replace(" ", "");
  169. ops.Replace(" ", "");
  170. ops.Replace(" ", "");
  171. ops.Replace(" ", "");
  172. ops.Replace("/s", " ");
  173.  
  174. return ops.ToString();
  175. }
  176.  
  177. private string GetKeywords()
  178. {
  179. StringBuilder kwds = new StringBuilder(@"b(
  180.  
  181. A list of all the keywords, omitted for brevity
  182.  
  183. )b");
  184.  
  185. kwds.Replace(" ", "");
  186. kwds.Replace(" ", "");
  187. kwds.Replace(" ", "");
  188. kwds.Replace(" ", "");
  189.  
  190. return kwds.ToString();
  191. }
  192.  
  193. private string GetNamespaces()
  194. {
  195. StringBuilder nsps = new StringBuilder(@"b.?(
  196.  
  197. A list of all the namespaces, omitted for brevity
  198.  
  199. ).?;?b");
  200.  
  201. nsps.Replace(" ", "");
  202. nsps.Replace(" ", "");
  203. nsps.Replace(" ", "");
  204. nsps.Replace(" ", "");
  205.  
  206. return nsps.ToString();
  207. }
  208.  
  209. private string GetClasses()
  210. {
  211. StringBuilder classes = new StringBuilder(@"b.?(
  212.  
  213. A list of all the classes, ommitted for brevity
  214.  
  215. )(?)?b");
  216.  
  217. classes.Replace(" ", "");
  218. classes.Replace(" ", "");
  219. classes.Replace(" ", "");
  220. classes.Replace(" ", "");
  221.  
  222. return classes.ToString();
  223. }
  224.  
  225. //Doesn’t work properly. For example, this matches
  226. //#region in blah#region. But for some reason, /b
  227. //isn’t working for this one.
  228. private string GetPreprocessorDirectives()
  229. {
  230. StringBuilder preproc = new StringBuilder(@"(
  231.  
  232. #if|
  233. #else|
  234. #elif|
  235. #endif|
  236. #define|
  237. #undef|
  238. #warning|
  239. #error|
  240. #line|
  241. #region|
  242. #endregion
  243.  
  244. )");
  245.  
  246. preproc.Replace(" ", "");
  247. preproc.Replace(" ", "");
  248. preproc.Replace(" ", "");
  249. preproc.Replace(" ", "");
  250. preproc.Replace("/s", " ");
  251.  
  252. return preproc.ToString();
  253. }
  254.  
  255. #endregion
  256.  
  257. #region "Match Handlers"
  258.  
  259. private string HandleMatch(Match m)
  260. {
  261. //Strings
  262. if (m.Groups[1].Success || m.Groups[2].Success || m.Groups[3].Success)
  263. {
  264. return "<span class = "str">" + m.Value + "</span>";
  265. }
  266.  
  267. //Single-line comments
  268. else if (m.Groups[4].Success)
  269. {
  270. return "<span class = "slc">" + m.Value + "</span>";
  271. }
  272.  
  273. //Formal documentation comments
  274. else if (m.Groups[5].Success)
  275. {
  276. return "<span class = "fdc">" + m.Value + "</span>";
  277. }
  278.  
  279. //Multi-Line Comments
  280. else if (m.Groups[6].Success)
  281. {
  282. return "<span class = "mlc">" + m.Value + "</span>";
  283. }
  284.  
  285. //Operators
  286. else if (m.Groups[7].Success)
  287. {
  288. return "<span class = "op">" + m.Value + "</span>";
  289. }
  290.  
  291. //Keywords
  292. else if (m.Groups[8].Success)
  293. {
  294. return "<span class = "kwd">" + m.Value + "</span>";
  295. }
  296.  
  297. //Namespaces
  298. else if (m.Groups[9].Success)
  299. {
  300. return "<span class = "nsp">" + m.Value + "</span>";
  301. }
  302.  
  303. //Classes
  304. else if (m.Groups[10].Success)
  305. {
  306. return "<span class = "cls">" + m.Value + "</span>";
  307. }
  308.  
  309. //Preprocessor directives
  310. else if (m.Groups[11].Success)
  311. {
  312. return "<span class = "preproc">" + m.Value + "</span>";
  313. }
  314.  
  315. else
  316. return m.Value;
  317. }
  318.  
  319. private string HandleLine(Match m)
  320. {
  321. //If the line is empty, add an &nbsp; to make it show up
  322. if (m.Value.Trim() == String.Empty)
  323. return "<li>&nbsp;</li> ";
  324.  
  325. //Line with tabs
  326. else if (m.Groups[1].Success)
  327. {
  328. //For each tab, we’re going to add 25px of left padding
  329. int numberOfTabs = Regex.Match(m.Value, @"^ +").Length;
  330.  
  331. string line = "<li style={0}padding-left:{1}px;{0}>{2}</li> ";
  332.  
  333. return String.Format(line, """, numberOfTabs * 25, m.Value.TrimEnd(‘ ’, ‘ ’));
  334. }
  335.  
  336. //Line with multiple spaces
  337. else if (m.Groups[2].Success)
  338. {
  339. //We’re going to add 25px of left padding for every 4 spaces
  340. int numberOfSpaces = Regex.Match(m.Value, @"^ {4,}").Length;
  341.  
  342. numberOfSpaces /= 4;
  343.  
  344. string line = "<li style={0}padding-left:{1}px;{0}>{2}</li> ";
  345.  
  346. return String.Format(line, """, numberOfSpaces * 25, m.Value.TrimEnd(‘ ’, ‘ ’));
  347. }
  348.  
  349. //All other lines
  350. else
  351. {
  352. return "<li>" + m.Value.TrimEnd(‘ ’, ‘ ’) + "</li> ";
  353. }
  354. }
  355.  
  356. private string HandleMLC(Match m)
  357. {
  358. StringBuilder value = new StringBuilder(m.Value);
  359.  
  360. value.Replace("<li>", "<li><span class = "mlc">");
  361.  
  362. value.Replace("px;">", "px;"><span class = "mlc">");
  363.  
  364. value.Replace("</li>", "<span></li>");
  365.  
  366. return value.ToString();
  367. }
  368.  
  369. private string HandleHardStrings(Match m)
  370. {
  371. StringBuilder value = new StringBuilder(m.Value);
  372.  
  373. value.Replace("<li>", "<li><span class = "str">");
  374.  
  375. value.Replace("px;">", "px;"><span class = "str">");
  376.  
  377. value.Replace("</li>", "<span></li>");
  378.  
  379. return value.ToString();
  380. }
  381.  
  382. #endregion
  383. }
  384. }

Efficient Paging for GridView

The GridView is a flexible control that’s quickly becoming my favorite. It makes so many things a breeze. One of these things happens to be pagination. All you need to do to get the data in pages is to add the following 2 lines (the second one being optional):

  1. <asp:GridView
  2. ID="PagedGridView"
  3. AllowPaging=true
  4. PageSize=25
  5. EnableViewState=false
  6. runat="server">
  7. </asp:GridView>

Looks too good to be true? Unfortunately, it is. The way that GridView does pagination is horribly inefficient: it gets all the records from the database, then goes on to discard all the records that it doesn’t need. And it does this every time the page loads. Which doesn’t matter much if your table contains only 100 records, but if it contains 10,000 records or 50,000 records, then it starts to become a problem.

Fortunately, creating a more efficient paging for the GridView is easy, though we need to write more than two lines to make it work.####Getting Back Only the Records That We Want

To make the pagination process more efficient, we want to get from the database only those records that we actually need. Duh! We’ll do this using a nice SQL function called ROW_NUMBER(). Unfortunately, this function is available only in SQL Server 2005, which not everyone has. We can’t simply substitute a increment-by-one primary key column for ROW_NUMBER() because we’ll probably be deleting rows in the middle, which will screw up anything. If you don’t have SQL Server 2005, here’s a link to a tutorial that shows how to get the same result using a temporary table.

The stored procedure that does use ROW_NUMBER():

  1. ALTER PROCEDURE dbo.EfficientGridViewPaging
  2.  
  3. @pageIndex int,
  4. @pageSize int
  5.  
  6. AS
  7.  
  8. BEGIN
  9.  
  10. WITH Entries AS (
  11.  
  12. SELECT ROW_NUMBER() OVER (ORDER BY ID DESC)
  13. AS RowNumber, random_data1, random_data2, random_data_etc
  14. FROM the_table
  15. )
  16.  
  17. SELECT random_data1, random_data2, random_data_etc
  18.  
  19. FROM Entries
  20.  
  21. WHERE RowNumber BETWEEN (@pageIndex - 1) * @pageSize + 1 AND @pageIndex * @pageSize
  22.  
  23. END

<

p class="note">I first came across ROW_NUMBER() in article while browsing the Internet as a means of procrastination learning more on SQL Server 2005. (Actually, to be technically accurate, I first learned about ROW_NUMBER() while studying for Oracle certification, but I forgot about it completely until the article refreshed my memory. Hehe.) I created a stored procedure from scratch, feeling rather awesome. But as often happens, I found out that I wasn’t the only person to have this problem. ;-)

The stored procedure takes in two parameters (the current page and the maximum number of records to return) and returns the appropriate records.

Total Number of Pages

One rather inconvenient fact is that GridView’s PageCount property is read-only. This means that we need to figure out a way to get the total number of pages. Most implementations I saw have the total number of records as the output parameter of our afore-mentioned stored procedure. But as it’s unlikely that the number of records changes frequently, I chose to create a stored procedure that gets the number of records in the table and store it in the Session variable.

Creating the Links

With that out of the way, creating the links to different pages is fairly straight-forward. Here’s a somewhat sloppy implementation that allows you to go to the first page, the previous page, the next page, or the last page:

  1. using System;
  2. using System.Data;
  3. using System.Configuration;
  4. using System.Web;
  5. using System.Web.Security;
  6. using System.Web.UI;
  7. using System.Web.UI.WebControls;
  8. using System.Web.UI.WebControls.WebParts;
  9. using System.Web.UI.HtmlControls;
  10.  
  11. using System.Data.Sql;
  12. using System.Data.SqlClient;
  13.  
  14. public partial class _Default : System.Web.UI.Page
  15. {
  16. private int pageCount = 0;
  17. private int pageIndex = 0;
  18. private int pageSize = 25;
  19.  
  20. private string dbConn = "Your Connection String — should be in web.config";
  21.  
  22. protected void Page_Load(object sender, EventArgs e)
  23. {
  24. if (Request["page"] == null)
  25. pageIndex = 1;
  26.  
  27. else
  28. pageIndex = Int16.Parse(Request["page"]);
  29.  
  30. if (Session["pageCount"] == null)
  31. {
  32. //Get the total number of pages in the table
  33. using (SqlConnection cn = new SqlConnection(dbConn))
  34. {
  35.     cn.Open();
  36.  
  37.     SqlCommand cmd = cn.CreateCommand();
  38.  
  39.     cmd.CommandText = "GetTotalPages";
  40.     cmd.CommandType = CommandType.StoredProcedure;
  41.  
  42.     //The stored procedure returns total number of records
  43.     pageCount = Convert.ToInt16(cmd.ExecuteScalar());
  44. }
  45.  
  46. pageCount = pageCount / pageSize;
  47.  
  48. Session["pageCount"] = pageCount;
  49. }
  50.  
  51. else
  52. pageCount = (int)Session["pageCount"];
  53.  
  54. GetCurrentPageData();
  55. }
  56.  
  57. private void GetCurrentPageData()
  58. {
  59. //Do error processing here
  60. if (pageIndex > pageCount || pageIndex < 1)
  61. return;
  62.  
  63. SqlDataSource sds = new SqlDataSource();
  64.  
  65. sds.ConnectionString = dbConn;
  66.  
  67. sds.SelectCommand = "EfficientGridViewPaging";
  68. sds.SelectCommandType = SqlDataSourceCommandType.StoredProcedure;
  69.  
  70. sds.SelectParameters.Add("pageIndex", pageIndex.ToString());
  71. sds.SelectParameters.Add("pageSize", pageSize.ToString());
  72.  
  73. PagedGridView.DataSource = sds;
  74. PagedGridView.DataBind();
  75.  
  76. UpdatePagination();
  77. }
  78.  
  79. private void UpdatePagination()
  80. {
  81. //Update the hrefs to point to the right page
  82. FirstPageLink.HRef = "Default.aspx?page=1";
  83. PrevPageLink.HRef = "Default.aspx?page=" + (pageIndex - 1);
  84. NextPageLink.HRef = "Default.aspx?page=" + (pageIndex + 1);
  85. LastPageLink.HRef = "Default.aspx?page=" + pageCount;
  86.  
  87. //We’re on first page
  88. if (pageIndex <= 1)
  89. {
  90. FirstPageLink.HRef = String.Empty;
  91. PrevPageLink.HRef = String.Empty;
  92. }
  93.  
  94. //We’re on last page
  95. else if (pageIndex >= pageCount)
  96. {
  97. NextPageLink.HRef = String.Empty;
  98. LastPageLink.HRef = String.Empty;
  99. }
  100.  
  101. CurrentPage.Text = "Page " + pageIndex.ToString() + " of " + pageCount.ToString();
  102. }
  103. }
  104.  

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.

<

p class="important-note">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!

18 of 19 pages « First  <  16 17 18 19 >

On the Side