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 ROWNUMBER(). 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 ROWNUMBER() 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

I first came across ROWNUMBER() 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 ROWNUMBER() 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.  

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.

5 of 6 pages « First  <  3 4 5 6 >