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.
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.
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.
For those of you in a hurry, here is a summary of what I did. Details of the implementation will follow:
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:
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.
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.
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:
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.
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.
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:
Complaints that the web site slows down, but nobody can see high utilization on the web server or database server (and no expensive queries)
Complaints that the web site sometimes stops responding, occasionally for minutes at a time
Complaints that the web site sometimes suddenly throws users back to the login screen while they’re working
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:
They show bringing back every row in the table and then page in the controller (No, no, please don’t do this!)
They show using a LINQ query in the controller to Skip and Take
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:
The MyRepository is an EntityFramework repository
AND you’re implementing your Repository in your web site
AND it doesn’t dispose of the Entity context inside the method
AND the GetClients() method doesn’t call ToList() inside of it
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.:
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…
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
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept All”, you consent to the use of ALL the cookies. However, you may visit "Cookie Settings" to provide a controlled consent.
This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.
Cookie
Duration
Description
cookielawinfo-checkbox-analytics
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional
11 months
The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
viewed_cookie_policy
11 months
The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.