0

I have this method in my components called ClearFiles(string PathName) which I currently call by clicking one of the buttons on the component, but now I'm trying to call of them at once but I cannot figure out how to do this. Calling a single ClearFiles method from the component works perfectly, but I'm not quite sure how to call all these methods at once using the parent class. I have made a button in the parent class with the name of ForceClean which I'm hoping can be pressed to call all of the components clear files function.

I create the components in the parent class using a for loop in my PageFileCleaner.razor file like this:

@foreach (var d in filters)
{
     <CompFileFilter FileFilter="d" OnDelete="Delete" ></CompFileFilter>
}

Where filters is a List of type FileFilter which is in the namespace Common.DB. This is how it is declared in the parent class:

private List<FileFilter> filters { get; set; }

And is populated with data from my database using:

        using var context = _db.CreateDbContext();
        filters = await context.FileFilters.AsNoTracking().ToListAsync();
        foreach( var filter in filters)
        {
            FileFilter f = new FileFilter();
        }

And this Parameter (in the child class) is what i use to get the values from that row in the database using FileFilter.Pathname or FileFilter.ID for example:

    [Parameter]
    public FileFilter FileFilter { get; set; }

Database / FileFilter object class:

namespace Common.DB
{
    public class FileFilter
    {
        [Key]
        public int ID { get; set; }
        public string TimerChoice { get; set; }
        public bool IsLoading;
        public string PathName { get; set; } = "";
        public string Extension { get; set; }
        //public string[] extensionList { get; set; }
       ...
    }
}

The clear files function is in my component/child CompFileFilter.razor file (took the code out for this example because its over a hundred lines of code). I'm creating the components in the parent class and each one will have this ClearFiles() method.

public async Task ClearFiles(string PathName)
{  
     filtering code... 
} 

How can I loop through my components from the parent class and call all of there ClearFiles() method?

  • Do you really need all the data logic in the component? Could it not be boilerplate code in a view service that gets called from the component. To do a clear all you then just call a method on the service that loops through the list and does whatever it does to clear. Looping through components to call methods that do data based activity seems not a very sound or logical design to me. – MrC aka Shaun Curtis Sep 01 '22 at 22:04

1 Answers1

2

You can use a dictionary and store the reference of each CompFileFilter component. Then use the references to call the ClearFiles method. You can use the ID property of FileFilter for the dictionary keys.

PageFileCleaner.razor

@foreach (var d in filters)
{
    <CompFileFilter @key="d.ID" @ref="_fileFilterRefs[d.ID]" FileFilter="d" OnDelete="Delete"></CompFileFilter>
}

@code {
    private Dictionary<int, CompFileFilter> _fileFilterRefs = new Dictionary<int, CompFileFilter>();

    private async Task ForceClean()
    {
        // Execute ClearFiles one at a time.
        foreach (var item in _fileFilterRefs)
        {
            await item.Value.ClearFiles();
        }
        
        // Or execute them in parallel.
        //var tasks = _fileFilterRefs.Select(item => Task.Run(item.Value.ClearFiles));
        //await Task.WhenAll(tasks);
    }
}

ClearFiles method doesn't need PathName parameter. You can get the PathName from the FileFilter parameter.

CompFileFilter.razor

@code {
    [Parameter]
    public FileFilter FileFilter { get; set; }

    public async Task ClearFiles()
    {  
        var pathName = FileFilter.PathName;
        // filtering code... 
    } 
}

Edit:

You should manually update the _fileFilterRefs dictionary whenever you remove an item from filters list to keep them synchronized. E.g.

private void Delete(FileFilter fileFilter)
{
    _filters.Remove(fileFilter);

    _fileFilterRefs.Remove(fileFilter.ID);
}

You should clear the _fileFilterRefs dictionary whenever you set the filters list to a new list. E.g.

private async Task RefreshFilters()
{
    _fileFilterRefs.Clear();

    _filters = await filterService.GetFileFilters();
}

But you don't have to do the same thing when you add a new item to the filters list (_filters.Add(...)). In that case it's handled automatically.

In summary:

  • filters.Add(new FileFilter()) -> do nothing.
  • filters.Remove(fileFilter) -> remove fileFilter.ID key from _fileFilterRefs
  • filters = new List() -> clear _fileFilterRefs before setting the list

https://github.com/dotnet/aspnetcore/issues/17361#issuecomment-558138782

Dimitris Maragkos
  • 8,932
  • 2
  • 8
  • 26
  • Thank you! it There is one slight but where if I'm calling this method on three components, it will only clear the ones from the last two components for some reason. – nathan10802 Sep 01 '22 at 22:13
  • Maybe the first one has a wrong path name. Also make sure to clear the dictionary **before** any changes to the `filters` list. – Dimitris Maragkos Sep 01 '22 at 22:43
  • I tried clearing the _fileFilterRefs before I add to or delete from my list of filters and I'm still having a similar problem. The only time the number of fileFilterrefs is accurate is right after I load the page and then click the force clean button. – nathan10802 Sep 02 '22 at 16:26
  • Ok, one last try. First of all, set the `@key` attribute on the `CompFileFilter` component ``. Check my updated answer for how you should handle changes to the `filters` list. – Dimitris Maragkos Sep 02 '22 at 18:43
  • @DimitrisMaragkos: `but that's not the case for the person who asked the question.` Why not? Are his requirements to specifically use Models instead of ViewModels ? `Also it doesn't help that this bad practice is found in...` No one force you to use `FileFilter` as a Model class... use it as a ModelView, as I did, in which case, adding additional properties which do not exist in the database are legitimate, and even sometimes necessary. As you can see, the net result of using a ViewModel is more efficient and robust code. – enet Sep 02 '22 at 22:36
  • He put `FileFilter` class in namespace **Common.DB** and the `ID` property has a `[Key]` attribute. Clearly it's an entity framework model that he's is using inside his component. I never compared my answer to yours or said mine was better, just stated the fact that OP uses `FileFilter` as a DB entity so it's weird to add component reference property to it. I'm not here to judge if uses best practices or not, just trying to help with his specific question. – Dimitris Maragkos Sep 02 '22 at 22:50
  • Also the usage of `Dictionary` for this scenario was suggested by Steve Sanderson on github as I linked in my asnwer. https://github.com/dotnet/aspnetcore/issues/17361#issuecomment-558138782. He created Blazor. – Dimitris Maragkos Sep 02 '22 at 23:05