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?
Related
I'll only include the relevant properties of the model and the related lines for simplicity:
Model: Department.cs
Property: public virtual Staff HOD { get; set; }
ControllerMethod:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Add([Bind] Department newDepartment)
{
await _departmentRepository.AddDepartmentAsync(newDepartment);
await _departmentRepository.SaveTransactionAsync(true);
return Redirect("Details" + "/" + newDepartment.Id);
}
View:
#model Department
#inject IStaffRepository staffRepository
#{
// select list
var listOfEmployees = new List<Staff>();
foreach (var staff in await staffRepository.GetAllStaffAsync())
{
listOfEmployees.Add(staff);
}
var selectListHOD = new SelectList(listOfEmployees, "EmployeeId", "Name"); //populates the option element with the correct Employee Id
}
<form class="form-group" method="post">
<div class="d-flex flex-column">
<div class="input-group" style="margin-bottom: 1%">
#Html.DropDownListFor(department => department.HOD, selectListHOD, htmlAttributes: new { #class="form-control" })
</div>
<div class="input-group">
<input class="btn btn-primary" type="submit" value="Save" />
</div>
</div>
</form>
Result:
(I have not included the other properties seen here in the question, but trust me, the properties of the parameter to the controller get correctly populated with them, except for the HOD property)
Source:
<form class="form-group" method="post">
<div class="d-flex flex-column">
<div class="input-group" style="margin-bottom: 1%">
<input class="form-control" id="Id" name="Id" placeholder="Id" type="text" value="" /> </div>
<div class="input-group" style="margin-bottom: 1%">
<input class="form-control" id="Name" name="Name" placeholder="Department" type="text" value="" /> </div>
<div class="input-group" style="margin-bottom: 1%">
<input class="form-control" id="Description" name="Description" placeholder="Description" type="text" value="" /> </div>
<div class="input-group" style="margin-bottom: 1%">
<select class="form-control" id="HOD" name="HOD">
<option value="EMP01">Employee 1</option>
<option value="EMP02">Employee 2</option>
</select>
</div>
<div class="input-group">
<input class="btn btn-primary" type="submit" value="Save" /> </div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8AIwSWkSO0hJqMxy-oQKoeG35LTDmI2N1XHDZL9qeaxRxc17TyZbT2z9Iq0GMPkRyE7HnaX1r4ZSIs0bQATYB7w_A_HZDBXGETMmpdSqlMXZCmf7cH9ECzrNGz0Wuu9zHkE50yI92vPY-GxNPG-pRhs" />
</form>
My guess is that the value being supplied to the controller seems to be wrong. How would I pass the actual object instead of the ID?
I tried using this instead of EmployeeId, but it throws Object Reference not found exception.
You cannot bind an object with <select></select>.You can try to bind Hod.EmployeeId with <select></select>.And add a hidden input to bind Hod.Name.When selected value changes,change the value of hidden input.Here is a demo:
<form class="form-group" method="post">
<div class="d-flex flex-column">
<div class="input-group" style="margin-bottom: 1%">
#Html.DropDownListFor(department => department.HOD.EmployeeId, selectListHOD, htmlAttributes: new { #class = "form-control",#onChange= "AddName()" })
<input hidden asp-for="HOD.Name" />
</div>
<div class="input-group">
<input class="btn btn-primary" type="submit" value="Save" />
</div>
</div>
</form>
#section scripts
{
<script>
$(function () {
AddName();
})
function AddName() {
$("#HOD_Name").val($("#HOD_EmployeeId option:selected").text());
}
</script>
}
result:
I have a question about .Net MVC, because Visual Studio shows me the application server error in the following form?
Server error in application '/'. The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its
dependencies) may have been removed, renamed, or temporarily
unavailable. Please review the URL below and make sure it is spelled
correctly.
Requested URL: / Register
<form action="Account/Registra" method="post">
<input class="form-control" type="number" name="id" value="" placeholder="Id" required/>
<input class="form-control" type="text" name="nombre" value="" placeholder="Nombre" required/>
<input class="form-control" type="text" name="correo" value="" placeholder="Correo" required/>
<input class="form-control" type="password" name="contrasena" value="" placeholder="Contraseña"/>
<input class="form-control" type="text" name="direccion" value="" placeholder="Direccion" required/>
<input class="form-control btn btn-primary" type="submit" name="btn" value="Registrar" />
</form>
And this one that has the same action text "controller / action" but does this work correctly for me.
<form action="Account/Validar" method="get">
<input type="text" name="correo" value="" placeholder="Correo" required class="form-control"/>
<input type="password" name="contrasena" value="" placeholder="Contraseña" required class="form-control"/>
<input type="submit" name="btn" value="Ingresar" class="btn btn-primary form-control"/>
</form>
They both have their respective existing methods and views. Any solution or opinion about this error?
public ActionResult Registra()
{
return View();
}
[HttpPost]
public ActionResult Registra(MUsuario usuario)
{
HttpClient _http = new HttpClient();
_http.BaseAddress = new Uri("http://localhost:60467/");
var request = _http.PostAsJsonAsync("api/Values",usuario).Result;
if (request.IsSuccessStatusCode)
{
return RedirectToAction("Index","Account");
}
else
{
return RedirectToAction("Registro", "Account");
}
}
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.
I'm trying to use a textbox value in controller route and am having some trouble with the syntax. Essentially, I have my end users selecting a file and then submitting the data to the api to process and send the data off. I have working static code below, but can't get the xml path to be dynamically populated via the textbox value.
note that (as far as I can tell) the forward slash at the end of the path is required since there is a dot in the file path.
#using (Html.BeginForm("", "api/food/dummy food file.xml/"))
{
<div class="row">
<div class="col-lg-6">
<label class="control-label">Select File</label>
<div class="input-group">
<label class="input-group-btn">
<span class="btn btn-default">
Browse… <input type="file" style="display: none;" single>
</span>
</label>
<input id="food.filepath" name="food.filepath" type="text" class="form-control" readonly>
</div>
</div>
</div>
<br />
<div>
<button id = "btnSubmit" type="submit" class="btn btn-primary">Submit</button>
</div>
}
I don't what the syntax would be, but I can't get something like the below to work.
#using (Html.BeginForm("", "api/food/" + food.filepath + "/"))
{
<div class="row">
<div class="col-lg-6">
<label class="control-label">Select File</label>
<div class="input-group">
<label class="input-group-btn">
<span class="btn btn-default">
Browse… <input type="file" style="display: none;" single>
</span>
</label>
<input id="food.filepath" name="food.filepath" type="text" class="form-control" readonly>
</div>
</div>
</div>
<br />
<div>
<button id = "btnSubmit" type="submit" class="btn btn-primary">Submit</button>
</div>
}
Because the form is rendered on server and the value food.filepath is on client, you cannot mix them. Changing form action according to client values need to be done on client with javascript.
You can remove the file from action a add it on submit javascript action, for example by changing BeginForm to this:
#using (Html.BeginForm("", "api/food/", FormMethod.Get, new { onSubmit = "this.dataset.act = this.dataset.act||this.action; this.action = this.dataset.act + this['food.filepath'].value" } ))
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.