paginated next button not working in asp.net core - asp.net-core

I'm new to asp.net MVC core
trying to build a page where you choose multiple search options
I need a get and post actions for that, the results should be in a partial view that is paginated, the code works fine until I click next or previous, I lose the whole search result object cuz the model doesn't bind them back
[HttpGet]
public ViewResult SearchOutbox(DocumentSearchViewModel doc)
{
var documentSearchViewModel = PopulateDocumentSearchViewModel(doc);//this method fills the dropdowns
return View(documentSearchViewModel);
}
[HttpPost]
public async Task<IActionResult> SearchOutbox(DocumentSearchViewModel doc, int? page)
{
var documentSearchViewModel = PopulateDocumentSearchViewModel(doc);
if (ModelState.IsValid)
{
IQueryable<Document> documents = _documentRepository.SearchDocument(documentSearchViewModel);
documentSearchViewModel.Documents = await PaginatedList<Document>.CreateAsync(documents.AsNoTracking(), page ?? 1, 1);
return View("SearchOutbox",documentSearchViewModel);
}
return View();
}
partial view
....
<a asp-action="SearchOutbox"
asp-route-page="#(Model.PageIndex - 1)"
class="btn btn-default #prevDisabled">
Previous
</a>
<a asp-action="SearchOutbox"
asp-route-page="#(Model.PageIndex + 1)"
class="btn btn-default #nextDisabled">
Next
</a>
main view calling partial
<partial name="_ListDocument" model="#Model.Documents">

Before diving into the details, it is worth to mention that for pagination you can use GET only, no need to use POST.
Back yo your question; if the result set is based on some filters, you have to pass all those filters parameters along with the pagination link.
e.g. if you have a URL like below one you can survive by sending only page parameter to the relevant action:
http://example.com/products/?page=1
But whenever you add some filters to the URL, you have to include all of them in the paging buttons, e.g. in below URL you have to send all paramters after ? to the paging action so it can select the next page from the filtered results using the same filtering options:
http://example.com/products/?page=1&category=mobile&brand=xyz
You can add all parameters manually, or you can use a function that will read the query string values then only increase the page number and generate new URL. Below is a function that do the page number increase and replace it via regex:
Previous
Next
#{
string CreateUrl(int newPage)
{
var index = int.Parse(Request.QueryString["page"].ToString());
var input = Request.QueryString.Value;
var replacement = $"page={index + newPage}";
var pattern = #"page=\d+";
return System.Text.RegularExpressions.Regex.Replace(input, pattern, replacement);
}
}
There is some nuget packages that can handle advanced paging functionalities like LazZiya.TagHelpers, install from nuget :
Install-Package LazZiya.TagHelpers -Version 3.0.2
Add paging tag helper to _ViewImports :
#addTagHelper *, LazZiya.TagHelpers
Then use it where you need a paging control:
<paging
total-records="Model.TotalRecords"
page-no="Model.PageNo"
query-string-key-page-no="page"
query-string-value="#(Request.QueryString.Value)">
</paging>
Notice : in the latest preview version (v3.1.0-preview1) no need to add query-string-value so the tag helper will work like below:
<paging
total-records="Model.TotalRecords"
page-no="Model.PageNo"
query-string-key-page-no="page"
</paging>
See tutorial, live demo and docs

Related

Invoke an ASP.NET Core View Component inside a Partial View

I have a partial view (_FormCustomer) that displays a form for creating a customer. I also have a View Component (Countrylist) that generates a options list of countries. Now I want to show the country list in my form. This is what I do:
Index.cshtml
<partial name="_FormCustomer" for="#Model._Customer" />
_FormCustomer.cshtml
<select asp-for="#Model.Land" class="form-control">
#await Component.InvokeAsync("Countrylist");
</select>
CountrylistViewComponent.cs
public async Task<IViewComponentResult> InvokeAsync()
{
return View(await _countryRepository.GetCountriesAsync());
}
(The function GetCountriesAsync() returns a list of countries; this works fine.)
Pages/Componenst/Countrylist/default.cshtml
#model List<Country>
#foreach (Country country in Model)
{
<option value="#country.code">#country.name</option>
}
Unfortunately, select-box stays empty when I call the partial. When I call #await Component.InvokeAsync("Countrylist"); directly from Index.cshtml, however, it works fine.
So it looks like you cannot use a View Component inside a Partial View. Is this conclusion right? Or am I doing something wrong?
Thanks Phantom2018, found the problem after your post.
#0: I'm using Razor pages
#1: this had no effect
#2: this was a typo in my question, not in my code
#3: the debugger shows me that the vie component gets called, so
My actual code is a little different, I want to pre select a country if it's available:
<select asp-for="#Model.Country" class="form-control">
#if (Model == null)
{
await Component.InvokeAsync("Countrylist");
}
else
{
await Component.InvokeAsync("Countrylist", Model.Country);
}
</select>
And after some testing, I found the solution:
<select asp-for="#Model.Country" class="form-control">
#if (Model == null)
{
#await Component.InvokeAsync("Countrylist");
}
else
{
#await Component.InvokeAsync("Countrylist", Model.Country);
}
</select>
Don't know why, but I had to use #'s before the awaits.
I have now tested this scenario and can confirm that the data loads fine - both, when the view component is directly included on the page or when it is included in a partial View. (I have tested this on Razor pages - but it is likely to work the same when using MVC. You have not mentioned if you are using MVC or Razor pages.)
A couple of things you can try to see if the loading works fine:
1) From all "Select"s and "Partials" remove the "for*" attributes. That way you can first check if the data loads & then you can worry about binding to the selected item. (Also, in your provided code, you have omitted the model variables - so it is not possible to comment on them.)
2) Remove the last ";" in your _FormCustomer.cshtml
<select asp-for="#Model.Land" class="form-control">
#await Component.InvokeAsync("Countrylist")
</select>
Note that I have removed the trailing ";" in the await statement. I noticed that the ";" was added as another "option" in the select !
3) I also noticed that even minor syntax errors (not picked up by Intellisense) can cause the select to not load. Debug to see if your InvokeAsync is actually being called - in a scenario where there was a minor syntax error, the InvokeAsync was not even being called.
Also keep in mind that:
"When a partial view is instantiated, it receives a copy of the
parent's ViewData dictionary. Updates made to the data within the
partial view aren't persisted to the parent view. ViewData changes in
a partial view are lost when the partial view returns."

MVC4 C# - How to submit list of object that are being displayed to the user?

I'm working on an MVC4 C# project in VS2010.
I would like to allow the user to upload the contents of a .csv file to a database but there is a requirement to first echo the contents of the file to screen (as a final visual check) before submitting. What would be the best approach of submitting to the database as I am struggling to find a way of persisting the complex object in the view?
Here is the view where I am using a form to allow the user to upload the csv file:
#model IEnumerable<MyNamespace.Models.MyModel>
#{
ViewBag.Title = "Upload";
WebGrid grid = new WebGrid(Model, rowsPerPage: 5);
}
<h2>Upload</h2>
<form action="" method="post" enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" />
<input type="submit" />
</form>
<h2>Grid</h2>
#grid.GetHtml(
//Displaying Grid here)
<p>
#Html.ActionLink("Submit", "Insert")
</p>
Here is the action in the controller that processes the csv file:
[HttpPost]
public ActionResult Upload(HttpPostedFileBase file)
{
var fileName = Path.GetFileName(file.FileName);
var path = Path.Combine(Server.MapPath("~/App_Data"), fileName);
file.SaveAs(path);
//Stream reader will read test.csv file in current folder
StreamReader sr = new StreamReader(path);
//Csv reader reads the stream
CsvReader csvread = new CsvReader(sr);
List<MyModel> listMyModele = new List<MyModel>(); // creating list of model.
csvread.Configuration.RegisterClassMap<MyModelMap>(); // use mapping specified.
listMyModel = csvread.GetRecords<MyModel>().ToList();
sr.Close();
//return View();
return View(listMyModel);
}
Up until this point everything is simple, I can upload the csv to the controller, read using CsvHelper, produce a list of MyModel objects and display in the view within a grid. To reiterate my initial question, is it now possible to submit the complex object (the list of MyModel) from the view as I can't figure out a way of making it available to an action within the controller.
Thank you.
Yes it's possible, It's "easier" if you have a Model with the IEnumerable in it so you can use the naming convention like this:
Property[index].ItemProperty
for every Html input/select field.
If you want to keep the IEnumerable as Model I think the naming convention is something like this:
ItemProperty[index]
So translated in code:
#Html.TextBoxFor(t => t.Property, new { name = "Property[" + i + "]" })
where i comes from a for loop to render all items or something like that.
I have already done it but I can't find the code at the moment. KendoUI uses this scheme for its multirows edit in the grid component.
You can check their POST AJAX requests for the right naming convention.
EDIT 1:
Otherwise you can think about store the model somewhere temporarily and retrieve it every time and updating with user inputs. It's a little more expensive but probably easier to write. Something like an updated csv file or a temporary db table.

asp mvc 4 passing a long string

I currently have a view containing a form. In the form there is a link which allows the user to view one of the textboxes in a separate tab/window.
My question is how do I retrieve the text from that textbox and pass only that text to a controller?
Currently I have the below code
(which returns:
The request filtering module is configured to deny a request where the query string is too long
)
View
#using (Html.BeginForm("PostValuation", "Property", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
....Some data (part of a view model)...
View as letter
....Some data (part of a view model)...
<input type="submit" value="Pass ...Some data... using a View Model" class="btn btn-default" />
}
Controller
public ActionResult ShowLetter(string letter)
{
ViewBag.Letter = letter;
return View();
}
I understand there is a limit on the length of the string I can pass and after reading suggestions I see I can change the web.config to resolve the max length. But this can potentially be thousands of characters long, so I feel that altering the web.config is not the right solution. Can someone offer a better suggestion please.

What I'm missing in this ChildAction or Razor view

I have a child action in my controller that returns an image to an image tag in my view which calls the action via #Url.action. My problem is that when i remove the ChildActionOnly Attribute from the action the image renders but when i add it back it does not. I need the action to carry this attribute so it cannot be invoked directly. Below is my code.
[ChildActionOnly]
public ActionResult GetImage(int id)
{
var photo = _Db.Photozs.Single(p => p.PhotozId == id);
string path = Server.MapPath("~/Photo"+photo.Url);
var fs = new FileStream(path, FileMode.Open);
return new FileStreamResult(fs, photo.ContentType);
}
This is my view
#model IEnumerable<okay.models.photoz>
#{
ViewBag.Title = "Index";
}
<<div class="container-fluid">
#foreach (var item in Model)
{
<div class="pull-left span3">
<img class="img-polaroid" src="#Url.Action("GetImage",new{id=item.PhotozId})"/>
<div class="text-center">#item.Caption</div>
</div>
}
</div>
Thanks guys in anticipation of your help
Your method is correct but you are using [ChildAction] at wrong place just remove it.
[ChildActionOnly] attribute represents an attribute that is used to indicate that an action method should be called only as a child action.
You are not executing method as a child action so it doesn't execute after putting that attribute. Also A child action method renders inline HTML markup for part of a view instead of rendering a whole view.
Any method that is marked with ChildActionOnlyAttribute can be called only with the Html.Action() or Html.RenderAction() HTML helper extension methods.
My problem is that when i remove the ChildActionOnly Attribute from
the action the image renders but when i add it back it does not
Use the [ChildActionOnly] attribute only on actions that you are rendering with the Html.Action or Html.RenderAction helper. Those actions are never directly accessible from the client using a direct HTTP request. When you point the src property of an <img> tag to a server side endpoint, the browser is sending an HTTP request to this endpoint. And if the corresponding endpoint is decorated with [ChildActionOnly] it cannot be rendered to the client.
So you should remove this attribute from your controller action if you want this action to be directly accessible from the client using an HTTP request.

MVC Partial view with controller, ajax - how do I ge the partial controller to get data?

I'm learning MVC and am stumped by this. I'm trying to factor some common code that gets data and displays it in a WebGrid into a partial view that i can use on multiple pages.
My home controller Index method just does a return View(). The Home view looks like this:
#using (Ajax.BeginForm("SearchAction", "Search",
new AjaxOptions { UpdateTargetId = "data-grid", HttpMethod = "Post" }))
{
#Html.TextBoxFor(model => model.name)
<input type="submit" value="Search" />
}
#{
<div id="data-grid">
#Html.Partial("SearchResults", Model)
</div>
}
I'm trying to use Ajax to avoid losing my search form data when clicking a WebGrid pager link, which are rendered as normal links.
My SearchController looks like this:
public ActionResult SearchAction(string name)
{
return RedirectToAction("SearchResults", new { name = name });
}
public ActionResult SearchResults(string name)
{
//does database query and sticks results in the viewbag
//filter on optional name parameter
VieweBag.Members = MyQueryResults;
return PartialView();
}
My SearchResults shared view, data is passed in via ViewBag.Members:
#{
var grid = new WebGrid(null, rowsPerPage: ViewBag.Pagesize);
grid.Bind(ViewBag.Members);
#grid.GetHtml(// etc. etc.)
}
The results I'm getting is that the ViewBag.Pagesize and ViewBag.Members binding fails since there is no data in the viewbag. Obviously, my partial controller is not being called to do the initial query and put stuff in the ViewBag when the home page is first loaded. How do I make that happen?
The other weird thing is that if I just copy the database query code into my home controller (where it originally was) to force the original query, then if I put some text into the search field and do a search, the partial view renders by itself on a new page. Why is that happening, I thought it would only render as part of my home page.
I've cobbled this partial view together from various answers/places and have no doubt gotten something horribly wrong :\
The partial page won't pass through a controller, but simply render the view directly. If you want to pass view data to the partial view, there is an overloaded function that takes a viewdata dictionary. I'm sorry I can't be more detailed, but I'm on my mobile (waiting for my son to fall asleep in the other room) :)
Update:
If you want to trigger a GET action for your partial view, you can use Html.Action. Here are some useful links:
MSDN RenderAction
Difference between RenderPartial and RenderAction
Further, it would probably make sense for you to move your form tags into your partial view, but those are details for when you clean up the code.
Jonass is right, the ViewBag only propagates between the Controller and the View.
One thing you can do is make the model of the partial view be the same as the type of data you're putting into the ViewBag.
So if for example your MyQueryResults is of type:
IEnumerable<Result>
In your partial view you'd add
#Model IEnumerable<Result>
And then in the main view, you'd pass it through the Render method:
#Html.Partial("SearchResults", ViewBag.Members);
You'll need to tweak this a bit to make sure it's the right type, but this should do the trick.
Good luck!