Preserve values when submitting Razor Pages form - c#

I'm using Razor Pages 7. My page has filtering, sorting and paging.
MyPage.cshtml.cs
public SelectList Products { get; set; }
public int Sort { get; set; }
public int Page { get; set; }
public int Filter { get; set; }
public async Task OnGetAsync(int sort, int page, int filter) {
//...
}
MyPage.cshtml
<form method="get" asp-page="" asp-route-sort=#Model.Sort asp-route-page=#Model.Page>
<select name="filter" asp-items=#Model.Products>
<option selected value="0">Filter by product</option>
</select>
<input type="submit" value="submit" />
</form>
That correctly renders as:
<form method="get" action="/mypage?sort=10&page=3">
...
</form>
But when I submit the form, the get action only binds the filter argument. The sort and page arguments are not bound from the form's action.
Does RazorPages offer a way to bind those too?
(I could do it with JavaScript, but I'm hoping there's a RazorPages-native approach.)
UPDATE
I found a way, using hidden inputs. In the form:
<input type="hidden" name="sort" value=#Model.Sort />
<input type="hidden" name="page" value=0 /> <!-- reset page on filter change -->
But this is a manual approach with many magic strings. Is there a RazorPages "native" way?

That correctly renders as:
<form method="get" action="/mypage?sort=10&page=3"> ... </form>
It already complied asp-route-somekey=someval to html correctly,appended the key value pairs to the query part.
But when I submit the form, the get action only binds the filter
argument. The sort and page arguments are not bound from the form's
action
In fact, it's an issue related with Html5,when you submit form with GET method,query parameters of action attribute would be dropped.If you check the url,it would be "/mypage?filter=someval"
The only solution to appending query parameters you've self-answered
If you would accept add the arguements to the route part
regist the route of your page like:
#page "/MyPage/{key1?}/{key2?}"
The result:
I just tried with hard coding to reproduced the error:
#{
var dic = new Dictionary<string, string>()
{
{"key1","10"},
{"key2","2"}
};
}
<form method="get" asp-page="/MyPage" asp-all-route-data=#dic >
<select name="filter" >
<option selected value="0">Filter by product</option>
<option value="1">item1</option>
<option value="2">item2</option>
</select>
<input type="submit" value="submit" />
</form>

Related

How to get an array of elements of one model from the form asp.net core?

I need to get an array of elements of one model from the form. Then return the same array back to shape.
Model:
public class Check
{
public int Id { get; set; }
public string Text { get; set; }
public int Number1 {get; set;}
public int Number2 {get; set;}
}
Controller:
[HttpGet]
public IActionResult Check(string tema)
{
IEnumerable<Check> less = _context.Checks.Where(e => e.Text == tema);
return View(less);
}
[HttpPost]
public IActionResult Check(IEnumerable<Check> Checks)
{
return View(Checks);
}
Form:
#model IEnumerable<Check>
#foreach (var check in Model)
{
<form asp-action="Check" asp-controller="Home" method="post">
<p>#check?.Text</p>
<p><input type="text"asp-for="#check.Number1"/></p>
#if(Number1 == Number2)
{<p>ok</p>}
<div class="form-group">
<input type="submit" value="Ok" class="btn btn-default" />
</div>
</form>
}
I have everything working for one model, and if I pass an array, I get an empty form. How to fix it?
1.If you just want to post single model, but the backend you want receive the IEnumerable<Check>, you need add name attribute(name="[0].propertyName") to override the asp-for generated name="check.propertyName"
2.For your code, it will generate multiple forms with submit button. If you want to post an array model, you need move <form> and submit button outside foreach loop.
3.If post array model from view to controller. The IEnumerable<Check> Checks is a list model, so the model binding system would find the name by [i].propertyName.
4.<p> element text cannot passed to backend by form submit. If you want to post it, try to add a hidden input.
If post array model from view to controller, change your view like below:
#model IEnumerable<Check>
#{
int i=0; //add this...
}
<form asp-action="Check" asp-controller="Home" method="post">
#foreach (var check in Model)
{
<p>
#check?.Text
<input type="text"asp-for="#check.Text" name="[#i].Text" hidden/> #* //add the hidden input*#
</p>
//add the name...
<p><input type="text"asp-for="#check.Number1" name="[#i].Number1"/></p>
#if(check.Number1 == check.Number2)
{<p>ok</p>}
i++; //add this...
}
<div class="form-group">
<input type="submit" value="Ok" class="btn btn-default" />
</div>
</form>
If you post single model, change your view like below:
#foreach (var check in Model)
{
<form asp-action="Check " asp-controller="Home" method="post">
<p>
#check?.Text
<input type="text"asp-for="#check.Text" name="[0].Text" hidden/> #* //add the hidden input*#
</p>
#*add the name...*#
<p><input type="text"asp-for="#check.Number1" name="[0].Number1"/></p>
#if(check.Number1 == check.Number2)
{<p>ok</p>}
<div class="form-group">
<input type="submit" value="Ok" class="btn btn-default" />
</div>
</form>
}

ASP Core Razor Pages Change BindProperty Value on Post

I'm creating a form using one razor page (I know this may not be a good idea, but I'm trying to do some [not so] rapid prototyping. Basically, the first step is that the user enters a bunch of data, and the next step is that the user will upload some files. My page model looks like this:
public class CreateModel : PageModel
{
private readonly DefaultDbContext _context;
public CreateModel(DefaultDbContext context)
{
_context = context;
}
public async Task<IActionResult> OnGet(Guid id)
{
FileUpload = false;
return Page();
}
[BindProperty]
public bool FileUpload { get; set; } // Stored in a hidden field in my cshtml
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
FileUpload = true; // Hidden field is always false
return Page();
}
}
This is what my form looks like:
<form method="post" enctype="multipart/form-data">
<input type="hidden" asp-for="FileUpload" />
#if (!Model.FileUpload)
{
// Do some stuff
}
else
{
<div class="form-group">
<label asp-for="Upload" class="control-label"></label>
<input type="file" asp-for="Upload" multiple />
<span asp-validation-for="Upload" class="text-danger"></span>
</div>
}
<div class="form-group">
#if (Model.FileUpload)
{
<input type="submit" value="Finished" class="btn btn-primary" />
}
else
{
<input type="submit" value="Next" class="btn btn-primary" />
}
</div>
</form>
When I click submit the first time, I would expect FileUpload to be true, which it is when I step through the .cshtml page in the debugger. The problem is that when the page is sent back to the browser, the value is always false:
<input type="hidden" data-val="true" data-val-required="The FileUpload field is required." id="FileUpload" name="FileUpload" value="False" />
What am I doing wrong?
A couple of things should be addressed:
<input type="hidden"> elements are useful only when you are trying to send information (contained in those elements) FROM the page TO the model, not the vice versa.
[BindProperty] attribute is used when you are trying to map some information FROM the page TO the model's property, not the vice versa:
Model Binding in Razor Pages is the process that takes values from HTTP requests and maps them to [...] PageModel properties. Model Binding
In the line #if (Model.FileUpload), you are not extracting any information from the page's hidden input element, but from the model itself. Your code should work as is (if you ignore the hidden input element).
That being said, you could completely remove the hidden input (and the [BindProperty] attribute, since nothing could be bound to FileUpload after you remove the input) and you should be good to go.
If you do, however, wish to change the hidden input value accordingly, you could do that in a couple of ways:
Remove [BindProperty] from the FileUpload and keep everything else the same.
Keep the [BindProperty], but render the changed FileUpload value:
<input type="hidden" asp-for="FileUpload" value="#Model.FileUpload"/>

Add parameters to the form in razor

I have this form in my view which leads me to some action in the controller. The thing is I don't know how to pass parameters to the action method.
<form class="full-search" asp-controller="Movies" asp-action="Search">
<input type="search" name="searchmovie" placeholder="Search..." class="search-form">
<select name="option" form="">
<option name="option" value="category1">category 1</option>
<option name="option" value="category2">category 2</option>
<option name="option" value="category3">category 3</option>
</select>
<i class="fas fa-caret-down"></i>
<input type="submit" name="" class="submit-full-search">
<div class="search-btn-submit">
<img src="~/img/search.svg">
</div>
</form>
And this is my controller:
[HttpGet("searchmoview/{option?}/{searchmovie}")]
public IActionResult Search(string option, string searchmovie)
{
//perform some search based on the filters
return View("Search", data);
}
But when I run my code, when I click on search the url looks like this:
https://localhost:33590/Information/Search
Instead of like this:
https://localhost:44320/seachmovie/category1/{searchString}
Any idea guys on how can I pass the parameters?
Long story short - there is no out of the box way of mapping form values to a route.
The best way to pass values back to your mvc controllers is to have a view model.
Create a search view model.
//View Model
public class SearchViewModel{
public string Query {get;set;}
public string Category {get;set;}
}
In your controller you'll want to pass this down to your view or partial view.
Use "ActionResult" in .NET and "IActionResult" in .Net Core
//Controller
public class SearchController{
[HttpGet]
public ActionResult SearchBox(){
return View();
}
[HttpPost]
public ActionResult SearchBox(SearchViewModel model){
//model is now populated with your values from your form.
//ex: model.Query
return View();
}
}
Create an enum for your categories, there is many other ways to do this but this one is the simplest.
//Enum
public enum Categories
{
Category1,
Category2
}
Now in your razor view bind your view model and you're good to go.
//Razor View
#model SearchViewModel
#using (Html.BeginForm("SearchBox", "Search", FormMethod.Post))
{
#Html.TextBoxFor(x => x.Query, new { #class = "search-form"})
#Html.DropDownListFor(x => x.Catergory,
new SelectList(Enum.GetValues(typeof(Categories))),
"Select Category", new { #class= "example-class"})
<i class="fas fa-caret-down"></i>
<input type="submit" name="" class="submit-full-search">
<div class="search-btn-submit">
<img src="~/img/search.svg">
</div>
}
If you want the form values to be posted to your url you can change it to FormMethod.Get
//Razor View
#model SearchViewModel
#using (Html.BeginForm("SearchBox", "Search", FormMethod.Get))
{
#Html.TextBoxFor(x => x.Query, new { #class = "search-form"})
#Html.DropDownListFor(x => x.Catergory,
new SelectList(Enum.GetValues(typeof(Categories))),
"Select Category", new { #class= "example-class"})
<i class="fas fa-caret-down"></i>
<input type="submit" name="" class="submit-full-search">
<div class="search-btn-submit">
<img src="~/img/search.svg">
</div>
}
If you change it to get, you'll also have to change your controller to expect these in your get method. You can also just bind the model and MVC will map the parameters back into the model for you automatically.
public class SearchController{
[HttpGet]
public ActionResult SearchBox(SearchViewModel model){
//if the model values are in the URL "model" will be populated, else it will be null.
return View();
}
}
You can't. That's not how things work. A form will by default send a POST request, in which case inputs in the form are sent as part of the request body (i.e. not the URL). You can set the method to GET, instead, but then the input values will be sent as part of the query string (i.e. ?foo=bar), not as part of your path, as you're looking for.
The only way to remotely achieve what you want is to use JavaScript to manipulate the form's action based on things like the category select box changing. You'd bind to the change event of that element, and then you'd alter the form element's action attribute in some way.

Filling model list property in mvc

I have a model with a list property. In my view the user will fill in a fixed number of similar data (name number etc). How can I collect that information and create a list of it on the post? Thanks for any help.
public class mainonject
{
public List<test> list{ get; set; }
}
public class test
{
string name;
string number;
}
<form>
HOW DO I COLLECT THE PROPERTY list AS A LIST TO THE SERVER
<input type="submit" value="submit"/>
</form>
Just add the fields to your form and use the right indexes in there name properties:
<input name="list[0].Name" type="text" />
<input name="list[0].Number" type="text" />
<input name="list[1].Name" type="text" />
<input name="list[1].Number" type="text" />
...

do a HTTPPOST MVC2

I have a MVC2-application. in this application I have a strongtyped view contending the modell NewHorseModel:
public class NewHorseModel
{
public List<Category> Faehigkeit { get; set; }
}
public class Choice
{
public int Id { get; set; }
public string Name { get; set; }
public string Beschreibung { get; set; }
public bool Selected { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public string Beschreibung { get; set; }
public List<Category> Subcategories { get; set; }
public List<Choice> Choices { get; set; }
public int Parent { get; set; }
}
The View looks like this:
<p>
<input faehigkeit_ID="1" id="Selected" name="Selected" type="checkbox" value="true" />
<input name="Selected" type="hidden" value="false" />
Mitteltrab
<input id="Id" name="Id" type="hidden" value="1" />
</p>
<p>
<input faehigkeit_ID="2" id="Selected" name="Selected" type="checkbox" value="true" />
<input name="Selected" type="hidden" value="false" />
Arbeitstrab
<input id="Id" name="Id" type="hidden" value="2" />
</p>
<p>
<input faehigkeit_ID="3" id="Selected" name="Selected" type="checkbox" value="true" />
<input name="Selected" type="hidden" value="false" />
Trab versammelt
<input id="Id" name="Id" type="hidden" value="3" />
</p>
<p>
<input faehigkeit_ID="11" id="Selected" name="Selected" type="checkbox" value="true" />
<input name="Selected" type="hidden" value="false" />
Trab
<input id="Id" name="Id" type="hidden" value="11" />
</p>
The view is created like in this post:
Categories and Subcategories MVC2
now I want to do a post, but how to get the datas?
When I do a post like this:
[HttpPost]
public void myAction(NewHorseModel newHorseModel)
{
// ...
}
Faehigkeit in NewHorseModel is null
Here my ASPX Code:
<div id="Div2">
<%
foreach (Category item2 in Model.Faehigkeit)
{
Html.RenderPartial("Faehigkeit", item2);
}
%>
</div>
The partial view Category (strong typed model Category):
<%
if (Model.Choices != null)
{
foreach (var item in Model.Choices)
{
Html.RenderPartial("Choice", item);
}
}
if (Model.Subcategories != null)
{
foreach (var item in Model.Subcategories)
{
Html.RenderPartial("Faehigkeit", item);
}
}
%>
And the partialview Choices (strongtyped model choice)
<p>
<%: Html.CheckBoxFor(m => m.Selected, new { faehigkeit_ID = Model.Id }) %>
<%: Model.Name %>
<%: Html.HiddenFor(u=>u.Id) %>
</p>
Update
Next test:
in the Faehigkeit.ascx partial I have added this code:
<input type="hidden" name="Faehigkeit[<%=Model.Id%>].Id" value="<%=Model.Id%>" />
<input type="hidden" name="Faehigkeit[<%=Model.Id%>].Name" value="<%: Model.Name%>" />
in the Choices.ascx partial I have added following code:
<input type="checkbox" name="Faehigkeit[0].Choices[<%=Model.Id%>].Selected" />
I don't need to know which choice is in wich category. I kust need to know which choice ID is checked and which on not.
The HTML-Output looks like this:
<input type="hidden" name="Faehigkeit[1].Id" value="1" />
<input type="hidden" name="Faehigkeit[1].Name" value="Qualität der Gangarten" />
<input type="hidden" name="Faehigkeit[1].Choices[4].Id" value="4" />
<input type="checkbox" name="Faehigkeit[1].Choices[4].Selected" />
My controler looks like this:
[HttpPost]
public ActionResult CreateNewHorse(NewHorseModel collection)
{
if (User.Identity.IsAuthenticated)
{
return View();
}
else
{
return View("Account/LogOn");
}
}
If I try to get the value of "Faehigkeit" -> "Choices" Every thing is Null (the Name of the "Faehigkeit", the ID of "Faehigkeit", and there are no choices
An image that shows the content of the NewHorseModel during debuging:
http://i.stack.imgur.com/6yLZW.png
Thank you very much!
There are 2 blog posts that I've written and will address your problem:
How to post IList<T> to controller actions is explained here and will guide you from start to finish so you'll understand how it actually works and why.
And since you may have have complex JSON objects on the client side and would like them to be model bound on the server in your action method (as is in your case), this post may be as well an interesting read. It explains the problem of sending complex JSON objects and provides a simple jQuery plugin that converts client objects into a form that will easily be data bound to your strong type action method parameters.
Note: There's also the possibility of using JsonValueProviderFactory as explained by Phil Haack, but that solution requires changes on the client as well as on the server side (because you're using MVC2).
Based on your code
You've only provided some parts of your code (and some that aren't really relevant but anyway) and I'm going to make an observation for your case.
All input fields that you wish to model bind on the server need to have correct names. From the rendered checkbox names we can see that is not the case at all.
Based on your model, your inputs should be named as (never mind input types because only you know which ones should be rendered and as which type):
<!-- erste fähigkeit -->
<input name="Faehigkeit[0].Id" />
<input name="Faehigkeit[0].Name" />
<input name="Faehigkeit[0].Beschreibung" />
<input name="Faehigkeit[0].Subcategories[0].Id" />
<input name="Faehigkeit[0].Subcategories[0].Name" />
...
<input name="Faehigkeit[0].Choices[0].Id" />
<input name="Faehigkeit[0].Choices[0].Name" />
<input name="Faehigkeit[0].Choices[0].Beschreibung" />
<input name="Faehigkeit[0].Choices[0].Selected" />
...
<input name="Faehigkeit[0].Choices[x].Id" /> <!-- "x" could be any number -->
...
<input name="Faehigkeit[0].Parent" />
<!-- zwite fähigkeit -->
<input name="Faehigkeit[1].Id" />
...
<!-- usw. -->
Based on this complex hierarchical model this form can be huge. And you probably don't wish to send all properties because you only need some that are relevant. But the main thing is that input naming should be as described.
Extremely important: I should point out that indexes (ie. Faehigkeit[0]) are not supposed to relate to IDs, but are rather array/list item indexes. They should be consecutive so they should always start at 0 and should be incremented by 1 to the last N hence should have no gaps whatsoever. Believe me I've tested that when I was writing my blog post. Otherwise model binding on the server will fail. Make sure you pass this requirement! Based on your edited question this is not the case, because you're putting in IDs instead of indexes.
If you'd transfer your data as JSON to the client, you'd have an object on the client as:
var data = {
Faehigkeit: [
{
Id: 1,
Name: "Some name",
Beschreibung: "Some description",
Subcategories: [
{ ... },
{ ... },
...
],
Choices: [
{ ... },
{ ... },
...
]
},
{ ... },
...
]
};
And if your client side view manipulated this object directly you could then afterwards send it back to the server by means of jQuery Ajax call ie:
$.ajax({
type: "POST",
url: "someController/myAction",
data: $.toDictionary(data), // this is my jQuery plugin mentioned earlier
success: function() { ... },
error: function() { ... }
});
So the choice is yours, but in either case, field names should be correct otherwise your model won't be bound to your parameter and you won't be able to use it.
You could try with <%= Html.EditorFor(m => m); %> but that may not be the kind of form you'd like to use. In such case manual input naming would have to be used. Or you can as well write some custom code.
One of the ways would be to chaneg your partial views mode types to become for instance Tuple<string, Category>. String would tell it what string should be pre-pent to your field names.
Html.RenderPartial("Faehigkeit", Tuple.New("Faehigkeit[0]", item2));
And then when you go along, previous data should be always added so your subcategories' lists and their respected subobjects will have correct input form names.
Not nice, but I suppose it's the only way to make it.
Usually if you put the HttpPost as an attribute of your action, its something like:
[HttpPost]
public void myAction(NewHorseModel newHorseModel)
{;}
Then put the class type as your argument it should autobind it for you, if there is anything crazy that you need to do, or are posting from an external non ASP page to this action you can just use the FormCollection (i think) as your argument and thats a glorified Dictionary containing all your elements and values.

Categories

Resources