public IEnumerable<HarvestShapeHit> HarvestShape(HarvestShapeInfo info) {
     var lastDash = info.FileName.LastIndexOf('-');
     var lastDot = info.FileName.LastIndexOf('.');
     if (lastDot <= 0 || lastDot < lastDash) {
         yield return new HarvestShapeHit {
             ShapeType = Adjust(info.SubPath, info.FileName, null)
         };
     }
     else {
         var displayType = info.FileName.Substring(lastDot + 1);
         yield return new HarvestShapeHit {
             ShapeType = Adjust(info.SubPath, info.FileName.Substring(0, lastDot), displayType),
             DisplayType = displayType
         };
     }
 }
        public IEnumerable <HarvestShapeHit> HarvestShape(HarvestShapeInfo info)
        {
            var lastDash = info.FileName.LastIndexOf('-');
            var lastDot  = info.FileName.LastIndexOf('.');

            if (lastDot <= 0 || lastDot < lastDash)
            {
                yield return(new HarvestShapeHit {
                    ShapeType = Adjust(info.SubPath, info.FileName, null)
                });
            }
            else
            {
                var displayType = info.FileName.Substring(lastDot + 1);
                yield return(new HarvestShapeHit {
                    ShapeType = Adjust(info.SubPath, info.FileName.Substring(0, lastDot), displayType),
                    DisplayType = displayType
                });
            }
        }
Beispiel #3
0
        public void Discover(ShapeTableBuilder builder)
        {
            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Start discovering shapes");
            }
            var harvesterInfos = _harvesters.Select(harvester => new { harvester, subPaths = harvester.SubPaths() });

            var activeFeatures = _featureManager.GetEnabledFeaturesAsync().Result;
            var activeExtensions = Once(activeFeatures);

            var hits = activeExtensions.Select(extensionDescriptor =>
            {
                if (_logger.IsEnabled(LogLevel.Information))
                {
                    _logger.LogInformation("Start discovering candidate views filenames");
                }

                var matcher = new Matcher();
                foreach(var extension in _shapeTemplateViewEngines.SelectMany(x => x.TemplateFileExtensions))
                {
                    matcher.AddInclude(string.Format("*.{0}", extension));
                }

                var pathContexts = harvesterInfos.SelectMany(harvesterInfo => harvesterInfo.subPaths.Select(subPath =>
                {
                    var basePath = _fileSystem.Combine(extensionDescriptor.Location, extensionDescriptor.Id);
                    var virtualPath = _fileSystem.Combine(basePath, subPath);
                    var files = _fileSystem.ListFiles(virtualPath, matcher).ToReadOnlyCollection();

                    return new { harvesterInfo.harvester, basePath, subPath, virtualPath, files };
                })).ToList();

                if (_logger.IsEnabled(LogLevel.Information))
                {
                    _logger.LogInformation("Done discovering candidate views filenames");
                }
                var fileContexts = pathContexts.SelectMany(pathContext => _shapeTemplateViewEngines.SelectMany(ve =>
                {
                    return pathContext.files.Select(
                        file => new
                        {
                            fileName = Path.GetFileNameWithoutExtension(file.Name),
                            fileVirtualPath = "~/" + _fileSystem.Combine(pathContext.virtualPath, file.Name),
                            pathContext
                        });
                }));

                var shapeContexts = fileContexts.SelectMany(fileContext =>
                {
                    var harvestShapeInfo = new HarvestShapeInfo
                    {
                        SubPath = fileContext.pathContext.subPath,
                        FileName = fileContext.fileName,
                        TemplateVirtualPath = fileContext.fileVirtualPath
                    };
                    var harvestShapeHits = fileContext.pathContext.harvester.HarvestShape(harvestShapeInfo);
                    return harvestShapeHits.Select(harvestShapeHit => new { harvestShapeInfo, harvestShapeHit, fileContext });
                });

                return shapeContexts.Select(shapeContext => new { extensionDescriptor, shapeContext }).ToList();
            }).SelectMany(hits2 => hits2);


            foreach (var iter in hits)
            {
                // templates are always associated with the namesake feature of module or theme
                var hit = iter;
                var featureDescriptors = iter.extensionDescriptor.Features.Where(fd => fd.Id == hit.extensionDescriptor.Id);
                foreach (var featureDescriptor in featureDescriptors)
                {
                    if (_logger.IsEnabled(LogLevel.Debug))
                    {
                        _logger.LogDebug("Binding {0} as shape [{1}] for feature {2}",
                        hit.shapeContext.harvestShapeInfo.TemplateVirtualPath,
                        iter.shapeContext.harvestShapeHit.ShapeType,
                        featureDescriptor.Id);
                    }
                    builder.Describe(iter.shapeContext.harvestShapeHit.ShapeType)
                        .From(new Feature { Descriptor = featureDescriptor })
                        .BoundAs(
                            hit.shapeContext.harvestShapeInfo.TemplateVirtualPath,
                            shapeDescriptor => displayContext => RenderAsync(shapeDescriptor, displayContext, hit.shapeContext.harvestShapeInfo, hit.shapeContext.harvestShapeHit));
                }
            }

            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Done discovering shapes");
            }
        }
Beispiel #4
0
        private async Task<IHtmlContent> RenderAsync(ShapeDescriptor shapeDescriptor, DisplayContext displayContext, HarvestShapeInfo harvestShapeInfo, HarvestShapeHit harvestShapeHit)
        {
            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);
            }
            IHtmlContent result;

            if (displayContext.ViewContext.View != null)
            {
                var htmlHelper = MakeHtmlHelper(displayContext.ViewContext, displayContext.ViewContext.ViewData);
                result = htmlHelper.Partial(harvestShapeInfo.TemplateVirtualPath, displayContext.Value);
            }
            else
            {
                // If the View is null, it means that the shape is being executed from a non-view origin / where no ViewContext was established by the view engine, but manually.
                // Manually creating a ViewContext works when working with Shape methods, but not when the shape is implemented as a Razor view template.
                // Horrible, but it will have to do for now.
                result = await RenderRazorViewAsync(harvestShapeInfo.TemplateVirtualPath, displayContext);
            }

            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Done rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);
            }
            return result;
        }
        public void Discover(ShapeTableBuilder builder) {
            Logger.Information("Start discovering shapes");

            var harvesterInfos = _harvesters.Select(harvester => new { harvester, subPaths = harvester.SubPaths() });

            var availableFeatures = _extensionManager.AvailableFeatures();
            var activeFeatures = availableFeatures.Where(FeatureIsEnabled);
            var activeExtensions = Once(activeFeatures);

            var hits = _parallelCacheContext.RunInParallel(activeExtensions, extensionDescriptor => {
                Logger.Information("Start discovering candidate views filenames");
                var pathContexts = harvesterInfos.SelectMany(harvesterInfo => harvesterInfo.subPaths.Select(subPath => {
                    var basePath = Path.Combine(extensionDescriptor.Location, extensionDescriptor.Id).Replace(Path.DirectorySeparatorChar, '/');
                    var virtualPath = Path.Combine(basePath, subPath).Replace(Path.DirectorySeparatorChar, '/');
                    var fileNames = _cacheManager.Get(virtualPath, ctx => {
                        if (!_virtualPathProvider.DirectoryExists(virtualPath))
                            return new List<string>();

                        if (!DisableMonitoring) {
                            Logger.Debug("Monitoring virtual path \"{0}\"", virtualPath);
                            ctx.Monitor(_virtualPathMonitor.WhenPathChanges(virtualPath));
                        }

                        return _virtualPathProvider.ListFiles(virtualPath).Select(Path.GetFileName).ToReadOnlyCollection();
                    });
                    return new { harvesterInfo.harvester, basePath, subPath, virtualPath, fileNames };
                })).ToList();
                Logger.Information("Done discovering candidate views filenames");

                var fileContexts = pathContexts.SelectMany(pathContext => _shapeTemplateViewEngines.SelectMany(ve => {
                    var fileNames = ve.DetectTemplateFileNames(pathContext.fileNames);
                    return fileNames.Select(
                        fileName => new {
                            fileName = Path.GetFileNameWithoutExtension(fileName),
                            fileVirtualPath = Path.Combine(pathContext.virtualPath, fileName).Replace(Path.DirectorySeparatorChar, '/'),
                            pathContext
                        });
                }));

                var shapeContexts = fileContexts.SelectMany(fileContext => {
                    var harvestShapeInfo = new HarvestShapeInfo {
                        SubPath = fileContext.pathContext.subPath,
                        FileName = fileContext.fileName,
                        TemplateVirtualPath = fileContext.fileVirtualPath
                    };
                    var harvestShapeHits = fileContext.pathContext.harvester.HarvestShape(harvestShapeInfo);
                    return harvestShapeHits.Select(harvestShapeHit => new { harvestShapeInfo, harvestShapeHit, fileContext });
                });

                return shapeContexts.Select(shapeContext => new { extensionDescriptor, shapeContext }).ToList();
            }).SelectMany(hits2 => hits2);


            foreach (var iter in hits) {
                // templates are always associated with the namesake feature of module or theme
                var hit = iter;
                var featureDescriptors = iter.extensionDescriptor.Features.Where(fd => fd.Id == hit.extensionDescriptor.Id);
                foreach (var featureDescriptor in featureDescriptors) {                    
                    Logger.Debug("Binding {0} as shape [{1}] for feature {2}", 
                        hit.shapeContext.harvestShapeInfo.TemplateVirtualPath,
                        iter.shapeContext.harvestShapeHit.ShapeType,
                        featureDescriptor.Id);

                    builder.Describe(iter.shapeContext.harvestShapeHit.ShapeType)
                        .From(new Feature { Descriptor = featureDescriptor })
                        .BoundAs(
                            hit.shapeContext.harvestShapeInfo.TemplateVirtualPath,
                            shapeDescriptor => displayContext => Render(shapeDescriptor, displayContext, hit.shapeContext.harvestShapeInfo, hit.shapeContext.harvestShapeHit));
                }
            }

            Logger.Information("Done discovering shapes");
        }
        private IHtmlString Render(ShapeDescriptor shapeDescriptor, DisplayContext displayContext, HarvestShapeInfo harvestShapeInfo, HarvestShapeHit harvestShapeHit) {
            Logger.Information("Rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);

            var htmlHelper = new HtmlHelper(displayContext.ViewContext, displayContext.ViewDataContainer);
            var result = htmlHelper.Partial(harvestShapeInfo.TemplateVirtualPath, displayContext.Value);

            Logger.Information("Done rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);
            return result;
        }
        public void Discover(ShapeTableBuilder builder)
        {
            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Start discovering shapes");
            }

            var harvesterInfos = _harvesters
                                 .Select(harvester => new { harvester, subPaths = harvester.SubPaths() })
                                 .ToList();

            var enabledFeatures = _shellFeaturesManager.GetEnabledFeaturesAsync().GetAwaiter().GetResult()
                                  .Where(Feature => !builder.ExcludedFeatureIds.Contains(Feature.Id)).ToList();

            var activeExtensions = Once(enabledFeatures);

            if (!_viewEnginesByExtension.Any())
            {
                foreach (var viewEngine in _shapeTemplateViewEngines)
                {
                    foreach (var extension in viewEngine.TemplateFileExtensions)
                    {
                        if (!_viewEnginesByExtension.ContainsKey(extension))
                        {
                            _viewEnginesByExtension[extension] = viewEngine;
                        }
                    }
                }
            }

            var hits = activeExtensions.Select(extensionDescriptor =>
            {
                if (_logger.IsEnabled(LogLevel.Information))
                {
                    _logger.LogInformation("Start discovering candidate views filenames");
                }

                var pathContexts = harvesterInfos.SelectMany(harvesterInfo => harvesterInfo.subPaths.Select(subPath =>
                {
                    var filePaths = _fileProviderAccessor.FileProvider.GetViewFilePaths(Path.Combine(
                                                                                            extensionDescriptor.SubPath, subPath), _viewEnginesByExtension.Keys.ToArray(),
                                                                                        inViewsFolder: true, inDepth: false).ToArray();

                    return(new { harvesterInfo.harvester, subPath, filePaths });
                }))
                                   .ToList();

                if (_logger.IsEnabled(LogLevel.Information))
                {
                    _logger.LogInformation("Done discovering candidate views filenames");
                }

                var fileContexts = pathContexts.SelectMany(pathContext => _shapeTemplateViewEngines.SelectMany(ve =>
                {
                    return(pathContext.filePaths.Select(
                               filePath => new
                    {
                        fileName = Path.GetFileNameWithoutExtension(filePath),
                        relativePath = filePath,
                        pathContext
                    }));
                }));

                var shapeContexts = fileContexts.SelectMany(fileContext =>
                {
                    var harvestShapeInfo = new HarvestShapeInfo
                    {
                        SubPath      = fileContext.pathContext.subPath,
                        FileName     = fileContext.fileName,
                        RelativePath = fileContext.relativePath,
                        Extension    = Path.GetExtension(fileContext.relativePath)
                    };
                    var harvestShapeHits = fileContext.pathContext.harvester.HarvestShape(harvestShapeInfo);
                    return(harvestShapeHits.Select(harvestShapeHit => new { harvestShapeInfo, harvestShapeHit, fileContext }));
                });

                return(shapeContexts.Select(shapeContext => new { extensionDescriptor, shapeContext }).ToList());
            }).SelectMany(hits2 => hits2);


            foreach (var iter in hits)
            {
                // templates are always associated with the namesake feature of module or theme
                var hit = iter;
                foreach (var feature in hit.extensionDescriptor.Features)
                {
                    if (_logger.IsEnabled(LogLevel.Debug))
                    {
                        _logger.LogDebug("Binding {0} as shape [{1}] for feature {2}",
                                         hit.shapeContext.harvestShapeInfo.RelativePath,
                                         iter.shapeContext.harvestShapeHit.ShapeType,
                                         feature.Id);
                    }

                    var viewEngineType = _viewEnginesByExtension[iter.shapeContext.harvestShapeInfo.Extension].GetType();

                    builder.Describe(iter.shapeContext.harvestShapeHit.ShapeType)
                    .From(feature)
                    .BoundAs(
                        hit.shapeContext.harvestShapeInfo.RelativePath, shapeDescriptor => displayContext =>
                    {
                        var viewEngine = displayContext.ServiceProvider
                                         .GetServices <IShapeTemplateViewEngine>()
                                         .FirstOrDefault(e => e.GetType() == viewEngineType);

                        return(viewEngine.RenderAsync(hit.shapeContext.harvestShapeInfo.RelativePath, displayContext));
                    });
                }
            }

            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Done discovering shapes");
            }
        }
        public void Discover(ShapeTableBuilder builder)
        {
            Logger.Information("Start discovering shapes");

            var harvesterInfos = _harvesters.Select(harvester => new { harvester, subPaths = harvester.SubPaths() });

            var availableFeatures = _extensionManager.AvailableFeatures();
            var activeFeatures    = availableFeatures.Where(FeatureIsEnabled);
            var activeExtensions  = Once(activeFeatures);

            var hits = _parallelCacheContext.RunInParallel(activeExtensions, extensionDescriptor => {
                Logger.Information("Start discovering candidate views filenames");
                var pathContexts = harvesterInfos.SelectMany(harvesterInfo => harvesterInfo.subPaths.Select(subPath => {
                    var basePath    = Path.Combine(extensionDescriptor.Location, extensionDescriptor.Id).Replace(Path.DirectorySeparatorChar, '/');
                    var virtualPath = Path.Combine(basePath, subPath).Replace(Path.DirectorySeparatorChar, '/');
                    var fileNames   = _cacheManager.Get(virtualPath, true, ctx => {
                        if (!_virtualPathProvider.DirectoryExists(virtualPath))
                        {
                            return(new List <string>());
                        }

                        if (!DisableMonitoring)
                        {
                            Logger.Debug("Monitoring virtual path \"{0}\"", virtualPath);
                            ctx.Monitor(_virtualPathMonitor.WhenPathChanges(virtualPath));
                        }

                        return(_virtualPathProvider.ListFiles(virtualPath).Select(Path.GetFileName).ToReadOnlyCollection());
                    });
                    return(new { harvesterInfo.harvester, basePath, subPath, virtualPath, fileNames });
                })).ToList();
                Logger.Information("Done discovering candidate views filenames");

                var fileContexts = pathContexts.SelectMany(pathContext => _shapeTemplateViewEngines.SelectMany(ve => {
                    var fileNames = ve.DetectTemplateFileNames(pathContext.fileNames);
                    return(fileNames.Select(
                               fileName => new {
                        fileName = Path.GetFileNameWithoutExtension(fileName),
                        fileVirtualPath = Path.Combine(pathContext.virtualPath, fileName).Replace(Path.DirectorySeparatorChar, '/'),
                        pathContext
                    }));
                }));

                var shapeContexts = fileContexts.SelectMany(fileContext => {
                    var harvestShapeInfo = new HarvestShapeInfo {
                        SubPath             = fileContext.pathContext.subPath,
                        FileName            = fileContext.fileName,
                        TemplateVirtualPath = fileContext.fileVirtualPath
                    };
                    var harvestShapeHits = fileContext.pathContext.harvester.HarvestShape(harvestShapeInfo);
                    return(harvestShapeHits.Select(harvestShapeHit => new { harvestShapeInfo, harvestShapeHit, fileContext }));
                });

                return(shapeContexts.Select(shapeContext => new { extensionDescriptor, shapeContext }).ToList());
            }).SelectMany(hits2 => hits2);


            foreach (var iter in hits)
            {
                // templates are always associated with the namesake feature of module or theme
                var hit = iter;
                var featureDescriptors = iter.extensionDescriptor.Features.Where(fd => fd.Id == hit.extensionDescriptor.Id);
                foreach (var featureDescriptor in featureDescriptors)
                {
                    Logger.Debug("Binding {0} as shape [{1}] for feature {2}",
                                 hit.shapeContext.harvestShapeInfo.TemplateVirtualPath,
                                 iter.shapeContext.harvestShapeHit.ShapeType,
                                 featureDescriptor.Id);

                    builder.Describe(iter.shapeContext.harvestShapeHit.ShapeType)
                    .From(new Feature {
                        Descriptor = featureDescriptor
                    })
                    .BoundAs(
                        hit.shapeContext.harvestShapeInfo.TemplateVirtualPath,
                        shapeDescriptor => displayContext => Render(shapeDescriptor, displayContext, hit.shapeContext.harvestShapeInfo, hit.shapeContext.harvestShapeHit));
                }
            }

            Logger.Information("Done discovering shapes");
        }
        private IHtmlString Render(ShapeDescriptor shapeDescriptor, DisplayContext displayContext, HarvestShapeInfo harvestShapeInfo, HarvestShapeHit harvestShapeHit)
        {
            Logger.Information("Rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);
            IHtmlString result;

            if (displayContext.ViewContext.View != null)
            {
                var htmlHelper = new HtmlHelper(displayContext.ViewContext, displayContext.ViewDataContainer);
                result = htmlHelper.Partial(harvestShapeInfo.TemplateVirtualPath, displayContext.Value);
            }
            else
            {
                // If the View is null, it means that the shape is being executed from a non-view origin / where no ViewContext was established by the view engine, but manually.
                // Manually creating a ViewContext works when working with Shape methods, but not when the shape is implemented as a Razor view template.
                // Horrible, but it will have to do for now.
                result = RenderRazorViewToString(harvestShapeInfo.TemplateVirtualPath, displayContext);
            }

            Logger.Information("Done rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);
            return(result);
        }
        private async Task<IHtmlContent> RenderAsync(ShapeDescriptor shapeDescriptor, DisplayContext displayContext, HarvestShapeInfo harvestShapeInfo, HarvestShapeHit harvestShapeHit)
        {
            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);
            }
            IHtmlContent result;

            if (displayContext.ViewContext.View != null)
            {
                var htmlHelper = MakeHtmlHelper(displayContext.ViewContext, displayContext.ViewContext.ViewData);
                result = htmlHelper.Partial(harvestShapeInfo.TemplateVirtualPath, displayContext.Value);
            }
            else
            {
                // If the View is null, it means that the shape is being executed from a non-view origin / where no ViewContext was established by the view engine, but manually.
                // Manually creating a ViewContext works when working with Shape methods, but not when the shape is implemented as a Razor view template.
                // Horrible, but it will have to do for now.
                result = await RenderRazorViewAsync(harvestShapeInfo.TemplateVirtualPath, displayContext);
            }

            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Done rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);
            }
            return result;
        }
        public void Discover(ShapeTableBuilder builder)
        {
            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Start discovering shapes");
            }

            var harvesterInfos = _harvesters
                                 .Select(harvester => new { harvester, subPaths = harvester.SubPaths() })
                                 .ToList();

            var enabledFeatures = _shellFeaturesManager.GetEnabledFeaturesAsync().Result
                                  .Where(Feature => !builder.ExcludedFeatureIds.Contains(Feature.Id)).ToList();

            var activeExtensions = Once(enabledFeatures);

            var matcher = new Matcher();

            foreach (var extension in _shapeTemplateViewEngines.SelectMany(x => x.TemplateFileExtensions))
            {
                matcher.AddInclude(string.Format("*.{0}", extension));
            }

            var hits = activeExtensions.Select(extensionDescriptor =>
            {
                if (_logger.IsEnabled(LogLevel.Information))
                {
                    _logger.LogInformation("Start discovering candidate views filenames");
                }

                var pathContexts = harvesterInfos.SelectMany(harvesterInfo => harvesterInfo.subPaths.Select(subPath =>
                {
                    var subPathFileInfo = _hostingEnvironment
                                          .GetExtensionFileInfo(extensionDescriptor, subPath);

                    var directoryInfo = new DirectoryInfo(subPathFileInfo.PhysicalPath);

                    var virtualPath = Path.Combine(extensionDescriptor.SubPath, subPath);

                    if (!directoryInfo.Exists)
                    {
                        return(new
                        {
                            harvesterInfo.harvester,
                            subPath,
                            virtualPath,
                            files = new IFileInfo[0]
                        });
                    }

                    var matches = matcher
                                  .Execute(new DirectoryInfoWrapper(directoryInfo))
                                  .Files;

                    var files = matches
                                .Select(match => _hostingEnvironment
                                        .GetExtensionFileInfo(extensionDescriptor, Path.Combine(subPath, match.Path))).ToArray();

                    return(new { harvesterInfo.harvester, subPath, virtualPath, files });
                })).ToList();

                if (_logger.IsEnabled(LogLevel.Information))
                {
                    _logger.LogInformation("Done discovering candidate views filenames");
                }
                var fileContexts = pathContexts.SelectMany(pathContext => _shapeTemplateViewEngines.SelectMany(ve =>
                {
                    return(pathContext.files.Select(
                               file => new
                    {
                        fileName = Path.GetFileNameWithoutExtension(file.Name),
                        fileVirtualPath = "~/" + Path.Combine(pathContext.virtualPath, file.Name),
                        pathContext
                    }));
                }));

                var shapeContexts = fileContexts.SelectMany(fileContext =>
                {
                    var harvestShapeInfo = new HarvestShapeInfo
                    {
                        SubPath             = fileContext.pathContext.subPath,
                        FileName            = fileContext.fileName,
                        TemplateVirtualPath = fileContext.fileVirtualPath
                    };
                    var harvestShapeHits = fileContext.pathContext.harvester.HarvestShape(harvestShapeInfo);
                    return(harvestShapeHits.Select(harvestShapeHit => new { harvestShapeInfo, harvestShapeHit, fileContext }));
                });

                return(shapeContexts.Select(shapeContext => new { extensionDescriptor, shapeContext }).ToList());
            }).SelectMany(hits2 => hits2);


            foreach (var iter in hits)
            {
                // templates are always associated with the namesake feature of module or theme
                var hit = iter;
                foreach (var feature in hit.extensionDescriptor.Features)
                {
                    if (_logger.IsEnabled(LogLevel.Debug))
                    {
                        _logger.LogDebug("Binding {0} as shape [{1}] for feature {2}",
                                         hit.shapeContext.harvestShapeInfo.TemplateVirtualPath,
                                         iter.shapeContext.harvestShapeHit.ShapeType,
                                         feature.Id);
                    }
                    builder.Describe(iter.shapeContext.harvestShapeHit.ShapeType)
                    .From(feature)
                    .BoundAs(
                        hit.shapeContext.harvestShapeInfo.TemplateVirtualPath,
                        shapeDescriptor => displayContext => RenderAsync(shapeDescriptor, displayContext, hit.shapeContext.harvestShapeInfo, hit.shapeContext.harvestShapeHit));
                }
            }

            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Done discovering shapes");
            }
        }
        private IHtmlString Render(ShapeDescriptor shapeDescriptor, DisplayContext displayContext, HarvestShapeInfo harvestShapeInfo, HarvestShapeHit harvestShapeHit) {
            Logger.Information("Rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);
            IHtmlString result;

            if (displayContext.ViewContext.View != null) {
                var htmlHelper = new HtmlHelper(displayContext.ViewContext, displayContext.ViewDataContainer);
                result = htmlHelper.Partial(harvestShapeInfo.TemplateVirtualPath, displayContext.Value);
            }
            else {
                // If the View is null, it means that the shape is being executed from a non-view origin / where no ViewContext was established by the view engine, but manually.
                // Manually creating a ViewContext works when working with Shape methods, but not when the shape is implemented as a Razor view template.
                // Horrible, but it will have to do for now.
                result = RenderRazorViewToString(harvestShapeInfo.TemplateVirtualPath, displayContext);
            }

            Logger.Information("Done rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);
            return result;
        }
        public void Discover(ShapeTableBuilder builder)
        {
            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Start discovering shapes");
            }

            var harvesterInfos = _harvesters
                .Select(harvester => new { harvester, subPaths = harvester.SubPaths() })
                .ToList();

            var activeFeatures = _featureManager.GetEnabledFeaturesAsync().Result.ToList();
            var activeExtensions = Once(activeFeatures).ToList();

            var matcher = new Matcher();
            foreach (var extension in _shapeTemplateViewEngines.SelectMany(x => x.TemplateFileExtensions))
            {
                matcher.AddInclude(string.Format("*.{0}", extension));
            }

            var hits = activeExtensions.Select(extensionDescriptor =>
            {
                if (_logger.IsEnabled(LogLevel.Information))
                {
                    _logger.LogInformation("Start discovering candidate views filenames");
                }

                var pathContexts = harvesterInfos.SelectMany(harvesterInfo => harvesterInfo.subPaths.Select(subPath =>
                {
                    var basePath = _fileSystem.Combine(extensionDescriptor.Location, extensionDescriptor.Id);
                    var virtualPath = _fileSystem.Combine(basePath, subPath);
                    var files = _fileSystem.ListFiles(virtualPath, matcher).ToReadOnlyCollection();

                    return new { harvesterInfo.harvester, basePath, subPath, virtualPath, files };
                })).ToList();

                if (_logger.IsEnabled(LogLevel.Information))
                {
                    _logger.LogInformation("Done discovering candidate views filenames");
                }
                var fileContexts = pathContexts.SelectMany(pathContext => _shapeTemplateViewEngines.SelectMany(ve =>
                {
                    return pathContext.files.Select(
                        file => new
                        {
                            fileName = Path.GetFileNameWithoutExtension(file.Name),
                            fileVirtualPath = "~/" + _fileSystem.Combine(pathContext.virtualPath, file.Name),
                            pathContext
                        });
                }));

                var shapeContexts = fileContexts.SelectMany(fileContext =>
                {
                    var harvestShapeInfo = new HarvestShapeInfo
                    {
                        SubPath = fileContext.pathContext.subPath,
                        FileName = fileContext.fileName,
                        TemplateVirtualPath = fileContext.fileVirtualPath
                    };
                    var harvestShapeHits = fileContext.pathContext.harvester.HarvestShape(harvestShapeInfo);
                    return harvestShapeHits.Select(harvestShapeHit => new { harvestShapeInfo, harvestShapeHit, fileContext });
                });

                return shapeContexts.Select(shapeContext => new { extensionDescriptor, shapeContext }).ToList();
            }).SelectMany(hits2 => hits2);


            foreach (var iter in hits)
            {
                // templates are always associated with the namesake feature of module or theme
                var hit = iter;
                var featureDescriptors = iter.extensionDescriptor.Features.Where(fd => fd.Id == hit.extensionDescriptor.Id);
                foreach (var featureDescriptor in featureDescriptors)
                {
                    if (_logger.IsEnabled(LogLevel.Debug))
                    {
                        _logger.LogDebug("Binding {0} as shape [{1}] for feature {2}",
                        hit.shapeContext.harvestShapeInfo.TemplateVirtualPath,
                        iter.shapeContext.harvestShapeHit.ShapeType,
                        featureDescriptor.Id);
                    }
                    builder.Describe(iter.shapeContext.harvestShapeHit.ShapeType)
                        .From(new Feature { Descriptor = featureDescriptor })
                        .BoundAs(
                            hit.shapeContext.harvestShapeInfo.TemplateVirtualPath,
                            shapeDescriptor => displayContext => RenderAsync(shapeDescriptor, displayContext, hit.shapeContext.harvestShapeInfo, hit.shapeContext.harvestShapeHit));
                }
            }

            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Done discovering shapes");
            }
        }
        private IHtmlString Render(ShapeDescriptor shapeDescriptor, DisplayContext displayContext, HarvestShapeInfo harvestShapeInfo, HarvestShapeHit harvestShapeHit)
        {
            Logger.Information("Rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);

            var htmlHelper = new HtmlHelper(displayContext.ViewContext, displayContext.ViewDataContainer);
            var result     = htmlHelper.Partial(harvestShapeInfo.TemplateVirtualPath, displayContext.Value);

            Logger.Information("Done rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);
            return(result);
        }