Wrong hidden filed value is being passed to the Action Method - c#

Update :
This is an SPA app.So it's having js file also for supporting the submit button.So I think problem is on it.Could you tell me how to modify it to support multiple kind of submit buttons ? At this moment I think it supports only for a single submit button.That is why it always get the first form's hidden field value I think.Thanks.
JS
var $loginForm = $('.login-form');
$loginForm.submit(function (e) {
e.preventDefault();
if (!$('.login-form').valid()) {
return;
}
abp.ui.setBusy(
null,
abp.ajax({
contentType: app.consts.contentTypes.formUrlencoded,
url: $loginForm.attr('action'),
data: $loginForm.serialize()
})
);
});
UI
VM
public class LoginViewModel
{
public string TenancyName { get; set; }
[Required]
public string UsernameOrEmailAddress { get; set; }
[Required]
public string Password { get; set; }
public bool RememberMe { get; set; }
}
CompanyLoginFormViewModel VM :
public class CompanyLoginFormViewModel
{
public LoginViewModel LoginViewModel { get; set; }
public List<TenantListDto> Tenants { get; set; }
}
*.cshtml page
#{
var companyLoginFormViewModel = TempData["CompanyLoginFormViewModel"] as CompanyLoginFormViewModel;
}
#foreach (var tenant in companyLoginFormViewModel.Tenants)
{
<form class="login-form" action="#Url.Action("Login")?returnUrl=#ViewBag.ReturnUrl" name="companyLoginForm" method="post">
<input type="hidden" name="usernameOrEmailAddress" value="#companyLoginFormViewModel.LoginViewModel.UsernameOrEmailAddress" />
<input type="hidden" name="password" value="#companyLoginFormViewModel.LoginViewModel.Password" />
<input type="hidden" name="rememberMe" value="true" />
<input type="hidden" name="companyUrl" value="true" />
<input type="hidden" name="tenancyName" value="#tenant.TenancyName" />
<div class="row margin-top-10">
<div class="col-xs-3">
<button type="submit" class="btn btn-success uppercase">#L("LogIn")</button>
</div>
</div>
</form>
}
Generated html
<form class="login-form" action="/Account/Login?returnUrl=/Application" name="companyLoginForm" method="post" novalidate="novalidate">
<input type="hidden" name="usernameOrEmailAddress" value="fake#gmail.com">
<input type="hidden" name="password" value="fake">
<input type="hidden" name="rememberMe" value="true">
<input type="hidden" name="companyUrl" value="true">
<input type="hidden" name="tenancyName" value="Asset_Management">
<div class="row margin-top-10">
<div class="col-xs-3">
<button type="submit" class="btn btn-success uppercase">Log in</button>
</div>
</div>
</form>
<form class="login-form" action="/Account/Login?returnUrl=/Application" name="companyLoginForm" method="post" novalidate="novalidate">
<input type="hidden" name="usernameOrEmailAddress" value="fake#gmail.com">
<input type="hidden" name="password" value="fake">
<input type="hidden" name="rememberMe" value="true">
<input type="hidden" name="companyUrl" value="true">
<input type="hidden" name="tenancyName" value="Associates">
<div class="row margin-top-10">
<div class="col-xs-3">
<button type="submit" class="btn btn-success uppercase">Log in</button>
</div>
</div>
</form>
<form class="login-form" action="/Account/Login?returnUrl=/Application" name="companyLoginForm" method="post" novalidate="novalidate">
<input type="hidden" name="usernameOrEmailAddress" value="fake#gmail.com">
<input type="hidden" name="password" value="fake">
<input type="hidden" name="rememberMe" value="true">
<input type="hidden" name="companyUrl" value="true">
<input type="hidden" name="tenancyName" value="ALL">
<div class="row margin-top-10">
<div class="col-xs-3">
<button type="submit" class="btn btn-success uppercase">Log in</button>
</div>
</div>
</form>
Post method
[HttpPost]
public virtual async Task<JsonResult> Login(LoginViewModel loginModel, string returnUrl = "", string returnUrlHash = "", bool companyUrl = false)
{
CheckModelState();
// removed for clarity
}
Question : Even though I have press the 2nd submit button,it always send the tenancyName as first submit button's value.That is Asset_Management.Could you tell me why ? Thanks.

Your problem is with the script.
var $loginForm = $('.login-form');
is a collection of all your forms, but
data: $loginForm.serialize(),
will only serialize the first one, so you always posting the vales of the first form. Modify the script to handle the buttons .click() event and get its associated form
$('.btn-success').click(function(e) {
e.preventDefault(); // if you makes the button type="button" this is not required
var form = $(this).closest('.login-form');
if (!form.valid()) {
return;
}
abp.ui.setBusy(
null,
abp.ajax({
contentType: app.consts.contentTypes.formUrlencoded,
url: form.attr('action'),
data: form.serialize()
})
);
});

Why do you even have <form> and <button> at all?
Why not create links in your foreach loop, something like this:
#Html.ActionLink("Login for " + tenant.Name, "LoginAction", new {Id=tenant.Id})
You can style these links all blue and pretty as you like using CSS afterwards.
Update1, you can pass parameters to your controller using the anonymous object. Do you see how I am passing Id? Your Action will need to accept id, see this answer: passing multiple parameters in #html.actionlink()
Update2, passing username and password like this is very bad practice. You are exposing secure credentials to the view. You should forward the user to the login page with username + password input boxes where the user will login.

Related

ASP.Net Core MVC how to post unknown number of fields against strongly-typed model

Totally stumped on this. I have a page where users can click an "Add Step" button, and some javascript appends rows with additional inputs from a <template> tag to a form. When debugging from the controller, the values are empty.
I looked at the following posts, but neither answer seem to help here (not sure if it's because I'm using .Net core - probably not):
Get post data with unknown number of variables in ASP MVC
Passing data from Form with variable number of inputs - MVC 5
Model (Procedure.cs)
public class Procedure
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public int MinNumber { get; set; }
public int MaxNumber { get; set; }
}
Controller (ProceduresController.cs)
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([FromForm]List<Procedure> model)
{
if (ModelState.IsValid)
{
foreach(var field in model)
{
// Database things will happpen eventually, but at this point the Count = 0
}
}
return View(model);
}
Currently the Create action signature includes the [FromForm] attribute, but I have no idea if that's required here. I also tried List<Procedure> model.
And the View (Create.cshtml)
I've stripped out a lot of the UI and javascript to make things easier to read
#model List<Procedure>
/* also tried #model Procedure */
#{
ViewData["Title"] = "Create Procedure";
ViewData["Sidebar"] = "App";
}
<form asp-action="Create" enctype="multipart/form-data">
<div asp-validation-summary="All" class="field-validation-summary"></div>
<div class="builder">
<div class="field-row">
<div class="field-group">
<label class="field-label">Instruction Name</label>
<input type="text" name="Name" class="field type:text" placeholder="Enter an instruction name" />
</div>
<div class="field-group">
<label for="Type" class="field-label">Instruction Type</label>
<select id="Type" name="Type" class="field type:select stretch">
/* removing for brevity */
</select>
</div>
</div>
<div class="field-row">
<div class="field-group">
<label class="field-label">Minimum Number</label>
<input type="number" name="MinNumber" class="field type:number" />
</div>
<div class="field-group">
<label class="field-label">Maximum Number</label>
<input type="number" name="MaxNumber" class="field type:number" />
</div>
</div>
</div>
<div class="field-row">
<div class="field-group">
<div class="add-step">Add Step</div>
</div>
</div>
<div class="field-row">
<div class="field-group">
<input type="submit" value="Submit Procedures" class="button button-submit" />
</div>
</div>
</form>
<template id="template">
<details class="accordion" open>
<summary>Procedure Step</summary>
<div class="details-content">
<div class="field-row">
<div class="field-group">
<label class="field-label">Instruction Name</label>
<input type="text" name="Name" class="field type:text" placeholder="Enter an instruction name" />
</div>
<div class="field-group">
<label for="Type" class="field-label">Instruction Type</label>
<select id="Type" name="Type" class="field type:select stretch">
/* removing for brevity */
</select>
</div>
</div>
<div class="field-row">
<div class="field-group">
<label class="field-label">Minimum Number</label>
<input type="number" name="MinNumber" class="field type:number" />
</div>
<div class="field-group">
<label class="field-label">Maximum Number</label>
<input type="number" name="MaxNumber" class="field type:number" />
</div>
</div>
</div>
</details>
</template>
#section Scripts {
#{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
<script>
const modal = new Modal();
function getParent(element, selector) {
for (; element && element !== document; element = element.parentNode) {
if (element.classList.contains(selector)) {
return element;
}
}
return null;
}
this.Builder = function () {
this.template = document.querySelector('#template');
this.builder = document.querySelector('.builder');
this.addStep = document.querySelector('.add-step');
this.clone = null;
this.counter = 0;
this.addStep.addEventListener('click', this.add.bind(this));
};
Builder.prototype = {
add: function () {
this.counter++;
this.clone = document.importNode(this.template.content, true);
this.builder.appendChild(this.clone);
// i'm doing things here to set the new field attribute values - removed for brevity
}
};
let builder = new Builder();
builder.add();
</script>
}
In the javascript do I need to set the new field name values a specific way? Should they look like <input type="text" name="Name[index]"> or <input type="text" name="#Model.Procedure[index].Name">?
I tried both but the Count is still 0 when posting back. I've pretty much hit a wall, so I appreciate any and all help.
.NET requires a little more effort for model binding on complex types. Naming of elements is important.
<input name="model[index].Name" />
<input name="model[index].Type" />
<input name="model[index].MinNumber" />
<input name="model[index].MaxNumber" />
The naming convention to bind is: Name of the parameter in your controller (model), index, property name. This will properly pass all objects to your list.
The same is true if you are passing objects to a list inside your model.
For example
public class Example {
public int Id { get; set; }
public List<Procedure> Procedures { get; set; }
}
in this case we just need to tell .NET what property you are mapping too.
<input name="model.Procedures[index].Name" />
In some cases you may need to also add a hidden field for the index: https://www.red-gate.com/simple-talk/dotnet/asp-net/model-binding-asp-net-core/

How to call a ASP.NET CORE WEB API 3.1 from HTML Submit button

I have a very basic question , How to send the text box values on Submit Click to Action method in ASP .Net Core Web API 3.1,
<form method="post" >
<label for="fname">First name:</label>
<input type="text" id="fname" name="firstName"><br><br>
<label for="lname">Last name:</label>
<input type="text" id="lname" name="lastname"><br><br>
<label for="email">Email ID:</label>
<input type="text" id="email" name="email"><br><br>
<input type="submit" value="Submit">
</form>
I have created my Employee Model
public string firstName { get; set; }
public string lastname { get; set; }
public string emailid { get; set; }
I have my Action Method too
[HttpPost]
public IActionResult InsertEmployeeData ([FromBody] Employee employee)
{
var emplFirstName = employee.firstName;
var empLastName = employee.lastname;
return Ok();
}
The available samples in internet are mostly with VIEW as example, but i have a plain html file "Home.html" my wwwroot directory of project, Which need to send value to action method on a click of submit button, is there any way of achieving it without using AJAX Call, View.
Do Share your Ideas/Knowledge
Thank you
Set name to form
<form method="post" name="FormEmployee" >
<label for="fname">First name:</label>
<input type="text" id="fname" name="firstName"><br><br>
<label for="lname">Last name:</label>
<input type="text" id="lname" name="lastname"><br><br>
<label for="email">Email ID:</label>
<input type="text" id="email" name="email"><br><br>
<input type="submit" value="Submit">
</form>
and add script
document.forms["FormEmployee"].addEventListener("submit", e => {
e.preventDefault();
const form = document.forms["FormEmployee"];
const response = await fetch("api/{controller name}", {
method: "POST",
headers: { "Accept": "application/json", "Content-Type": "application/json" },
body: JSON.stringify({
firstName: form.elements["firstName"].value,
lastname: form.elements["lastname"].value,
email: form.elements["email"].value
})
});
});
As far as I know, the fomrbody will not accept Form-data, it will accept json format or else.
If you want to post the data from html to webapi, you should use [FromForm] instead and make sure the html page and the web api is inside the same project.
Then you should add the right controller/action path in the action attribute in the form tag.
More details, you could refer to below codes:
<form method="post" action="WeatherForecast/InsertEmployeeData">
<label for="fname">First name:</label>
<input type="text" id="fname" name="firstName"><br><br>
<label for="lname">Last name:</label>
<input type="text" id="lname" name="lastname"><br><br>
<label for="email">Email ID:</label>
<input type="text" id="email" name="email"><br><br>
<input type="submit" value="Submit">
</form>
Api:
[HttpPost("InsertEmployeeData")]
public IActionResult InsertEmployeeData([FromForm] Employee employee)
{
var emplFirstName = employee.firstName;
var empLastName = employee.lastname;
return Ok();
}
Result:

Upload multiple files with parameters in ASP.NET Core

My view model:
public class FileInfo
{
[Required]
[StringLength(50, ErrorMessage = "TitleErrorMessage", MinimumLength = 2)]
public string Title { get; set; }
[Required]
[StringLength(100, ErrorMessage = "DesErrorMessage", MinimumLength = 3)]
public string Description { get; set; }
[Required]
[DataType(DataType.Upload)]
public IFormFile File { get; set; }
}
The following is _UploadForm partial view file:
#model SessionStateTest.Models.FileInfo
<div class="form-group">
<label>Title</label>
<input class="form-control" asp-for="Title" />
</div>
<div class="form-group">
<label>Description</label>
<input class="form-control" asp-for="Description" />
</div>
<div class="form-group">
<label></label>
<input type="file" asp-for="File" />
</div>
That is used in another View with this code:
<form asp-action="AddUploadForm" asp-controller="Home" method="Post">
<input type="submit" value="Add another file" class="btn btn-sm btn-primary" />
</form>
<form asp-action="Upload" asp-controller="Home" method="Post" enctype="multipart/form-data">
#foreach (var item in Model.Upload)
{
#(await Html.PartialAsync("_UploadForm", item))
}
<div class="col-xs-12">
<input type="submit" value="Upload" class="btn btn-sm btn-info" />
</div>
</form>
Basically AddUploadForm action adds a new view model of type FileInfo to Model.Upload which is my main view model.
The problem is that the list List<FileInfo> vm in Upload action below is totally empty:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Upload(List<FileInfo> vm)
{
.... some other logic
return View();
}
I don't want to use multiple attribute because I would like to force user to provide a title and description for every file.
Any help is kindly appreciated!
Your approach with using _UploadForm generates the following html (let's focus on input's only since this is the most important part)
<input class="form-control" name="Title" />
<input class="form-control" name="Description" />
<input type="file" name="File" />
...
<input class="form-control" name="Title" />
<input class="form-control" name="Description" />
<input type="file" name="File" />
... and so on
So name attributes contains only FileInfo model's properties names without indexes and this is only suitable for the case when your controller expects single model
public IActionResult Upload(FileInfo vm)
And in order to make your html work with your current controller with list of models
public IActionResult Upload(List<FileInfo> vm)
It should look like this
<!-- version 1 -->
<input class="form-control" name="[0].Title" />
<input class="form-control" name="[0].Description" />
<input type="file" name="[0].File" />
...
<input class="form-control" name="[1].Title" />
<input class="form-control" name="[1].Description" />
<input type="file" name="[1].File" />
... and so on
Or
<!-- version 2 -->
<!-- the name before index must match parameter name in controller -->
<input class="form-control" name="vm[0].Title" />
<input class="form-control" name="vm[0].Description" />
<input type="file" name="vm[0].File" />
...
<input class="form-control" name="[1].Title" />
<input class="form-control" name="[1].Description" />
<input type="file" name="vm[1].File" />
... and so on
This is possible to accomplish using tag helpers and partial view in slightly different way. All you need to do is turn partial view's model to list and update asp-for expressions.
_UploadForm.cshtml
#model List<SessionStateTest.Models.FileInfo>
#for (int i = 0; i < Model.Count; i++)
{
<div class="form-group">
<label>Title</label>
<input class="form-control" asp-for="#Model[i].Title" />
</div>
<div class="form-group">
<label>Description</label>
<input class="form-control" asp-for="#Model[i].Description" />
</div>
<div class="form-group">
<label></label>
<input type="file" asp-for="#Model[i].File" />
</div>
}
View
<form asp-action="Upload" asp-controller="Home" method="Post" enctype="multipart/form-data">
#await Html.PartialAsync("_UploadForm", Model.Upload)
<div class="col-xs-12">
<input type="submit" value="Upload" class="btn btn-sm btn-info" />
</div>
</form>
It will generate html like in version 1.

Add antiforgery token in handlebars template

I have changed a razor view to a handlebars template.
In the razor view:
using (Html.BeginForm("Start", "Form", FormMethod.Post, new { formTypeId = #Model.TypeId, organizationId = #Model.OrganizationId }))
{
#Html.AntiForgeryToken()
<input type="hidden" name="formTypeId" value="#Model.TypeId" />
<input type="hidden" name="organizationId" value="#Model.OrganizationId" />
<button class="btn btn-primary btn-block" type="submit">Start</button>
}
In the handlebars template:
<form action="{{StartLink}}" method="post">
<input type="hidden" name="formTypeId" value="{{TypeId}}" />
<input type="hidden" name="organizationId" value="{{OrganizationId}}" />
<button class="btn btn-primary btn-block" style="margin-bottom: 5px;" type="submit">Start</button>
</form>
I am unsure how to add the antiforgery token to the handlebars form.
ok here is what I came up with.
in my index.cshtml I pass an antiforgery token to the js that creates the handlebars template.
#section scripts {
<script>
$(function () {
window.formListBuilder = new app.components.FormListBuilder({
container: '#forms-container',
baseUrl: '#baseUrl.ToString()',
antiForgeryToken: '#Html.AntiForgeryToken()',
currentUser: JSON.parse('#Html.Raw(Json.Serialize(currentUser))')
})
});
</script>
}
then in the handlebars template I use the antiforgery token.
<form action="{{StartLink}}" method="post">
{{{antiForgeryToken}}}
<input type="hidden" name="formTypeId" value="{{TypeId}}" />
<input type="hidden" name="organizationId" value="{{OrganizationId}}" />
<button class="btn btn-primary btn-block" style="margin-bottom: 5px;" type="submit">Start</button>
</form>
is that an acceptable solution or does that bypass security in some way?

.Net Core 2.0 Razor Page handle button click data

With Razor Pages what is the best way to handle this situation where I'd like to have a user click a button within a table and then have a task which uses elements passed from the row for an action?
So far I have this table loaded with a data model and I'd like them to click a row and use that row in my action:
Data Model:
public struct CollapseReportModel
{
public HighLevelOutput Summary { get; set; }
public IEnumerable<HighLevelOutput> WeeksOfData { get; set; }
}
Table:
<div class="container">
<div id="accordion">
#{int counter = 0; }
#foreach (var dataVM in Model.CollapseReport)
{
<div class="card">
<div class="card-header" id="heading#(counter)">
<h5 class="mb-0">
<button class="btn btn-link" data-toggle="collapse" data-target="#collapse#(counter)" aria-expanded="true" aria-controls="collapse#(counter)">
#dataVM.ToString()
</button>
</h5>
</div>
<div id="collapse#(counter)" class="collapse" aria-labelledby="heading#(counter)" data-parent="#accordion">
<div class="card-body">
I'm trying to use this button click to send data to a task but it just passes null values:
<form method="post">
<input type="submit" class="btn btn-default" value="Add to Log" asp-page-handler="AddLog" asp-route-data="#dataVM.Summary.CorpCustomer, #dataVM.Summary.Item)" />
</form>
<ul class="list-group">
#foreach (var item in dataVM.WeeksOfData)
{
<li class="list-group-item">#($"{item.Item}, {item.ItemDescription}, {item.CorpCustomer}, {item.SummedOrder}, {item.SummedForecast}, {item.Difference}, {item.Week}")</li>
}
</ul>
</div>
</div>
</div>
counter++;
}
</div>
</div>
<script src="~/jquery.js"></script>
<script src="~/bootstrap.js"></script>
To this handler:
public Task<IActionResult> OnPostAddLog(string var1, string var2)
{
//Use elements from the row to perform an action
var test = var1;
var t2 = var2;
return null;
}
Just add inputs to your form so they will be posted to your action.
<form method="post">
<input type="hidden" name="var1" value="#dataVM.WhateverProperty1" />
<input type="hidden" name="var2" value="#dataVM.WhateverProperty2" />
<input type="submit" class="btn btn-default" value="Add to Log" asp-page-handler="AddLog" asp-route-data="#dataVM.Summary.CorpCustomer, #dataVM.Summary.Item)" />
</form>
Be sure to match the input name to the parameter name in your action.
Also, decorate your OnPostAddLog method with [HttpPost] as your form use POST method.

Categories