Skip to content
This repository was archived by the owner on Nov 6, 2018. It is now read-only.

Implement a CombinedFileProvider #142

Closed
wants to merge 10 commits into from
Closed

Implement a CombinedFileProvider #142

wants to merge 10 commits into from

Conversation

mgrosperrin
Copy link
Contributor

This PR is for the issue #49

The behavior for each method is:

  1. GetFileInfo: returns the first IFileInfo of all provided IFileProvider where the Exists property is true,
  2. GetDirectoryContents: merges the the contents of all provided IFileProvider. On duplicate entries (based on Name property), only the first entry is kept,
  3. Watch: returns a IChangeToken that merges the IChangeToken of all provided IFileProvider. When one of the token raises the change notification, the returned token raises it.

@dnfclas
Copy link

dnfclas commented Nov 21, 2015

Hi @mgrosperrin, I'm your friendly neighborhood .NET Foundation Pull Request Bot (You can call me DNFBOT). Thanks for your contribution!
You've already signed the contribution license agreement. Thanks!

The agreement was validated by .NET Foundation and real humans are currently evaluating your PR.

TTYL, DNFBOT;

"projects": ["src"]
"projects": [ "src" ],
"sdk": {
"version": "1.0.0-beta8"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this modification should be part of the PR. Especially not since the latest stable version is RC1, not beta8.

Remove the sdk version in global.json
@mgrosperrin
Copy link
Contributor Author

Hi @MartinJohns,
thanks for your remarks. I've updated the global.json to remove my changes.
I have added documentation to the Watch method, and add more info on the behavior of the others methods as well. I have put it in the returns part of the documentation. Let me know if this is good, or if it will be better in a remarks element.

/// <returns>The file information. Caller must check Exists property. This will be the first existing <see cref="IFileInfo"/> returned by the provided <see cref="IFileProvider"/> or a not found <see cref="IFileInfo"/> if no existing files is found.</returns>
public IFileInfo GetFileInfo(string subpath)
{
foreach (var fileProvider in _fileProviders)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you could change _fileProviders's type to IFileProvider[] to avoid allocating an enumerator here (you don't have to explicitly use a for loop, since the compiler is supposed to optimize that for you).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand your point: IFileProvider[] implements IEnumerable<IFileProvider> so when an enumerator will be allocated (which will not be in your case) ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a variable is declared as an array, the compiler automatically replaces the foreach loop by a for loop and directly accesses the items via array[index] without having to instantiate a new enumerator. The compiler can't apply the same optimization when the type is IEnumerable<>.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'm happy to learn something today!

@kevinchalet
Copy link

Definitely a must have! 😄


public CombinedDirectoryContents(IEnumerable<IEnumerable<IFileInfo>> listOfFiles)
{
foreach (var files in listOfFiles)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code path will immediately enumerate all the files exposed by the different providers, which may take some time. You should maybe consider deferring this operation to the last moment (in GetEnumerator) and using an iterator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made the assumption that the directory contents will be pre-calculated (based on the two other providers), but you're right. I can move it to a Lazy<IEnumerable<IFileInfo>> (to avoiding dealing with the thread-safe and evaluating only once issues).

/// Initializes a new instance of the <see cref="CombinedFileProvider" /> class using a list of file provider.
/// </summary>
/// <param name="fileProviders"></param>
public CombinedFileProvider(params IFileProvider[] fileProviders)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd avoid params. Just take an IEnumerable<IFileProvider>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A params overload definitely makes sense, because that's how people will use this combined thingy:

options.FileProvider = new CombinedFileSystemProvider(
    new EmbeddedFileProvider(...),
    new EmbeddedFileProvider(...));

Since the file provider is a hot path, using IEnumerable<IFileProvider> is not really appropriate. Not to mention that, semantically, it's not immediately clear that the order matters when using IEnumerable. IMHO, an array or a list is much more appropriate in this case. That said, nothing prevents you from adding an IEnumerable<> overload that calls ToArray() or ToList() to avoid allocating enumerators in GetFileInfo/GetDirectoryContents.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A params overload definitely makes sense, because that's how people will use this combined thingy:

Not really. Just make an array if you need that:

new CombinedFileSystemProvider(new[] {
    new EmbeddedFileProvider(...),
    new EmbeddedFileProvider(...) });

I'd keep the params array overload for a static factory method instead of baking it into the ctor.

Converting it to an array or list is more appropriate than enforcing an array be passed in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @davidfowl, is it a general purpose to avoid params, or just in this situation ?

@VaclavElias
Copy link

Just a question. Is this provider going to allow to have Views in Class Library? If yes, how can we use it? If not, please ignore my question :). Thanks.

@MartinJohns
Copy link

@VaclavElias You can do that already.

services.Configure<RazorViewEngineOptions>(options =>
{
    options.FileProvider = yourFileProvider;
});

By the default the used file provider is a PhysicalFileProvider pointing to your views directory. You can replace this with a custom IFileProvider, e.g. the EmbeddedFileProvider to support views embedded in class libraries.

The here proposed CombinedFileProvider approach let's you use a PhysicalFileProvider as a first option, and the EmbeddedFileProvider as a second option. This would use views present in the local file system if they exist, with a fall back to the views embedded in the assembly.

@VaclavElias
Copy link

Thanks @MartinJohns making it clear for me!


public CombinedFileChangeToken(List<IChangeToken> changeTokens)
{
_changeTokens = changeTokens ?? new List<IChangeToken>();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: looks like changeTokens cannot be null when used by Watch. I'd personally removed the null coalescing operator 😄

}
public void Dispose()
{
for (int i = 0; i < _disposables.Count; i++)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of other places with the same issue.

@pranavkm
Copy link
Contributor

@pranavkm
Copy link
Contributor

cc @Eilon for CompositeFileProvider versus CombinedFileProvider. Mvc has the habit of using Composite so it sounded like it would be a familiar pattern for users.

…tead of Lazy)

Add tests for the combined IDsposable
Fix formatting and typos
@Eilon
Copy link
Contributor

Eilon commented Nov 28, 2015

Yeah Composite sounds better to me for this.

@mgrosperrin
Copy link
Contributor Author

Hi,
is there anything else I can do to respond to your remarks?

@pranavkm
Copy link
Contributor

pranavkm commented Dec 7, 2015

Sorry, GitHub doesn't send an email when you push an update. A comment saying you had it updated would be great (for future use). I'll have a look in a bit.

@mgrosperrin
Copy link
Contributor Author

Oh OK, didn't know that 😄
I'll know for the next time.

@muratg muratg added this to the 1.0.0-rc2 milestone Dec 8, 2015
@muratg
Copy link

muratg commented Dec 8, 2015

@pranavkm Could you merge once it's good?

@mgrosperrin Thanks for the contribution!


namespace Microsoft.AspNet.FileProviders
{
internal class CompositeDirectoryContents : IDirectoryContents
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you make these types public? No reason to have them be internal.

@pranavkm
Copy link
Contributor

pranavkm commented Dec 8, 2015

Looks good. If you could get the last few changes.

@MartinJohns
Copy link

This might be a crazy idea given the state of so much code in the repositories.. But can't we start to comment the code? Add some documentation?

Add a constructor to CompositeFileProvider with IEnumerable
@mgrosperrin
Copy link
Contributor Author

Hi,
I have made the changes.
For the constructor of CompositeFileProvider, I have added an overload with an IEnumerable and kept the current one.

@pranavkm
Copy link
Contributor

pranavkm commented Dec 8, 2015

I'll get this merged in soon.

@pranavkm
Copy link
Contributor

pranavkm commented Dec 9, 2015

Merged in 15c9f1d

@pranavkm pranavkm closed this Dec 9, 2015
@pranavkm
Copy link
Contributor

pranavkm commented Dec 9, 2015

Thanks for the PR!

@mgrosperrin
Copy link
Contributor Author

You're welcome!
Thanks for your reviews.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants