I submit a form, lets say this form contains
<input name="address" ..>
and
<input name="billingAddress" ..>
i have 2 objects to which i need to bind to:
class Address {
String address;
..
}
class BillingAddress {
String address;
..
}
obviously billingAddress wont bind to address in BillingAddress without some magic.
lets say i have multiple identical fields in both Address and BillingAddress but on the form i prefix the billing inputs with billing, ie billingFirstName, billingLastName etc.
is there some elegant way i can bind to BillingAddress that i can reuse for similar problems?
(or is there a better way to solve this then what i have come up with?)
If you wand to use more than one ModelAttribute, you have to create a wrapper object, which holds an instance of each ModelAttribute. In your case I would create a wrapper object called "FormModel" which holds an instance of Address and an instance of a BillingAddress.
class FormModel {
private Address address;
private BillingAddress billingAddress;
// Getters and Setters
}
Now use FormModel as your ModelAttribute.
In your Form you can define your input-elements like:
<input name="address.address" ..>
<input name="billingAddress.address" ..>
Controller:
#RequestMapping(value = "/save", method = RequestMethod.POST)
public String save(Model model, #ModelAttribute() FormModel formModel) {
// process formModel.getAddress()
// process formModel.getBillingAddress()
return "redirect:home";
}
If you use custom validators for Address and BillingAddress, you also have to create a FormModelValidator that calls the AddressValidator and BillingAddressValidator:
public class FormModelValidator implements Validator {
private final AddressValidator addressValidator;
private final BillingAddressValidator billingAddressValidator;
public FormModelValidator(AddressValidator addressValidator,
BillingAddressValidator billingAddressValidator) {
this.addressValidator = addressValidator;
this.billingAddressValidator = billingAddressValidator;
}
public boolean supports(Class<?> clazz) {
return FormModel.class.equals(clazz);
}
public void validate(Object target, Errors errors) {
FormModel formModel = (FormModel) target;
try {
errors.pushNestedPath("address");
ValidationUtils.invokeValidator(this.addressValidator,
formModel.getAddress(), errors);
} finally {
errors.popNestedPath();
}
try {
errors.pushNestedPath("billingAddress");
ValidationUtils.invokeValidator(this.billingAddressValidator,
formModel.getBillingAddress(), errors);
} finally {
errors.popNestedPath();
}
}
}
Related
I have a component that accepts an int parameter - which the component uses to make an API call to retrieve some data. This logic is currently in the component's OnParametersSetAsync().
This component also has a complex-typed parameter.
When this component is used by a parent component that re-renders itself for various reasons, OnParametersSetAsync() is called on this child component - even if none of its parameters have changed. My understanding is that this is because of the complex-type parameter (blazor can't tell if it changed, so it assumes it did).
This results in the API call retriggering needlessly (the actual int parameter didn't change).
Is doing data-retrieval like this not appropriate for OnParametersSetAsync()? If so, how should I change my components to work with the Blazor framework?
Parent Component
Call to ChangeName() triggers the re-render of the parent
<div>
<EditForm Model="favoriteNumber">
<InputSelect #bind-Value="favoriteNumber">
<option value="0">zero</option>
<option value="1">one</option>
<option value="2">two</option>
<option value="3">three</option>
</InputSelect>
</EditForm>
#* This is the child-component in question *#
<TestComponent FavoriteNumber="favoriteNumber" FavoriteBook="favoriteBook" />
<br />
<EditForm Model="person">
First Name:
<InputText #bind-Value="person.FirstName" />
<br />
Last Name:
<InputText #bind-Value="person.LastName" />
</EditForm>
<button #onclick="ChangeName">Change Name</button>
</div>
#code {
private int favoriteNumber = 0;
private Book favoriteBook = new();
private Person person = new() { FirstName = "Joe", LastName = "Smith" };
private void ChangeName()
{
person.FirstName = person.FirstName == "Susan" ? "Joe" : "Susan";
person.LastName = person.LastName == "Smith" ? "Williams" : "Smith";
}
}
Child Component
<div>#infoAboutFavoriteNumber</div>
#code {
[Parameter]
public int FavoriteNumber { get; set; }
[Parameter]
public Book FavoriteBook { get; set; }
private string infoAboutFavoriteNumber = "";
protected override async Task OnParametersSetAsync()
{
infoAboutFavoriteNumber = await ApiService.GetAsync<string>(id: FavoriteNumber.ToString());
}
}
which the component uses to make an API call to retrieve some data.
Your child component should not perform any API calls. It is the parent component that should manage the state of the parent itself and its children, downstreaming the data. If things get complicated, then you'll have to implement a service that handle state. #Peter Morris would certainly advise you to use Blazor State Management Using Fluxor.
Not sure why you use two EditForm components, when you actually should use none. Realize that components are very expensive, and they make your code slow. So use it wisely
Answering your question:
Define a local field to hold the FavoriteNumber parameter property's value as follows, in the child component:
#code
{
[Parameter]
public int FavoriteNumber { get; set; }
private int FavoriteNumberLocal = -1;
}
Note: The FavoriteNumberLocal variable stores the value passed from the parent component. It allows you to locally store and check if its value has changed, and accordingly decide whether to call the Web Api end point or not (Again, you shouldn't do that like this)
protected override async Task OnParametersSetAsync()
{
if( FavoriteNumberLocal != FavoriteNumber)
{
FavoriteNumberLocal = FavoriteNumber;
infoAboutFavoriteNumber = await ApiService.GetAsync<string>(id:
FavoriteNumberLocal.ToString());
}
}
Read the last two comments to this question
You can implement your own state logic with a private int.
A lot cheaper than calling an API again.
<div>#infoAboutFavoriteNumber</div>
#code {
[Parameter]
public int FavoriteNumber { get; set; }
[Parameter]
public Book FavoriteBook { get; set; }
private string infoAboutFavoriteNumber = "";
private int currentNumber = -1; // some invalid value
protected override async Task OnParametersSetAsync()
{
if (currentNumber != FavoriteNumber)
{
currentNumber = FavoriteNumber;
infoAboutFavoriteNumber = await ApiService.GetAsync<string>(id: FavoriteNumber.ToString());
}
}
}
I don't think that this is necessarily a poor practice to put this logic into the OnParametersSetAsync() method. But there is a way to prevent it from making so many API calls. I would create a private variable that stores the value of the public parameter and then every time the OnParametersSetAsync() method is called you compare the two variables and if they are the same, then you don't make the API call, if they are different, then you make the API call, and after it finishes, you assign the private variable to the public parameter's value. To account for the very first time the component calls the method I would probably assign the private variable to default to a -1, as typically ID values aren't negative. But basically I would assign it to a value that would never be equal to any value passed as the parameter. Otherwise the first time it is called your API might not actually get called. Here is an example:
<div>#infoAboutFavoriteNumber</div>
#code {
[Parameter]
public int FavoriteNumber { get; set; }
private int CurrentFavoriteNumber { get; set; } = -1;
[Parameter]
public Book FavoriteBook { get; set; }
private string infoAboutFavoriteNumber = "";
protected override async Task OnParametersSetAsync()
{
if (FavoriteNumber != CurrentFavoriteNumber)
{
infoAboutFavoriteNumber = await ApiService.GetAsync<string>(id: FavoriteNumber.ToString());
CurrentFavoriteNumber = FavoriteNumber;
}
}
}
You can introduce local field and compare its value like other suggests or catch the old value before it changes in SetParametersAsync and it will work in basic scenarios.
However, what if:
the parameter changes too quickly? You will get concurrent requests and the response can come in wrong order.
you leave the page and the response come later?
you want to delay or throttle the parameter changes, e.g. when the parameter is bound to user input.
Reactive Extensions (IObservable) is designed to deal exactly with such scenarios. In Angular (very simmilar to Blazor), the RxJS is first class citizen.
In Blazor, just turn the parameter into IObservable, use RX Operators to deal with it without introducing your own local variables.
readonly Subject<Unit> _parametersSet = new ();
protected override Task OnParametersSetAsync()
{
_parametersSet.OnNext(Unit.Default); //turn OnParametersSetAsync into Observable stream
return base.OnParametersSetAsync();
}
[Parameter] public int FavoriteNumber { get; set; }
protected override void OnInitialized()
{
_parametersSet.Select(_ => FavoriteNumber) //turn parameter into Observable
.DistinctUntilChanged() //detect changes
.Select(value => Observable.FromAsync(cancellationToken =>
{
Console.WriteLine($"FavoriteNumber has changed: {value}");
infoAboutFavoriteNumber = await ApiService.GetAsync(value, cancellationToken);
})
.Switch() //take care of concurrency
.Subscribe();
}
The nice thing about it, that you can create a reusable class or helper method with all the boilerplate. You just specify a parameter and the async method, e.g:
Loader.Create(ObserveParameter(() => FavoriteNumber), LoadAsync);
for more reading, check:
this article: https://blog.vyvojari.dev/blazor-take-advantage-of-system-reactive-aka-observables-part-2/
live demo: https://blazorrepl.telerik.com/QQullYbo21fLFclq27
this question: How to add "reload" and IsLoading status to 2nd level Observable
You're facing a common problem: doing data and data access activity in the UI. Things tend to get messy! In this answer I've separated the data from the components. The data and data access reside in a Dependancy Injection service.
I've also done away with EditForm as you aren't actually using it, and changed the Select to a simple select so we can capture updates, update the model and trigger the data retrieval in the service. This also means that the component gets re-rendered after the model has updated. The Blazor UI event handler for the OnChanged event calls StateHasChanged after calling NumberChanged.
First a class for our Favourites data.
public class MyFavourites
{
public int FavouriteNumber { get; set; }
public string FavouriteNumberInfo { get; set; } = string.Empty;
}
Second a DI service to hold our Favourites data and datastore operations.
namespace Server;
public class MyFavouritesViewService
{
public MyFavourites Favourites { get; private set; } = new MyFavourites();
public async Task GetFavourites()
{
// Emulate a database get
await Task.Delay(100);
Favourites = new MyFavourites { FavouriteNumber = 2, FavouriteNumberInfo = "The number is 2" };
}
public async Task SaveFavourites()
{
// Emulate a database save
await Task.Delay(100);
// Save code here
}
public async Task GetNewNumberInfo(int number)
{
if (number != Favourites.FavouriteNumber)
{
// Emulate a database get
await Task.Delay(100);
Favourites.FavouriteNumberInfo = $"The number is {number}";
Favourites.FavouriteNumber = number;
}
}
}
Next register the Service in Program:
builder.Services.AddScoped<MyFavouritesViewService>();
The component:
<h3>MyFavouriteNumber is #this.Favourites.FavouriteNumber</h3>
<h3>MyFavouriteNumber info is #this.Favourites.FavouriteNumberInfo</h3>
#code {
[Parameter][EditorRequired] public MyFavourites Favourites { get; set; } = new MyFavourites();
}
And finally the page. Note I'm using OwningComponentBase to tie the scope of MyFavouritesViewService to the component lifecycle.
#page "/favourites"
#page "/"
#inherits OwningComponentBase<MyFavouritesViewService>
#namespace Server
<h3>Favourite Number</h3>
<div class="p-5">
<select class="form-select" #onchange="NumberChanged">
#foreach (var option in options)
{
if (option.Key == this.Service.Favourites.FavouriteNumber)
{
<option selected value="#option.Key">#option.Value</option>
}
else
{
<option value="#option.Key">#option.Value</option>
}
}
</select>
<div>
<button class="btn btn-success" #onclick="SaveFavourites">Save</button>
</div>
</div>
<MyFavouriteNumber Favourites=this.Service.Favourites />
#code {
private Dictionary<int, string> options = new Dictionary<int, string>
{
{0, "Zero"},
{1, "One"},
{2, "Two"},
{3, "Three"},
};
// Use OnInitializedAsync to get the original values from the data store
protected async override Task OnInitializedAsync()
=> await this.Service.GetFavourites();
// Demo to show saving
private async Task SaveFavourites()
=> await this.Service.SaveFavourites();
// Async setup ensures GetNewNumberInfo runs to completion
// before StatehasChanged is called by the Handler
// Renderer the checks what's changed and calls SetParamaterAsync
// on MyFavouriteNumber because FavouriteNumber has changed
private async Task NumberChanged(ChangeEventArgs e)
{
if (int.TryParse(e.Value?.ToString(), out int value))
await this.Service.GetNewNumberInfo(value);
}
}
Usually i have seen a server side validation in spring mvc like this for example there is customer page which contains customer related information like customerName ,customerAddress,customerPhoneNumber for this we going to have
customer model object then in the spring controller we are going to call like this
Spring Controller
#RequestMapping(value = "/customerRegistrationScreen")
public String customerRegistrationScreen(Model model) {
Customer customer= new Customer();
model.addAttribute("customer", customer);
return "customerRegistrationScreen";
}
#RequestMapping(value = "/doCustomerRegistration", method = RequestMethod.POST)
public ModelAndView registerCustomer(#ModelAttribute("customer") #Validated Customer customer, BindingResult result,Model model) {
if (result.hasErrors()) {
} else {
}
}
CustomerValidator class
#Component
public class CustomerValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Customer.class.equals(clazz);
}
#Override
public void validate(Object obj, Errors err) {
ValidationUtils.rejectIfEmpty(err, "name", "customer.name.empty");
ValidationUtils.rejectIfEmpty(err, "email", "customer.email.empty");
ValidationUtils.rejectIfEmpty(err, "gender", "customer.gender.empty");
ValidationUtils.rejectIfEmpty(err, "languages", "customer.languages.empty");
User user = (User) obj;
Pattern pattern = Pattern.compile("^[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,6}$",
Pattern.CASE_INSENSITIVE);
if (!(pattern.matcher(customer.getEmail()).matches())) {
err.rejectValue("email", "user.email.invalid");
}
}
}
customerRegistration.jsp
<form:form method="post" modelAttribute="customer" action="doCustomerRegistration">
</form:form>
what if the jsp have two model object information like Customer and product information like customerName,customerAddress,customerPhoneNumber,productID,productName,productPrice here i like to have two model object like customer
& Product if i have two model object how can i map model attribute from jsp and Spring contoller and how can i do server side validation for both the validation
Although it is a good idea to keep models separate in each form, but for this particular use case it can be achieved by following below steps.
The best way to achieve this is to wrap both the Model attributes in one wrapper class and use it in validation.
Lets say Product class looks like this.
public class Product{
String productName;
// other fields and their getter setters
}
Create a wrapper class which wraps both models Customer and Product
public class CustomerProductWrapper {
private Customer customer;
private Product product;
//getter setter
}
In your validator class , change the implementation of supports() method as below
#Override
public boolean supports(Class clazz) {
return CustomerProductWrapper .class.equals(clazz);
}
2.1 Change the implementation of Validation Method as below
#Override
public void validate(Object obj, Errors err) {
//The object that you get now is CustomerProductWrapper object
// refer the fields using this object
ValidationUtils.rejectIfEmpty(err, "customer.name", "customer.name.empty");
ValidationUtils.rejectIfEmpty(err, "customer.email", "customer.email.empty");
ValidationUtils.rejectIfEmpty(err, "customer.gender", "customer.gender.empty");
ValidationUtils.rejectIfEmpty(err, "customer.languages", "customer.languages.empty");
CustomerProductWrapper cpw= (CustomerProductWrapper ) obj;
Pattern pattern = Pattern.compile("^[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,6}$",
Pattern.CASE_INSENSITIVE);
if (!(pattern.matcher(cpw.getCustomer().getEmail()).matches())) {
err.rejectValue("customer.email", "user.email.invalid");
}
//validate a Product field
ValidationUtils.rejectIfEmpty(err, "product.productName", "product.name.empty");
}
In your controller mapping ,
public String customerRegistrationScreen(Model model) {
CustomerProductWrapper cpw= new CustomerProductWrapper ();
model.addAttribute("cpw", cpw);
return "customerRegistrationScreen";
}
And
#RequestMapping(value = "/doCustomerRegistration", method = RequestMethod.POST)
public ModelAndView registerCustomer(#ModelAttribute("cpw") #Validated CustomerProductWrapper cpw, BindingResult result,Model model) {
if (result.hasErrors()) {
} else {
}
}
And finally in your view page
<form:form method="post" modelAttribute="cpw" action="doCustomerRegistration">
</form:form>
Also refer fields using cpw's attributes that is
<form:input path="name" />
<form:errors path="name" cssClass="error" />
will change to
<form:input path="customer.name" />
<form:errors path="customer.name" cssClass="error" />
Similarly for product validation you can use
<form:input path="product.productName" />
<form:errors path="product.productName" cssClass="error" />
That's all.
My task is to upload file.
class FileUploadController : Controller {
public ActionResult Index(HttpPostedFileBase postedFile) {
// When and how to validate it and return appropriate view and model data
// How to store the file in database and appropriately return view and model data
}
}
Validation requires to check if filename already exists(database access) and if file extension(database access) is supported.
So far I architected it like this:
class FileUploadController : Controller {
public ActionResult Index(HttpPostedFileBase postedFile) {
FileUploadModel model=new FileUploadModel();
model.UploadedFile = postedFile;
FileUploadService service = new FileUploadService();
bool valid = service.Validate(postedFile);
if (valid) {
FileUploadViewModel viewModel = service.Save(postedFile);
return View("some_view", viewModel);
}
else {
return View("some_view", service.ViewModel);
}
}
}
public class FileUploadModel
{
public HttpPostedFileBase UploadedFile { get; set; }
}
class FileUploadViewModel {
public ModelState ModelState;
public String Filename;
}
public class FileUploadService
{
private FileUploadViewModel viewModel = new FileUploadViewModel();
public FileUploadViewModel Save(FileUploadModel fileUploadModel)
{
// here i will just save it to the database
// and return viewModel with valid state
}
public bool Validate(FileUploadModel fileUploadModel)
{
// I do the filename, size, etc validation here together with database validation if the file exists and appropriately attach errors to viewModel.ModelState so views can render the error
}
}
As you can see my validate method populates viewModel.ModelState and my Save method returns new FileUploadViewModel. I really can't make up my mind how to design this so it can grow.
My questions are:
- If suddenly update action is added and my service serve update method, I will need to return different data as ViewModel and the validation would be different, should I create new ViewModel class and new Validation..?
- Does my validation occur at valid place?
Thanks in advance for any help.
I have a form that is being validated with JSR 303. After validation fails, the controller returns the form, shows validation errors, and renders the form with the original values. This works fine with all types of form elements except the mutli-select element.
The command object:
public class TaskOrder implements Serializable {
private static final long serialVersionUID = 1L;
...
#XmlTransient
#ManyToMany
#<OtherJPAAnnotations...>
private List<Contractor> subcontractors;
...
}
Contractor class:
public class Contractor implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#<OtherJPAAnnotations...>
private Integer id;
#<OtherJPAAnnotations...>
private String name;
}
Controller:
#RequestMapping(value="processingPath", method=RequestMethod.POST)
public String createNewTaskOrder(#Valid #ModelAttribute TaskOrder taskOrder,
BindingResult result,
Model model) {
...
if (!result.hasErrors()) {
//No binding errors, lots of processing...
else {
model.addAllAttributes(result.getModel());
model.addAttribute(taskOrder);
model.addAttribute("subs", myDAOInstance.getSubs());
return this.setupNewTaskOrder(model);
}
}
#RequestMapping("getFormPath")
public String setupNewTaskOrder(Model model) {
if (!model.containsAttribute("taskOrder")) {
TaskOrder taskOrder = new TaskOrder();
taskOrder.setId(0);
model.addAttribute(taskOrder);
}
return "_n/admin/taskOrder/new";
}
The form:
<form:form commandName="taskOrder" action="processPath">
...
<form:select path="subcontractors">
<form:options items="${subs}" itemValue="id" itemLabel="name"/>
</form:select>
...
</form>
When I open an existing "TaskOrder" with the same form, the values are selected in the "subcontractors" multi-select.
However, trying to add a new "TaskOrder", when it returns from validation the values aren't selected. I know that the selected values are being attached to the model and returned, but just not being selected in the select element.
Thanks for any help!
Thanks for the help. Biju...you were correct!
For those who may stumble across this issue, I added the "EqualsUtil" class described here to my project:
http://www.javapractices.com/topic/TopicAction.do?Id=17
Then added the following method to my Contractor class:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Contractor)) return false;
Contractor c = (Contractor) o;
//Here I ignore the other properties since "id" and "name" are what
//I'm primarily concerned with...
return EqualsUtil.areEqual(this.name, c.name) &&
EqualsUtil.areEqual(this.id, c.id);
}
I am trying to create a custom attribute in mvc to use it's parameters in a view as breadCrumb.
well, this is the code of the attribute
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class BreadCrumbAttribute : Attribute {
public BreadCrumbAttribute(string title, string parent, string url) {
this._title = title;
this._parent = parent;
this._url = url;
}
#region named parameters properties
private string _title;
public string Title {
get { return _title; }
}
private string _url;
public string Url {
get { return _url; }
}
private string _parent;
public string Parent {
get { return _parent; }
}
#endregion
#region positional parameters properties
public string Comments { get; set; }
#endregion
}
this is the call of the attribute
[BreadCrumbAttribute("tile", "parent name", "url")]
public ActionResult Index() {
//code goes here
}
this is a way of how I'd like to get the values. (this is a partial view)
System.Reflection.MemberInfo inf = typeof(ProductsController);
object[] attributes;
attributes = inf.GetCustomAttributes(typeof(BreadCrumbAttribute), false);
foreach (Object attribute in attributes) {
var bca = (BreadCrumbAttribute)attribute;
Response.Write(string.Format("{0}><a href={1}>{2}</a>", bca.Parent, bca.Url, bca.Title));
}
Unfortunately, the attribute didn't get call with the way I implement it. Although, If I add the attribute in Class instead of an Action method it worked.
How could I make it work?
Thanks
The problem is that you are using reflection to get the attributes for the class, so naturally it does not include attributes defined on the action method.
To get those, you should define an ActionFilterAttribute, and in the OnActionExecuting or OnActionExecuted method, you can use filterContext.ActionDescriptor.GetCustomAttributes() method (MSDN description here).
Note that with this solution, you will likely have two different types of attributes: The first one is the one you wrote, to define the breadcrumbs. The second is the one that looks at the attributes on the executing action and builds up the breadcrumb (and presumably adds it to the ViewModel or sticks it in HttpContext.Items or something).