I'm hitting what I think is a pretty stupid issue that I am obviously missing something simple on.
I made a simple asp.net mvc site (.net 4.5) and changed the index to have a simple form that I'd like to just post back to itself and spit back the variables.
here is my form
#using(Html.BeginForm())
{
<input type="text" class="form-control" id="empId" placeholder="Enter EmployeeId (ex. 999999)">
<input type="text" class="form-control" id="account" placeholder="Enter account)">
<input type="email" class="form-control" id="email" placeholder="Enter email">
<input type="submit" class="btn btn-default" value="Submit" />
}
and here is my post method
[HttpPost]
public ActionResult Index(string empId, string account, string email)
{
return Content(Request["empId"]);
}
I get nothing back when the page posts. Also in the debugger I can see that the method gets hit, however all the parameters are null even though I filled in the form.
Am I missing something obvious?
You just forget the name attribute:
#using(Html.BeginForm())
{
<input type="text" class="form-control" name="empId" id="empId" placeholder="Enter EmployeeId (ex. 999999)">
<input type="text" class="form-control" name="account" id="account" placeholder="Enter account)">
<input type="email" class="form-control" name="email" id="email" placeholder="Enter email">
<input type="submit" class="btn btn-default" value="Submit" />
}
I always recommend to use model binding instead of some strings or int. If you use them well, it will make the model binding work effortlessly:
Model:
public class ExampleModel
{
public int empId { get; set; }
public string account{ get; set; }
public string email{ get; set; }
}
In the Razor page:
#using(Html.BeginForm())
{
#Html.EditorFor((m => m.intempId, new { #class = "form-control" } ))
#Html.EditorFor((m => m.account, new { #class = "form-control" }))
#Html.EditorFor((m => m.email, new { #class = "form-control" }))
}
and then in controller:
[HttpPost]
public ActionResult Index(ExampleModel model)
{
return Content(model.empId);
}
With the model, you can also add validation and so on, directly on the model and then ASP.NET MVC can put validation in both front-end with jQuery validation and back-end (if (ModelState.IsValid)). Lots of benefits to use models!
Related
I realized in ASP.NET Core 2.2 that no data-val-* attributes are generated if ViewBag and Model has the same property name (in my case it is about the propery "Title"). Because of missing data-val-* attributes client side validation does not work.
This is my Model:
public class ActivityModel
{
public int Id { get; set; }
[Required]
public string Title { get; set; }
}
This is my View:
#model ActivityModel
#{ ViewBag.Title = "This is my title"; }
#using (Html.BeginForm("Create", "Activities"))
{
#Html.EditorFor(model => model.Title)
<input type="submit" value="Create" class="btn btn-default" />
}
#section Scripts {
#{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
The ViewBag.Title property is used in _layout.cshtml to display the current title name.
If I either change the ViewBag or Model property name to something else data-val-* attributes are generated and client side validation works.
From the technical perspective what is causing this behaviour?
While I couldn't figure out the exact reason of why this happening I can suggest you a few workarounds. The obvious one is to assign ViewBag values after rendering inputs
#using (Html.BeginForm("Create", "Activities"))
{
#Html.EditorFor(model => model.Title)
<input type="submit" value="Create" class="btn btn-default" />
}
#{ ViewBag.Title = "This is my title"; }
Or you can make use of input tag helper since it doesn't suffer from this problem
<input asp-for="Title" />
<input type="submit" value="Create" class="btn btn-default" />
Might be interesting
You can disable adding client validation attributes by setting ViewContext.ClientValidationEnabled to false within razor view (this is pretty obvious)
#{
ViewContext.ClientValidationEnabled = false;
}
If for some reason you need to render more then 1 input for the same property with validation the following code
#Html.EditorFor(model => model.Title)
#Html.EditorFor(model => model.Title)
will render no validation for second input
<input class="text-box single-line" data-val="true" data-val-required="The Title field is required." id="Title" name="Title" type="text" value="Activity">
<input class="text-box single-line" id="Title" name="Title" type="text" value="Activity">
This happens because the renderer tracks rendered properties and prevent adding validation more than once for the same property. The properties are tracked by ViewContext.FormContext so it is possible to control this behavior. Before adding validation attributes the renderer check if property was rendered by calling ViewContext.FormContext.RenderedField(propertyName) and if it renders a property it calls ViewContext.FormContext.RenderedField(propertyName, true) to mark property as rendered. But you can call this method with false like this
#Html.EditorFor(model => model.Title)
#{
ViewContext.FormContext.RenderedField("Title", false);
}
#Html.EditorFor(model => model.Title)
which gives the following result
<input class="text-box single-line" data-val="true" data-val-required="The Title field is required." id="Title" name="Title" type="text" value="Activity">
<input class="text-box single-line" data-val="true" data-val-required="The Title field is required." id="Title" name="Title" type="text" value="Activity">
I'm trying to understand how Razor pages work, as well as .Net Core, by creating a small web application and I'm stuck on how to handle the button action within a form. I'm used to the MVC type of process (from when I first tried web apps 5 years ago) where the button would have a onClick action that could be accessed from the code behind but it seems like that's not the same with a Razor page (unless I'm just not seeing it). I have a basic form like this
<form method="post">
<fieldset>
<input type="text" value="" placeholder="user name"/>
<input type="password" value="" placeholder="password"/>
<input type="button" value="Submit" id="submitButton"/>
</fieldset>
So what I'm trying to achieve is when the button is pressed an action in the .cs file is called that will perform a couple different operations (like calling an API, getting a result and then depending on result route to a different page) but even if I add an "onClick" to the button I can't figure out how to hook it up to the code behind. I've seen various answers, most using models and a database but since that's not the same as what I'm doing those examples haven't helped.
I will try to make a simple example for you. Create a razor page and use the name "Test". The Test.cshtml file should have the following contents:
#page
#model WebApplication1.Pages.TestModel
<form method="post">
<fieldset>
<input asp-for="username" placeholder="user name" />
<span asp-validation-for="username" class="text-danger"></span>
<br />
<input asp-for="password" type="password" placeholder="password" />
<span asp-validation-for="password" class="text-danger"></span>
<br />
<input type="submit" value="Submit" id="submitButton" />
</fieldset>
</form>
The Test.cshtml.cs should have the following contents
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace WebApplication1.Pages
{
public class TestModel : PageModel
{
[BindProperty]
public string username { get; set; }
[BindProperty]
public string password { get; set; }
public void OnGet()
{
// you can initialize the values. for example I set the username
username = "test";
}
public IActionResult OnPost()
{
// do something with username and password
if (string.IsNullOrEmpty(password))
{
ModelState.AddModelError("password", "Password is a required field.");
return Page();
}
// or you can redirect to another page
return RedirectToPage("./Index");
}
}
}
Tell me if you need extra explanation for this example. I hope it helps.
I'm new to ASP.net MVC and I am struggling to make this work at the moment. I have a controller method called Add, it looks like this:
public ActionResult Add()
{
// check user is authenticated
if (Request.IsAuthenticated)
{
return View();
}
return RedirectToAction("Index", "Home");
}
//
// POST: /Home/Add
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Add(string title, string description, string priority, string color, FormCollection collection)
{
if (ModelState.IsValid)
{
// create instance of todo object
todo obj = new todo();
try
{
// gather fields
obj.priority = Convert.ToInt32(priority);
obj.color = Convert.ToInt32(color);
obj.title = title;
obj.description = description;
todosDataContext objLinq = new todosDataContext();
// get the users id, convert to string and store it
var userid = Membership.GetUser().ProviderUserKey;
obj.userid = userid.ToString();
// save
objLinq.todos.InsertOnSubmit(obj);
objLinq.SubmitChanges();
return RedirectToAction("Index", "Home");
}
catch
{
return View(obj);
}
}
return RedirectToAction("Index", "Home");
}
If data is sent via POST to the method, it should add the data to the database. That is working fine and everything is added correctly. However, the RedirectToAction is not firing, and the application gets stuck at /Home/Add, when it should redirect to /Home/Index. The view loads however, so it shows /Home/Index but the URL says /Home/Add.
Here is a copy of the partial view that contains the form:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<todo_moble_oauth.Models.todo>" %>
<% using (Html.BeginForm()) { %>
<%: Html.AntiForgeryToken() %>
<%: Html.ValidationSummary(true) %>
<fieldset>
<h3>Title:</h3>
<div class="editor-field">
<input type="text" name="title" />
</div>
<h3>Description:</h3>
<div class="editor-field">
<input type="text" name="description" />
</div>
<h3>Priority:</h3>
<div class="editor-field">
<select name="priority">
<option value="1">Low</option>
<option value="2">Medium</option>
<option value="3">High</option>
</select>
</div>
<div data-role="fieldcontain">
<fieldset data-role="controlgroup">
<h3>Color:</h3>
<input type="radio" name="color" id="radio-choice-1" value="0" checked="checked" />
<label for="radio-choice-1">None</label>
<input type="radio" name="color" id="radio-choice-2" value="1" />
<label for="radio-choice-2">Red</label>
<input type="radio" name="color" id="radio-choice-3" value="2" />
<label for="radio-choice-3">Blue</label>
<input type="radio" name="color" id="radio-choice-4" value="3" />
<label for="radio-choice-4">Yellow</label>
</fieldset>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
So data is being sent to the database and stored, however the redirect is broken.
Turns out it is an issue with jQuery mobile, this threads solution resolved the issue for me:
jQuery Mobile/MVC: Getting the browser URL to change with RedirectToAction
My form code is not generating correct HTML. Here's my code.
Here's the form code:
#using (Html.BeginForm("SendEmail", "PropertyDetails", FormMethod.Post))
{
<fieldset>
<div class="left">
<label for="Name">Your Name</label>
<input type="text" required name="Name" />
<label for="Phone">Your Phone Number</label>
<input type="text" required name="Phone" />
<label for="Email">Your Email</label>
<input type="email" class="text" required name="Email" />
</div>
<div class="right">
<label for="Message">Your Message</label>
<textarea name="Message">
</textarea>
<input type="submit" name="submit" value="send" />
<div class="clear"></div>
</div>
<div class="clear"></div>
</fieldset>
}
Here's my Controller. I have a breakpoint on the first line of the SendMail message which isn't being hit:
public class PropertyDetailsController : Controller
{
[HttpPost]
public ActionResult SendEmail(EmailData email)
{
if (ModelState.IsValid) // BREAKPOINT ON THIS LINE ISN'T BEING HIT
{
etc.
Here's the class that should get populated with the form data and passed into the method (I'm not doing this myself. I assume it's happening automatically under the covers).
public class EmailData
{
private string _name;
private string _email;
private string _phone;
private string _message;
[Required]
[StringLength(50, MinimumLength = 1)]
public string Name
{
get { return _name; }
set { _name = value == null ? string.Empty : value.Trim(); }
}
[Required]
[RegularExpression(RegEx.Email, ErrorMessage = "Invalid e-mail address.")]
public string Email
{
get { return _email; }
set { _email = value == null ? string.Empty : value.Trim(); }
}
public string Phone
{
get { return _phone; }
set { _phone = value == null ? string.Empty : HtmlHelper.StripIllegalXmlChars(value.Trim()); }
}
[StringLength(500, MinimumLength = 1)]
public string Message
{
get { return _message; }
set { _message = value == null ? string.Empty : HtmlHelper.StripIllegalXmlChars(value.Trim()); }
}
}
Here's the HTML that's being generated:
<form action="" method="post">
<fieldset>
<div class="left">
<label for="Name">Your Name</label>
<input type="text" required name="Name" />
<label for="Phone">Your Phone Number</label>
<input type="text" required name="Phone" />
<label for="Email">Your Email</label>
<input type="email" class="text" required name="Email" />
</div>
<div class="right">
<label for="Message">Your Message</label>
<textarea name="Message">
</textarea>
<input type="submit" name="submit" value="send" />
<div class="clear"></div>
</div>
<div class="clear"></div>
</fieldset>
</form>
Any ideas why the action is empty?
Change your <form> declaration to this:
<% using (Html.BeginForm("SendEmail", "[Controller]")) { %>
<!-- Form data -->
<% } %>
Where [Controller] is the name of your Controller minus "Controller". In other words, if the controller name is HomeController, you would use "Home".
Try changing the form declaration within your view to this (using Razor syntax):
#using (this.Html.BeginForm("SendEmail", "[your controller name]", FormMethod.Post))
{
[form HTML]
}
The using statement will ensure that your form is closed correctly.
Also the HtmlHelper.BeginForm extension method is flexible enough that it generates the URI for the action based on the routes defined in your application's RouteCollection, such that if your routes change the form does not need to be updated; the change in URI is handled gracefully and automatically.
You need to add an action to your form
<form action="/SendEmail/" method="post">
But really this should be done using the MVC3 BeginForm helper.
You need to have your HTML rendered by a controller. So put your HTML in a View called "SendEmail" and write a controller action with an [HttpGet] attribute.
Call the Action "SendEmail" and in it just do return View().
Like this
[HttpGet]
public ActionResult SendEmail()
{
return View();
}
That will cause the BeginForm to render the action="" properly.
Then once that's working you are going to have a problem when you do post the data back because the input fields aren't bound to you model. You need to add #model yournamespace.EmailData to the very top of your View and then you need to change your inputs to bind to the model properties.
I've no idea how good this link is but it's the first one I found that looks like it might guide you through a similar process http://geekswithblogs.net/WinAZ/archive/2010/10/30/an-mvc-3-contact-form.aspx
I am trying to bind a dynamic array of elements to a view model where there might be missing indexes in the html
e.g. with the view model
class FooViewModel
{
public List<BarViewModel> Bars { get; set; }
}
class BarViewModel
{
public string Something { get; set; }
}
and the html
<input type="text" name="Bars[1].Something" value="a" />
<input type="text" name="Bars[3].Something" value="b" />
<input type="text" name="Bars[6].Something" value="c" />
at the moment, bars will just be null. how could I get the model binder to ignore any missing elements? i.e. the above would bind to:
FooViewModel
{
Bars
{
BarViewModel { Something = "a" },
BarViewModel { Something = "b" },
BarViewModel { Something = "c" }
}
}
Add the .Index as your first hidden input to deal with out of sequence elements as explained in this Phil Haacked blog post:
<input type="text" name="Bars.Index" value="" />
<input type="text" name="Bars[1].Something" value="a" />
<input type="text" name="Bars[3].Something" value="b" />
<input type="text" name="Bars[6].Something" value="c" />
A possible workaround could be to instantiate the ViewModel and the collection to the correct size (assuming it's known), then update it with TryUpdateModel... something like:
[HttpPost]
public ActionResult SomePostBack(FormCollection form)
{
// you could either look in the formcollection to get this, or retrieve it from the users' settings etc.
int collectionSize = 6;
FooViewModel bars = new FooViewModel();
bars.Bars = new List<BarViewModel>(collectionSize);
TryUpdateModel(bars, form.ToValueProvider());
return View(bars);
}H
MVC is able to populate list itself.
public ActionResult Index(FooViewModel model)
{
...
So no matter if anything is missing mvc will create new List<BarViewModel> and
for each found index - [1],[3],[6] it will create new BarViewModel and add it to List. So you will get FooViewModel with populated Bars.
i didnt know even that worked!
bearing that in mind, id have done something like:
<input type="text" name="Bars.Something" value="a" />
<input type="hidden" name="Bars.Something" value="" />
<input type="text" name="Bars.Something" value="b" />
<input type="hidden" name="Bars.Something" value="" />
<input type="hidden" name="Bars.Something" value="" />
<input type="text" name="Bars.Something" value="c" />
which would hopefully post
a,,b,,,c
but I suspect that will bind in the same way as you describe
Youre probably going to have write a custom model binder that looks for the max index, makes a list of that size then puts the elements in the correct place.
Saying all that, wait for someone else to post a really simple attribute you can put on your property that makes it just work ;D