Add parameters to the form in razor - c#

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.

Related

why post method doesn't refresh the page in the action method?

I know the post action method below is anti-pattern, but still I assume to see a new page with Name being set to null. But when I click the submit button, the page is not reloaded and I still see the old name displayed, is this a browser thing or asp.net core framework thing?
public class HomeController : Controller
{
private IRepository repository;
public HomeController(IRepository repo)
{
repository = repo;
}
// ...
public IActionResult Create() // create a Employer that has a name in the browser
{
return View();
}
[HttpPost]
public IActionResult Create(Employee model)
{
model.Name = ""; // <----------------set it to null so I expect to see the Name being empty in the reponse
return View(model);
}
}
// view file, Create.cshtml:
#model Employee
#{
ViewData["Title"] = "Create Employee";
}
<h2>Create Employee</h2>
<form asp-action="Create" method="post">
<div class="form-group">
<label asp-for="Id"></label>
<input asp-for="Id" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" />
</div>
<div class="form-group">
<label asp-for="DOB"></label>
<input asp-for="DOB" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Role"></label>
<select asp-for="Role" class="form-control" asp-items="#new SelectList(Enum.GetNames(typeof(Role)))"></select>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Firstly, a get method to invoke the Create() action method and fill some data:
After clicking submit button, the page still shows the original name where I exepct to see a empty name since I modify it in the post action method
so why the name still exists after the second request?
So why the name still exists after the second request?
Well, altough, you have rebind your model property value. However, your current model state still remain unchanged. Therefore, you are getting the older value even after the form resubmission. To overcome this issue, please try following code snippet:
Solution:
You have clear your current model state by using ModelState.Clear(); method before assigning the new value.
So, your create method should be as bellow:
[HttpPost]
public IActionResult Create(Employee model)
{
ModelState.Clear();
model.Name = "";
return View(model);
}
Note: Rest of the code will remain unchanged.
Output:
ModelState.Clear(); wwill resolve your issue completely.

ASP.NET Core - Passing Form data to Controller, then redirect

I currently am trying to build a "Index" form which has two submit boxes, and then when you hit submit, it adds the two numbers together and then displays the result.
I tried this without a Model (Using ViewData) and managed to get it working, but I'd like it to work with a model. I have attempted this a magnitude of ways but I just cannot see what I am missing.
Journey Class - Two string values and a value (this will be int once I get it working)
public class JourneyCalculator
{
public string PostCodeOne { get; set; }
public string PostCodeTwo { get; set; }
public string DistanceValue { get; set; }
}
Then my controller which has two actions, one standard Index form and one that is supposed to Calculate the mileage. With this I've tried the following:
Passing the Model as below
Trying to pass string values from the form (This just fails)
Just simply returning View(model)
public class MapsController : Controller
{
public IActionResult Index(JourneyCalculator journeyCalculator)
{
return View(journeyCalculator);
}
[HttpPost]
public IActionResult CalculateMileage(JourneyCalculator model)
{
model.DistanceValue = model.PostCodeOne + model.PostCodeTwo;
return RedirectToAction("Index",model);
}
}
My form which I would hope passes PostCodeOne and PostCodeTwo to the "CalculateMileage" action. And then we update that value and then the Model.DistanceValue displays
<form>
<div class="form-row">
<div class="form-group col-md-6">
<label>Postcode One</label>
<input type="text" class="form-control" asp-for="PostCodeOne" placeholder="Postcode One">
</div>
<div class="form-group col-md-6">
<label>Postcode Two</label>
<input type="text" class="form-control" asp-for="PostCodeTwo" placeholder="Postcode Two">
</div>
</div>
<button type="submit" class="btn btn-primary" asp-controller="Maps" asp-action="CalculateMileage">Calculate Mileage</button>
</form>
#if(Model != null)
{
<h2>#Model.DistanceValue</h2>
}
I feel I am missing something really obvious, so any help would be fantastic!
When I hit "Submit" the URL updates to https://localhost:44301/Maps/CalculateMileage?PostCodeOne=123&PostCodeTwo=123 or https://localhost:44301/Maps/Index?PostCodeOne=123&PostCodeTwo=123 and both are failing, but I want to just pass the values to the CalculateMileage.
Thank you,
Don't redirect to the Index action, but return the Index view straight from the CalculateMileage action passing the model: return View("Index", model);
// controller
[HttpPost]
public IActionResult CalculateMileage(JourneyCalculator model)
{
model.DistanceValue = model.PostCodeOne + model.PostCodeTwo;
return View("Index", model);
}
I would also prefer to put the action/method on the form rather than the button:
<form asp-controller="Maps" asp-action="CalculateMileage" method="post">
<div class="form-row">
...
</form>
Alternatively, add a method to handle POST requests on the Index action:
[HttpPost]
public IActionResult Index(JourneyCalculator model)
{
model.DistanceValue = model.PostCodeOne + model.PostCodeTwo;
return View(model);
}

Implementing a Simple Search Function in ASP.NET MVC

I'm trying to implement a basic search page in the web app I'm developing. Right now the page looks like this
When a user enters a last name, the controller gets called to search the backend Microsoft SQL Server database for all accounts with that Last name
Right now the HTML form looks like this
#using (Html.BeginForm("SearchAct", "HomeController", FormMethod.Post))
{
<form>
<div>
Last Name:<br>
<input type="text" id="nameToFind">
<input type="button" id="submitId" value="submit" />
</div>
</form>
}
It's supposed to call this controller
[HttpPost]
public void SearchAct()
{
Console.WriteLine();
}
which will eventually execute the search and then put the results on the page. However, I can't get the controller to be called. I set a break point on the WriteLine so I know its never getting there and I don't know what I'm doing wrong
Add name attribute to your text box. Form collection build based on only name attributes.
Change button type to submit, then it will post your form to controller.
#using (Html.BeginForm("SearchAct", "Home", FormMethod.Post))
{
<div>
Last Name:<br>
<input type="text" id="nameToFind" name="nameToFind">
<input type="submit" id="submitId" value="submit" />
</div>
}
#{
if(ViewBag.SearchKey != null)
{
<span>
Search Key: #ViewBag.SearchKey
</span>
}
}
Instead on Console.WriteLine() use ViewBag to send your required data back to view
Refer below actions
//Get Action for rendering view
public ActionResult SearchAct()
{
return View();
}
[HttpPost]
public ActionResult SearchAct(string nameToFind)
{
ViewBag.SearchKey = nameToFind;
return View();
}
Action parameter name and text box name attribute value must be same otherwise it will null
If your form contains multiple text box then read all input values form FromCollection or Request
[HttpPost]
public ActionResult SearchAct(FormCollection form)
{
ViewBag.SearchKey = form["nameToFind"];
return View();
}
correct your form HomeController should be just Home
#using (Html.BeginForm("SearchAct", "Home", FormMethod.Post))
{
<form>
<div>
Last Name:<br>
<input type="text" id="nameToFind">
<input type="button" id="submitId" value="submit" />
</div>
</form>
}
controller should accept the input parameter for your filter
[HttpPost]
public void SearchAct(string nameToFind)
{
// filter snomething
Console.WriteLine(nameToFind);
}
First: To create a form via using razor syntax:
#using (Html.BeginForm("SearchAct", "HomeController", FormMethod.Post))
{
}
That would generate a form like this:
<form method="post" action="/HomeController/SearchAct">
</form>
So, no need to create a nested form inside that.
#using (Html.BeginForm("SearchAct", "HomeController", FormMethod.Post))
{
<form> // <- here
<div>
Last Name:<br>
<input type="text" id="nameToFind">
<input type="button" id="submitId" value="submit" />
</div>
</form> // <- here
}
Second: HomeController would match the controller what the full name is HomeControllerController.
So, if you want to hit Home controller, remove Controller from HomeController in #using (Html.BeginForm("SearchAct", "HomeController", FormMethod.Post)) {}
Thirth: if you want to catch nameToFind, you can try:
[HttpPost]
public void SearchAct(string nameToFind)
{
Console.WriteLine();
}
Hope this help!
first of all we dont need to two form tag and you dont need to add controller suffix. remve that:
#using (Html.BeginForm("SearchAct", "Home", FormMethod.Post))
{
<div>
Last Name:<br>
<input type="text" id="nameToFind" name="nameToFind">
<input type="button" id="submitId" value="submit" />
</div>
}
Secondly if you need to search you need to add paramater same as the name of input type text :
[HttpPost]
public void SearchAct(string nameToFind)
{
Console.WriteLine();
}

How can I pass hidden field value from view to controller ASP.NET MVC 5?

I am trying to pass hidden field value from view to controller by doing the following
#Html.HiddenFor(model => model.Articles.ArticleId)
and also tried
<input type="hidden" id="ArticleId" name="ArticleId" value="#Model.Articles.ArticleId" />
On both instances the value of ArticleId is 0 but when i use TextboxFor i can see the correct ArticleId, please help
Here it is
View
#model ArticlesCommentsViewModel
....
#using (Html.BeginForm("Create", "Comments", FormMethod.Post))
{
<div class="row">
<div class="col-xs-10 col-md-10 col-sm-10">
<div class="form-group">
#Html.LabelFor(model => model.Comments.Comment, new { #class = "control-label" })
#Html.TextAreaFor(m => m.Comments.Comment, new { #class = "ckeditor" })
#Html.ValidationMessageFor(m => m.Comments.Comment, null, new { #class = "text-danger"})
</div>
</div>
</div>
<div class="row">
#*#Html.HiddenFor(model => model.Articles.ArticleId)*#
<input type="hidden" id="ArticleId" name="ArticleId" value="#Model.Articles.ArticleId" />
</div>
<div class="row">
<div class="col-xs-4 col-md-4 col-sm-4">
<div class="form-group">
<input type="submit" value="Post Comment" class="btn btn-primary" />
</div>
</div>
</div>
}
Controller
// POST: Comments/Create
[HttpPost]
public ActionResult Create(CommentsViewModel comments)//, int ArticleId)
{
var comment = new Comments
{
Comment = Server.HtmlEncode(comments.Comment),
ArticleId = comments.ArticleId,
CommentByUserId = User.Identity.GetUserId()
};
}
Model
public class CommentsViewModel
{
[Required(ErrorMessage = "Comment is required")]
[DataType(DataType.MultilineText)]
[Display(Name = "Comment")]
[AllowHtml]
public string Comment { get; set; }
public int ArticleId { get; set; }
}
ViewModel
public class ArticlesCommentsViewModel
{
public Articles Articles { get; set; }
public CommentsViewModel Comments { get; set; }
}
The model in the view is ArticlesCommentsViewModel so therefore the parameter in your POST method must match. Your use of
#Html.HiddenFor(model => model.Articles.ArticleId)
is correct, but you need to change the method to
[HttpPost]
public ActionResult Create(ArticlesCommentsViewModel model)
and the model will be correctly bound.
As a side note, your ArticlesCommentsViewModel should not contain data models, and instead should contain only those properties you need in the view. If typeof Articles contains properties with validation attributes, ModelState would be invalid because your not posting all properties of Article.
However, since CommentsViewModel already contains a property for ArticleId, then you could just use
#Html.HiddenFor(model => model.Comments.ArticleId)
and in the POST method
[HttpPost]
public ActionResult Create([Bind(Prefix="Comments")]CommentsViewModel model)
to effectively strip the "Comments" prefix
In your controller, you need to pass the hidden value with the model,
for example, if you have a userId as a hidden value, in your Page you add:
#Html.HiddenFor(x => x.UserId)
In your model of course you would already have UserId as well.
In your controller, you need the model as a parameter.
public async Task<ActionResult> ControllerMethod(YourViewmodel model) { model.UserId //this should be your HiddenValue
I guess your model have another class called Articles inside CommentsViewModel.Change your controller function for accessing the ArticleId accordingly.
[HttpPost]
public ActionResult Create(CommentsViewModel comments)//, int ArticleId)
{
var comment = new Comments
{
Comment = Server.HtmlEncode(comments.Comment),
ArticleId = comments.Articles.ArticleId, // Since you are using model.Articles.ArticleId in view
CommentByUserId = User.Identity.GetUserId()
};
}
In my case, I didn't put the hidden input in the form section, but out of form, so it's not send to backend. Make sure put hidden input inside the form.
Also make sure name attribute is specified on the hidden field. Element's "id" is often used on client side but "name" on server side.
<input type="hidden" value="#ViewBag.selectedTraining" id="selectedTraining"
name="selectedTraining" />
In my case, I was passing a couple of fields back and forth between controllers and views. So I made use of hidden fields in the views.
Here's part of the view. Note a controller had set "selectedTraining" and "selectedTrainingType" in the ViewBag to pass to the view. So I want these values available to pass on to a controller. On the hidden tag, the critical thing is set to the "name" attribute. "id" won't do it for you.
#using (Html.BeginForm("Index", "ComplianceDashboard"))
{
<input type="hidden" value="#ViewBag.selectedTraining" id="selectedTraining" name="selectedTraining" />
<input type="hidden" value="#ViewBag.selectedTrainingType" id="selectedTrainingType" name="selectedTrainingType" />
if (System.Web.HttpContext.Current.Session["Dashboard"] != null)
{
// Show Export to Excel button only if there are search results
<input type="submit" id="toexcel" name="btnExcel" value="Export To Excel" class="fright" />
}
<div id="mainDiv" class="table">
#Html.Grid(Model).Columns(columns =>
Then back on the controller:
// POST: Dashboard (Index)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(string excel)
{
string selectedTraining, selectedTrainingType;
selectedTraining = Request["selectedTraining"];
selectedTrainingType = Request["selectedTrainingType"];
Or can put the requests as parameters to the method: public ActionResult Index(string excel, string selectedTraining, string selectedTrainingType)

MVC Form not able to post List of objects

so I have an MVC Asp.net app that is having issues. Essentially, I have a View that contains a form, and its contents are bound to a list of objects. Within this loop, it loads PartialView's with the items being looped over. Now everything works up till the submittion of the form. When it gets submitted, the controller is sent a null list of objects. The code below demonstates the problems.
Parent View:
#model IEnumerable<PlanCompareViewModel>
#using (Html.BeginForm("ComparePlans", "Plans", FormMethod.Post, new { id = "compareForm" }))
{
<div>
#foreach (var planVM in Model)
{
#Html.Partial("_partialView", planVM)
}
</div>
}
_partialView:
#model PlanCompareViewModel
<div>
#Html.HiddenFor(p => p.PlanID)
#Html.HiddenFor(p => p.CurrentPlan)
#Html.CheckBoxFor(p => p.ShouldCompare)
<input type="submit" value="Compare"/>
</div>
And these are the classes for the above code:
PlanViewModel:
public class PlansCompareViewModel
{
public int PlanID { get; set; }
public Plan CurrentPlan { get; set; }
public bool ShouldCompare { get; set; }
public PlansCompareViewModel(Plan plan)
{
ShouldCompare = false;
PlanID = plan.PlanId;
CurrentPlan = plan;
}
public PlansCompareViewModel()
{
// TODO: Complete member initialization
}
public static IEnumerable<PlansCompareViewModel> CreatePlansVM(IEnumerable<Plan> plans)
{
return plans.Select(p => new PlansCompareViewModel(p)).AsEnumerable();
}
}
Controller:
public class PlansController : MyBaseController
{
[HttpPost]
public ActionResult ComparePlans(IEnumerable<PlanCompareViewModel> model)
{
//the model passed into here is NULL
}
}
And the problem is in the controller action. As far as I am aware, it should be posting an enumerable list of PlanCompareViewModels, yet it is null. When in inspect the post data being sent, it is sending the correct params. And if I were to change 'IEnumerable' to 'FormCollection', it contains the correct values. Can anyone see why the binder is not creating the correct object? I can get around this using javascript, but that defeats the purpose! Any help would be greatly appreciated!
Your model is null because the way you're supplying the inputs to your form means the model binder has no way to distinguish between the elements. Right now, this code:
#foreach (var planVM in Model)
{
#Html.Partial("_partialView", planVM)
}
is not supplying any kind of index to those items. So it would repeatedly generate HTML output like this:
<input type="hidden" name="yourmodelprefix.PlanID" />
<input type="hidden" name="yourmodelprefix.CurrentPlan" />
<input type="checkbox" name="yourmodelprefix.ShouldCompare" />
However, as you're wanting to bind to a collection, you need your form elements to be named with an index, such as:
<input type="hidden" name="yourmodelprefix[0].PlanID" />
<input type="hidden" name="yourmodelprefix[0].CurrentPlan" />
<input type="checkbox" name="yourmodelprefix[0].ShouldCompare" />
<input type="hidden" name="yourmodelprefix[1].PlanID" />
<input type="hidden" name="yourmodelprefix[1].CurrentPlan" />
<input type="checkbox" name="yourmodelprefix[1].ShouldCompare" />
That index is what enables the model binder to associate the separate pieces of data, allowing it to construct the correct model. So here's what I'd suggest you do to fix it. Rather than looping over your collection, using a partial view, leverage the power of templates instead. Here's the steps you'd need to follow:
Create an EditorTemplates folder inside your view's current folder (e.g. if your view is Home\Index.cshtml, create the folder Home\EditorTemplates).
Create a strongly-typed view in that directory with the name that matches your model. In your case that would be PlanCompareViewModel.cshtml.
Now, everything you have in your partial view wants to go in that template:
#model PlanCompareViewModel
<div>
#Html.HiddenFor(p => p.PlanID)
#Html.HiddenFor(p => p.CurrentPlan)
#Html.CheckBoxFor(p => p.ShouldCompare)
<input type="submit" value="Compare"/>
</div>
Finally, your parent view is simplified to this:
#model IEnumerable<PlanCompareViewModel>
#using (Html.BeginForm("ComparePlans", "Plans", FormMethod.Post, new { id = "compareForm" }))
{
<div>
#Html.EditorForModel()
</div>
}
DisplayTemplates and EditorTemplates are smart enough to know when they are handling collections. That means they will automatically generate the correct names, including indices, for your form elements so that you can correctly model bind to a collection.
Please read this: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
You should set indicies for your html elements "name" attributes like planCompareViewModel[0].PlanId, planCompareViewModel[1].PlanId to make binder able to parse them into IEnumerable.
Instead of #foreach (var planVM in Model) use for loop and render names with indexes.
In my case EditorTemplate did not work. How I did -
ViewModel file -
namespace Test.Models
{
public class MultipleFormViewModel
{
public int Value { get; set; }
public string Title { get; set; }
}
}
Main View (cshtml) file -
#model List<MultipleFormViewModel>
#{
var list = new List<MultipleFormViewModel>();
ViewData["index"] = 0;
}
#using (Html.BeginForm("SaveDataPoint", "MultipleForm", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
for (int i = 0; i < 3; i++)
{
var dataPoint = new MultipleFormViewModel();
list.Add(dataPoint);
ViewData["index"] = i;
#Html.Partial("_AddDataPointFormPartial", ((List<MultipleFormViewModel>)list)[i], ViewData)
}
<input type="submit" value="Save Data Points" />
}
Partial View (under Shared folder) file -
#model MultipleFormViewModel
#{
var index = ViewData["index"];
}
<div>
<div>
<label>Value:</label>
<input asp-for="Value" name="#("["+index+"].Value")" id="#("z"+index+"__Value")" />
</div>
<div>
<label>Title:</label>
<input asp-for="Title" name="#("["+index+"].Title")" id="#("z"+index+"__Title")" />
</div>
</div>
And Finally Controller -
[HttpPost]
public IActionResult SaveDataPoint(List<MultipleFormViewModel> datapoints)
{
Console.WriteLine(datapoints.Count);
//Write your code
return Content("hello..");
}

Categories

Resources