System.StackOverflowException inside Html.BeginForm - c#

I am experiencing very strange behavior. I am creating a simple User creation module. I have created a very simple models,
public class CreateUser
{
[Required]
public string Email { get; set; }
[Required]
[Display(Name = "User name")]
public string Username { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm Password")]
[Compare("Password", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
When I access the view, the following exception message is displayed,
An exception of type 'System.Web.HttpException' occurred in System.Web.dll but was not handled in user code
Additional information: Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper'.
and sometimes the same page gives this exception,
An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
And sometime just this error,
An exception of type 'System.Web.HttpException' occurred in System.Web.dll but was not handled in user code
I have isolated the problem but I am unable to understand what is problem with my view. Below is my View,
#model Practice_Project.Models.Accounts.CreateUser
#{
ViewBag.Title = "Register";
}
#using (Html.BeginForm("Register", "Account", FormMethod.Post, new { #class = "validator-form form-horizontal", role = "form" }))
{
<div class="form-group">
<label for="Email" class="col-sm-3">Email</label>
<div class="col-md-9">
<input type="text" class="form-control inputmask" id="Email" name="Email" placeholder="Email">
</div>
</div>
<div class="form-group">
<label for="username" class="col-sm-3">User Name</label>
<div class="col-md-9">
<input type="text" class="form-control inputmask" id="Username" name="Username" placeholder="User Name">
</div>
</div>
<div class="form-group">
<label for="Password" class="col-sm-3">Password</label>
<div class="col-md-9">
<input type="password" class="form-control" id="Password" name="Password" placeholder="Password">
</div>
</div>
<div class="form-group">
<label for="ConfirmPassword" class="col-sm-3">Confirm Password</label>
<div class="col-md-9">
<input type="password" class="form-control" id="ConfirmPassword" name="ConfirmPassword" placeholder="Re-Type your Password">
</div>
</div>
<div class="form-group">
<div class="col-md-7">
<button type="submit" class="btn btn-success" value="Validate">Sign In</button>
<span class="pull-right">
<small>Not Register ?</small>
<span>
Signup Now
</span>
</span>
</div>
</div>
}
When I remove this last piece of code from the form then the exception does not occur,
<div class="form-group">
<div class="col-md-7">
<button type="submit" class="btn btn-success" value="Validate">Register</button>
<span class="pull-right">
<small>Already Registered?</small>
<span>
SignIn Now
</span>
</span>
</div>
</div>
I don't see anything wrong with this piece of code which is when removed the error is gone. In troubleshooting options it is mentioned that it might be due to Infinite loop but how?
Edit:
The controller is very simple and has just two methods for now as I am adding as I move forward. The following is the code for controller,
[Authorize]
public class AccountController : Controller
{
[AllowAnonymous]
public ActionResult Register()
{
return View();
}
[AllowAnonymous]
public ActionResult Login()
{
return View();
}
}
Note I am trying to generate a link on the Register page that navigates to the Login page.

#Html.Action("Login", "Account") is a method that calls the Login() method of AccountController and renders the partial view it returns. In order to generate a url for a link, you need to use #Url.Action("Login", "Account") although I suggest you use
#Html.ActionLink("SignIn Now", "Login", "Account", null, new { #class = "btn btn-info" })
The reason for the exception (as identified in the comments) was that you had not yet created a view for the Login method (the Login method is trying to return a view which does not exist).
However there are other issues with you code. Your properties are decorated with validation attributes, but because your manually generating the html and omitting the data-val-* attributes, you will not get client side validation, and because you do not have #Html.ValidationSummary() or #Html.ValidationMessageFor() helpers in the form, server side validation errors will not be displayed. Remove the manual html for each property and replace with
<div class="form-group">
#Html.LablFor(m => m.Email, new { #class = "col-sm-3" })
<div class="col-md-9">
#Html.TextBoxFor(m => m.Email, new ( placeholder="Email" })
#Html.ValidationMessageFor(m => m.Email)
</div>
<div>
and ditto for the other properties. Also include the relevant files for client side validation (typically by including #Scripts.Render("~/bundles/jquery") and #Scripts.Render("~/bundles/jqueryval") in the view).
Finally, the typical approach is that you decorate methods required authorization with the [Authorize] attribute. If an unauthorized user navigates to the method, they are automatically redirected to the login page which may include a link to the register page, both of which are used to authorize the user. Having a link in the Register page that navigates to the Login page seems unusual (why would a user that is already registered navigate to the register page?)

Related

.Net Core Ajax form submit not returning non binded model property

I have .Net core web project with form ajax submit. While loading the form, I am setting all property value to the view model. But while posting, it returns only property that binded to the html input. The other values are coming null. How can I get all the view model properties I passed to the form.
Controller
public IActionResult General(string templateId)
{
return View(new TemplateGeneralViewModel { Id = "12-3", Name = "Name", Code = "code" });
}
[HttpPost]
public IActionResult General(TemplateGeneralViewModel model)
{
return View();
}
Cshtml
<form asp-area="AdminPanel" asp-controller="Template" asp-action="General"
method="post" data-ajax-begin="OnFormAjaxBegin" data-ajax-complete="OnFormAjaxComplete"
data-ajax-failure="OnFormAjaxFailed" data-ajax-success="OnFormAjaxSuccess"
data-ajax="true" data-ajax-method="POST">
<div class="form-group">
<label for="tf1">Id</label>
<input type="email" asp-for="Id" class="form-control" id="tf1" aria-describedby="tf1Help" placeholder="e.g. johndoe#looper.com">
<small id="tf1Help" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div class="form-group">
<label class="form-control-label" for="tfValid">Name</label>
<input type="text" asp-for="Name" class="form-control is-valid" id="tfValid">
<div class="valid-feedback"> Success! You've done it. </div>
</div>
</form>
View Model
public class TemplateGeneralViewModel
{
public string Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
Here the property 'Code' is null on submit. How can I get this value without using any hidden field ?
Keep in mind that the model returned in the Post action is not the same instance of the model returned in the Get action. Is built anew with the data present in the html. Where is the field for the code property in your cshtml? Nowhere so when the model is created anew to pass it back in the post action no value could be given.
Just add the field and if you don't want to show it, add the style to hide it
<div class="form-group">
<label class="form-control-label" for="tfValid">Name</label>
<input type="text" asp-for="Name" class="form-control is-valid" id="tfValid">
<div class="valid-feedback"> Success! You've done it. </div>
</div>
<div class="form-group">
<input type="text" asp-for="code" class="d-none">
</div>
You can try to pass the other data in query string with asp-route-{value},you can refer to the official doc of anchor tag helpers.Here is a piece of sample code which passes Id,Name and Code to action:
<form asp-area="AdminPanel" asp-controller="Template" asp-action="General"
method="post" data-ajax-begin="OnFormAjaxBegin" data-ajax-complete="OnFormAjaxComplete"
data-ajax-failure="OnFormAjaxFailed" data-ajax-success="OnFormAjaxSuccess"
data-ajax="true" data-ajax-method="POST" asp-route-Code="#Model.Code">
<div class="form-group">
<label for="tf1">Id</label>
<input type="email" asp-for="Id" class="form-control" id="tf1" aria-describedby="tf1Help" placeholder="e.g. johndoe#looper.com">
<small id="tf1Help" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div class="form-group">
<label class="form-control-label" for="tfValid">Name</label>
<input type="text" asp-for="Name" class="form-control is-valid" id="tfValid">
<div class="valid-feedback"> Success! You've done it. </div>
</div>
</form>

Need to pass text box values as parameters when calling controller method on button click

I'm working on an MVC form that's supposed to let the user enter some descriptions of their problem in various fields, and the text in those fields will then be compiled into an email and sent to us after the user clicks the "Submit" button.
I've got enough wired up to pass hardcoded strings as parameters when the user clicks the button.
#model IntakeFormV2.Models.BotRequest
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<style type="text/css">
#textarea {
height: 500px;
}
</style>
<form class="form-horizontal" method="post" asp-controller="HomeController" asp-action="Create">
#Html.AntiForgeryToken()
<fieldset>
<!-- Form Name -->
<legend>New Request</legend>
<!-- Text input-->
<div class="form-group">
<label class="col-md-4 control-label" for="textinput">Summarize your issue in 100 characters</label>
<div class="col-md-4">
<input id="textinput" name="title" type="text" class="form-control input-md" required="" />
</div>
</div>
<!-- Textarea -->
<div class="form-group">
<label class="col-md-4 control-label" for="textarea">Let us know about any additional information here</label>
<div class="col-md-4">
<textarea class="form-control" id="textarea1" name="description"></textarea>
</div>
</div>
<!-- Button -->
<div class="form-group">
<label class="col-md-4 control-label" for="singlebutton"></label>
<div class="col-md-4">
<p>#Html.ActionLink("Submit Request", "Create", "Home", routeValues: new { title = "hello", description = "world" }, htmlAttributes: new { #class = "btn btn-primary"})</p>
</div>
</div>
</fieldset>
</form>
But rather than passing "Hello" and "World", I need to pass whatever the user inputs into the "textinput" and "textarea1" fields.
I'm fairly new at MVC, but I know the model is supposed to be involved here somewhere. But I haven't been able to find how to set the model values from the view once there's actually text in the input.
namespace RPAIntakeFormV2.Models
{
public class BotRequest
{
public string RequestTitle { get; set; }
public string RequestDescription { get; set; }
public string RequesterName { get; set; }
public string RequesterDepartment { get; set; }
private bool RequestAdded = false;
}
}
And here's my controller, broadly speaking - I've held off adding a lot of the intended functionality until I can actually get the parameters I need in there.
public class HomeController : Controller
{
private IEnumerable<IssuePriority> priorities;
private IEnumerable<IssueStatus> statusses;
public HomeController()
{
index i = 1;
}
public ViewResult Index()
{
return View("Index");
}
public ActionResult Create(string title, string description)
{
try
{
MessageBox.Show("Hello world");
return RedirectToAction("Index");
}
catch (Exception ex)
{
return View("Index2");
}
}
}
Can anyone tell me what I'm missing?
Because you set name for input tag and textarea same with your parameter in action so that you can add action="/Home/Create" to form and change action link to <button> tag or <input type="submit" /> tag like this
<form class="form-horizontal" method="post" action="/Home/Create">
#Html.AntiForgeryToken()
<fieldset>
<!-- Form Name -->
<legend>New Request</legend>
<!-- Text input-->
<div class="form-group">
<label class="col-md-4 control-label" for="textinput">Summarize your issue in 100 characters</label>
<div class="col-md-4">
<input id="textinput" name="title" type="text" class="form-control input-md" required="" />
</div>
</div>
<!-- Textarea -->
<div class="form-group">
<label class="col-md-4 control-label" for="textarea">Let us know about any additional information here</label>
<div class="col-md-4">
<textarea class="form-control" id="textarea1" name="description"></textarea>
</div>
</div>
<!-- Button -->
<div class="form-group">
<label class="col-md-4 control-label" for="singlebutton"></label>
<div class="col-md-4">
<button>Submit Request</button>
</div>
</div>
</fieldset>
</form>
If you have two Create method, you need mark HttpPost attribute
[HttpPost]
public ActionResult Create(string title, string description)
Your form is type of Post then your controller action method should be [HttpPost] your "hello" and "world" is works because of you send this in your query parameter and it consider as [HttpGET]
so first if you require to submit form then your button should be input type of SUBMIT
rather than actionlink use input type submit
<input type="submit" value="Submit Request" class="btn btn-primary" />
if you are used asp.net core then
<form class="form-horizontal" method="post" asp-controller="HomeController" asp-action="Create">
HERE asp-controller and asp-action is asp.net core taghelpers.
your form action was consider as Home/Create just use controller as Home
and if you are using MVC 4 as you tag in MVC-4 taghelpers is not supported.
for MVC-4 you should used like
#using(Html.BeginForm("create", "home", FormMethod.Post))
{
}
then issue with your code is set your action in form and set [HttpPost] attribute on your action method.
and your action should be like below
[HttpPost]
public ActionResult Create(string title, string description)
{
//----
}
and your HTML textbox name must be same as your model Property which you want bind. or you can use #Html.TextBoxFor()
like example
let me know if you require more information.
<div class="col-md-4">
<input id="textinput" name="RequestTitle" type="text" class="form-control input-md" required="" />
</div>
now id="textinput" is bind with your model property RequestTitle

Submiting Ajax form in Net .Core

I created wizard with the jQuery-Steps lib with simple form for now. There is no Submit button. I submit form on finish step via jQuery.
I am already using jQuery calls all over the place, and I have included all scripts, and I needed form for uploading images and other stuff, it's easier with it.
Nothing happens, the controller action is not called.
I just getting redirected on start page with all this parameters in query string.
Like this:
https://localhost:44380/Dashboard?Name=Aaaa&Surename=Bbbb&City=Cccc
Dashboard is all made with Ajax and partial views. And from person option in menu I'm getting redirected on home view in Dashboard with all this parameters.
This is the form:
<form asp-controller="PersonSettings" asp-action="SaveForm" data-ajax="true" data-ajax-method="POST" id="personForm" class="steps-validation wizard-notification">
<!-- Step 1 -->
<h6>Person</h6>
<fieldset>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label for="Name">
Person Name :
<span class="danger">*</span>
</label>
<input autocomplete="off" type="text" class="form-control required" id="Name" name="Name">
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="Surname">
Person Surname:
<span class="danger">*</span>
</label>
<input autocomplete="off" type="url" class="form-control required" id="Surname" name="Surname">
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="City">
Person City:
<span class="danger">*</span>
</label>
<input autocomplete="off" type="url" class="form-control required" id="City" name="City">
</div>
</div>
</div>
</fieldset>
</form>
This is the Model:
public class PersonDto {
public string Name { get; set; }
public string Surname { get; set; }
public string City { get; set; }
}
Here is Action in PersonSettings controller:
[Route("SaveForm")]
[HttpPost]
public IActionResult SaveForm(PersonDto Person) {
//Do something with person model
return Ok();
}
And on finish button I have jQuery submit called on this form:
$('#personForm').submit();
EDIT:
I tried with this parameters:
<form action="PersonSettings/SaveForm" method="post" id="personFrom" class="steps-validation wizard-notification">
And this works.. it's good, but why does the first method not work? Because I need this in Ajax and finish method.
I didn't find any way to post image with form with classic Ajax call. Because I can't read image path (it's not exposed to the browsers).
I think that you didn't installed Tag Helpers. If it works with action attribute, it should work with Tag Helpers attribute. Because you're using Asp-Net-Controller attribute.
https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.TagHelpers
Please let me know.

Controller action not being hit

I have the following form that is trying to let a User SignIn:
<form method="post" asp-controller="Auth" asp-action="SignIn">
<div class="form-group row">
<div class="col">
<input class="form-control" type="email" placeholder="Email" id="email-input" name="email">
</div>
</div>
<div class="form-group row">
<div class="col">
<input class="form-control" type="password" placeholder="Password" id="password-input" name="password">
</div>
</div>
<div class="form-group row">
<div class="col">
<button class="btn btn-success" type="submit" name="button" style="width: 100%;">Login</button>
</div>
</div>
</form>
As you can see the form is using a Controller named Auth and an action on the Controller named SignIn. Here is a sample of the Controller:
public class AuthController : Controller
{
public IActionResult Index()
{
return View(new SignInViewModel());
}
[HttpPost]
public async Task<IActionResult> SignIn(SignInViewModel model)
{
if (ModelState.IsValid)
{
if (await _userService.ValidateCredentials(model.Email, model.Password, out var user))
{
await SignInUser(user.Email);
return RedirectToAction("Index", "Home");
}
}
return View(model);
}
}
I've set a breakpoint on the line if (ModelState.IsValid) and it never gets hit. When I click the login button the page simply refreshes. I really cannot see why this breakpoint is not being hit when the Controller and the Action seem OK to me.
EDIT: SignInViewModel:
public class SignInViewModel
{
[Required(ErrorMessage = "Please Enter an Email Address")]
public string Email { get; set; }
[Required(ErrorMessage = "Please Enter a Password")]
public string Password { get; set; }
}
Snippet from Chrome:
So I've made a new project in an attempt to replicate the issue and it works perfectly for me.
I'm assuming you're not using Areas also? If you are using areas, add this attribute to your controller class:
[Route("AreaNameHere")]
Although if you are able to get to the index page then this isn't the issue.
Are your breakpoints valid? Are symbols being loaded? It could be refreshing due to the ModelState being invalid or the details being invalid and so it's returning the same view. You could use fiddler to see if it's actually making a request at all.
EDIT:
Do you have this line "#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers" in your _ViewImports.cshtml file?

IFormFile always null (ASP.NET Core with MVC/Razor)

I have an ASP.NET Core MVC app attempting to upload an IFormFile. However, the IFormFile is always null. None of the other solutions I've found have solved this issue. What am I doing wrong?
Model
public class EmailForm
{
[Display(Name = "Add a picture")]
[DataType(DataType.Upload)]
[FileExtensions(Extensions = "jpg,png,gif,jpeg,bmp,svg")]
public IFormFile SubmitterPicture { get; set; }
}
Controller
public async Task<ActionResult> Contribute([Bind("SubmitterPicture")] EmailForm model)
{
if (ModelState.IsValid)
{
//Do some stuff
}
}
View
<form method="post" asp-action="Contribute" asp-antiforgery="true" enctype="multipart/form-data" >
<div class="form-group" >
<div class="col-md-2">#Html.LabelFor(m => m.SubmitterPicture)</div>
<div class="col-md-4">
<input type="file" name="SubmitterPicture" id="SubmitterPicture" />
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Contribute" />
</div>
</div>
<form method="post" enctype="multipart/form-data">
</form>
enctype="multipart/form-data"
The multipart form data is the key.
You can alternatively get the file from the HttpContext.Request.Form.Files and get rid of the IFormFile interface in your model. I recommend this method as I believe that files have nothing to do with data models.
The example would be:
public IActionResult Index()
{
//Please note that if no form data is posted
// HttpContext.Request.Form will throw an exception
if (HttpContext.Request.Form.Files[0] != null) {
var file = HttpContext.Request.Form.Files[0];
using (FileStream fs = new FileStream("Your Path", FileMode.CreateNew, FileAccess.Write, FileShare.Write)) {
file.CopyTo(fs);
}
}
return View();
}
If this method also fails, that means there is something wrong with the multipart request.
After sitting with the same problem for hours I found the solution.
The Problem:
Submitting a single input in a form.
The Conclusion:
Basically in your html form this won't work if you only have one input besides the submit button. My property was no longer null as soon I added another property on my viewModel and another input in my form.
I hope this helps someone else.
Razor Page HTML:
#page
#model SomeModel
<form method="post" enctype="multipart/form-data">
<div class="form-group">
<div class="col-md-10">
<p>Upload one or more files using this form:</p>
<input asp-for="Input.SingleInput" type="file" class="form-control" />
<input asp-for="Input.AnotherInput" class="form-control" />
</div>
</div>
<div class="form-group">
<div class="col-md-10">
<input type="submit" value="Upload" />
</div>
</div>
</form>
Razor Page Code Behind:
public class SomeModel: PageModel
{
[BindProperty]
public SomeViewModel Input { get; set; }
public async Task OnGetAsync()
{
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
// This won't be null.
var showMeSomething = Input.SingleInput;
return RedirectToPage();
}
}
SomeViewModel:
public class SomeViewModel
{
public IFormFile SingleInput{ get; set; }
public string AnotherInput{ get; set; }
}
I found that by providing names on my html form, I was able to map to parameters (ASP.NET Core 2.1):
Client side:
<form method="post" enctype="multipart/form-data" action="...">
<input type="file" name="myFile" required />
<input type="password" name="myPass" required />
<input type="submit" value="post" />
</form>
Server side:
[HttpPost()]
public async Task<IActionResult> Post(IFormFile myFile, [FromForm]string myPass)
{
//...
}
Similarly, if anyone working in .Net Core 2.1 and are using asp-for inorder bind the model for your input elements, then do not give name & id properties for that <input> elements. Ideally, the InputTagHelper upon the rendering on the browser generates the name and id properties with their values. If you give the same value for name & id w.r.t Model class, then everything works fine. Else, system doesn't know to which model property it should bind.
Better approach is not to give id and name on the <input>
Below is the sample code.
<form id="uploadForm" enctype="multipart/form-data" name="uploadForm" asp-action="UploadExcel" method="post">
<div class="form-group form-group-lg form-group-sm row ">
<div class="col-sm-12 col-md-10 col-lg-10 uploadDiv" style="display: flex !important">
<label asp-for="FileName" class="col-sm-12 col-md-10 col-lg-10" style="font-size: 15px; max-width: fit-content ">File Name :</label>
<input asp-for="FileName" class="form form-control fileName"
type="text"
placeholder="Enter your file name" />
<!--File upload control-->
<label asp-for="FileName" class="col-sm-12 col-md-10 col-lg-10" style="font-size: 15px; max-width: fit-content ">Attachment :</label>
<input asp-for="File" required class="form-control" type="file" placeholder="File Name" />
</div>
</div>
<div class=" form-group form-group-lg form-group-sm row">
<span asp-validation-for="FileName" class="text-danger"></span>
</div>
<div class=" form-group form-group-lg form-group-sm row">
<small>Please upload .xls or .xlxs or json or xml formatted files only</small>
</div>
<div class="form-group form-group-lg form-group-sm row">
<div class="col-sm-12 col-md-10 col-lg-10">
<input type="submit" class="btn btn-primary" name="submit" id="fileUploadButton" value="Upload" />
<input type="reset" class="btn btn-Gray" name="result" id="resetButton" value="Reset" />
</div>
</div>
<a asp-action="DownloadTemplate" asp-controller="Download" title="Click to download template">Import Batch Transactions Template</a>
</form>
Model.cs
public class ExcelUploadViewModel
{
/// <summary>
/// Gets or Sets the FileName
/// </summary>
[Required(ErrorMessage = "FileName is required")]
public string FileName { get; set; }
[Required(ErrorMessage = "File is required")]
[DataType(DataType.Upload)]
[FromForm(Name = "File")]
public IFormFile File { get; set; }
}
Upon Submit
Thank you.
Your code looks perfectly fine and it should work as long as you are using the same version of code you posted on the question. I have a very strong feeling that you are getting false for the ModelState.IsValid expression and hence seeing the some sort of unexpected behavior.
The FileExtensions data annotation is supposed to be used with String type properties, not with IFormFile type properties. Because of this reason, the IsValid returns false.
So remove that from your view model.
public class EmailForm
{
[Display(Name = "Add a picture")]
[DataType(DataType.Upload)]
public IFormFile SubmitterPicture { get; set; }
}
Here is a relevant GH issue and explanation from one of the team member, for your reference.
FileExtensions Data annotation invalid ModelState #5117
I had this issue and the fix was to make sure the element of "input type="file"" had an id and name set. Then, in the controller, set the IFormFile parameter name to be exactly the same, case as well.
G-luck
Adding the below to my controller fixed the issue for me. It was because my file was large. Adjust the limits as needed.
[RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = int.MaxValue)]

Categories

Resources