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>
Related
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
I try to link my MongoDB class model to my Blazor page component. I tried to keep all the System.ComponentModel.DataAnnotations.ValidationAttribute to an interface and let the 'real' class with the Bson decoration apart as:
public interface ITestIt
{
int id { get; set; }
[Required(ErrorMessage = "Material cost is required")]
[StringLength(5, ErrorMessage = "Name is too long.")]
string MyName { get; set; }
}
public class TestIt : ITestIt
{
[BsonId]
public int id { get; set; }
public string MyName { get; set; }
}
And include it in my page as:
<h1>Hello, world!</h1>
Welcome to your new app.
<EditForm Model=#testIt OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group">
<label for="Name">Name</label>
<InputText #bind-Value=testIt.MyName class="form-control" id="Name" />
<ValidationMessage For="() => testIt.MyName" Description="Salut" />
</div>
<input type="submit" class="btn btn-primary" value="Save" />
</EditForm>
#code{
private ITestIt testIt;
private bool IsDone = false;
protected override async Task OnInitializedAsync()
{
if (IsDone) return;
testIt = new TestIt();
IsDone = true;
}
private void HandleValidSubmit()
{
Console.WriteLine("OnValidSubmit");
}
But it don't work, what is the best way to separate the both without having to decorate all my data model with the DataAnnotations tags and not having to copy one by one each property one by one to and other object?
Thanks!
I don't think Blazor reflects over data annotations of interfaces, only the properties of the implementing object.
I keep my validations in a separate project completely. To do this I use FluentValidation.
You can write a component that accepts an EditContext as a cascading parameter, hook into the events where it requests validation, and execute the FluentValidation code.
Or you can use a pre-made library such as https://www.nuget.org/packages/PeterLeslieMorris.Blazor.FluentValidation/
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();
}
AppUser identity model:
public virtual ICollection<UserPhones> UserPhones { get; set; }
Using Razor Pages, I call a partial view, like so:
#await Html.PartialAsync("_NameAndID", Model.AppUser)
PageModel:
[BindProperty]
public AppUser AppUser { get; set; }
public IActionResult OnGet()
{
AppUser = _userManager.Users
//.Include(x => x.UserAddresses) //OMITTED BC USING LAZY LOADING
.SingleOrDefaultAsync(x => x.UserName ==
_httpContext.HttpContext.User.Identity.Name).Result;
return Page();
}
Within _NameAndID.cshtml, I explicitly reference a particular telephone from the UserPhones entity. With:
<input type="hidden" asp-for="UserPhones
.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).UserPhoneId" />
//other properties removed for brevity
<div class="rvt-grid__item">
<label asp-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber">Mobile Phone</label>
<input asp-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber" autocomplete="tel" />
<span asp-validation-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber"></span>
</div>
At runtime, the explicit mobile phone number is loaded properly. However when posting to public async Task<IActionResult> OnPostAsync() the related AppUser.UserPhones is null. (The problem)
Can you help?
Thank you in advance!!!!
The Reason
The asp-for does not work well for this scenario.
Considering your code in _NameAndID.cshtml :
<input asp-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber" autocomplete="tel" />
Note the LINQ extension method .SingleOrDefault(...) here. The asp-for here does not know how to get the name for UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber, so it just render it as PhoneNumber. As a result, the rendered html will be :
<input autocomplete="tel" type="text" id="PhoneNumber" name="PhoneNumber" value="">
Let's say someone inputs an value of 911, when posted to server, the payload will be :
PhoneNumber=911
As your page model on server side is :
[BindProperty]
public AppUser AppUser{get;set;}
public IActionResult OnGet()
{
// ...
}
public IActionResult OnPostAsync()
{
return Page();
}
Note the AppUser.UserPhones property is a collection. in other words, AppUser expects a payload like :
UserPhones[0].UserPhoneId=1&UserPhones[0].PhoneNumber=911&UserPhones[1].UserPhoneId=2&UserPhones[1].PhoneNumber=119
However, what you send to the server is :
PhoneNumber=911
So the App.UserPhones will always be null and the AppUser.PhoneNumber property will be 911.
How to Fix
Firstly, in order to bind the UserPhones automatically, I change the type of App.UserPhones to IList<UserPhones> , so that we can use a index syntax
public class AppUser : IdentityUser{
// public virtual ICollection<UserPhones> UserPhones { get; set; }
public virtual IList<UserPhones> UserPhones { get; set; }
}
Secondly, don't use complex query in asp-for, use simple index syntax instead. For example, if you would like to post some UserPhones or post all UserPhones, you can add an index for each field :
#for(var i=0;i <Model.UserPhones.Count(); i++) {
<div class="rvt-grid__item">
<label asp-for="#Model.UserPhones[i].UserPhoneId"></label>
<input asp-for="#Model.UserPhones[i].UserPhoneId"/>
<label asp-for="#Model.UserPhones[i].PhoneNumber"></label>
<input asp-for="#Model.UserPhones[i].PhoneNumber"/>
<span asp-validation-for="#Model.UserPhones[i].PhoneNumber"></span>
</div>
}
In this way, when someone submits the form, AppUser.UserPhones will be the correctly set. Here's a screenshot of demo :
I have a EF-model which contains a "key" and a "value". The value-table contains a FK to the key. In the EF-model it looks like this:
public partial class dict_key
{
public dict_key()
{
this.dict_value = new HashSet<dict_value>();
}
public int id { get; set; }
public string name { get; set; }
...
public virtual ICollection<dict_value> dict_value { get; set; } //dict_value contains a string "value"
}
My controller is passing the information for editing like this:
// GET: Keys/Texts/5
[Authorize]
public async Task<ActionResult> Texts(int? id)
{
var key = await db.dict_key
.Include(x => x.dict_value)
.Where(x => x.id.Equals(id.Value))
.FirstOrDefaultAsync();
return View(key);
// Debugging 'key' shows that dict_value has 3 correct values.
}
This gets passed to my View which shows the dict_value's correct:
#model Dict.Models.dict_key
#using (Html.BeginForm())
{
<div>Key: #Model.name </div>
<table class="table">
<tr>
<th>Language</th>
<th>Text</th>
</tr>
#for (var i = 0; i < Model.dict_value.Count(); i++)
{
<tr>
<td> #Model.dict_value.ElementAt(i).dict_lang.name_en </td>
<td> #Html.EditorFor(x => x.dict_value.ElementAt(i).value) </td>
</tr>
}
<div class="form-group">
<input type="submit" value="Save" />
</div>
</table>
}
When submitting my changes back to the controller...
[HttpPost]
public async Task<ActionResult> Texts(dict_key dict_key)
{
if (ModelState.IsValid)
{
//Also tried: db.Entry(dict_key).State = EntityState.Modified;
db.Entry(dict_key.dict_value).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Texts");
}
return View(dict_key);
}
..then my "dict_key" is totally different from the object I passed to my edit-view. The passed object contained the collection of dict_value's and the "returned" and edited object returns with the proper key object, but with an empty dict_value collection.
I try to avoid using a userdefined model or the viewbag to do all of that stuff manually. What is the best practise solution for this?
Collection.ElementAt doesn't generate a proper field name in Razor. You need a List. Here you should use a view model instead of your entity directly and simply make your dict_value collection a List<dict_value> there.
Alternatively, you can create an editor template for dict_value and then in your view just do:
#Html.EditorFor(m => m.dict_value)
Where dict_value there is your entire collection. Razor will render an instance of the editor template for each member of the collection and properly index everything.