ASP.NET Core web.config publishing blues
I’ve been working on being able to use Octopus Deploy to deploy ASP.NET Core applications through IIS and I was faced with an interesting challenge. When publishing to a File System through the Visual Studio Publish command, it will trigger a PowerShell script that essentially will call Microsoft Web Deploy V3 (msdeploy) to put your FileSystem up to date with your release. But before calling MSDeploy, it will update (or should I say overwrite) your web.config with the proper handlers that match your application. This means that it will create a generic web.config and removing all the settings you may want. The ASP.NET team is aware I believe of the fact that the publishing tool is limited and thus in need of some customization features wise.
Using Octopus Deploy as a deployment solution, there are some solutions which you can take to fix the above situation.
One solution is to have a copy of your web.config say, web.{environment}.config and replace the original web.config with that said web.{environment}.config file. I find that approach not as smooth as I would like. MSDeploy uses variables to replace certain attributes values and this means you will have to maintain that on each of your environment web.config.
I chose another approach where I run a PreDeploy PowerShell script that will add the proper configurations to my web.config before the application is deployed.
Here is the PreDeploy PowerShell script that I use to set the Rewrite Rule for HTTP to HTTPs and to set some custom headers. Feel free to use any other language that Octopus Deploy supports (C#, F#, etc).
The script can definitely be improved, but it is simple enough that I did not bother 🙂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
function SetRewrite([xml]$config) { Write-Host "Setting HTTP(s) to HTTPS rewrite rule"; $systemWebHandlers = $config.SelectSingleNode("//system.webServer"); $rewrite = $config.CreateElement("rewrite"); $rules = $config.CreateElement("rules"); $rule = $config.CreateElement("rule"); $ruleNameAttribute = $config.CreateAttribute("name"); $ruleNameAttribute.Value = "HTTP/S to HTTPS Redirect"; $stopProcessingAttribute = $config.CreateAttribute("stopProcessing"); $stopProcessingAttribute.Value = "true"; $rule.Attributes.Append($ruleNameAttribute); $rule.Attributes.Append($stopProcessingAttribute); $matchNode = $config.CreateElement("match"); $matchNodeUrlAttribute = $config.CreateAttribute("url"); $matchNodeUrlAttribute.Value = "(.*)"; $matchNode.Attributes.Append($matchNodeUrlAttribute); $rule.AppendChild($matchNode); $conditionsNode = $config.CreateElement("conditions"); $conditionAddNode = $config.CreateElement("add"); $inputAttribute = $config.CreateAttribute("input"); $inputAttribute.Value = "{HTTPS}"; $patternAttribute = $config.CreateAttribute("pattern"); $patternAttribute.Value = "^OFF$"; $conditionAddNode.Attributes.Append($inputAttribute); $conditionAddNode.Attributes.Append($patternAttribute); $conditionsNode.AppendChild($conditionAddNode); $rule.AppendChild($conditionsNode); $actionNode = $config.CreateElement("action"); $typeAttribute = $config.CreateAttribute("type"); $typeAttribute.Value = "Redirect"; $actionNodeUrlAttribute = $config.CreateAttribute("url"); $actionNodeUrlAttribute.Value = "https://{HTTP_HOST}"; $actionNode.Attributes.Append($typeAttribute); $actionNode.Attributes.Append($actionNodeUrlAttribute); $rule.AppendChild($actionNode); $rules.AppendChild($rule); $rewrite.AppendChild($rules); $systemWebHandlers.AppendChild($rewrite); } function SetCustomHeaders([xml]$config){ Write-Host "Setting CustomHeaders"; $systemWebHandlers = $config.SelectSingleNode("//system.webServer"); $httpProtocolNode = $config.CreateElement("httpProtocol"); $customHeadersNode = $config.CreateElement("customHeaders"); $customHeaderRemoveNode = $config.CreateElement("remove"); $customHeaderRemoveNodeAttributeName = $config.CreateAttribute("name"); $customHeaderRemoveNodeAttributeName.Value = "X-Powered-By"; $customHeaderRemoveNode.Attributes.Append($customHeaderRemoveNodeAttributeName); $customHeadersNode.AppendChild($customHeaderRemoveNode); $httpProtocolNode.AppendChild($customHeadersNode); $systemWebHandlers.AppendChild($httpProtocolNode); } $deploymentPath = $OctopusParameters["Octopus.Action[Deploy site].Output.Package.InstallationDirectoryPath"]; $filepath = (Join-Path $deploymentPath "web.config"); Write-Host "Modifying $filepath"; $config = [xml](Get-Content $filepath); [void](SetRewrite($config)); [void](SetCustomHeaders($config)); Write-Host "Saving changes..."; $config.Save($filepath); |
There are probably other ways that I am not aware of and I would love to hear from you so don’t be shy to comment or to tweet me!