public PublishRepository(ISiteBuilder builder, ICompileService compileService, ProjectFinder projectFinder, IProcessRunner processRunner) { this.builder = builder; this.compileService = compileService; this.projectFinder = projectFinder; this.processRunner = processRunner; }
/// <summary> /// Run the compiler. /// </summary> public void Compile(ISiteBuilder builder, IUserInfo compilingUser) { var userInfo = new BuilderUserInfo() { Email = compilingUser.Email, Name = compilingUser.PrettyUserName }; workQueue.Fire(() => { lock (locker) { this.lastProgress = null; this.currentBuilder = builder; } Stopwatch sw = new Stopwatch(); sw.Start(); //Do the build process on the thread pool, this way async will work correctly, this thread //will then wait for the result and process any exceptions that occur. var task = Task.Run(async() => { try { await builder.BuildSite(userInfo); return(default(Exception)); } catch (Exception ex) { return(ex); } }); var runException = task.Result; //This line has threading implications, don't move it sw.Stop(); lock (locker) { this.lastProgress = mapper.Map <CompileProgress>(builder.GetCurrentProgress()); this.lastProgress.Completed = true; this.lastProgress.Success = runException == null; //If the run exception is null, the process was sucessful. this.lastProgress.ErrorMessage = runException?.Message; this.currentBuilder = null; } }); }
private void cleanMenuItems(MenuItem parent, ISiteBuilder siteBuilder) { if (parent.Children != null) { List <MenuItem> itemsToRemove = new List <MenuItem>(); foreach (var item in parent.Children) { cleanMenuItems(item, siteBuilder); if (item.Link != null) { //Only remove absolute paths into this website, also don't remove paths pointing to the site root if (item.Link.Length > 1 && (item.Link[0] == '\\' || item.Link[0] == '/')) { if (!siteBuilder.DoesOutputFileExist(item.Link + ".html")) { itemsToRemove.Add(item); } } } else //No link, is a folder, check to see if its empty { if (item.Children == null || item.Children.Count == 0) { itemsToRemove.Add(item); } } } foreach (var remove in itemsToRemove) { parent.Children.Remove(remove); } } }
/// <summary> /// Builds and deploys a particular changeset. Puts all build artifacts in a deployments/{id} /// </summary> private async Task Build( ChangeSet changeSet, ITracer tracer, IDisposable deployStep, IRepository repository, DeploymentInfoBase deploymentInfo, DeploymentAnalytics deploymentAnalytics, bool fullBuildByDefault) { if (changeSet == null || String.IsNullOrEmpty(changeSet.Id)) { throw new ArgumentException("The changeSet.Id parameter is null or empty", "changeSet.Id"); } ILogger logger = null; IDeploymentStatusFile currentStatus = null; string buildTempPath = null; string id = changeSet.Id; try { logger = GetLogger(id); ILogger innerLogger = logger.Log(Resources.Log_PreparingDeployment, TrimId(id)); currentStatus = _status.Open(id); currentStatus.Complete = false; currentStatus.StartTime = DateTime.UtcNow; currentStatus.Status = DeployStatus.Building; currentStatus.StatusText = String.Format(CultureInfo.CurrentCulture, Resources.Status_BuildingAndDeploying, id); currentStatus.Save(); ISiteBuilder builder = null; // Add in per-deploy default settings values based on the details of this deployment var perDeploymentDefaults = new Dictionary <string, string> { { SettingsKeys.DoBuildDuringDeployment, fullBuildByDefault.ToString() } }; var settingsProviders = _settings.SettingsProviders.Concat( new[] { new BasicSettingsProvider(perDeploymentDefaults, SettingsProvidersPriority.PerDeploymentDefault) }); var perDeploymentSettings = DeploymentSettingsManager.BuildPerDeploymentSettingsManager(repository.RepositoryPath, settingsProviders); string delayMaxInStr = perDeploymentSettings.GetValue(SettingsKeys.MaxRandomDelayInSec); if (!String.IsNullOrEmpty(delayMaxInStr)) { int maxDelay; if (!Int32.TryParse(delayMaxInStr, out maxDelay) || maxDelay < 0) { tracer.Trace("Invalid {0} value, expect a positive integer, received {1}", SettingsKeys.MaxRandomDelayInSec, delayMaxInStr); } else { tracer.Trace("{0} is set to {1}s", SettingsKeys.MaxRandomDelayInSec, maxDelay); int gap = _random.Next(maxDelay); using (tracer.Step("Randomization applied to {0}, Start sleeping for {1}s", maxDelay, gap)) { logger.Log(Resources.Log_DelayingBeforeDeployment, gap); await Task.Delay(TimeSpan.FromSeconds(gap)); } } } try { using (tracer.Step("Determining deployment builder")) { builder = _builderFactory.CreateBuilder(tracer, innerLogger, perDeploymentSettings, repository, deploymentInfo); deploymentAnalytics.ProjectType = builder.ProjectType; tracer.Trace("Builder is {0}", builder.GetType().Name); } } catch (Exception ex) { // If we get a TargetInvocationException, use the inner exception instead to avoid // useless 'Exception has been thrown by the target of an invocation' messages var targetInvocationException = ex as System.Reflection.TargetInvocationException; if (targetInvocationException != null) { ex = targetInvocationException.InnerException; } _globalLogger.Log(ex); innerLogger.Log(ex); MarkStatusComplete(currentStatus, success: false); FailDeployment(tracer, deployStep, deploymentAnalytics, ex); return; } // Create a directory for the script output temporary artifacts // Use tick count (in hex) instead of guid to keep the path for getting to long buildTempPath = Path.Combine(_environment.TempPath, DateTime.UtcNow.Ticks.ToString("x")); FileSystemHelpers.EnsureDirectory(buildTempPath); var context = new DeploymentContext { NextManifestFilePath = GetDeploymentManifestPath(id), PreviousManifestFilePath = GetActiveDeploymentManifestPath(), IgnoreManifest = deploymentInfo != null && deploymentInfo.CleanupTargetDirectory, // Ignoring the manifest will cause kudusync to delete sub-directories / files // in the destination directory that are not present in the source directory, // without checking the manifest to see if the file was copied over to the destination // during a previous kudusync operation. This effectively performs a clean deployment // from the source to the destination directory. Tracer = tracer, Logger = logger, GlobalLogger = _globalLogger, OutputPath = GetOutputPath(deploymentInfo, _environment, perDeploymentSettings), BuildTempPath = buildTempPath, CommitId = id, Message = changeSet.Message }; if (context.PreviousManifestFilePath == null) { // this file (/site/firstDeploymentManifest) capture the last active deployment when disconnecting SCM context.PreviousManifestFilePath = Path.Combine(_environment.SiteRootPath, Constants.FirstDeploymentManifestFileName); if (!FileSystemHelpers.FileExists(context.PreviousManifestFilePath)) { // In the first deployment we want the wwwroot directory to be cleaned, we do that using a manifest file // That has the expected content of a clean deployment (only one file: hostingstart.html) // This will result in KuduSync cleaning this file. context.PreviousManifestFilePath = Path.Combine(_environment.ScriptPath, Constants.FirstDeploymentManifestFileName); } } PreDeployment(tracer); using (tracer.Step("Building")) { try { await builder.Build(context); builder.PostBuild(context); await RestartMainSiteIfNeeded(tracer, logger, deploymentInfo); await PostDeploymentHelper.SyncFunctionsTriggers(_environment.RequestId, new PostDeploymentTraceListener(tracer, logger), deploymentInfo?.SyncFunctionsTriggersPath); TouchWatchedFileIfNeeded(_settings, deploymentInfo, context); FinishDeployment(id, deployStep); deploymentAnalytics.VsProjectId = TryGetVsProjectId(context); deploymentAnalytics.Result = DeployStatus.Success.ToString(); } catch (Exception ex) { MarkStatusComplete(currentStatus, success: false); FailDeployment(tracer, deployStep, deploymentAnalytics, ex); return; } } } catch (Exception ex) { FailDeployment(tracer, deployStep, deploymentAnalytics, ex); } finally { // Clean the temp folder up CleanBuild(tracer, buildTempPath); } }
/// <summary> /// Builds and deploys a particular changeset. Puts all build artifacts in a deployments/{id} /// </summary> private async Task Build(string id, ITracer tracer, IDisposable deployStep, IFileFinder fileFinder, DeploymentAnalytics deploymentAnalytics) { if (String.IsNullOrEmpty(id)) { throw new ArgumentException("The id parameter is null or empty", "id"); } ILogger logger = null; IDeploymentStatusFile currentStatus = null; string buildTempPath = null; try { logger = GetLogger(id); ILogger innerLogger = logger.Log(Resources.Log_PreparingDeployment, TrimId(id)); currentStatus = _status.Open(id); currentStatus.Complete = false; currentStatus.StartTime = DateTime.UtcNow; currentStatus.Status = DeployStatus.Building; currentStatus.StatusText = String.Format(CultureInfo.CurrentCulture, Resources.Status_BuildingAndDeploying, id); currentStatus.Save(); ISiteBuilder builder = null; string repositoryRoot = _environment.RepositoryPath; var perDeploymentSettings = DeploymentSettingsManager.BuildPerDeploymentSettingsManager(repositoryRoot, _settings); try { using (tracer.Step("Determining deployment builder")) { builder = _builderFactory.CreateBuilder(tracer, innerLogger, perDeploymentSettings, fileFinder); deploymentAnalytics.ProjectType = builder.ProjectType; tracer.Trace("Builder is {0}", builder.GetType().Name); } } catch (Exception ex) { // If we get a TargetInvocationException, use the inner exception instead to avoid // useless 'Exception has been thrown by the target of an invocation' messages var targetInvocationException = ex as System.Reflection.TargetInvocationException; if (targetInvocationException != null) { ex = targetInvocationException.InnerException; } _globalLogger.Log(ex); innerLogger.Log(ex); MarkStatusComplete(currentStatus, success: false); FailDeployment(tracer, deployStep, deploymentAnalytics, ex); return; } // Create a directory for the script output temporary artifacts // Use tick count (in hex) instead of guid to keep the path for getting to long buildTempPath = Path.Combine(_environment.TempPath, DateTime.UtcNow.Ticks.ToString("x")); FileSystemHelpers.EnsureDirectory(buildTempPath); var context = new DeploymentContext { NextManifestFilePath = GetDeploymentManifestPath(id), PreviousManifestFilePath = GetActiveDeploymentManifestPath(), Tracer = tracer, Logger = logger, GlobalLogger = _globalLogger, OutputPath = GetOutputPath(_environment, perDeploymentSettings), BuildTempPath = buildTempPath, CommitId = id }; if (context.PreviousManifestFilePath == null) { // this file (/site/firstDeploymentManifest) capture the last active deployment when disconnecting SCM context.PreviousManifestFilePath = Path.Combine(_environment.SiteRootPath, Constants.FirstDeploymentManifestFileName); if (!FileSystemHelpers.FileExists(context.PreviousManifestFilePath)) { // In the first deployment we want the wwwroot directory to be cleaned, we do that using a manifest file // That has the expected content of a clean deployment (only one file: hostingstart.html) // This will result in KuduSync cleaning this file. context.PreviousManifestFilePath = Path.Combine(_environment.ScriptPath, Constants.FirstDeploymentManifestFileName); } } PreDeployment(tracer); using (tracer.Step("Building")) { try { await builder.Build(context); TryTouchWebConfig(context); // Run post deployment steps FinishDeployment(id, deployStep); deploymentAnalytics.Result = DeployStatus.Success.ToString(); } catch (Exception ex) { MarkStatusComplete(currentStatus, success: false); FailDeployment(tracer, deployStep, deploymentAnalytics, ex); return; } } } catch (Exception ex) { FailDeployment(tracer, deployStep, deploymentAnalytics, ex); } finally { // Clean the temp folder up CleanBuild(tracer, buildTempPath); } }
public BuildCommand(ISiteBuilder siteBuilder) { this.siteBuilder = siteBuilder; }
public BuildCommand(IConsole console, ISiteBuilder siteBuilder) { this.console = console; this.siteBuilder = siteBuilder; }
/// <summary> /// Builds and deploys a particular changeset. Puts all build artifacts in a deployments/{id} /// </summary> private async Task Build(ChangeSet changeSet, ITracer tracer, IDisposable deployStep, IFileFinder fileFinder, DeploymentAnalytics deploymentAnalytics) { if (changeSet == null || String.IsNullOrEmpty(changeSet.Id)) { throw new ArgumentException("The changeSet.Id parameter is null or empty", "changeSet.Id"); } ILogger logger = null; IDeploymentStatusFile currentStatus = null; string buildTempPath = null; string id = changeSet.Id; try { logger = GetLogger(id); ILogger innerLogger = logger.Log(Resources.Log_PreparingDeployment, TrimId(id)); currentStatus = _status.Open(id); currentStatus.Complete = false; currentStatus.StartTime = DateTime.UtcNow; currentStatus.Status = DeployStatus.Building; currentStatus.StatusText = String.Format(CultureInfo.CurrentCulture, Resources.Status_BuildingAndDeploying, id); currentStatus.Save(); ISiteBuilder builder = null; string repositoryRoot = _environment.RepositoryPath; var perDeploymentSettings = DeploymentSettingsManager.BuildPerDeploymentSettingsManager(repositoryRoot, _settings); string delayMaxInStr = perDeploymentSettings.GetValue(SettingsKeys.MaxRandomDelayInSec); if (!String.IsNullOrEmpty(delayMaxInStr)) { int maxDelay; if (!Int32.TryParse(delayMaxInStr, out maxDelay) || maxDelay < 0) { tracer.Trace("Invalid {0} value, expect a positive integer, received {1}", SettingsKeys.MaxRandomDelayInSec, delayMaxInStr); } else { tracer.Trace("{0} is set to {1}s", SettingsKeys.MaxRandomDelayInSec, maxDelay); int gap = _random.Next(maxDelay); using (tracer.Step("Randomization applied to {0}, Start sleeping for {1}s", maxDelay, gap)) { logger.Log(Resources.Log_DelayingBeforeDeployment, gap); await Task.Delay(TimeSpan.FromSeconds(gap)); } } } try { using (tracer.Step("Determining deployment builder")) { builder = _builderFactory.CreateBuilder(tracer, innerLogger, perDeploymentSettings, fileFinder); deploymentAnalytics.ProjectType = builder.ProjectType; tracer.Trace("Builder is {0}", builder.GetType().Name); } } catch (Exception ex) { // If we get a TargetInvocationException, use the inner exception instead to avoid // useless 'Exception has been thrown by the target of an invocation' messages var targetInvocationException = ex as System.Reflection.TargetInvocationException; if (targetInvocationException != null) { ex = targetInvocationException.InnerException; } _globalLogger.Log(ex); innerLogger.Log(ex); MarkStatusComplete(currentStatus, success: false); FailDeployment(tracer, deployStep, deploymentAnalytics, ex); return; } // Create a directory for the script output temporary artifacts // Use tick count (in hex) instead of guid to keep the path for getting to long buildTempPath = Path.Combine(_environment.TempPath, DateTime.UtcNow.Ticks.ToString("x")); FileSystemHelpers.EnsureDirectory(buildTempPath); var context = new DeploymentContext { NextManifestFilePath = GetDeploymentManifestPath(id), PreviousManifestFilePath = GetActiveDeploymentManifestPath(), Tracer = tracer, Logger = logger, GlobalLogger = _globalLogger, OutputPath = GetOutputPath(_environment, perDeploymentSettings), BuildTempPath = buildTempPath, CommitId = id, Message = changeSet.Message }; if (context.PreviousManifestFilePath == null) { // this file (/site/firstDeploymentManifest) capture the last active deployment when disconnecting SCM context.PreviousManifestFilePath = Path.Combine(_environment.SiteRootPath, Constants.FirstDeploymentManifestFileName); if (!FileSystemHelpers.FileExists(context.PreviousManifestFilePath)) { // In the first deployment we want the wwwroot directory to be cleaned, we do that using a manifest file // That has the expected content of a clean deployment (only one file: hostingstart.html) // This will result in KuduSync cleaning this file. context.PreviousManifestFilePath = Path.Combine(_environment.ScriptPath, Constants.FirstDeploymentManifestFileName); } } PreDeployment(tracer); using (tracer.Step("Building")) { try { await builder.Build(context); builder.PostBuild(context); await _functionManager.SyncTriggersAsync(); if (_settings.TouchWebConfigAfterDeployment()) { TryTouchWebConfig(context); } FinishDeployment(id, deployStep); deploymentAnalytics.VsProjectId = TryGetVsProjectId(context); deploymentAnalytics.Result = DeployStatus.Success.ToString(); } catch (Exception ex) { MarkStatusComplete(currentStatus, success: false); FailDeployment(tracer, deployStep, deploymentAnalytics, ex); return; } } } catch (Exception ex) { FailDeployment(tracer, deployStep, deploymentAnalytics, ex); } finally { // Clean the temp folder up CleanBuild(tracer, buildTempPath); } }
public RunCommand(IConsole console, ISiteBuilder siteBuilder, IFileSystem fileSystem) { this.console = console; this.siteBuilder = siteBuilder; this.fileSystem = fileSystem; }
/// <summary> /// Builds and deploys a particular changeset. Puts all build artifacts in a deployments/{id} /// </summary> private void Build(string id, ITracer tracer, IDisposable deployStep) { if (String.IsNullOrEmpty(id)) { throw new ArgumentException(); } ILogger logger = null; DeploymentStatusFile currentStatus = null; IDisposable buildStep = null; try { logger = GetLogger(id); ILogger innerLogger = logger.Log(Resources.Log_PreparingDeployment, TrimId(id)); currentStatus = OpenStatusFile(id); currentStatus.Complete = false; currentStatus.StartTime = DateTime.Now; currentStatus.Status = DeployStatus.Building; currentStatus.StatusText = String.Format(CultureInfo.CurrentCulture, Resources.Status_BuildingAndDeploying, id); currentStatus.Save(_fileSystem); ReportStatus(id); ISiteBuilder builder = null; try { builder = _builderFactory.CreateBuilder(tracer, innerLogger); } catch (Exception ex) { _globalLogger.Log(ex); tracer.TraceError(ex); innerLogger.Log(ex); MarkFailed(currentStatus); ReportStatus(id); deployStep.Dispose(); return; } buildStep = tracer.Step("Building"); var context = new DeploymentContext { ManifestWriter = GetDeploymentManifestWriter(id), PreviousMainfest = GetActiveDeploymentManifestReader(), Tracer = tracer, Logger = logger, GlobalLogger = _globalLogger, OutputPath = _environment.DeploymentTargetPath, }; builder.Build(context) .Then(() => { // End the build step buildStep.Dispose(); // Run post deployment steps FinishDeployment(id, tracer, deployStep); }) .Catch(ex => { // End the build step buildStep.Dispose(); MarkFailed(currentStatus); ReportStatus(id); // End the deploy step deployStep.Dispose(); }); } catch (Exception ex) { tracer.TraceError(ex); logger.LogUnexpetedError(); if (buildStep != null) { buildStep.Dispose(); } deployStep.Dispose(); } }
public BuildEventArgs(IBuildStatusTracker tracker, ISiteBuilder siteBuilder, BuilderUserInfo userInfo) { this.Tracker = tracker; this.SiteBuilder = siteBuilder; this.UserInfo = userInfo; }
/// <summary> /// Builds and deploys a particular changeset. Puts all build artifacts in a deployments/{id} /// </summary> private async Task Build(string id, ITracer tracer, IDisposable deployStep, IFileFinder fileFinder) { if (String.IsNullOrEmpty(id)) { throw new ArgumentException("The id parameter is null or empty", "id"); } ILogger logger = null; IDeploymentStatusFile currentStatus = null; try { logger = GetLogger(id); ILogger innerLogger = logger.Log(Resources.Log_PreparingDeployment, TrimId(id)); currentStatus = _status.Open(id); currentStatus.Complete = false; currentStatus.StartTime = DateTime.UtcNow; currentStatus.Status = DeployStatus.Building; currentStatus.StatusText = String.Format(CultureInfo.CurrentCulture, Resources.Status_BuildingAndDeploying, id); currentStatus.Save(); ISiteBuilder builder = null; string repositoryRoot = _environment.RepositoryPath; var perDeploymentSettings = DeploymentSettingsManager.BuildPerDeploymentSettingsManager(repositoryRoot, _settings); try { using (tracer.Step("Determining deployment builder")) { builder = _builderFactory.CreateBuilder(tracer, innerLogger, perDeploymentSettings, fileFinder); tracer.Trace("Builder is {0}", builder.GetType().Name); } } catch (Exception ex) { // If we get a TargetInvocationException, use the inner exception instead to avoid // useless 'Exception has been thrown by the target of an invocation' messages var targetInvocationException = ex as System.Reflection.TargetInvocationException; if (targetInvocationException != null) { ex = targetInvocationException.InnerException; } _globalLogger.Log(ex); tracer.TraceError(ex); innerLogger.Log(ex); currentStatus.MarkFailed(); deployStep.Dispose(); return; } var context = new DeploymentContext { NextManifestFilePath = GetDeploymentManifestPath(id), PreviousManifestFilePath = GetActiveDeploymentManifestPath(), Tracer = tracer, Logger = logger, GlobalLogger = _globalLogger, OutputPath = GetOutputPath(_environment, perDeploymentSettings), }; if (context.PreviousManifestFilePath == null) { // In the first deployment we want the wwwroot directory to be cleaned, we do that using a manifest file // That has the expected content of a clean deployment (only one file: hostingstart.html) // This will result in KuduSync cleaning this file. context.PreviousManifestFilePath = Path.Combine(_environment.ScriptPath, Constants.FirstDeploymentManifestFileName); } using (tracer.Step("Building")) { try { await builder.Build(context); TryTouchWebConfig(context); // Run post deployment steps FinishDeployment(id, deployStep); } catch (Exception ex) { tracer.TraceError(ex); currentStatus.MarkFailed(); // End the deploy step deployStep.Dispose(); return; } } } catch (Exception ex) { tracer.TraceError(ex); deployStep.Dispose(); } }
private String CreateWebConfig(ISiteBuilder siteBuilder) { var usingDeploymentFolder = false; var homePage = siteBuilder.Project.DefaultPage; if (siteBuilder.DeploymentSubFolder != null) { homePage = Path.Combine(siteBuilder.DeploymentSubFolder, homePage); usingDeploymentFolder = true; } var webConfig = @"<?xml version=""1.0"" encoding=""UTF-8""?> <configuration> <system.webServer>"; //Don't add index.html since iis will define that by default. This reduces the server configuration. if (!homePage.Equals("index", StringComparison.InvariantCultureIgnoreCase)) { webConfig += $@" <defaultDocument> <files> <add value=""{homePage}.html""/> </files> </defaultDocument>"; } if (usingDeploymentFolder) { webConfig += $@" <rewrite> <rules>"; webConfig = AddHttpsRedirect(webConfig); webConfig += $@" <rule name=""RewriteToGuidDir""> <match url=""^(.*)$"" ignoreCase=""false"" /> <conditions logicalGrouping=""MatchAll"" > <add input=""{{REQUEST_FILENAME}}"" matchType=""IsFile"" ignoreCase=""false"" negate=""true"" /> <add input=""{{REQUEST_FILENAME}}"" matchType=""IsDirectory"" ignoreCase=""false"" negate=""true"" /> <add input=""{{REQUEST_URI}}"" pattern=""^/(EmbdSvcs-)"" negate=""true"" /> </conditions> <action type=""Rewrite"" url=""{siteBuilder.DeploymentSubFolder}/{{R:1}}"" /> </rule> <rule name=""RewriteHtmlToGuidDir""> <match url=""(.*)"" /> <conditions logicalGrouping=""MatchAll"" > <add input=""{{REQUEST_FILENAME}}"" matchType=""IsFile"" negate=""true"" /> <add input=""{{REQUEST_FILENAME}}"" matchType=""IsDirectory"" negate=""true"" /> <add input=""{{REQUEST_URI}}"" pattern=""^/(EmbdSvcs-)"" negate=""true"" /> </conditions> <action type=""Rewrite"" url=""{{R:1}}.html"" /> </rule> </rules> </rewrite>"; } else { webConfig += @" <rewrite> <rules>"; webConfig = AddHttpsRedirect(webConfig); webConfig += @" <rule name=""RewriteToHtml""> <match url=""(.*)"" /> <conditions logicalGrouping=""MatchAll"" > <add input=""{REQUEST_FILENAME}"" matchType=""IsFile"" negate=""true"" /> <add input=""{REQUEST_FILENAME}"" matchType=""IsDirectory"" negate=""true"" /> <add input=""{REQUEST_URI}"" pattern=""^/(EmbdSvcs-)"" negate=""true"" /> </conditions> <action type=""Rewrite"" url=""{R:1}.html"" /> </rule> </rules> </rewrite>"; } if (cacheControlMaxAge > TimeSpan.Zero) { webConfig += $@" <staticContent> <clientCache cacheControlMode=""UseMaxAge"" cacheControlMaxAge=""{cacheControlMaxAge.ToString("c")}"" /> </staticContent>"; } webConfig += @" </system.webServer> </configuration>"; return(webConfig); }