C# MVC Detect which button was clicked

Detecting which button was clicked to cause a post back is very easy, once you know how to do it!

I often have screens that have multiple submit buttons on a single form that require a post back. I needed to pass the button value to the controller in MVC. For instance, I may have a data entry screen where a user can click a button that says “Save” or “Save as New”. I need to be able to detect which button they clicked when the page posts back.

Fortunately, there is an easy way to tell, or determine, which button the user selected when the page posts back on a submit action.

First, you need to have a button setup like so:

<form id="rvWidgetForm" method="post" enctype="application/x-www-form-urlencoded" asp-controller="Search" asp-action="SearchBegin">

    <button id="btnTopTen" type="submit" name="submitButton" class="btn text-center" value="TopTen">
        TOP 10 SEARCH
    </button>

    <button id="btnTraditional" type="submit" name="submitButton" class="btn  text-center" value="Trad">
        TRADITIONAL SEARCH
    </button>

</form>

The buttons must be of type=submit. Having type=button won’t post back. You have a choice here, to use the value= or not use it. If you don’t declare a value attribute, then what you’ll receive in the controller is the text of the button. While this is okay, you or another developer may change the text in the future and not realize they are going to break your code. I recommend using the value= like I’ve used above. It’s less likely to change in the future.

I recommend using the value attribute as it’s less likely than the button text to change in the future.

The next most important part is the name attribute. Every button that will post back should have the same name. This will be the name of your parameter in your controller as well and they must match. The value you declare on the button will be the argument passed to your controller method.

public async Task<ActionResult> SearchBegin([FromForm] SearchPageModel _searchModelIn, [FromForm] string submitButton)
    {
        // If you "name" your buttons as "submitButton"
        // then you can grab the value of the button
        // here to make decisions on which button was clicked
        switch (submitButton)
        {
            case "TopTen":
                return TopTen(_searchModelIn);
            case "Trad":
                return Traditional(_searchModelIn);
            default:
                break;
        }

        return View("~/");  // Go home

 }

The parameter name in your method must match the name attribute on your buttons exactly. The type passed in will be a string, although I imagine if your value attribute on all your buttons was numeric, that you could declare it as an int.

Once you’re in your method, you can use logic in a switch statement to detect the value passed in and make a decision how to proceed.

Get one list of Task return objects

Using the Task Async/Await pattern for grabbing data can be a real performance enhancement. When you thread off the calls, it’s pretty normal to want Task return objects to be in one single usable collection. The example I can give is a method that needs to gather up several different categories of lookup items. These calls all return a collection of the same type.

When you await the tasks, you generally have a few options:

Await each item individually

            List<Task<List<LuItem>>> _allLus = new();
            List<LuItem> _return = new();

            _allLus.Add(LookupsSvc.GetLuItemsByCatShortNameAsync("URLTYPES"));
            _allLus.Add(LookupsSvc.GetLuItemsByCatShortNameAsync("RVFUELTYPES"));
            _allLus.Add(LookupsSvc.GetLuItemsByCatShortNameAsync("GENERATORFUELTYPES"));

            Task<List<LuItem>>.WaitAll(_allLus.ToArray());

            List<LuItem> _task1 = await _allLus[0];
            List<LuItem> _task2 = await _allLus[1];
            List<LuItem> _task3 = await _allLus[2];

            _return.AddRange(_task1);
            _return.AddRange(_task2);
            _return.AddRange(_task3);

            return _return;

Not sure how you feel, but this is horrible. I’m sure I’ve done something like this in the past, but I’d prefer not to think about it.

Use WhenAll to retrieve them in an Array

The Task.WhenAll, when declared with a type, will return an array of the return type. So in this case, it would return an Array of List<LuItem>. We can then do a simple LINQ query to push them all into one collection.

            List<Task<List<LuItem>>> _allLus = new();
            List<LuItem> _return = new();

            _allLus.Add(LookupsSvc.GetLuItemsByCatShortNameAsync("URLTYPES"));
            _allLus.Add(LookupsSvc.GetLuItemsByCatShortNameAsync("RVFUELTYPES"));
            _allLus.Add(LookupsSvc.GetLuItemsByCatShortNameAsync("GENERATORFUELTYPES"));

            List<LuItem>[] _await = await Task<List<LuItem>>.WhenAll(_allLus);
            _await.ToList().ForEach(lus => _return.AddRange(lus));

            return _return;

In this example, we await the Task with WhenAll, which has a return type, as opposed to WaitAll which does not. As stated earlier, this example will return a collection as Task<List<LuItem>[]>. So we’re most of the way there. We use the ToList().ForEach LINQ query to transform the Array of Lists into a single list called _return.\

Sum of a list of values in a collection

Summing a collection that is within a collection without using nested foreach loops can be easily done with LINQ

It’s hard to think of a good name for this post. But if you have a collection and each item has a collection of values that you need to get a sum on, you can do that easily with LINQ.

Say you have a List<CartItem> in a shopping cart. Each item has a list of DecimalCost, possibly the user has ordered different sizes or colors and they each have an associated cost.

decimal _sum;
_return.CartItems.ForEach(c => _sum = c.DecimalCost.Sum());

Above we’re basically setting up an inline ForEach loop and then summing on the DecimalCost field which is actually a List<decimal>.

Jack’s Top 10 String Extension Methods

String Extension Methods in C# .NET can make life so much easier.

String Extension Methods Add Functionality
Extension Method

String Extension Methods in C# .NET can make life so much easier. Everyday functions that used to require extended syntax with a return value are a thing of the past. Now with Microsoft Visual Studio, we can just add a using to our Extensions collection and have full use of them from our Share Library.What is a String Extension Method?

An extension method is simply an additional method. It is a way of attaching additional functionality to a type which is available to you throughout your code without need to instantiate another class.

There’s plenty of best practices for extension methods. A great article is here on Microsoft’s site.

ToBoolean() Extension Method

Probably my most used and handiest is a simple one. There are so many times when we receive text, whether in a JSON payload or in a view that we need to see if it is a legitimate boolean value.

Here is a simple implementation of ToBoolean()

    /// <summary>
    /// Convert a string to a boolean
    /// Yasgar Technology Group, Inc.
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static bool ToBoolean(this string value)
    {
        if (value == null) { return false; }
        if (value.Trim().ToLower() == "true" || value == "1" || value == "yes")
        { return true; }
        else
        { return false; }
    }

This is a simple implementation that does a quick compare against a set of strings.

IsNumericInteger() Extension Method

Often, during a view post back, I need to determine if a particular value is numeric that was accepted in a text box. While I usually try to validate this type of input using javascript, there are many ways that people can bypass that validation. I use this specific one to validate that this value is indeed an integer and not a decimal.

   /// <summary>
    /// Return bool whether the value in the string is a numeric integer
    /// Yasgar Technology Group, Inc. - www.ytgi.com
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static bool IsNumericInteger(this string value)
    {
        return long.TryParse(value, out long _tempvalue);
    }

Here is a simple sample implementation that does a quick TryParse() to see if it is a pass or fail.

IsNumericDecimal() Extension Method

Often there are numeric fields that you’re receiving via a post back or JSON or XML payload. This is a quick way to determine if it’s a decimal fit or not. Remember, that integers will pass this test as well. So use the IsNumericInteger() extension method if you want to determine if the numeric value has a decimal in it.

    /// <summary>
    /// Return bool whether the value in the string is a numeric decimal
    /// Yasgar Technology Group, Inc. - www.ytgi.com
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static bool IsNumericDecimal(this string value)
    {
        return decimal.TryParse(value, out decimal _tempvalue);
    }

ToDateFromCCYYMMDD() Extension Method

There are often cases where dates are passed around in CCYYMMDD format, such as 20220329. This is a preferred method for me when I need to transfer a date as a query string parameter argument and don’t want the mess of a full DateTime. This extension method converts that string to a DateTime object.

    /// <summary>
    /// Convert a string in CCYYMMDD format to a valid date
    /// Yasgar Technology Group, Inc. - www.ytgi.com
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static DateTime ToDateFromCCYYMMDD(this string value)
    {
        if (!string.IsNullOrWhiteSpace(value))
        {
            if (value == "99999999")
            {
                return DateTime.MaxValue;
            }
            else
            {
                string _value = value.Trim();
                if (_value.IsNumericInteger() && _value.Trim().Length == 8)
                {
                        int.TryParse(_value.Substring(0, 4), out int year);
                        int.TryParse(_value.Substring(4, 2), out int month);
                        int.TryParse(_value.Substring(6, 2), out int day);

                        DateTime datItem = new DateTime(year, month, day);
                        return datItem;
                }
            }
        }

        return DateTime.MinValue;

    }

Notice that I check for the “99999999” string. This is a very popular marker for “no expiration” date, especially in mainframe data.

ToDateFromString() Extension Method

This is a variation on the ToDateFromCCYYMMDD() extension method. You might ask why I would have an extension method that probably does the same thing as DateTime.TryParse()? Well, simple, I’ve worked with lots of data where they have dates like “99999999” and “99/99/9999” which I want to handle properly.

    /// <summary>
    /// Convert a string in MM/DD/CCYY format to a valid date
    /// Yasgar Technology Group, Inc. - www.ytgi.com
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static DateTime ToDateFromString(this string value)
    {
        if (!string.IsNullOrWhiteSpace(value))
        {
            if ((value == "99999999") || (value == "99/99/9999"))
            {
                return DateTime.MaxValue;
            }
            else
            {
                if (!string.IsNullOrWhiteSpace(value))
                {
                    DateTime.TryParse(value, out DateTime _value);
                    return _value;
                }
                else
                {
                    return DateTime.MinValue;
                }
            }
        }

        return DateTime.MinValue;

    }

Notice it does use the standard DateTime.TryParse(), but only after it checks for funky dates. You may also want to put in checks for dates that are popular in your environment, such as the old SQL Server minimum date of “1/1/1753”

Trim(int MaxLength) (Accepting a maximum length)

This extension method accepts an integer specifying the maximum length of the returned string. I use this method all the time, especially when stuffing data into old data tables where data needs to be truncated. To be honest, I find it hard to believe that after all this time, it’s still not an overload in the framework.

    /// <summary>
    /// Trim a string down to a particular size
    /// Yasgar Technology Group, Inc. - www.ytgi.com
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static string Trim(this string value, int p_MaxLength)
    {
        try
        {
            if (value != null)
            {
                return value.Substring(0, Math.Min(p_MaxLength, value.Length));
            }
            else
            {
                return string.Empty;
            }
        }
        catch (Exception)
        {
            throw;
        }
    }

Remember, you should never just randomly trim data without assessing whether it is going to cause data corruption. Important data should not be truncated with a method like this unless you’re logging the activity somewhere.

RemoveSpecialChars(bool p_DashOkay = false, bool p_HashOkay = false)

This is one of my favorite extension methods, not only because I use it so often when validating data, but because it’s proven to be so versatile that I haven’t had to modify very much over the years. This method accepts two parameters to allow you to keep dashes and hash signs in the return if you want. They both default to false if you don’t set them.

        /// <summary>
        /// Remove special characters from a string with option to 
        /// retain Dashes and Hash signs
        /// Yasgar Technology Group, Inc. - www.ytgi.com
        /// </summary>
        /// <param name="value"></param>
        /// <param name="dashOkay"></param>
        /// <param name="hashOkay"></param>
        /// <returns></returns>
        public static string RemoveSpecialChars(this string value, 
                                                bool dashOkay = false, 
                                                bool hashOkay = false)
        {
            try
            {
                StringBuilder sb = new StringBuilder();

                if (value != null)
                {
                    if (dashOkay && hashOkay)
                    {
                        foreach (char c in value)
                        {
                            if ((c >= '0' && c <= '9') || 
                                (c >= 'A' && c <= 'Z') || 
                                (c >= 'a' && c <= 'z') || 
                                c == '-' || 
                                c == '#' || 
                                c == ' ')
                            {
                                sb.Append(c);
                            }
                        }
                    }
                    else if (dashOkay && hashOkay == false)
                    {
                        foreach (char c in value)
                        {
                            if ((c >= '0' && c <= '9') || 
                                (c >= 'A' && c <= 'Z') || 
                                (c >= 'a' && c <= 'z') || 
                                c == '-' || c == ' ')
                            {
                                sb.Append(c);
                            }
                        }
                    }
                    else if (dashOkay == false && hashOkay)
                    {
                        foreach (char c in value)
                        {
                            if ((c >= '0' && c <= '9') || 
                                (c >= 'A' && c <= 'Z') || 
                                (c >= 'a' && c <= 'z') || 
                                c == '#' || c == ' ')
                            {
                                sb.Append(c);
                            }
                        }
                    }
                    else if (!dashOkay && !hashOkay)
                    {
                        foreach (char c in value)
                        {
                            if ((c >= '0' && c <= '9') || 
                                (c >= 'A' && c <= 'Z') || 
                                (c >= 'a' && c <= 'z') || 
                                c == ' ')
                            {
                                sb.Append(c);
                            }
                        }
                    }

                }

                return sb.ToString();

            }
            catch (Exception)
            {
                throw;
            }
        }

RemoveSpaces(bool StripInternal = false)

The standard Trim() extension method in the .NET framework will remove spaces from the beginning and end, but does it remove spaces inside the string? No, of course not. But there are times when that is needed and I have just the method read for it. It also trims the front and back as well, so no need to do an extra Trim() on it.

    /// <summary>
    /// Strip spaces from a string
    /// Yasgar Technology Group, Inc. - www.ytgi.com
    /// </summary>
    /// <param name="value"></param>
    /// <param name="StripInternal">strip spaces from within the string</param>
    /// <returns></returns>
    public static string RemoveSpaces(this string value, bool StripInternal = false)
    {
        if (!string.IsNullOrWhiteSpace(value))
            if (StripInternal)
            {
                return new string(value.ToCharArray()
                                    .Where(c => !Char.IsWhiteSpace(c))
                                    .ToArray());
            }
            else
            {
                return value.Trim();
            }
        else
        {
            return string.Empty;
        }
    }

ToDecimal() Extension Method

If you need to retrieve a decimal value from a string, you can use this extension method. It will actually return a nullable decimal (decimal?). It will be null if the value could not be coerced into a decimal. This one could be used in place of the IsNumericDecimal() method if you need to retrieve the value and not simply pass it on if it validates. There is the extra step to check whether the return value is null though.

    /// <summary>
    /// Convert a string to a Decimal, return null if fails
    /// Yasgar Technology Group, Inc. - www.ytgi.com
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static decimal? ToDecimal(this string value)
    {
        if (decimal.TryParse(value, out decimal decItem))
        { return decItem; }
        else
        { return null; }

    }

These are two powerful extension methods that I group together. They come in so handy for encrypting and decrypting values on the fly. While it’s probably not the greatest plan to use this for an encryption strategy, I often use them while data is in flight. For instance, I parse a text file and save it to a staging database table for later processing. If there is Protected Health Information (PHI), or even Personally Identifiable Information (PII), then I’ll use this method to protect it from prying eyes before it winds up in its final resting place.

Both of these extension methods make use of the CRijndael namespaces in the framework.

ToProperCase()

How often do we need to convert a standard text string to proper or title case. All the time! So this is a solution for your needs!

using System.Globalization;
using System.Threading;

        /// <summary>
        /// Convert a string to Proper case 
        /// based on the current thread culture
        /// Yasgar Technology Group, Inc. - www.ytgi.com
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public static string ToProperCase(this string text)
        {
            if (text != null)
            {
                CultureInfo _cultureInfo = Thread.CurrentThread.CurrentCulture;
                TextInfo textInfo = _cultureInfo.TextInfo;
                // Send in the text as lower case to allow the method to
                // make all the decisions
                return textInfo.ToTitleCase(text.ToLower());
            }
            else
            {
                return text;
            }
        }

If you love Extension Methods, take a look at some other posts I have about others:

OrderBy Extension for Linq Queries

Knock out 1/1/0001 Dates in MVC

Extension Method to Convert Types

Stay tuned for more extension methods that I’ve used for years coming soon!

Trim a String to a Particular Length

One piece of code that is very popular, besides checking for null, is being able to trim a string to a particular length, making sure a string is not too long to save in the database or display on your page or MVC View. In the olden days, we had lots of code like this:

if (myString.Length > 10)
{
	dbTable.Property = myString.Substring(0,10);
}
else
{
	dbTable.Property = myString;
}

In one form or another. Now, with extension methods, we can add this into our bag of tricks to have available everywhere:

public static class StringExtensions
{
	public static string Trim(this string value, int MaxLength)
	{
		return value.Substring(0, Math.Min(MaxLength, value.Length));
	}
}

Notice that I’m using the Math.Min function to tell the Substring whether to return the string as it is, or use the MaxLength argument. Now the code above will look like this:

dbTable.Property = myString.Trim(10);

And it will save the value of myString as it’s current value, unless it’s longer than 10, which will cause it to be trimmed.

One of the big advantages of using String Extension Methods like this is that they can be used in Linq queries very easily. If you want to see more about extension methods, check out my other posts below: