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.

MVC AJAX JSON Null DTO in Controller

A few months ago, I was enabling paging on a .NET Core 3.1 MVC application and had my search model passed into a controller method via AJAX. Well, it didn’t work. I received a NULL DTO object no matter what I tried. Trying to figure out what to do about an MVC Ajax JSON Null DTO in a controller method had me chasing my tail.

Fast forward to a few days ago, and guess what, another web app, same use case, same issue. Problem was, I couldn’t remember how I resolved it. Well, after another two hours of tinkering around with different objects, removing default settings in my DTO, and more endless googling, I finally found the issue… again.

Main issue I had is that System.Text.Json is not really usable. I found out that unless all your properties are strings, you have to setup custom comparers for each type. That about sums it up. Unless you’re passing in a very simple object that only has string properties, you can pretty much forget about using this library out of the box.

Take a look at the things that are not supported in System.Text.Json: https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to

For those of you in a hurry, here is a summary of what I did. Details of the implementation will follow:

  1. Make sure you have “FromBody” in your controller method. I already had this, but it’s what most blog posts focus on.
[HttpPost]
public async Task<IActionResult> CatDisplay([FromBody] SearchModel<LuCategory> searchModelIn)

2. Change the default JSON serializer in your Startup.cs

using Microsoft.AspNetCore.Mvc;
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews().AddNewtonsoftJson();

If you get the little squigglies under this method name, then add the Nuget package: Microsoft.AspNetCore.Mvc.NewtonsoftJson

Just so you can see how I’m calling this, here is the Javascript/Jquery/JSON that I’m sending in:

    function GetPaging(ToPage) {

        var _url = "/@Model.controllerName/@Model.actionName";

        // Set the global values for sorting post back
        var searchModel = {};
        searchModel.SortColumn = '@Model.SortColumn';
        searchModel.PrevSortColumn = ''; // Leave blank so sorting doesn't kick;
        searchModel.CurrentPage = ToPage;
        searchModel.PageSize = @Model.PageSize;
        searchModel.SearchTerm = '@Model.SearchTerm';
        searchModel.SearchFilter = '@Model.SearchFilter';
        searchModel.SortDescending = '@Model.SortDescending';
        searchModel.ActiveOnly = '@Model.ActiveOnly';
        searchModel.RefId = @Model.RefId;
        searchModel.RefUniqueId = '@Model.RefUniqueId';

        $.ajax({
            type: "POST",
            url: _url,
            async: true,
            contentType: "application/json",
            data: JSON.stringify(searchModel),
            dataType: "html",
            success: function (result, status, xhr) {
                $("#gridPartial").html(result)
            },
            error: function (xhr, status, error) {
                alert("Result: " + status + " " + error + " " + xhr.status + " " + xhr.statusText)
            }
        });

    }

3. The last problem I ran into was boolean values. In the above example, the boolean value was coming from the Model, so there is no issue. However, if you are trying to get a boolean value from javascript or jquery, big problems. In order to be sure that what is being passed as a value can be deserialized into an object, you should have code like:

searchModel.SortDescending = $('descending').val().toLowerCase() != 'false') ? true : false;

This issue has hit me several times, so I’ve used this code to resolve it.

Thanks for reading and happy coding!

MVC Paging Index View

This post was revised and updated on 10/24/2020.

Below is a sample of an Index View utilizing the Generic paging partial views and another partial view that displays my data in a grid.

If you’re falling onto this page from a search link, then you may want to start at the beginning: Ultimate Guide to MVC Paging.

It contains a search box and an “Active Only” check box, if you have a requirement to knock out inactive records. Make special note of the required “jquery.unobtrusive-ajax”.

Since this view is specifically written for the LUCategory, there is no need to reference the model as an interface. You can specify the exact model implementation you’re expecting on the page.

The next important note is that you must store any values from the search model that you’re not displaying on the page in Hidden fields, or else they won’t be passed back on a click, say if the user clicks “Clear”.

@using YTG.MVC.Lookups.Models
@using YTG.Models
@model SearchModel<LuCategory>

@{
    ViewData["Title"] = "Index";
}

<h1>Lookups Category Index</h1>

<section id="main-content" class="animated fadeInUp">
    <div class="row">
        <div class="w-100">
            @using (Html.BeginForm("Index", "Lookups", FormMethod.Get, new { id = "frmCategorySearch" }))
            {
                @Html.HiddenFor(m => m.SortColumn, new { id = "SortColumn" })
                @Html.HiddenFor(m => m.SortDescending, new { id = "SortDescending" })
                @Html.HiddenFor(m => m.PageSize, new { id = "PageSize" })
                @Html.HiddenFor(m => m.CurrentPage, new { id = "CurrentPage" })

                <table class="table-responsive" cellspacing="0" width="100%" style="border-color: red !important;">
                    <tr>
                        <td>
                            @Html.TextBoxFor(m => m.SearchTerm, new
                               {
                                   id = "searchTerm",
                                   placeholder = "Category Name",
                                   CurrentPage = Model.CurrentPage,
                                   PageSize = Model.PageSize,
                                   SortExpression = Model.SortColumn,
                                   SortOrder = Model.SortDescending
                               })
                            <input type="submit" value="Search" class="btn btn-sm btn-primary" name="submitButton" />
                            <input type="submit" value="Clear" class="btn btn-sm btn-primary" name="submitButton" />
                            <a asp-action="Edit" asp-controller="Lookups" asp-route-id="-1" class="btn btn-sm btn-primary">Add New Category</a>
                            <div>
                                @Html.CheckBoxFor(m => m.ActiveOnly, new { id = "ActiveOnly", name = "ActiveOnly" })
                                @Html.LabelFor(m => m.ActiveOnly)
                            </div>
                        </td>
                    </tr>
                </table>

                <div id="gridPartial" style=" border-color: blue !important;">
                    @{ await Html.RenderPartialAsync("_CatsDisplay", Model); }
                </div>

            }
        </div>
    </div>
</section>

This view implements searching, filtering by active, paging, and length of page selection, yet it’s only 51 lines of code!

Before we can finish up, I want to explain the Paging and PageCount partial views.

MVC Paging and PageCount Partial Views

Page Count Partial View

In order to keep the paging generic in MVC, I’ve set them up as two partial views. A MVC Paging and PageCount Partial Views. In the partial view, there is a parameter called refid that I didn’t use in the sample code, but it’s there to service a parent/child relationship. Let’s say you display a list of projects. The user clicks on a project and are displayed a list of tasks for that project. When the user clicks to go to the next page of the tasks, the query will need to pass through the ProjectID in order to “Filter” the values to still only be for the project that was selected. That’s where you’ll use the refid.

Also notice the use of the ISearchModel as the model. That’s because the view won’t allow a model defined generically.

// This won't work
@model SearchModel<TEntity>

By using the Interface, I can get around this restriction and the view will allow a SearchModel to be passed in with whichever collection is needed.

@using YTG.MVC.Lookups.Models

@model ISearchModel

<div>
    <center>
        Change Page Size:<br />
        <ul class="pagination justify-content-center">
            <li class="@(Model.PageSize == 25 ? "active strong" : "") page-item"><a class="page-link" href="#" onclick="GetPageSize(25)">25</a></li>
            <li class="@(Model.PageSize == 50 ? "active strong" : "") page-item"><a class="page-link" href="#" onclick="GetPageSize(50)">50</a></li>
            <li class="@(Model.PageSize == 100 ? "active strong" : "") page-item"><a class="page-link" href="#" onclick="GetPageSize(100)">100</a></li>
        </ul><br />
        Records on this Page: @Model.RecordCount<br />
        Total records found: @Model.TotalItems<br />
    </center>

</div>


<script type="text/javascript">

    function GetPageSize(PageSize) {

        var _url = "/@Model.controllerName/@Model.actionName";

        // Set the global values for sorting post back
        var searchModel = {};
        searchModel.SortColumn = '@Model.SortColumn';
        searchModel.PrevSortColumn = '' // Leave blank so sorting doesn't kick;
        searchModel.CurrentPage = @Model.CurrentPage;
        searchModel.PageSize = PageSize;
        searchModel.SearchTerm = '@Model.SearchTerm';
        searchModel.SearchFilter = '@Model.SearchFilter';
        searchModel.SortDescending = '@Model.SortDescending';
        searchModel.ActiveOnly = '@Model.ActiveOnly';
        searchModel.RefId = @Model.RefId;
        searchModel.RefUniqueId = '@Model.RefUniqueId';

        $.ajax({
            type: "POST",
            url: _url,
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(searchModel),
            dataType: "html",
            success: function (result, status, xhr) {
                $("#gridPartial").html(result)
            },
            error: function (xhr, status, error) {
                alert("Result: " + status + " " + error + " " + xhr.status + " " + xhr.statusText)
            }
        });

    }

</script>

Paging Partial View

Just as above, this partial view is written to be generic, so it can be use all throughout your site without any special code for the particular page implementation.

@using YTG.MVC.Lookups.Models

@model ISearchModel
<!-- Pager -->
<nav>
    <ul class="pagination justify-content-center">
        @if (Model.CurrentPage > 1)
        {
            <li class="page-item"><a class="page-link" href="#" onclick="GetPaging(1)">First</a></li>
            <li class="page-item"><a class="page-link" href="#" onclick="GetPaging(@Model.CurrentPage - 1)">Previous</a></li>
        }

        @for (var displayPage = Model.StartPage; displayPage <= Model.EndPage; displayPage++)
        {
            <li class="@(displayPage == Model.CurrentPage ? "active" : "") page-item"><a class="page-link" href="#" onclick="GetPaging(@displayPage)">@displayPage</a></li>
        }

        @if (Model.CurrentPage < Model.TotalPages)
        {
            <li class="page-item"><a class="page-link" href="#" onclick="GetPaging(@Model.CurrentPage + 1)">Next</a></li>
            <li class="page-item"><a class="page-link" href="#" onclick="GetPaging(@Model.TotalPages)">Last</a></li>
        }
    </ul>
</nav>
<!-- /Pager -->

<script type="text/javascript">

    function GetPaging(ToPage) {

        var _url = "/@Model.controllerName/@Model.actionName";

        // Set the global values for sorting post back
        var searchModel = {};
        searchModel.SortColumn = '@Model.SortColumn';
        searchModel.PrevSortColumn = ''; // Leave blank so sorting doesn't kick;
        searchModel.CurrentPage = ToPage;
        searchModel.PageSize = @Model.PageSize;
        searchModel.SearchTerm = '@Model.SearchTerm';
        searchModel.SearchFilter = '@Model.SearchFilter';
        searchModel.SortDescending = '@Model.SortDescending';
        searchModel.ActiveOnly = '@Model.ActiveOnly';
        searchModel.RefId = @Model.RefId;
        searchModel.RefUniqueId = '@Model.RefUniqueId';

        $.ajax({
            type: "POST",
            url: _url,
            async: true,
            contentType: "application/json",
            data: JSON.stringify(searchModel),
            dataType: "html",
            success: function (result, status, xhr) {
                $("#gridPartial").html(result)
            },
            error: function (xhr, status, error) {
                alert("Result: " + status + " " + error + " " + xhr.status + " " + xhr.statusText)
            }
        });

    }

</script>

I recommend that these are placed in your Shared folder under the Views folder.

Heart of MVC Paging

This post was revised and updated on 10/24/2020.

If you fell on this post first, you’ll notice that the implementation of paging is a little more involved than the average example on the web. That’s because most of the other examples are either, limited, or outright “The wrong way to do it”. To start from the begining, click here: The Ultimate Guide to MVC Paging!.

Once you have a PagedResult object, or any object with the collection and total count of records that match the filters, back from your web service, you’ll need to convert it to a SearchModel that can be used throughout your MVC views and partial views to enable the paging.

In the implementation demo, we’re going to call a function to return the SearchModel, both from the original call to the Index controller, and then later from a Ajax when the user clicks on a page number or sort column. So I create a function that both controllers can share:

/// <summary>
/// Category search with paged results
/// </summary>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <param name="searchTerm"></param>
/// <param name="sortFilter"></param>
/// <param name="sortColumn"></param>
/// <param name="sortDescending"></param>
/// <param name="ActiveOnly"></param>
/// <returns></returns>
public async Task<SearchModel<LuCategory>> GetCategoriesPagedAsync(int page = 1, 
            int pageSize = 25, 
            string searchTerm = "",
            string searchFilter = "", 
            string sortColumn = "ShortName",
            bool sortDescending = false, 
            bool ActiveOnly = true)
{
    SearchModel<LuCategory> _smlus = new SearchModel<LuCategory>();
    try
    {
        PagedResult<LuCategory> _cats = await LookupsSvc.GetCategoriesPagedAsync(page, pageSize, searchTerm, searchFilter, sortColumn, sortDescending, ActiveOnly);
        _smlus.TotalItems = _cats.TotalCount;
        _smlus.CurrentPage = page;
        _smlus.PageSize = pageSize;
        _smlus.RecordCount = _cats.Items.Count();
        _smlus.SortColumn = sortColumn;
        _smlus.SearchTerm = searchTerm;
        _smlus.SortDescending = sortDescending;
        _smlus.ActiveOnly = ActiveOnly;
        _smlus.SortedResults = _cats.Items.ToList();

        _smlus.actionName = "CatDisplay";
        _smlus.controllerName = "Lookups";

        return _smlus;

    }
    catch (Exception)
    {
        throw;
    }
}

While the code might be self explanatory, I’ll explain what’s happening here. Notice the two properties, actionName and contollerName. These are used by the partial views to know how to get to the controller method that’s going to serve up the partial view, so you can set it to go anywhere you like. The reason these properties are part of the object, is because this allows the paging partial views to be generic and reusable.

We would normally return the PagedResults object from our web service call. Then I transfer the results into the SearchModel while appending all the search, paging and filtering criteria passed into the method. The reason for this is that we want to retain this data in order to use the SearchModel in the Index view, as well as the partial views used for paging.

So lets go over the controllers first. The Index GET has all the parameters needed for the SearchModel with defaults, so that it can be called the first time with no arguments.

/// <summary>
/// Index of categories paged
/// </summary>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <param name="searchTerm"></param>
/// <param name="sortFilter"></param>
/// <param name="sortColumn"></param>
/// <param name="sortDescending"></param>
/// <param name="ActiveOnly"></param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> Index(int page = 1, int pageSize = 25, string searchTerm = "", string sortFilter = "", string sortColumn = "ShortName", bool sortDescending = false, bool ActiveOnly = true)
{
    try
    {
        var _smlus = await GetCategoriesPagedAsync(page, pageSize, searchTerm, sortFilter, sortColumn, sortDescending, ActiveOnly);

         return View(_smlus);

    }
    catch (Exception)
    {
        throw;
    }
}

Notice how having all the code in a sub method keeps our controller method clean and uncluttered.

Next, we need to have the controller method that returns the PartialView that will service the Ajax calls:

/// <summary>
/// Edit Category, with display of items
/// </summary>
/// <param name="id"></param>
/// <param name="pageSize"></param>
/// <returns></returns>
[HttpGet]
public async Task<ActionResult> Edit(long id, int pageSize = 25)
{
    LuCategoryModel model = new LuCategoryModel();
    try
    {
         if (id >= 0)
         {
             // Get the base LuCategory
             LuCategory _return = await LookupsSvc.GetLuCategoryByIdAsync(id);

            model = Helpers.MappingUtils.LuCatToLuCatModel(_return, false);

             // Get a paged result of some items because we want a paged list of child items as well
            model.Items = await GetItemsPagedAsync(id, 1, pageSize);
        }

        return View(model);

    }
    catch (Exception)
    {
        throw;
    }
}

This is all that’s needed to service the Index view, grid partial view, page count partial view and the pager partial view.

Ultimate Guide to MVC Paging!

MVC Paging Partial Views

After searching the web for MVC paging techniques, I realized that 90% or more of the examples I found on the web would not solve my problem. Some of them are so bad that they could get me fired if I followed their example. (copy -> paste). They so oversimplify the need, that they render their solutions useless. The intentions are good, but the implementation is lacking.

Have you:

  • Wanted to add paging to your MVC site, but think it’s too complicated?
  • Implemented it on one page and hope you never have to do it again?
  • After implementing paging thought, “There’s got to be a better way?”

The most popular problem with most examples that I found is that they show pulling back the entire collection from the database and paging the results in the UI (in the controller). This is fine if you’re absolutely positive, sure beyond belief, willing to bet your job, that the results will never exceed a hundred records or so…

If you want to become an expert on how to implement fast, extremely flexible paging in all your MVC applications, then figure that this is going to take an hour or two. If you’re looking to “turn on paging”, then feel free to click on the “Back” button, I won’t be offended.

Not implementing paging, or implementing it incorrectly can cause serious problems with the performance of your web site. Below are some symptoms that I’ve seen that leads me to believe there could be a problem with the paging implementation on a web site:

  1. Complaints that the web site slows down, but nobody can see high utilization on the web server or database server (and no expensive queries)
  2. Complaints that the web site sometimes stops responding, occasionally for minutes at a time
  3. Complaints that the web site sometimes suddenly throws users back to the login screen while they’re working
  4. Complaints that the slow downs are intermittent, and can’t be reproduced in the test environment

I know you may be thinking, what does this have to do with paging? Well, it’s tangentially related, as it has to do with writing bad code without thinking about what the consequences are. The reason these issues don’t tax the servers is that doing a SELECT * FROM Clients doesn’t really tax the CPU that much. Now, if you were watching the NIC card traffic, that would be a different story.

The biggest issue that I see with code examples on the web:

  1. They show bringing back every row in the table and then page in the controller (No, no, please don’t do this!)
  2. They show using a LINQ query in the controller to Skip and Take
  3. They assume that I’m pulling back my data directly from the database in my Web Application

// Example of code that is ridiculous outside of a POC app
return View(MyRepository.GetClients().Skip((page - 1 ?? 0) * (rows ?? 3)).Take(rows ?? 3));

This LINQ query will work well if:

  1. The MyRepository is an EntityFramework repository
  2. AND you’re implementing your Repository in your web site
  3. AND it doesn’t dispose of the Entity context inside the method
  4. AND the GetClients() method doesn’t call ToList() inside of it
  5. AND GetClients() is not executing a stored procedure

If any of the above is not true, then this method is no better than the first method that pulls the entire table back.

Another issue with this example is that it assumes that the Repository is part of your web project AND you’re using the objects that are in your Entity Model. So no DTO’s, no business objects, and absolutely no Web Service in the middle.

The reason I’m showing this, is that I’ve never seen these issues discussed in the posts. A developer could easily implement this on a GetClients() that again, returns the entire table contents and then pages on the UI side.

Just in case you forgot, the web is stateless, so if IIS or most other Web Servers feel that they’ve had about enough with the application memory usage, they just reset themselves. Shouldn’t cause a problem right? Except if you’re depending on session to keep people logged in, then they get kicked back to the login screen. (BTW, this only happens if you have your own custom code looking for a session object to verify the user is logged in. If you use the proper MVC standard Authorization, they shouldn’t be sent to the login screen.) But not to minimize the impact, because all users get kicked out, not just the one user.

Almost every web, and even windows project I’ve ever worked on followed the Repository pattern in that there were separations of concerns, i.e.:

Database → DataLayer → ServiceLayer → WebService → ProxyClass → ServiceLayer → UI

I don’t want to digress into a discussion of patterns. But the ServiceLayer behind the WebService will convert the Entity Framework object or DataSet to a DTO collection. Then the ServiceLayer behind the UI will likely convert it to a ViewModel. We don’t want to do all that work on 5000 records so that we can ultimately display 25 to the user…


This search on Google returned 466,000 records. Do you think that they were all returned and paged in the controller? I think not…

The reason I’m discussing this is to get your imagination started. If you didn’t want to transfer tons of data over the wire from your WebService, then where is the best place to implement Paging? You guessed it, in the DataLayer (also referred to as Data Access Layer (DAL)). As close to the data as you can possibly get.

Alright, let’s get to the nuts and bolts. I’m going to show you a full implementation, so you can trim it down if your particular need is less complicated.

Anatomy of a Paging Request

The following are the properties that I’ve been able to accomplish almost any paging task I’ve ever needed on the client side:

Property Type Notes
SortedResults List<T> The list of results, without it, we don’t need paging!
TotalItems int The total number of items that would be returned, if we returned them all at once
CurrentPage int The current page the user is on
PageSize int The size of the page, usually an option for the user to select
TotalPages int The total number of pages (TotalItems/PageSize rounded up)
StartPage int The start page, for when you have more pages than will fit on one widget
EndPage int The end page, for when you have more pages than will fit on one widget
SearchFilter string A filter to use in addition to any search criteria, usually selected from a dropdown, i.e. year, division, salesman, etc.
SearchTerm string A search term, usually typed in a search box by the user
SortExpression string The column name to sort the results by
RecordCount int The quantity of records in this particular result set
SortOrder string The order that you want the result set, i.e.: “ASC”, “DESC”
ActiveOnly bool Another filter flag, if you have an IsActive flag in your table, or any other criteria to knock out inactive rows
RefId int Typically used when there is a Parent/Child relationship. i.e. Your SortedList is a list of Project tasks, then you’ll need to know the parent project PK Id