Blazor Webassembly (.NET 5) customized component with files: validation does not work - validation

I found this great post by Chris Sainty: Creating Bespoke Input Components for Blazor from Scratch. It is exactly what I need, but not with string, but with uploaded files IBrowserFile. So I have adapted and extended the example for me. The customized component displays the new files and saves it in my model, but in the CSS the status unfortunately stays on class="modified invalid".
I must be missing a small detail here. What is it? Thanks in advance for any hints.
Here is my code reduced to the essentials.
Selection.razor
#page "/selection"
#inherits ParentComponent<SelectionTestModel>
<PageComponent #ref="Page" Model="Model" StatusCode="StatusCode" PageType="PageType.Touch">
<PageBody>
<EditForm Model="Model" OnValidSubmit="Save">
<DataAnnotationsValidator />
<DocumentComponent #ref="DocumentUpload" #bind-Documents="Model.Files" />
</EditForm>
</PageBody>
</PageComponent>
#code {
private DocumentComponent DocumentUpload;
}
SelectionTestModel.cs
public class SelectionTestModel
{
public int? KeyID { get; set; }
/* ... */
[System.ComponentModel.DisplayName("Document")]
[System.ComponentModel.DataAnnotations.Display(Name = "Document")]
[System.ComponentModel.DataAnnotations.Range(2, 2, ErrorMessage = "You have to bring exactly two files!")]
public List<DocumentModel> Files { get; set; } = new List<DocumentModel>();
}
DocumentModel
public class DocumentModel
{
public int? Id { get; set; }
public string Reference { get; set; }
public string Name { get; set; }
public long Size { get; set; }
public string ContentType { get; set; }
public string Content { get; set; } /*file as base64 string*/
}
DocumentComponent.razor
#using System.Linq.Expressions
<div class="dropzone rounded #_dropClass #_validClass">
<InputFile id="inputDrop" multiple
ondragover="event.preventDefault()"
ondragstart="event.dataTransfer.setData('', event.target.id)"
accept="#AllowedFileTypes"
OnChange="OnInputFileChange"
#ondragenter="HandleDragEnter"
#ondragleave="HandleDragLeave" />
#*...*#
</div>
#code {
[CascadingParameter] public EditContext EditContext { get; set; }
[Parameter] public List<DocumentModel> Documents { get; set; } = new List<DocumentModel>();
[Parameter] public EventCallback<List<DocumentModel>> DocumentsChanged { get; set; }
[Parameter] public Expression<Func<List<DocumentModel>>> DocumentsExpression { get; set; }
/*...*/
public List<string> AllowedFileTypes { get; set; } = new List<string> { ".pdf", /*...*/ };
private FieldIdentifier _fieldIdentifier;
private string _validClass => EditContext?.FieldCssClass(_fieldIdentifier) ?? null;
protected override void OnInitialized()
{
base.OnInitialized();
_fieldIdentifier = FieldIdentifier.Create(DocumentsExpression);
}
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
// validation: do we accept the file (content type, amount of files, size)
if (e.FileCount == 1) // keep it simple for this example
{
// read from IBrowserFile and return DocumentModel in memory only
Documents.Add(await SaveFile(e.File));
await DocumentsChanged.InvokeAsync(Documents);
EditContext?.NotifyFieldChanged(_fieldIdentifier);
}
}
/*...*/
}
How does it behave in the browser (Chrome)
After loading the page everything looks as expected.
After that I upload a single file. So I have one file and I expect two. The validation turns red and I get "modified invalid". So far everything is great.
Finally I drag another file into the component and get two files. I can also see this in the model. But unfortunately the class attribute "modified valid" is not set.
Thanks again for any advice

I dug too deep in the wrong direction and didn't see the obvious.
The problem is that there is an attribute set in the model that does not throw an error, but also cannot validate.
The Range attribute is not for lists and therefore the model could never validate. With an own attribute I could work around this.
SelectionTestModel.cs
[Library.Validation.Attribute.ListRange(2, 2)]
public List<DocumentModel> Files { get; set; } = new List<DocumentModel>();
ListRangeAttribute.cs
namespace Library.Validation.Attribute
{
public class ListRangeAttribute : ValidationAttribute
{
public int Minimum { get; set; }
public int Maximum { get; set; }
public ListRangeAttribute(int minimum = 0, int maximum = int.MaxValue)
{
Minimum = minimum > 0 ? minimum : 0;
Maximum = maximum;
}
public string GetErrorMessage(string displayName) { /* ... */ }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var list = value as IList;
if (list == null)
{
throw new InvalidOperationException($"Attribute {nameof(ListRangeAttribute)} must be on a property of type {nameof(IList)}.");
}
if ((list?.Count ?? 0) < Minimum || (list?.Count ?? int.MaxValue) > Maximum)
{
return new ValidationResult(GetErrorMessage(validationContext.DisplayName), new[] { validationContext.MemberName });
}
return ValidationResult.Success;
}
}
}
I hope this post can help others.
Remaining: Now I am left with a new mystery.
Why does the validation text disappear after a save button click, which could not be saved due to an invalid state of the model!?

Related

FluentValidation Set Valid Result to a Custom Property

I am validating the content for file import and I have an IsValid property for each line.
public class Header
{
public int LineNumber { get; set; }
public string Property1 { get; set; }
public string Property2 { get; set; }
public bool IsValid { get; set; }
}
public class Detail
{
public int LineNumber { get; set; }
public string Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
public bool IsValid { get; set; }
}
public class Trailer
{
public int LineNumber { get; set; }
public string Property1 { get; set; }
public bool IsValid { get; set; }
}
public class ImportFile
{
public Header Header { get; set; }
public List<Detail> Details { get; set; }
public Trailer Trailer { get; set; }
}
and my validators look somewhat like:
public class DetailValidator : AbstractValidator<Detail>
{
public DetailValidator()
{
RuleFor(d => d.Property1)
.Cascade(CascadeMode.Stop)
.NotEmpty()
.WithState(d => d.LineNumber)
.Length(3)
.WithState(d => d.LineNumber);
RuleFor(d => d.Property2)
.Cascade(CascadeMode.Stop)
.NotEmpty()
.WithState(d => d.LineNumber)
.MaximumLength(50)
.WithState(d => d.LineNumber);
...
}
}
public class ImportFileValidator : AbstractValidator<ImportFile>
{
public ImportFileValidator()
{
RuleFor(f => f.Header)
.SetValidator(new HeaderValidator());
RuleForEach(f => f.Details)
.SetValidator(new DetailsValidator());
...
}
}
After I call the validation, I wanted to set the IsValid property of each line of the file (be it header, detail or trailer) base from the result of the validation.
What is possible for now is, since I am using WithState to store the LineNumber, I can match the ValidationResult against the ImportFile instance to set each line's validity like below:
ImportFile file = // parsed file content
var result = new ImportFileValidator().Validate(file);
foreach (var detail in file.Details)
{
var error = result.Errors.FirstOrDefault(e =>
Convert.ToInt32(e.CustomState) == detail.LineNumber);
detail.IsValid = error == null;
}
And I have to check for the header and trailer as well.
Is there a way I can do this inside the validators? I am trying to explore the FluentValidation's documentation, but I can't seem to find what I needed there.
As I was exploring the available methods in FluentValidation, I saw OnFailure and OnAnyFailure methods. This methods might be a good help to what I needed to do, but the problem is they're obsolete as of 10.3.0 and will be removed on version 11. They're suggesting to use a custom validator instead.
The Header, Detail and Trailer Abstract Validators remain as is.
I created custom validator extensions for those 3.
Each extension methods creates an instance of the corresponding validator and executes it. I can make them generic for header, detail and trailer since they will do the same thing, set IsValid property to the validation result.
public static IRuleBuilderOptionsConditions<ImportFile, T> IsHeaderValid<T>(this IRuleBuilder<ImportFile, T> ruleBuilder)
where T : Header
{
return builder.Custom((header, context) =>
{
// Create the Header Abstract Validator Instance
var validator = new HeaderValidator();
var result = validator.Validate(Header);
header.IsValid = result.IsValid;
// Pass the errors to the context
result.Errors.ForEach(context.AddFailure);
}
}
I had to change the ImportFileValidator to call the custom validators, instead of using setvalidator.
The ImportFileValidator looks like this:
public class ImportFileValidator : AbstractValidator<ImportFile>
{
public ImportFileValidator()
{
RuleFor(f => f.Header)
.IsHeaderValid();
RuleForEach(f => f.Details)
.IsDetailValid();
...
}
}
This is pretty much how I was able to set the IsValid property without having to do the matching I initially did in the question.

Xamarin ImageSource in ListView not updating

I have a ListView that contains images, the images are fetched from a server. The first API call I make gets the data in var people. I load the listview with a placeholder image, then run the 2nd API call to get the images for each item in the listview. I receive a byte[] as the image, and I convert it to an ImageSource. I have a search button at the top of the page that I set binding to TempImage which uses the byte[] as its source, and it changes to the images that are loaded. So the conversion of byte[] to ImageSource is fine. The initial setting of p.PictureImageSource = "name_circle.png" also works correctly. However, setting p.PictureImageSource to the converted byte[] does NOT work. It never changes from the initial "name_circle.png". Any ideas?
var people = peopleModel.Response;
if(people.Count == 0)
{
ShowNoResults = true;
}
else
{
ShowNoResults = false;
Results = peopleModel.Response;
foreach (PersonViewModel p in Results)
{
p.Initials = p.FirstName[0].ToString() + p.LastName[0];
p.PictureImageSource = "name_circle.png";
}
}
//must do 2 seperate loops so the initials load before going on with 2nd search
foreach (PersonViewModel p in Results)
{
IsBusy = false;
var peopleImage = await peopleService.GetPersonImage("p.Email");
if ((peopleImage.Error == null) && (peopleImage.Response != null))
{
p.Picture = peopleImage.Response;
byte[] imageAsBytes = (byte[])peopleImage.Response;
p.PictureImageSource = ImageSource.FromStream(() => new MemoryStream(imageAsBytes));
TempImage = ImageSource.FromStream(() => new MemoryStream(imageAsBytes));
}
}
OnPropertyChanged();
-
public class PersonViewModel : INotifyPropertyChanged
{
public WorkstationViewModel WorkstationDetail { get; set; }
public List<PointViewModel> Points { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName { get; set; }
public string Initials { get; set; }
public string Email { get; set; }
public string ID { get; set; }
public string Department { get; set; }
public string BuildingName { get; set; }
public string SiteID { get; set; }
public string BuildingID { get; set; }
public string FloorNumber { get; set; }
public string FloorID { get; set; }
public string Workstation { get; set; }
public string Title { get; set; }
public string Phone { get; set; }
public byte[] Picture { get; set; }
public ImageSource PictureImageSource { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
Apineda was correct in thinking the listview wasn't refreshing at all with the new data. I separated the two API calls into two different methods (not sure if this part is entirely necessary). After the completion of the 2nd call, I set the listview's ItemSource to null, then back to the Results value, forcing a refreshing. Now the images show.
searchGestureRecognizer.Tapped += async (s, e) => {
await _viewModel.GetResults();
await _viewModel.GetImagesForResults();
resultsListView.ItemsSource = null;
resultsListView.ItemsSource = _viewModel.Results;
};

Get value from IsolatedStorageSetting in windows phone7

Can anyone help me to solve this problem?
I am using visual studio2010 and its windows phone 7 application
I have created addpet.xaml and mypet.xaml.
Created IsolatedStorageSetting object in mypet.cs file
{
public static IsolatedStorageSettings settings=IsolatedStorageSettings.ApplicationSettings;
}
I have 5 textboxes and I am storing its value in list item.That list stores in IsolatedStorageSetting object.
{
SaveMypet savepet = new SaveMypet();
savepet.Name = txt_name.ToString();
savepet.Birthday = txt_birthday.ToString();
savepet.FavFood = txt_favouritefood.ToString();
savepet.DocNo = txt_doctorsno.ToString();
savepet.VacDate = txt_vacdate.ToString();
savepet.FavToy = txt_favouritetoy.ToString();
// savepet.Image1 = image1.Source;
listMyPet.Add(savepet);
settings.Add("iso_listMyPet", listMyPet);
}
Now I want to access this object in addpet.cs and cast it to list and then want to assign to listbox.
Like this, I have did but does not work
Created list object in addpet.cs
{
static List<pg_addPet> list_listMyPet = null;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
list_listMyPet = (List<pg_addPet>)pg_addPet.settings["iso_mypet_list"];
listbox_mypet.ItemsSource = list_listMyPet;
}
And my SaveMypet class is
public class SaveMypet
{
public string Name { get; set; }
public string Birthday { get; set; }
public string FavFood { get; set; }
public string DocNo { get; set; }
public string VacDate { get; set; }
public string FavToy { get; set; }
// public ImageSource Image1 { get; set; }
}
You've declared the settings property as static. Therefore, you need to use the class name to access it:
list_listMyPet = (List<pg_addPet>)pg_mypet.settings["iso_mypet_list"];
Try the following:
if(!pg_mypet.settings.TryGetValue("iso_mypet_list", out list_listMyPet))
{
list_listMyPet = new List<pg_addPet>();
}
This will try to retrieve the value and if if fails it will create an empty list instead.

update element of list of Complex object with linq

Hi everybody and thanks in advance for solution
I've these objects
List<AccessModuleInfo> listOfAccessModuleInfo = new List<AccessModuleInfo>();
public class AccessModuleInfo
{
public int AccessModuleId { get; set; }
public string AccessModuleName { get; set; }
public List<AccessRoleInfo> ListOfAccessRole { get; set; }
}
public class AccessRoleInfo
{
public int AccessRoleId { get; set; }
public string AccessRoleName { get; set; }
public bool AccessRoleValue { get; set; }
}
I receive listOfAccessModuleInfo that is already fill and when I try to update AccessRoleValue in specific AccessModuleInfo, all AccessRoleValue with the same AccessRoleId are updated .
I want update only AccessRoleValue of specific AccessModuleInfo (for example with AccessModuleId = 4)
Thanks
Normally something like this should work just fine:
// You didn't confuse == vs = did you?
// Single also helps us debug and ensure we're only getting one result
var itemToUpdate = listOfAccessModuleInfo.Where(x => x.AccessModuleId == 4).Single();
itemToUpdate.ListOfAccessRole.ForEach(x => x.AccessRoleValue = newValue);

Silverlight RIA request only returns 1

I have the following code...
internal sealed class Menu_Metadata
{
private Menu_Metadata() { }
[Key]
public int MenuHeaderID { get; set; }
public string MenuHeaderName { get; set; }
[Include]
[Association("MenuHeader_MenuItem", "MenuHeaderID", "MenuHeaderID")]
public IEnumerable<MenuItem> MenuItems { get; set; }
}
public class EmployeeMenuItem
{
[Key]
public int MenuItemID { get; set; }
public int MenuHeaderID { get; set; }
public string MenuItemName { get; set; }
}
[MetadataType(typeof(Menu_Metadata))]
public class EmployeeMenu
{
public int MenuHeaderID { get; set; }
public string MenuHeaderName { get; set; }
public IEnumerable<EmployeeMenuItem> MenuItems { get; set; }
}
[EnableClientAccess()]
public class EmployeeMenuService : DomainService
{
public IQueryable<EmployeeMenu> GetEmployeeMenu()
{
BusinessLogic.Employee blEmployee = new BusinessLogic.Employee();
int employeeId = blEmployee.GetEmployeeIdFromUserName(HttpContext.Current.User.Identity.Name);
var menuHeaders = blEmployee.GetEmployeeMenuHeaders(employeeId);
// This works here!
IQueryable<EmployeeMenu> retValue = from mh in menuHeaders
select new EmployeeMenu
{
MenuHeaderID = mh.ID,
MenuHeaderName = mh.HeaderName,
MenuItems = from mhi in mh.MenuHeaderItems
select new EmployeeMenuItem
{
MenuItemID = mhi.MenuItemID,
MenuHeaderID = mhi.MenuHeaderID,
MenuItemName = mhi.MenuItem.MenuItemName
}
};
return retValue;
}
}
which is consumed by a Silverlight Accordion control
EmployeeMenuContext employeeMenuContext = new EmployeeMenuContext();
accordion2.ItemsSource = employeeMenuContext.EmployeeMenus;
employeeMenuContext.Load(employeeMenuContext.GetEmployeeMenuQuery());
The MenuHeaderName's are coming through just fine, and the MenuItems is populated for the 1st MenuHeader, but the other 3 MenuItems are empty.
Any ideas why?
At what point is it easier to use EF4 and RIA??? This seems so incredibly and needlessly complex to get a simple Entity with a sub-class in it!
I'm not entirely sure, but it appears that the problem may have been that I was trying to databind in the xaml constructor. I created a Loaded event and moved the code there and it seems to work now.

Resources