Insert commas into your currency values

Make your currency more readable


Last post I showed how to limit the decimal places of a double and decimal, but when you display a long number that represents currency, this is not what you want your user to see: $1123443322.25, you want it nicely formatted like this: $1,123,443,322.25 - much easier to read!

To do that, just extend an existing class, I am going to use string, but you could easily make this into a number as well.
public static string ToMoney(this string s)
{
  int period = s.IndexOf(".");
  if (period == -1) return s;
  else
  {
    string right_side = s.Substring(period, s.Length - period);
    string left_side = s.Substring(0, period);
    while (left_side.Length > 3)
    {
      right_side = "," + left_side.Substring(left_side.Length - 3, 3) + right_side;
      left_side = left_side.Substring(0, left_side.Length - 3);
    }
    return left_side + right_side;
  }
}

Now just use this as you would any other method of string:
string long_money = 1123443322.25;
string money = long_money.ToMoney();

money now equals 1,123,443,322.25. This would be easy to change in to working with double, int, etc.

UPDATE - 2009-01-20

Or just forego all my silly programming and use the built in function Howard pointed out (that I didn't know :P):
value.ToString("c")

//Or if the currency symbol isn't required use:
value.ToString("#,##0.00")

Thanks Howard... I feel like a fool; isn't the internet great?

Searching for multiple strings using LINQ

text searching using LINQ

In the past I had covered Record Search with LINQ and that worked well, but did not cover text searching. Since you can't nest if-thens inside of a LINQ query it can be tough to get a useful text search.  Here is a method using a string array to search through a field for matches.

 

First thing is to take a TextBox and Split the Text to put it into a string array, but, it is not that simple. Since Linq can not make dynamic queries, we are going to have to run through an array with a set size. For this example I will choose 5, which will limit our search to only the first 5 terms. You could easily make this more, but this is a demo damn it. Once we decide that, we want to fill the array with String.Empty values as nulls will error out anything.

 

int wordLimit = 5;

string[] keywords = new string[wordLimit];

for (int i = 0; i < wordLimit; i++) keywords[i] = string.Empty;

string[] inputKeywords = txtTitle.Text.Split(new char[] { ' ', ',', '' }, wordLimit + 1, StringSplitOptions.RemoveEmptyEntries);

int max = inputKeywords.Length > wordLimit ? wordLimit : inputKeywords.Length;

for (int i = 0; i < max; i++)

      keywords[i] = inputKeywords[i];

 

This may seem like a lot, but this makes sure we get an array of exacly 5 elements that there are no empties in.  That way, if the user does not fill it in, *every* string contains a String.Empty, so it will work fine.  Next we just go and run through all the strings comparing it in this example to the 'title' property


return from p in someIQueryable where

             p.title.Contains(keywords[0]) &&

             p.title.Contains(keywords[1]) &&

             p.title.Contains(keywords[2]) &&

             p.title.Contains(keywords[3]) &&

             p.title.Contains(keywords[4])

     select p;


Right... now this isn't the most beautiful and elegant solution, but it works! it can be expanded to whatever size you want and will be accurate. It may seem like a lot of lines of code, but without dynamic LINQ query production, this is the only way I found it works stock. Simple and to the point!

Extend an existing or custom class

Writing a extension for an existing class such as string.DoSomethingHere()

I usually run my database logic through Linq and a dbi.cs class for my database interaction. The dbml file will produce all of my custom classes which is great, but, I wanted to add some custom extensions. Looking at that code created by the dbml is overwhelming and I would rather not go poking around in there, so, I decided to make my own extensions, it's not a difficult process.


I had a class named ticket and I wanted to add an extension IEnumerable<ticket>.open() which would list all open tickets. Now, to get in to my calss structure and all of that would be a waste of time, so I will show how to do this with something that is a bit more familiar and likely useful, I am going to extend the string class with string.capitalizeFirstLetter() which would turn:

a senTENCE like this ONE hErE

into one like this

A Sentence Like This One Here

And yes, I know there is the CSS text-transform, but that isn't the point, it is the method I am showing.


First thing is first, make a new class called MyCustomExtensions.cs or something like that. Next, make sure to declare the class as a public static class. Now make a public static string method. normally in a method you would take a string input like this:

methodName(string someString)

For extensions, you do them slightly different like this:

methodName(this string someString)

Notice that this is now ahead of string, that will tell you program to 'look' for this extension whever a single string is used; it will also populate your intellisense in Visual Studio.


That is really the only big difference, now you just treat it like any other method. This is my full example class:

using System.Collections.Generic;
using System.Text;

/// <summary>
/// Custom Class Extensions
/// </summary>

public static class custom_extensions
{
  public static string CapitalizeFirstLetters(this string s)
  {
    string[] splitString = s.Split(new char[] { ' ' });
    List capitalized = new List();
    foreach (string word in splitString)
    {
      string add;
      if (word.Length == 1) add = word.ToUpper();
      else if (word.Length == 0) add = string.Empty;
      else add = word.Substring(0, 1).ToUpper() + word.Substring(1, word.Length - 1);
      capitalized.Add(add);
    }
    StringBuilder sb = new StringBuilder();
    foreach (string word in capitalized) sb.Append(word + " ");
    sb.Remove(sb.Length - 1, 1); return sb.ToString();
  }
}

Anonymous Contact Web Part and Code

Allow your users to send an anonymous contact/comment through your SharePoint site

Recently I was asked to allow anonymous comments on a SharePoint site. Keep in mind that this site already had anonymous access enabled and I suggested just opening a discussion and recommending that users post on it anonymously... that was shot down. Soooo... I built this simple little web part that allows the administrator to set up the anonymous contact form. It requires that the admin set four things:

  • SMTP Server - umm... you need this to send email
  • Mailto Address - whoever is going to get these anonymous contacts
  • From address - the dummy address it is 'coming from'
  • Subject Line - what you want the subject line to be

Set those and you are all done -- pretty simple really. All of the code is included in the solution zip, or if you just want the web part, download the wsp. To deploy it, use the command:
stsadm -o addsolution -filename folder_you_put_the_wsp_in/anonymous_contact.wsp
Just be sure to deploy the solution via central admin and add it in your web part gallery with the 'new' button.





GUI css writer: change your page style and rewrite your css on the fly

I have seen a lot of theme pickers out there, but not one that gives you 100% control like this

While working on a recent project, I had a complaint about the colors... I hate colors and I am no good at picking them, and when I do find some colors that I think are nice, apparently everyone else thinks they are crap.  This is why I truly respect web designers, as that is a true skill.  But to my chagrin I do not have a web designer, so I did the next best thing: get the blame off my back.  That's right, if you think you are so good with colors, why don't you pick them Mr. User?

 

So I went ahead and used the Farbtastic jQuery color picker Plug-in that I recently fell in love with, along with some nice Linq, XML, some good old .Net IO and a dash of clever css writing, and I was able to put together a (almost) fool-proof theme-designer with a friendly GUI.

 

First thing first, I needed both jQuery and the Farbtastic jQuery color picker Plug-in I just mentioned to be downloaded and called in my <head>.  Once I ran the Farbtastic demo to make sure everything was working fine, I set out on my adventure.

 

Next, I had to seperate my css into two distinct sheets, one: main_style.css would be jsut that, the main style; this never changes and is static.  Then, I made another stylesheet: theme.css that will hold my changing data and be re-written.  By using some clever techniques like not relying on any css border properties, I will be able to minimize the amount of code i have to re-write and be able to produce what looks like borders without actually using borders.  So instead of

/*theme.css*/
div.border { border: solid 5px Black; }

<!--html-->
<div class="border">some stuff with a border</div>

 

I will instead use something like this:

/*main_style.css*/
.border { padding:5px; }

/*theme.css*/
.border_color { background-color:Black; }
.content_color { background:White; }

<!--html-->
<div class="border border_color">
  <div class="inner_color">some stuff with a border</div>
</div>

 

These both produce what looks to be the exact same, the difference being that the second one does not actually have a border, it just looks like it with the padding and background color.  Now I know that the second one is more code, but that is the price you pay to have less code in your css and have your website be maintainable.  Once you write teh css writer one time, you will never have to touch the code again.  You can easily incorporate borders into the re-written css if you would like, that is just not the path I am going.

 

Now that that is understood, I can put in the color picker and the related TextBox controls to correspond to what colors I will be changing.  To do that, simply put this isnot your page where you want the picker: <div id="picker"></div> and then add your TextBox controls where you need them; each TextBox is the class 'colorwell' that allows them to be accessed easier by jQuery.  Once that is done, add a Button to submit everything.  So far, mine looks like the picture at the top of the post.  You will have to bind everything with jQuery now:

[code:js]

 <script type="text/javascript" charset="utf-8">
     $(document).ready(function() {
         var f = $.farbtastic('#picker');
         var p = $('#picker').css('opacity', 0.25);
         var selected;
         $('.colorwell')
      .each(function() { f.linkTo(this); $(this).css('opacity', 0.75); })
      .focus(function() {
          if (selected) {
              $(selected).css('opacity', 0.75).removeClass('colorwell-selected');
          }
          f.linkTo(this);
          p.css('opacity', 1);
          $(selected = this).css('opacity', 1).addClass('colorwell-selected');
      });
     });
 </script>

[/code]

 

Now comes the code-behind.  First thing I decided is that I am going to hold the data in an xml file; you could easily do this in a SQL database as well.  I named the file theme.xml and it looks like this:

<?xml version="1.0" encoding="utf-8"?>
<theme>
  <text>#444444</text>
  <borders>#4a4a4a</borders>
  <body>#ffffff</body>
  <links>#ff7700</links>
  <link_hover>#ffa500</link_hover>
  <button_text>#ffffff</button_text>
  <headers>#ffffff</headers>
  <background>#a53c3c</background>
</theme>

 

I populated it with my default color values of what will be changing.  Next, I made a class that will help me interact with the xml using Linq, I named this file xmlHelper.cs and placed it in my App_Code folder:

public static XElement getElement(XElement x, string element)
{ return (from e in x.Descendants(element) select e).First(); }

public static void writeElement(XElement x, string element, string value)
{
    XElement xe = getElement(x, element);
    xe.Value = value;
}

 

Now in my Page_Load I made a Dictionary<string, TextBox> and populated it with all of my TextBox controls and their corresponding xml Element name.  Now each time the page loads fresh, I simply load my values into the TextBox controls like this:

if (!IsPostBack)
{
    foreach (var v in colors)
        v.Value.Text = xmlHelper.getElement(x, v.Key).Value;
}

 

Now every time the page loads, it pulls the values out of the xml file and populates the TextBox controls.  Now that I have that, I need a way to write to the css file and the xml file.  A iadd that to my btnSubmit_Click event.  Once again, I can look through the Dictionary I had set up to this time use my xmlHelper class and write to the xml file.  That is done like this:

protected void btnSubmit_Click(object sender, EventArgs e)
{
    foreach (var v in colors) xmlHelper.writeElement(x, v.Key, v.Value.Text);
    x.Save(themePath);
    //writeCss(); //this will be used in a moment
}

 

Now the xml is written, we need to write the css.  For this I just use the old IO and StringBuilder to write my css and overwrite the old file:

protected void writeCss()
{
    string cssThemeFile = Server.MapPath("~") + "\\css\\theme.css";
    StringBuilder sb = new StringBuilder();
    sb.Append("html{background-color:" + txtBg.Text + ";}");
    sb.Append(".border_color{ background-color:" + txtBorders.Text + ";}");
    sb.Append(".content_color{ background-color:" + txtBody.Text + ";}");
    sb.Append(".header_text{color:" + txtHeader.Text + ";}");
    sb.Append("a{color:" + txtLink.Text + ";}");
    sb.Append("a:hover{color:" + txtLinkHover.Text + ";}");
    sb.Append(".button:hover{background-color:" + txtLinkHover.Text + ";}");
    sb.Append(".button{background:" + txtLink.Text + ";color:" + txtButtonText.Text + ";}");
    File.Delete(cssThemeFile);
    TextWriter tw = new StreamWriter(cssThemeFile);
    tw.WriteLine(sb.ToString());
    tw.Close();
}

 

That will write my theme.css file, and that is that.  Now on every click, the values will be written to the xml file (this is unneccessary, but it loads the previous settings up for your user).  After that, the css file is re-written and immediately used by your page.  This does require that you have write permissions to whatever directory/directories you house your theme.css and theme.xml files in.  This also is easy to implement with each user profile having their own saved style (easier with SQL).  I hope this comes in handy for someone, I love it and have used it on a few projects now.

 

...And now they see it's not soo easy picking colors that actually look good :)

 

 

Saving multiple files of the same name (C#)

Save multiple files of the same name without overwriting anything

I am sure I am not the only one to run into the problem of saving files with the FileUpload control.  The problem with this can be if a user has already uploaded a file someFile.txt and they are trying to upload another file someFile.txt.  Normally, you have 2 options:

  • Delete the old file and save the new one
  • Inform the user there is already a file there under that name


Recently, one of these options just wouldn't cut it, so I borrowed an idea from Microsoft Windows: if you save two identical files on your desktop, it simply changes the filename slightly.   For example, if there is already someFile.txt on my desktop, and I try to paste a file with the same name, Windows will just rename it Copy of someFile.txt and if I paste again, it will be named Copy (2) of someFile.txt.  Now that is a decent idea, but I hate the 'Copy of' part of the name, so I decided to make that part a little better.

 

What this snippet does, is take in the FileUpload control you are saving from and saves it to the directory you specify (saveTo).  It will first try to save the file as someFile.txt, but if that is already there, it will save as someFile[1].txt and if that is there it will save as someFile[2].txt and so on.  This is very handy if you need to keep mulitple versions of one file.  It return a FileInfo object so you can get the name it saved as, file size, etc..

 

[code:c#]

public static FileInfo saveFileWithoutOverwrite(FileUpload fu, string saveTo)
{
    int count = 1;
    string fileName = fu.FileName;
    string[] fileNameSplit = fileName.Split(new char[] { '.' });
    string ext = "." + fileNameSplit[fileNameSplit.Count() - 1];
    string prefix = fileName.Substring(0, fileName.Length - ext.Length);
    while (File.Exists(saveTo+ fileName))
    {
        fileName = prefix + "[" + count.ToString() + "]" + ext;
        count++;
    }
    fu.SaveAs(saveTo + fileName);
    return new FileInfo(saveTo + fileName);
}

[/code]

 

This has proved to be very useful in developing a Trouble Ticket System, where files are often updated and saved, but we do not want to lose the older records. This could be easily modified to take a Stream input and save it as well if you are not working directly with a FileUpload control.

Finding/counting Keywords out of a Text Document

A quick program to find keywords out of a document - great for a knowledge base or .txt file repository

I wrote this a while back and it became relevant again today.  All this does is take in a text file, go through it making a Dictionary of all the words and counting them (ignoring the words in the exclusion list) and then sorts and outputs them.  The logic is pretty straightforward and looks like this:

[code:c#]

public partial class keywords : System.Web.UI.Page
{
    Dictionary<string, int> _keywords = new Dictionary<string, int>();
    char[] delimiter = {' '}; // whatever you want to delimit the text with, I just use spaces
    string[] DEFAULT_EXCLUDE = { "a", "the", "is", "and" };//things you want excluded even if the user does not specify (you can leave it blank too)

    protected void Page_Load(object sender, EventArgs e)
    {
        lblExcluded.Text = string.Empty;
        for(int i=0;i<DEFAULT_EXCLUDE.Length;i++)
            lblExcluded.Text += i == DEFAULT_EXCLUDE.Length - 1 ? DEFAULT_EXCLUDE[i] + " " : DEFAULT_EXCLUDE[i] + ", ";
    }
    protected void submit_Click(object sender, EventArgs e)
    {
        string[] words_to_exclude = txtExclude.Text.Length<1 ? new string[] {" "} : txtExclude.Text.Split(new char[] {','});
        for (int i = 0; i < words_to_exclude.Length; i++) words_to_exclude[i] = words_to_exclude[i].Trim(); //cut off all empty space
        List<string> excluded = new List<string>();
        foreach (string s in DEFAULT_EXCLUDE)
            if (!excluded.Contains(s)) excluded.Add(s);
        foreach (string s in words_to_exclude)
            if (!excluded.Contains(s)) excluded.Add(s);

        int keyword_threshhold = Int32.Parse(txtThreshhold.Text);
        if (fu.HasFile)
        {
            try
            {
                //Save the file here
                StreamReader sr = new StreamReader(fu.FileContent);//this pulls from the user, or you can pull it from where you saved it to now
                while (!sr.EndOfStream)
                {
                    string line = sr.ReadLine();
                    string[] splitString = line.Split(delimiter);
                    foreach (string s in splitString)
                    {
                        if (!excluded.Contains(s))
                        {
                            try { _keywords[s] = _keywords[s] + 1; }
                            catch { _keywords.Add(s, 1); }
                        }
                    }
                }
                bool switcher = false;
                phKeywords.Controls.Add(new LiteralControl("<div id=\"keys\" class=\"main_area\"><h2>Keywords</h2>"));

                var query = from p in _keywords orderby p.Value descending select p;

                foreach (KeyValuePair<string, int> d in query)
                {
                    if ((int)d.Value >= keyword_threshhold)
                    {
                        string color = switcher?"blue": "white";
                        phKeywords.Controls.Add(new LiteralControl("<div class=\""+ color +"\">"+ d.Value + " - " + d.Key + "</div>"));
                        switcher = !switcher;
                    }
                }
                phKeywords.Controls.Add(new LiteralControl("</div>"));
            }

            catch (Exception ex)
            { phError.Controls.Add(new LiteralControl("<div id=\"error\" class=\"main_area\"><b>Error:</b>" + ex.Message + "</div>")); }
        }
        else
            phError.Controls.Add(new LiteralControl("<div id=\"error\" class=\"main_area\"><b>Error:</b> Can't process what you don't give me...</div>"));

    }

[/code]

 

The inputs are fu, which is a FileUpload control that holds the file you are parsing, txtExclude which is a TextBox of the words to exclude (added to default exclusions), and txtThreshhold which is a TextBox where you enter the number of words to consider before it is counted as a keyword.

 

 

 

 

 

 

Translate an Excel Serial Date into a C# DateTime

An annoying little 'feature' of Excel is that it stores dates in a strange format

I stole the logic and from Code Project and wrote some code that will give you a DateTime variable if you feed it a Excel serial integer (number of days after 2/29/1900).  Strangely enough, DateTime.Parse("2/29/1900"); throws an error, so the simple solution I thought of: DateTime.Parse("2/29/1900").AddDays(excelInteger); does not work.  But this does:

 

[code:c#]

public DateTime ExcelSerialDateToDT(int nSerialDate)
{
    int l = nSerialDate + 68569 + 2415019;
    int n = ((4 * l) / 146097);
    l = l - ((146097 * n + 3) / 4);
    int i = ((4000 * (l + 1)) / 1461001);
    l = l - ((1461 * i) / 4) + 31;
    int j = ((80 * l) / 2447);
    int nDay = l - ((2447 * j) / 80);
    l = (j / 11);
    int nMonth = j + 2 - (12 * l);
    int nYear = 100 * (n - 49) + i + l;

    return DateTime.Parse(nMonth + "/" + nDay + "/" + nYear);
}

[/code]

 

As long as your date isn't within 60 days of 2/29/1900, this will work perfectly (don't ask why those are screwed up Tongue out).

Record Search with LINQ: searching just got a lot easier!

A suprisingly simple search technique that will work for SQL tables, XML files, DataTables, etc.

Linq is just awesome.  I was recently asked a question on how to search through a data file with linq.  At first it seemed difficult, but after some though and fighting my way through the strange quirks, I realized how much easier Linq could [once again] make my life.

 

Here is how it's done (this article uses XML, but I have done the same exact thing with both SQL and DataTables, the concept is the same.)

 

The original question stirred from how to search through a bunch of recipes.  Each recipe (XML elements bolded) has a name, type (chicken, vegetable, etc.), calories that it contained, the amount of people that the recipe serves, and the instructions on how to make it.  Here is the dummy XML:


<?xml version="1.0" encoding="utf-8" ?>
<recipes>
    <recipe>
        <name>Hamburger</name>
        <serves>1</serves>
        <calories>500</calories>
        <type>Beef</type>
        <instructions>throw some crap together and cook it</instructions>
    </recipe>
    <recipe>
        <name>Chicken Nuggets</name>
        <serves>4</serves>
        <calories>400</calories>
        <type>Chicken</type>
        <instructions>throw some crap together and cook it</instructions>
    </recipe>
    <recipe>
        <name>Lasagna</name>
        <serves>8</serves>
        <calories>800</calories>
        <type>Pasta</type>
        <instructions>throw some crap together and cook it</instructions>
    </recipe>
    <recipe>
        <name>Ribeye</name>
        <serves>1</serves>
        <calories>600</calories>
        <type>Beef</type>
        <instructions>throw some crap together and cook it</instructions>
    </recipe>
    <recipe>
        <name>Drumsticks</name>
        <serves>3</serves>
        <calories>700</calories>
        <type>Chicken</type>
        <instructions>throw some crap together and cook it</instructions>
    </recipe>
    <recipe>
        <name>Beef Kabobs</name>
        <serves>4</serves>
        <calories>350</calories>
        <type>Beef</type>
        <instructions>throw some crap together and cook it</instructions>
    </recipe>
    <recipe>
        <name>Green Beans</name>
        <serves>5</serves>
        <calories>50</calories>
        <type>Vegetables</type>
        <instructions>throw some crap together and cook it</instructions>
    </recipe>
</recipes>

 

Now that we know our data structure, we can figure outhow to set up this search.  I am going to show a few different approaches so you can pick and choose which ones to use.  I am going to cover keyword search, number range search and specific text search.

 

First we have name search.  For that I am going to look for keywords.  I will allow users to enter none or as many as they want (to be more specific) into a TextBox.  These will be delimited with the standard space( ), comma(,) and semicolon(;).

 

Next there is type and serves which I am going to set to DropDownLists.  I am going to do this because not all numbers/words will be supported, I feel it is best to guide the user along with this and only supply available search possibilities, with 'any' always being the first option.

 

Finally there is the calories range.   For this, I will use two TextBoxes that will take in numbers of course; a minimum and a maximum.

 

Here is what the search interface looks like:

 

Here is the markup:

 

<fieldset>
    <legend>Search Recipes</legend>
    <ul>
        <li>
            <label for="name">Name</label>
            <asp:TextBox runat="server" ID="txtName" />
        </li>
        <li>
            <label for="type">Type</label>
            <asp:DropDownList ID="ddlType" runat="server" />
        </li>
       
        <li>
            <label for="serves">Serves:</label>
            <asp:DropDownList ID="ddlServes" runat="server" />
        </li>

        <li>
            <label for="calories">Calories</label>
            <asp:TextBox ID="txtCalMin" runat="server" Columns="3" />
            <span style="float:left"> to </span>
            <asp:TextBox ID="txtCalMax" runat="server" Columns="3" />
        </li>

        <li>
            <asp:Button ID="btnSearch" runat="server" Text="Search"
                onclick="btnSearch_Click" />
        </li>
    </ul>
</fieldset>

 

Now with that out of the way, we can start with the code.  First a global XElement x; and IEnumerable<XElement> filteredResults; has to be declared, x will be used within the program and initialized on Page_Load, filteredResults will be explained later.  After that is initialized, on a fresh page load (!IsPostBack)the DropDownLists must be populated:

 

protected void Page_Load(object sender, EventArgs e)
{
    x = XElement.Load(Server.MapPath(".") + "\\App_Data\\recipes.xml");
    if(!IsPostBack)
    {
        // get all of your different food 'types' and put them in the ddl
        ddlType.Items.Add("any");
        foreach(string s in ((from p in x.Descendants("type") orderby p.Value select p.Value).Distinct()))
            ddlType.Items.Add(s);
        ddlServes.Items.Add("any");
        foreach (string s in (from p in x.Descendants("serves") orderby p.Value select p.Value).Distinct())
            ddlServes.Items.Add(s);
    }
}

 

Now that the form is all ready,it is time to delve in to the actualy search.  Now keep in mind, the Linq syntax will be a bit differen, but the methods are teh EXACT same when you are working with another data type (SQL, DataTable, etc.)  After a lot of deliberation on how to do this, I decided to split it up in to two seperate parts.  First filter the elements using all the filters EXCEPT the keywords (name), then apply the keyword search.  The reason I am doing it this way is because that search could include zero terms, or 5,000; therefore iteration makes the least amount of work.  Also, for ease of use, the search will workregardless of what a user enters.  By default, the search will return everything, then narrow it down as users select more criteria.  For this I will make it so the default min/max calories are 0/9999 respectively (I will ignore any input that isn't integers) and make sure to ignore the type and  serves if 'any' is selected.  Here is the code for search, I will explain it afterwards:

 

protected void btnSearch_Click(object sender, EventArgs e)
{
    try
    {
        string[] searchTerms = txtName.Text.Split(new string[] {" ", ",", ";"}, StringSplitOptions.RemoveEmptyEntries); //gets all your search terms
        int calMin = 0;
        int calMax = 9999;
        try { calMin = int.Parse(txtCalMin.Text); }catch { }
        try { calMax = int.Parse(txtCalMax.Text); }catch { }
        if (calMin > calMax) Response.Write("<h2 style=\"color:red;\">Error: Calories<div style=\"font-size:.5em;\">Minimum can not be larger than maximum</div></h2>");
      
        var searchResults = from p in x.Descendants("recipe") // filters everything by all of the fields except Name
                        where
                            (ddlType.SelectedIndex == 0 ? true : p.Element("type").Value.Equals(ddlType.SelectedValue.ToString())) &&
                            (ddlServes.SelectedIndex == 0 ? true : p.Element("serves").Value.Equals(ddlServes.SelectedValue)) &&
                            (int.Parse(p.Element("calories").Value) >= calMin) &&
                            (int.Parse(p.Element("calories").Value) <= calMax)
                    select p;

        foreach (string s in searchTerms) // since name can be multiple words, this iterates through them all, making sure that all of the terms are present
            searchResults = from p in searchResults where p.Element("name").Value.ToLower().Contains(s.ToLower()) select p;

        if (searchResults.Count() > 0)
        {
            // output your results
            foreach (XElement xe in searchResults)
                pnlOutput.Controls.Add(new LiteralControl("<a href=\"#" + xe.Element("name").Value + "\"><div><h3>" + xe.Element("name").Value + "</h3> Calories: " + xe.Element("calories").Value + "</div></a>"));
        }
        else
            pnlOutput.Controls.Add(new LiteralControl("No Entries match your search criteria"));
    }
    catch (Exception ex)
    {
        pnlOutput.Controls.Add(new LiteralControl("<h3>Error</h3>"+ex.Message));
    }
    pnlOutput.Visible = true;
}

 

First off, you can see that the searchTerms are made by splitting the input into an array, pretty simple.  Next the min/max calories are set to defaults and attempted to be changed to the inputs, and will only be changed if there are valid inputs; it will output an error if minCal > maxCal.

 

The first leg of the search is pretty simple just return all descendants in the XML file of type recipe that follow the searhc criteria.  First I check the type, if the DropDownList is at SelectedIndex of 0 (which is the 'any' selection) I will return all, otherwise, jsut the ones that equal the selected type; I do the exact same for serves.  Then a simple check that returns those that calMin >= calories <=calMax.

 

That was not the part that confused me, it was how to get a dynamic number of search terms to be iterated through.  But this is where I cam up with the simplest of solutions.  Just search EACH term alone, and interate through it with Linq and a foreach... the beauty of the IEnumerable.  For each search term in searchTerms I simply run a new Linq statement that checks if the ameElement.Contains(that_search_term).  Therefore, every time this runs through, it will drop all entries that don't contain the term, each iteration [likely] returning fewer entries -- so simple!  But not really, for some reason (still not completely sure why), if I simply run:

 

foreach (string s in searchTerms)
    searchResults = from p in searchResults where p.Element("name").Value.ToLower().Contains(s.ToLower()) select p;

 

Does NOT work, it really only honors the last term; it is not overwriting searchResults every iteration.  After some testing, I had found out that I had to make a new variable (that's the filteredResult that we declared above) that would instead take place of searchResults and then we can write over it.  But also, you may not do this inside the foreach loop itself, as that still only honors the last term.  BUT, if the method is taken out and placed seperately, the overwrite seems to work.  But NOT for searchResults, I have to use filteredResults.  If anyone understnad exactly why this extra step is necessary, pleae enlighten me!  Here is how it is called:

 

filteredResults = searchResults;
foreach (string s in searchTerms) // since name can be multiple words, this iterates through them all, making sure that all of the terms are present
    filteredResults = iterateThroughSearchTerm(s);

 

And here is the method:

 

protected IEnumerable<XElement> iterateThroughSearchTerm(string term)
{
    return (from p in filteredResults where p.Element("name").Value.ToLower().Contains(term.ToLower()) select p);
}

 

All that is left is to simply output your findings, an error message or a 'sorry, nothing found' message.  And there you go, a bunch of different search approaches all covered at once.  Here is some example code to get you started.

 

 





 

Cascading DropDowns with SQL

an improved cascade class that includes methods using both xml and sql

I posted an entry of how to make cascading ddls with xml in the past, now I extended the class to include sql.  In this case, one of the few times I may say so I decided not to use LINQ as in this case it actually seemed to make more work.  The idea is basically the samejust usingan SQL database.  This is what the info in my DB looks like:

 

dropDowns

idcategoryitem
1 ford mustang
2 ford f-150
3 ford focus
4 chevy impala
5 chevy corvette
6 chevy blazer

 

Now I just have to make my ddls, notice the second one is empty:

 

<asp:DropDownList ID="ddlMake" runat="server" AutoPostBack="true"   onselectedindexchanged="ddlMake_SelectedIndexChanged"
    cascadeTo="ddlModel"  cascadeBlank="- select make -" >
    <asp:ListItem>- select make -</asp:ListItem>
    <asp:ListItem>ford</asp:ListItem>
    <asp:ListItem>chevy</asp:ListItem>
</asp:DropDownList>
<asp:DropDownList ID="ddlModel" Enabled="false" runat="server">
    <asp:ListItem>- select make -</asp:ListItem>
</asp:DropDownList>

 

Also notice that the attributes cascadeTo and cascadeBlank are set.   cascadeTo is the ddl you are going to cascade to, and cascadeBlank is what the empty value of the  cascaded-to ddl will be if the original is returned to index 0 (that is much easier to demonstrate than explain).

 

Now just call it in the code-behind (don't forget to put the class in your App_Code folder):

 

[code:c#]

protected void ddlMake_SelectedIndexChanged(object sender, EventArgs e)
{
    DropDownList dll = (DropDownList)sender;
    string connString = ConfigurationManager.ConnectionStrings[ "mytestDB" ].ConnectionString;
    cascade2.fromThisDropDownSql(this.Page, (DropDownList)sender, connString, "item", "category", "dropDowns" );
}

[/code]

 

This takes more inputs than the xml one, but is quite simple if you compare it to the database above.  The first two variables will always be the same, and the next 4 are simply the connection string, the table column you wish to populate the cascaded ddl with, the table column that you are filtering with, and the table name.  Doesn't really matter how your tables are layed out or where everything is located, you just change your inputs accordingly.  Really that is all that it takes.