Chris Barr
Chris Barr

Reputation: 34135

Ignoring files in MVC Bundles

We are using feature flags (set in Web.Config) to toggle the visibility in the UI of certain features that aren't yet complete. I'd also like my MVC bundles to NOT include the related JS files since they would just be useless files the client would have to download when the feature isn't enabled.

So far I've found IgnoreList.Ignore but I can't seem to get it to work. This is basically what I'm doing:

public static void RegisterBundles(BundleCollection bundles)
{
    if (!appConfiguration.FeatureXEnabled)
    {
        //Skip these files if the Feature X is not enabled!
        //When this flag is removed, these lines can be deleted and the files will be included like normal
        bundles.IgnoreList.Ignore("~/App/Organization/SomeCtrl.js", OptimizationMode.Always);
        bundles.IgnoreList.Ignore("~/App/Filters/SomeFilter.js", OptimizationMode.Always);
    }

    var globalBundle = new ScriptBundle("~/bundles/app-global").Include(
        "~/App/RootCtrl.js",
        "~/App/Directives/*.js",
        "~/App/Services/*.js",
        "~/App/Application.js"
    );
    bundles.Add(globalBundle);

    var appOrgBundle = new ScriptBundle("~/bundles/app-org");
    appOrgBundle.Include(
        "~/App/Filters/*.js",
        "~/App/Organization/*.js"
    );

    bundles.Add(appOrgBundle);
}

With the above code, the files in the ignore list are still being included! What am I doing wrong here?

Upvotes: 5

Views: 3562

Answers (5)

Brian S
Brian S

Reputation: 5785

I upvoted Gent's answer above because it helped me accomplish what I wanted.

But then I expanded upon it and created a generic IBundleOrderer that uses lambdas to exclude whatever items you want to exclude very simply.

Note the excludes parameter is a params list so you are not limited to a single exclusion pattern. This doesn't do any ordering, but it could be easily added with another parameter.

public class ExcludeItemsOrderer : IBundleOrderer
{
    private Func<BundleFile, bool>[] _excludes;

    public ExcludeItemsOrderer(params Func<BundleFile, bool>[] excludes)
    {
        _excludes = excludes;
    }

    public IEnumerable<BundleFile> OrderFiles(BundleContext context, IEnumerable<BundleFile> files)
    {
        if(_excludes == null || _excludes.Length == 0)
        {
            return files;
        }

        foreach(var exclude in _excludes)
        {
            var exclusions = files.Where(exclude);
            files = files.Except(exclusions);
        }

        return files;
    }
}

And then I created an extension method to simplify using it:

public static class BundleExtensions
{
    public static Bundle AsIsOrderExcluding(this Bundle bundle, params Func<BundleFile, bool>[] excludes)
    {
        bundle.Orderer = new ExcludeItemsOrderer(excludes);
        return bundle;
    }
}

So that it can be easily applied as you create your bundles, like this:

bundles.Add(
            new ScriptBundle("~/Bundles/App/js")
                .IncludeDirectory("~/App", "*.js", true)
                .AsIsOrderExcluding(bf => bf.VirtualFile.VirtualPath.IndexOf("/skip_me/") >= 0)
            );

Upvotes: 1

Alireza
Alireza

Reputation: 10486

Bundles.IgnoreList.Ignore(pattern) doesn't work like that. It compares just the Name part of files (i.e not the full (virtual)path ) with pattern in any bundle going to be returned. For example you can ignore files starting with name old_style prefix. This filtering will be applied for files under any folder.

public IEnumerable<BundleFile> FilterIgnoredFiles(BundleContext context, IEnumerable<BundleFile> files) { return files.Where(f => !ShouldIgnore(context, f.VirtualFile.Name)); }

See: https://aspnetoptimization.codeplex.com/SourceControl/latest#src/System.Web.Optimization/IgnoreList.cs

Upvotes: 0

Gent
Gent

Reputation: 2685

I have fought the ignore list when using expressions as well. A simple work around I have found is to implement an IBundleOrderer that will exclude the files I do not want, and apply some ordering to how the files get included. Although this is not really its intended use, I find it fills the gap.

The IBundleOrderer gets access to the full list of files (expression expanded to what files it matched).

public class ApplicationOrderer : IBundleOrderer {
    public IEnumerable<BundleFile> OrderFiles(BundleContext context, IEnumerable<BundleFile> files)
    {
        if (!AppSettings.FeatureFlag_ServiceIntegrationsEnabled)
        {
            //Skip these files if the Service Integrations Feature is not enabled!
            //When this flag is removed, these lines can be deleted and the files will be included like normal
            var serviceIntegrationPathsToIgnore = new[]
            {
                "/App/ServiceIntegrations/IntegrationSettingsModel.js",
                "/App/ServiceIntegrations/IntegrationSettingsService.js",
                "/App/ServiceIntegrations/ServiceIntegrationsCtrl.js"
            };
            files = files.Where(x => !serviceIntegrationPathsToIgnore.Contains(x.VirtualFile.VirtualPath));
        }

        return files;
    }
}

Example Usage:

public static void RegisterBundles(BundleCollection bundles)
{
        var appBundle = new ScriptBundle("~/bundles/app")
            .IncludeDirectory("~/App/", "*.js", true)
        appBundle.Orderer = new ApplicationOrderer();
        bundles.Add(appBundle);
}

Upvotes: 4

Rick
Rick

Reputation: 841

I think that the bundling code only happens once - during the initialisation of the website. If you are switching the appConfiguration.FeatureXEnabled flag without restarting/republishing the website then your change will be ignored.

One solution would be to create 2 separate bundles and then use Razor to display the correct bundle reference in your HTML.

e.g.

@{if (appConfiguration.FeatureXEnabled){
    <script type="text/javascript" src="~/bundles/bundle-large.js"></script>
}else{
    <script type="text/javascript" src="~/bundles/bundle-small.js"></script>
}

Upvotes: 2

Pablo Romeo
Pablo Romeo

Reputation: 11406

Try defining the ignore list as follows (using wildcards instead of relative paths):

   bundles.IgnoreList.Ignore("*/App/Organization/SomeCtrl.js", OptimizationMode.Always);
   bundles.IgnoreList.Ignore("*/App/Filters/SomeFilter.js", OptimizationMode.Always);

Upvotes: 0

Related Questions