I have the following class which is being used as an input model for an EditForm in a Blazor server side application.
public class KundeInput
{
[ValidateComplexType]
public List<AnsprechpartnerInput> Ansprechpartner { get; } = new List<AnsprechpartnerInput>();
public string? Kundennummer { get; }
[Required]
[MaxLength(60)]
public string Firma { get; set; } = String.Empty;
[MaxLength(60)]
public string? Name2 { get; set; }
[MaxLength(60)]
public string? Name3 { get; set; }
}
As you can see, my model contains a list of another model called AnsprechpartnerInput. Here is this model:
public class AnsprechpartnerInput
{
public string? Kundennummer { get; set; }
public int Nummer { get; } = -1;
[MaxLength(60)]
[Required]
public string Vorname { get; set; } = String.Empty;
[MaxLength(60)]
[Required]
public string Nachname { get; set; } = String.Empty;
[MaxLength(40)]
[Required]
public string? Bereich { get; set; }
/ * More properties */
}
The validation works fine. However, once I have multiple invalid AnsprechpartnerInput models in my list, the ValidationSummary becomes a mess. Because it displays e.g. 5 times field xyz is invalid.
I know I can set a custom message with the ErrorMessage property but I am not able to use other attributes from my model in this message.
What I want to achive is this:
[Required(ErrorMessage = $"Vorname of {Kundennummer} is required")]
public string Vorname { get; set; } = String.Empty;
I already tried to change the message with reflection but accoridng to Microsoft this way is not recommend or supported
https://github.com/dotnet/aspnetcore/issues/25611
Is there any way to get it to work? I thought of string replacement but I am not sure how I can figure out the right model for my ValidationMessage.
Also is there any way to validate the items of the list by one and get a boolean result? Let's say I want to achive this:
#foreach (var ansprechpartner in Input.Ansprechpartner)
{
if (Input.SelectedAnsprechpartner is null)
Input.SelectedAnsprechpartner = ansprechpartner;
<a #onclick="() => Input.SelectedAnsprechpartner = ansprechpartner"
class="#GetNavListClass(Input.SelectedAnsprechpartner == ansprechpartner)"
id="list-ansprechpartner-tab-#(ansprechpartner.Nummer)"
data-toggle="list"
href="#list-ansprechpartner-#(ansprechpartner.Nummer)"
role="tab"
aria-controls="#(ansprechpartner.Nummer)">
#((MarkupString)(ansprechpartner.Nummer < 0 ? "<span class=\"font-weight-bold\">NEU</span>" : $"({ansprechpartner.Nummer})")) #ansprechpartner.Vorname #ansprechpartner.Nachname
</a>
// When the model ansprechpartner is invalid, I want to display an icon
}
Thanks for any help!
PS: Blazor rocks!
You should use a custom validation attribute where you can explicitly add any error message you want
public class KundennummerValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = (AnsprechpartnerInput)validationContext.ObjectInstance;
if(string.IsNullOrEmpty((string)value))
{
return new ValidationResult($"Vorname of {model.Kundennummer} is required", new[] { "Kundennummer" });
}
return ValidationResult.Success;
}
}
then use
[KundennummerValidation]
public string Vorname { get; set; } = String.Empty;
result :
Validation summary:
Related
In this example, form 1 and 2 use the same data model (User).
In form 1 all fields are mandatory.
In form 2, all fields are mandatory except the Name.
I would like to know how I can manually modify the validation of the Name field in this second form to suit this rule.
User.cs
public class User
{
[Required]
public string Name { get; set; }
[Required]
public string Cpf { get; set; }
[Required]
public string Rg { get; set; }
[Required]
public string Phone { get; set; }
}
Page.razor
//Form 1
<EditForm Model="user">
...
</EditForm>
//Form 2
<EditForm Model="user">
...
</EditForm>
#code {
User user = new User();
}
Well since the attributes are kinda coupled with your properties one way is to have an abstract User Class containing all the properties except Name.
Then extend this class with 2 child classes one with a Required Name the other with an optional Name.
Another way is to implement your own custom requiredIF attribute.
See this example and you can customize it adding some context to your form.
RequiredIf Conditional Validation Attribute
Solution 1: In Form 2 you can use OnSubmit instead of OnValidSubmit so the validation won't stop you. And in the method you passed to OnSubmit you can do the validation yourself.
FormExample:
#(canSendData ? "Sent" : "Not sent")
#if (User is not null)
{
<EditForm Model="User" OnSubmit="Submit">
<DataAnnotationsValidator />
<label>Phone</label>
<InputText #bind-Value="User.Phone" />
<ValidationMessage For="#(() => User.Phone)" />
<label>Name</label>
<InputText #bind-Value="User.Name" />
<ValidationMessage For="#(() => User.Name)" />
<button class="btn btn-success" type="submit">Save</button>
</EditForm>
}
#code {
private bool canSendData;
[Parameter]
public User User { get; set; } = null!;
private void Submit(EditContext editContext)
{
var phoneFieldIdentifier = editContext.Field("Phone");
var nameFieldIdentifier = editContext.Field("Name");
editContext.NotifyFieldChanged(phoneFieldIdentifier);
var validationMessagesCount = editContext.GetValidationMessages().Count();
if (validationMessagesCount == 0)
{// every field is valid
canSendData = true;
StateHasChanged();
}
else if (validationMessagesCount == editContext.GetValidationMessages(nameFieldIdentifier).Count())
{// every field is valid except the field for the `Name` property, but we dont care about it
canSendData = true;
StateHasChanged();
}
else
{// there is/are some invalid field/s that we care about
canSendData = false;
StateHasChanged();
}
}
}
I tried it and it works- it validates and even shows validation messages!
Some links that provided info: Binding a form (docs) and this answer.
I would say that this solution is easy and fast to implement, but it has a downside... Let's say you fill the name field, click Save, but some field was invalid so it didn't send the data and showed the validation message... but before you click Save again, you (for some reason) decide that you don't want the name field to be filled anymore, so you delete its content, now you click Save and the problem has arrived... The validation message for Name property shows. I don't know why but it does... On the other hand, even though the validation message shows the form will be saved. So it seems everything works "properly", BUT for some reason in this scenario the name field validation message is shown.
Solution 2: This another solution is more difficult to implement, but in my opinion it might be the most proper way how to do this- implementation of the custom validator. More here.
Bonus: While searching for info I came across this interesting blog post. It looks exactly like what you need but it says:
"If you use FluentValidation in a commercial project, please sponsor the project financially."
You need to separate out your edit context from your base record.
The user record:
public class User
{
public string Name { get; set; } = String.Empty;
public string Cpf { get; set; } = String.Empty;
public string Rg { get; set; } = String.Empty;
public string Phone { get; set; } = String.Empty;
}
And then two edit model classes that are used by the edit forms:
public class UserEditModel1
{
[Required]
public string Name { get; set; } = String.Empty;
[Required]
public string Cpf { get; set; } = String.Empty;
[Required]
public string Rg { get; set; } = String.Empty;
[Required]
public string Phone { get; set; } = String.Empty;
public UserEditModel1(User user)
{
this.Name = user.Name;
this.Cpf = user.Cpf;
this.Rg = user.Rg;
this.Phone = user.Phone;
}
public User User => new User
{
Name = this.Name,
Cpf = this.Cpf,
Rg = this.Rg,
Phone = this.Phone
};
}
public class UserEditModel2
{
public string Name { get; set; } = String.Empty;
[Required]
public string Cpf { get; set; } = String.Empty;
[Required]
public string Rg { get; set; } = String.Empty;
[Required]
public string Phone { get; set; } = String.Empty;
public UserEditModel2(User user)
{
this.Name = user.Name;
this.Cpf = user.Cpf;
this.Rg = user.Rg;
this.Phone = user.Phone;
}
public User User => new User
{
Name = this.Name,
Cpf = this.Cpf,
Rg = this.Rg,
Phone = this.Phone
};
}
Comment: My User class would be a record rather that a class which I use to check edit state.
Although the thing I want to do seems be really trivial I can not find a way to achieve what I want. I know there exist multiple questions how to put class properties into the list together and separate it by a comma like that on SO, but none of them seemed to be relevant to my case.
I have a class Form defined as follows:
public class Form
{
public string CustomerName { get; set; }
public string CustomerAdress { get; set; }
public string CustomerNumber { get; set; }
public string OfficeAdress { get; set; }
public string Date { get; set; }
public Boolean FunctionalTest { get; set; }
public string Signature { get; set; }
public Form()
{
}
}
In the MainPage.xaml.cs, I create a List<Form> with the Form class properties and subsequently I would like to create a string with all of those class properties separated by a comma. For that case I use basic Join method with Select which converts any kinds of objects to string.
I do that by createCSV method inside MainPage.xaml.cs :
void createCSV()
{
var records = new List<Form>
{
new Form {CustomerName = customerName.Text,
CustomerAdress = customerAdress.Text,
CustomerNumber = customerNumber.Text,
OfficeAdress = officeAdress.Text,
Date = date.Date.ToString("MM/dd/yyyy"),
FunctionalTest = testPicker.ToString()=="YES" ? true : false,
Signature = signature.Text
}
};
string results = String.Join(",", (object)records.Select(o => o.ToString()));
}
The problem is instead of the desirable outcome which is:"Mark Brown,123 High Level Street,01578454521,43 Falmouth Road,12/15/2020,false,Brown"
I am getting: "System.Linq.Enumerable+SelectListIterator'2[MyApp.Form,System.String]"
PS. As you have noticed I am newbie in C#. Instead of non constructive criticism of the code, please for a valuable reply which would help me to understand what am I doing wrong.
Thanks in advance
In the Form class, You can override the ToString() method and use System.Reflection to get your comma string.
Form.cs
public class Form
{
public string CustomerName { get; set; }
public string CustomerAdress { get; set; }
public string CustomerNumber { get; set; }
public string OfficeAdress { get; set; }
public string Date { get; set; }
public bool FunctionalTest { get; set; }
public string Signature { get; set; }
public override string ToString()
{
string modelString = string.Empty;
PropertyInfo[] properties = typeof(Form).GetProperties();
foreach (PropertyInfo property in properties)
{
var value = property.GetValue(this); // you should add a null check here before doing value.ToString as it will break on null
modelString += value.ToString() + ",";
}
return modelString;
}
}
Code
List<string> CSVDataList = new List<string>();
List<Form> FormList = new List<Form>();
...
foreach (var data in FormList)
{
CSVDataList.Add(data.ToString());
}
Now you have a list of string CSVDataList with each Form object's data in comma style
P.S.
for DateTime
var value = property.GetValue(this);
if(value is DateTime date)
{
modelString += date.ToString("dd.MM.yyyy") + ",";
}
I have the following view model in asp.net mvc app.
[Required]
public string Name { get; set; }
[Required]
public int Age { get; set; }
public DateTime DateOfBirth { get; set; }
public Address CurrentAddress { get; set; }
My Address object contains Post Code property, that has RegularExpession attribute to validate UK post codes.
public class Address
{
...
[RegularExpression(#"^[A-Z]{1,2}[0-9][0-9A-Z]? [0-9][A-Z]{2}$")]
public string PostCode { get; set; }
...
}
I want to expand the current functionality to validate PostCode using different regular expression when for example person is non-Uk resident.
Any ideas how I could achieve that? Is there any way to modify regular expression value at run-time?
If you need more information, please let me know and I'll update the question.
You can create your own Person dependand attribute:
public class MyTestAttribute : ValidationAttribute
{
private readonly Regex _regex1;
private readonly Regex _regex2;
public MyTestAttribute(string regex1, string regex2)
{
_regex1 = new Regex(regex1);
_regex2 = new Regex(regex2);
}
public override bool Match(object obj)
{
var input = (string) obj;
if (IsUk())
{
return _regex1.IsMatch(input);
}
return _regex2.IsMatch(input);
}
private bool IsUk()
{
//is person in UK
}
}
Building an OpenGraph .NET Parser but stuck in property binding. I simple fetch the HTML Document and parse it using HtmlAgilityPack. After that I want to check each Node for the specific OpenGraph Key:
Custom Attribute
public class OpenGraphAttribute : Attribute
{
public string Name { get; set; }
public OpenGraphAttribute(string name)
{
Name = name;
}
}
Container Class
public class OGVideoContainer
{
[OpenGraphAttribute("og:video:url")]
public string DefaultUrl { get; set; }
[OpenGraphAttribute("og:video:secure_url")]
public string SecureUrl { get; set; }
[OpenGraphAttribute("og:video:type")]
public string Type { get; set; }
[OpenGraphAttribute("og:video:width")]
public string Width { get; set; }
[OpenGraphAttribute("og:video:height")]
public string Height { get; set; }
[OpenGraphAttribute("og:video:url")]
public string Url { get; set; }
}
Parser
public OGVideoContainer ParseVideo(HtmlDocument doc)
{
var result = new OGVideoContainer();
var parseableAttr = typeof(OGVideoContainer).GetProperties();
foreach (var prop in parseableAttr)
{
var ca = prop.GetCustomAttributes(true).ElementAtOrDefault(0) as OpenGraphAttribute;
if (doc.DocumentNode.SelectSingleNode(String.Format("/html/head/meta[#property='{0}']", ca.Name)) != null)
{
// i am stuck here how can i access the result.propery value?
}
}
return result;
}
But stuck at the result.parameter binding. I have to assign result.DefaultUrl with the corresponding custom attribute name value. How can this be done?
Thanks for any help.
Use prop.GetValue(result) to get the property value.
Thanks. The Setter can be reflected as follows:
var targets = result.GetType().GetProperties();
targets.FirstOrDefault(m => m.Name == prop.Name).SetValue(result, "Nice String here");
I am using MVC4 Application. Below is my model
public class ViewModel
{
public string AssignedUserName { get; set; }
public IList<SelectListItem> PossibleAssignees { get; set; }
[Required]
public string Comment { get; set; }
}
View:
<table style="width: 100%; ">
<tr>
<td>Assigned To:</td>
<td>#Html.DropDownListFor(x => x.AssignedUserName, Model.PossibleAssignees)</td>
<td>#Html.ValidationMessageFor(m => m.AssignedUserName, "Assigned To Required")</td>
<tr></table>
PossibleAssignes is a dropdown, the values should be -> EmptyString,"Mr.Barr".
So if the user selected EmptyString means i need to throw validation like " it is Required" field.
i tried with adding [Required] field validator. it is a collection which having some empty string values as drop down value,
so i am not sure how to use the [Required] field for collection which having empty strings.
i don't want to allow empty strings for dropdown.
how can i validate this ?
don't assign an emtpty string to the values in your list, use it as default value instead and the [Required] will work just fine.
In your view use it as:
#Html.DropDownListFor(x => x.AssignedUserName, Model.PossibleAssignees, String.Empty)
and in your viewmodel:
public class ViewModel
{
[Required]
public string AssignedUserName { get; set; }
public IList<SelectListItem> PossibleAssignees { get; set; }
[Required]
public string Comment { get; set; }
}
EDIT (i'll leave my first answer because it might work for someone else)
You can make a custom validation:
public class ViewModel
{
[Required]
[ValidateAssignedUserName()]
public string AssignedUserName { get; set; }
public IList<SelectListItem> PossibleAssignees { get; set; }
[Required]
public string Comment { get; set; }
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public sealed class ValidateAssignedUserName : ValidationAttribute
{
private const string _defaultErrorMessage = "Select a user.";
public ValidateAssignedUserName()
: base(_defaultErrorMessage)
{ }
public override bool IsValid(object value)
{
string user = value as string;
if (user != null && user.Length > 0)
{
return true;
}
return false;
}
}
Alternatively, you could also use a custom model binder .e.g
public class ViewModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = base.BindModel(controllerContext, bindingContext) as ViewModel;
if (model.AssignedUserName == "-1")
bindingContext.ModelState.AddModelError("AssignedUserName", "No assignee selected");
return model;
}
}
Global.asax.cs
protected void Application_Start()
{
// .....
ModelBinders.Binders.Add(typeof(ViewModel), new ViewModelBinder());
// ....
}
Change your view slightly :
<td>#Html.ValidationMessageFor(m => m.AssignedUserName)</td>
Remove the text from the 2nd param for the above line.
Doing the above will result in ModelState.IsValid being set to false. When you render the view, the text "No assignee selected" will appear where the ValidationMessageFor() call is made.
So there are options to achieve what you need.