Clearing a form after post when using Razorpages model binding - forms

I am using Razor Pages with model binding. When my form posts, the values remain in there when the page reloads after posting. I have tried using ModelState.Clear() but this doesn't seem to do anything.
Specifically, I have an HTML form like this:
<form method="post">
<textarea asp-for="Input.Text" class="form-control" placeholder="No data"></textarea>
<button type="submit" asp-route-param="Submit">Submit</button>
</form>
and the following controller:
public class TestFormModel : PageModel
{
[BindProperty]
public InputModel Input { get; set; }
public IActionResult OnPost()
{
ModelState.Clear();
return Page();
}
}
public class InputModel
{
public string Text {get;set;}
}
On submission, the form remembers the text submitted - I want it to be cleared.
I can do this with jQuery on the client side, but I wondered if there's a RazorPages trick. ModelState.Clear() doesn't seem to do what I want.
Many thanks

Rather than return Page(), redirect to it instead:
public IActionResult OnPost()
{
return RedirectToPage("/TestForm");
}
That will force a new GET request.
Having said that, the usual pattern is to redirect to a different page if the form submission is successful rather than presenting the form again.

I addition to clearing the model state you need to also clear the bound property. like this:
public IActionResult OnPost()
{
ModelState.Clear();
Input.Text = string.Empty;
return Page();
}

Related

How and where to validate uniqueness of attribute/property in ASP.NET Core MVC using Entity Framework Core 6

I want to check, that users table records does not have specific email already stored.
If there is, then ModelState.IsValid returns false in controller action.
I understand need of unique constraint and I understand issues with race conditions. Those are not my concern right now.
At this point I just want to make ModelState.IsValid to return false after querying data in right place and making model invalid.
Should I implement such validation:
in DbContext?
in my entity classes (User / Company etc.)?
in SomeCustomValidation class?
directly in controller (since there I can already query the database...)
somewhere else...
And nice bonus would be create solution reusable across all entities :)
How should I do it?
You can custom validation attribute like below:
public class TestEmailAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
var context = (YourDbContext)validationContext.GetService(typeof(YourDbContext));
if(!context.User.Any(a=>a.Email==value.ToString()))
{
return ValidationResult.Success;
}
return new ValidationResult("Email exists");
}
}
Model:
public class User
{
public int Id { get; set; }
[TestEmail]
public string Email { get; set; }
}
View(Test.cshtml):
#model User
<form method="post" asp-action="Test" asp-controller="Home">
<div class="form-group">
<input asp-for="Email" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<input type="submit" value="Post"/>
</form>
Controller:
//GET render the Test.cshtml
public async Task<IActionResult> Test()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Test(User user)
{
if(!ModelState.IsValid)
{
return View(user);
}
return RedirectToAction("Index");
}
better way is that you check it before every insert or update by :
if(db.Table.Any(x => x.UniqueCloumn == newValue))
error = "this record is already exist"
else
{
db.Table.Add(newObject);
db.Savechanges()
}
also there is some approach for reusable code that I do not recommend :
https://www.codeproject.com/Tips/1143215/Validate-a-Unique-Constraint-at-dbContext-Validate

Dynamic form with multiple objects submission in Spring?

I have an Object CreateProjectFormModel as follows (I am using Spring 4).
public class CreateProjectFormModel {
private Project project;
private List<DUser> users;
public CreateProjectFormModel() {
this.project = new Project();
this.users = new ArrayList<DUser>();
}
public Project getProject() {
return project;
}
public void setProject(Project project) {
this.project = project;
}
public List<DUser> getUsers() {
return users;
}
public void setUsers(List<DUser> users) {
this.users = users;
}
}
I am not able to figure out how to create Controller and a corresponding form so that multiple DUser can be submitted at once - can do it if the object does not consist of a collection?
Read this, but I don't know how may users will be added to the project in advance, so cannot fix the users size.
I read through thymeleaf tutorial, but would be interested to know if can do without use of thymeleaf.
Thanks.
The link you posted in the question List<Foo> as form backing object using spring 3 mvc, correct syntax? should provide a solution for you, what is discussed in the comments
I assume that this solution requires having a fixed amount of input
fields, is that correct? What if you have a dynamic number of input
fields?
does not concern the number of users, which doesn't have to be fixed, rather it concerns the fact that that the properties of the object is differing, which I don't believe is your case. So, if your DUser has a property userName, and e.g. your Project has a property name. Your controller method could simply be,
#RequestMapping(value = "/test", method=RequestMethod.POST)
public String processSubmit(CreateProjectFormModel createProjectFormModel) {
...
}
and your form
<form:form action="/form/test" method="post">
<div class="single">
<input type="text" name="project.name"/>
<input type="text" name="users[0].userName"/>
add another user
<input type="submit" value="Save">
</div>
</form:form>
where you will have to provide some effort is to create a javascript function addNewUserInputSection that will add new set of input fields for the users property with an incremented index, e.g.
<form:form action="/form/test" method="post">
<div class="single">
<input type="text" name="project.name"/>
<input type="text" name="users[0].userName"/>
<input type="text" name="users[1].userName"/>
add another user
<input type="submit" value="Save">
</div>
</form:form>
the examples are basic, but should be enough to have you resolve your issue
Although the above answer works, here's an alternate that does not require you to create a wrapper class/ form class.
Model And Controller
public class Foo {
private String name;
private List<Foo> fooList;
public Foo() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFooList() {
return fooList;
}
public void setFooList(String fooList) {
this.fooList = fooList;
}
}
#Controller("/")
public class FooController{
//returns the ModelAttribute fooListWrapper with the view fooForm
#RequestMapping(value = "/FOO", method = RequestMethod.GET)
public String getFooForm(Model model) {
List<Foo> fooList = service.getFooList();
model.addAttribute("fooList", fooList);
return "list_foo"; //name of the view
}
#RequestMapping(value = "/FOO", method = RequestMethod.POST)
public String postFooList(#ModelAttribute("foo")Foo foo, Model model) {
List<Foo> list = foo.getFooList(); // **This is your desired object.
//If you debug this code, you can easily find this is the list of
//all the foo objects that you wanted, provided you pass them properly.
//Check the jsp file to see one of the ways of passing such a list of objects**
//Rest of the code
}
}
JSP View
<form:form id="form" action="<paste-target-url-here>" method="POST" modelAttribute="fooList">
<c:forEach items="${fooList}" varStatus="i">
<form:input path="fooList[${i.index}].name" type="text"/>
<!-- Here you are setting the data in the appropriate index which will be caught in the controller -->
</c:forEach>
<button>submit</button>
</form:form>

Javascript & partial View

hi
I have page which is action "create" for order.
I want to be able dynamically to add multiple boxes and submit all the items as one request.
I know how to add one a time and save as ajax request but not all at once
Any Ideas how to do that?
public class oder{
public int Id{get;set;}
public string Recipient{get;set;}
public List<Box> Boxes{get;set;}
}
public class Box{
public int Id{get;set;}
public string
}
I have simplified the html code
<% html.SubmitForm(){ %>
<div><%: html.textboxfor(model=>model.Recipient) %></div>
<div class="Action">Add Box</div>
<fieldset>
<legend>Boxes</legend>
</fieldset>
<%}%>
You may find the following blog post useful.

ModelState.IsValid works on a "new" form, but not on an "edit" form

I have a ASP.NET MVC 2.0 application using Entity Framework. All my views use view models, most of them complex. Meaning...the object to be edited is a property of the view model, and not the view model itself.
I am using partial classes with data annotations, and checking ModelState.IsValid inside the POST actions in the controller.
I have a "NEW" form and an "EDIT" form for a simple object with 3 fields!
The ModelState.IsValid check works on the NEW form, and shows the correct "required field" errors, if I try to submit a blank form.
But if I load an EDIT form, and clear the values from some textboxes that are required, and submit the form, I do NOT get validation errors, I just get an exception:
Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerWrapper'.
So my question is, does ModelState.IsValid not work with an EDIT form, since perhaps it's looking at the values from the view model object that were loaded, instead of the FormCollection?
// this one does not validate
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int accountStatusKey, AccountStatusEditViewModel model, FormCollection values)
{
if (ModelState.IsValid)
{
db.UpdateAccountStatus(accountStatusKey, values);
return RedirectToAction("States");
}
else
{
return View("Edit", model);
}
}
// this one does validate
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult New(AccountStatusNewViewModel model, FormCollection values)
{
if (ModelState.IsValid)
{
db.AddAccountStatus(values);
return View("States", new AccountStatusStatesViewModel());
}
else
{
return View("New", model);
}
}
// how I arrive AT the edit form
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Edit(int accountStatusKey)
{
return View("Edit", new AccountStatusEditViewModel(accountStatusKey));
}
// and finally, the view model code
public class AccountStatusEditViewModel : ViewModelBase
{
public AccountStatus AccountStatus { get; private set; }
public IEnumerable States { get; private set; }
public List StatusTypes { get; private set; }
public AccountStatusEditViewModel(int accountStatusKey)
{
AccountStatus = db.GetAccountStatusByKey(accountStatusKey);
States = db.GetAllStates();
StatusTypes = new List();
StatusTypes.Add("Primary Status");
StatusTypes.Add("Secondary Status");
StatusTypes.Add("External Status");
}
public AccountStatusEditViewModel()
{
}
}
// this action method does not work at all either - no db updating, no validation
// the page simply redirects to "States" view, which should only happen if the db
// was being updated, right? But nothing is changing in the DB, and the validation
// never happens.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(AccountStatusEditViewModel model)
{
if (ModelState.IsValid)
{
if (TryUpdateModel(model, "AccountStatus"))
{
return RedirectToAction("States");
}
else
{
return View("Edit", model);
}
}
else
{
return View("Edit", model);
}
}
Since the 2.0 version of MVC i'm not using the formcollection anymore.
I only use the viewmodel in the action's parameter when i have a post, like this:
[HttpPost]
public ActionResult Activatecard(ActivateCardViewModel model)
{
When 'it' can't create my model (entered blabla for a datetime field, or when the validations are not met (i use the validation attributes from the System.ComponentModel.DataAnnotations namespace) i get the ModelState.IsValid equals to false.
I created a blank asp.net mvc2 app, and this was the model that the standard template used for the logon action.
Please eliminate the FromCollection parameters. You can use the default ModelBinders for your view model. Asp.net MVC try to maps the values from your form into your model.
Can you please post the action method, which leads you to the edit form, too?

MVC2 DisplayFormat Attribute Doesn't Work After PostBack

I have a ViewModel with a property as below:
[DisplayName("As Configured On:")]
[DisplayFormat(DataFormatString="{0:d}", ApplyFormatInEditMode=true)]
public DateTime ConfigDate { get; set; }
The Form that displays the ConfigDate is as below:
<%= Html.EditorFor(Model => Model.ConfigDate)%>
When the Index Action comes back, everything looks formatted correctly, i.e. the <input> box has the date value as 12/12/2001. When the form is posted, the result that comes back is as though the DisplayFormat attribute isn't being applied.
EDIT:
More info was requested: here is the code en toto:
The Search Form
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Config.Web.Models.AirplanesViewModel>" %>
<% using (Html.BeginForm("Details", "Airplanes", FormMethod.Post, new { id = "SearchForm" })) { %>
<%= Html.LabelFor(model => model.ConfigDate) %>
<%= Html.EditorFor(Model => Model.ConfigDate)%>
<input id="searchButton" type="submit" value="Search" />
<% } %>
The AirplanesViewModel
public class AirplanesViewModel
{
[DisplayName("As Configured On:")]
[DisplayFormat(DataFormatString="{0:d}", ApplyFormatInEditMode=true)]
public DateTime ConfigDate { get; set; }
}
}
The Controller
[HttpGet]
public ActionResult Index()
{
AirplanesViewModel avm = new AirplanesViewModel
{
ConfigDate = DateTime.Now
};
return View(avm);
}
[HttpPost]
[ActionName("Details")]
public ActionResult Details_Post(AirplanesViewModel avm)
{
return RedirectToAction("Details", avm);
}
[HttpGet]
public ActionResult Details(AirplanesViewModel avm)
{
int page = 0;
int pageSize = 10;
if (!ModelState.IsValid)
{
avm.Airplanes = new PaginatedList<Airplane>();
return View(avm);
}
try
{
Query q = new Query(avm.Query);
PaginatedList<Airplane> paginatedPlanes = new PaginatedList<Airplane>(repo.ByQuery(q), page, pageSize);
avm.Airplanes = paginatedPlanes;
return View(avm);
}
catch (Exception)
{
// Should log exception
avm.Airplanes = new PaginatedList<Airplane>();
return View(avm);
}
}
Additional Information
It has something to do with the redirection to the GET Action. When I take out the POST Action and remove the GET attribute (so both GET and POST use the Details() Action) the problem goes away - but this also gets rid of my pretty URL's when the form is submitted (and causes the annoying "are you sure?" popup on refresh). Strangely, the only problem is the loss of formatting in that field. Everything else is fine.
While waiting for you to clearly specify the problem, here's a full working counter example that what you describe in your question doesn't actually happen:
Model:
public class MyViewModel
{
[DisplayName("As Configured On:")]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime ConfigDate { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
ConfigDate = DateTime.Now
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
View:
<% using (Html.BeginForm()) { %>
<%= Html.EditorFor(x => x.ConfigDate) %>
<input type="submit" value="OK" />
<% } %>
You can submit the form as much as you wish, the formatting will be preserved.
UPDATE:
After providing additional information here's why the problem occurs. When you redirect to the Details action with return RedirectToAction("Details", avm); a query string parameter is applied to the url:
http://localhost:1114/Airplanes/Details?ConfigDate=11/30/2010%2000:00:00
Notice how the hour is included and that's normal. Now when you return the view in the Details GET action the HTML helper responsible for generating the editor template will do the following tasks:
Check to see whether there's a ConfigDate parameter (either GET or POST). If none was found check the value of the Model which is passed to the view and use the ConfigValue property of the model and generate the textbox.
As a ConfigValue is found in the query string the model is not used at all. So it simply takes the value passed in the redirect which also contains the time and uses it to bind to it.