我正致力于建立一个持续构建和部署系统,该系统将管理.NET应用程序的构建和部署到多个环境。我们希望这样做,以便我们构建,将该构建部署到我们的开发环境,并在以后可以选择使用不同的配置文件设置将相同的构建部署到我们的测试环境。目前,我们的开发人员习惯使用web.config转换来管理每个环境的配置值,他们更愿意继续这样做。最后,我们希望使用MS Web Deploy 3.6及其软件包部署选项进行部署。
在做了一些研究后,我们发现并考虑了以下选项:
我们还没有考虑过其他选择吗?有没有办法让我们继续使用web.configs,但只生成一个Web Deploy包?
答案 0 :(得分:2)
在.NET 4.7.1中,可以使用另一个选项:使用ConfigurationBuilder
。
我们的想法是,自定义类有机会在将web.config传递给应用程序之前对其进行操作。这允许插入其他配置系统。
例如:使用与ASP.NET Core类似的配置方法,它包含的NuGet包可以在.NET Framework上独立使用,也可以加载json和覆盖json文件。然后可以使用环境变量(或任何其他值,如IIS应用程序池ID,计算机名等)来确定要使用哪个覆盖json文件。
例如:如果有appsettings.json
文件,如
{
"appSettings": { "Foo": "FooValue", "Bar": "BarValue" }
}
和包含
的appsettings.Production.json
文件
{
"appSettings": { "Foo": "ProductionFooValue" }
}
可以写一个像
这样的配置构建器public class AppSettingsConfigurationBuilder : ConfigurationBuilder
{
public override ConfigurationSection ProcessConfigurationSection(ConfigurationSection configSection)
{
if(configSection is AppSettingsSection appSettingsSection)
{
var appSettings = appSettingsSection.Settings;
var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production";
var appConfig = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile($"appsettings.{environmentName}.json", optional: true)
.Build();
appSettings.Add("Foo", appConfig["appSettings:Foo"]);
appSettings.Add("Bar", appConfig["appSettings:Bar"]);
}
return configSection;
}
}
然后在Web.config
中连接配置构建器:
<configSections>
<section name="configBuilders" type="System.Configuration.ConfigurationBuildersSection, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" restartOnExternalChanges="false" requirePermission="false"/>
</configSections>
<configBuilders>
<builders>
<add name="AppSettingsConfigurationBuilder" type="My.Project.AppSettingsConfigurationBuilder, My.Project"/>
</builders>
</configBuilders>
<appSettings configBuilders="AppSettingsConfigurationBuilder" />
如果您在开发机器上将ASPNETCORE_ENVIRONMENT
(仅在同一服务器上选择的名称将使用相同的默认值)环境变量设置为Development
,{{1会看到ConfigurationManager.AppSettings["Foo"]
而不是FooValue
。
您还可以使用FooProductionValue
对环境名称进行硬编码,或使用IIS 10功能set environment variables on app pools。通过这种方式,您可以真正构建一次并将相同的输出复制到不同的服务器,甚至复制到同一服务器上的多个目录,并且仍然为不同的服务器使用不同的配置。
答案 1 :(得分:0)
我不知道它是否比上面的选项4复杂,但我们要解决的问题是在运行MSBuild之前立即运行PowerShell脚本,该脚本解析web.config转换并生成或扩充参数。 xml文件。这使我们可以灵活地使用参数化,并且能够修改web.config以外的配置文件,同时保留web.config转换的100%当前功能。以下是我们目前用于未来寻求者的脚本:
function Convert-XmlElementToString
{
[CmdletBinding()]
param([Parameter(Mandatory=$true)] $xml, [String[]] $attributesToExclude)
$attributesToRemove = @()
foreach($attr in $xml.Attributes) {
if($attr.Name.Contains('xdt') -or $attr.Name.Contains('xmlns') -or $attributesToExclude -contains $attr.Name) {
$attributesToRemove += $attr
}
}
foreach($attr in $attributesToRemove) { $removedAttr = $xml.Attributes.Remove($attr) }
$sw = New-Object System.IO.StringWriter
$xmlSettings = New-Object System.Xml.XmlWriterSettings
$xmlSettings.ConformanceLevel = [System.Xml.ConformanceLevel]::Fragment
$xmlSettings.Indent = $true
$xw = [System.Xml.XmlWriter]::Create($sw, $xmlSettings)
$xml.WriteTo($xw)
$xw.Close()
return $sw.ToString()
}
function BuildParameterXml ($name, $match, $env, $value, $parameterXmlDocument)
{
$existingNode = $parameterXmlDocument.selectNodes("//parameter[@name='$name']")
$value = $value.Replace("'","'") #Need to make sure any single quotes in the value don't break XPath
if($existingNode.Count -eq 0){
#no existing parameter for this transformation
$newParamter = [xml]("<parameter name=`"" + $name + "`">" +
"<parameterEntry kind=`"XmlFile`" scope=`"\\web.config$`" match=`"" + $match + "`" />" +
"<parameterValue env=`"" + $env + "`" value=`"`" />" +
"</parameter>")
$newParamter.selectNodes('//parameter/parameterValue').ItemOf(0).SetAttribute('value', $value)
$imported=$parameterXmlDocument.ImportNode($newParamter.DocumentElement, $true)
$appendedNode = $parameterXmlDocument.selectNodes('//parameters').ItemOf(0).AppendChild($imported)
} else {
#parameter exists but entry is different from an existing entry
$entryXPath = "//parameter[@name=`"$name`"]/parameterEntry[@kind=`"XmlFile`" and @scope=`"\\web.config$`" and @match=`"$match`"]"
$existingEntry = $parameterXmlDocument.selectNodes($entryXPath)
if($existingEntry.Count -eq 0) { throw "There is web.config transformation ($name) that conflicts with an existing parameters.xml entry" }
#parameter exists but environment value is different from an existing environment value
$envValueXPath = "//parameter[@name='$name']/parameterValue[@env='$env' and @value='$value']"
$existingEnvValue = $parameterXmlDocument.selectNodes($envValueXPath)
$existingEnv = $parameterXmlDocument.selectNodes("//parameter[@name=`"$name`"]/parameterValue[@env=`"$env`"]")
if($existingEnvValue.Count -eq 0 -and $existingEnv.Count -gt 0) {
throw "There is web.config transformation ($name) for this environment ($env) that conflicts with an existing parameters.xml value"
} elseif ($existingEnvValue.Count -eq 0 -and $existingEnv.Count -eq 0) {
$newParamter = [xml]("<parameterValue env=`"" + $env + "`" value=`"`" />")
$newParamter.selectNodes('//parameterValue').ItemOf(0).SetAttribute('value', $value)
$imported=$parameterXmlDocument.ImportNode($newParamter.DocumentElement, $true)
$appendedNode = $existingNode.ItemOf(0).AppendChild($imported)
}
}
}
function UpdateSetParams ($node, $originalXml, $path, $env, $parametersXml)
{
foreach ($childNode in $node.ChildNodes)
{
$xdtValue = ""
$name = ""
$match = ($path + $childNode.toString())
if($childNode.Attributes -and $childNode.Attributes.GetNamedItem('xdt:Locator').Value) {
$hasMatch = $childNode.Attributes.GetNamedItem('xdt:Locator').Value -match ".?\((.*?)\).*"
$name = $childNode.Attributes.GetNamedItem($matches[1]).Value
$match = $match + "[@" + $matches[1] + "=`'" + $name + "`']"
}
if($childNode.Attributes -and $childNode.Attributes.GetNamedItem('xdt:Transform')) {
$xdtValue = $childNode.Attributes.GetNamedItem('xdt:Transform').Value
}
if($xdtValue -eq 'Replace') {
if($childNode.Attributes.GetNamedItem('xdt:Locator').Value) {
$hasMatch = $childNode.Attributes.GetNamedItem('xdt:Locator').Value -match ".?\((.*?)\).*"
$name = $childNode.Attributes.GetNamedItem($matches[1]).Value
} else {
$name = $childNode.toString()
}
$nodeString = Convert-XmlElementToString $childNode.PsObject.Copy()
BuildParameterXml $name $match $env $nodeString $parametersXml
} elseif ($xdtValue.Contains('RemoveAttributes')) {
if($originalXml.selectNodes($match).Count -gt 0) {
$hasMatch = $xdtValue -match ".?\((.*?)\).*"
$nodeString = Convert-XmlElementToString $originalXml.selectNodes($match).ItemOf(0).PsObject.Copy() $matches[1].Split(',')
$newParamter = BuildParameterXml $childNode.toString() $match $env $nodeString $parametersXml
$newParamters += $newParamter
}
} elseif ($xdtValue.Contains('SetAttributes')) {
if($originalXml.selectNodes($match).Count -gt 0) {
$nodeCopy = $originalXml.selectNodes($match).ItemOf(0).PsObject.Copy()
$hasMatch = $xdtValue -match ".?\((.*?)\).*"
foreach($attr in $matches[1].Split(',')){
$nodeCopy.SetAttribute($attr, $childNode.Attributes.GetNamedItem($attr).Value)
}
$nodeString = Convert-XmlElementToString $nodeCopy
BuildParameterXml $childNode.toString() "($match)[1]" $env $nodeString $parametersXml
}
} elseif ($xdtValue) {
throw "Yikes! the script doesn't know how to handle this transformation!"
}
#Recurse into this node to check if it has transformations on its children
if($childNode) {
UpdateSetParams $childNode $originalXml ($match + "/") $env $parametersXml
}
}
}
function TransformConfigsIntoParamters ($webConfigPath, $webConfigTransformPath, $parametersXml)
{
#Parse out the environment names
$hasMatch = $webConfigTransformPath -match ".?web\.(.*?)\.config.*"
[xml]$transformXml = Get-Content $webConfigTransformPath
[xml]$webConfigXml = Get-Content $webConfigPath
UpdateSetParams $transformXml $webConfigXml '//' $matches[1] $parametersXml
}
$applicationRoot = $ENV:WORKSPACE
if(Test-Path ($applicationRoot + '\parameters.xml')) {
[xml]$parametersXml = Get-Content ($applicationRoot + '\parameters.xml')
$parametersNode = $parametersXml.selectNodes('//parameters').ItemOf(0)
} else {
[System.XML.XMLDocument]$parametersXml=New-Object System.XML.XMLDocument
[System.XML.XMLElement]$parametersNode=$parametersXml.CreateElement("parameters")
$appendedNode = $parametersXml.appendChild($parametersNode)
}
TransformConfigsIntoParamters ($applicationRoot + '\web.config') ($applicationRoot + '\web.Development.config') $parametersXml
TransformConfigsIntoParamters ($applicationRoot + '\web.config') ($applicationRoot + '\web.SystemTest.config') $parametersXml
TransformConfigsIntoParamters ($applicationRoot + '\web.config') ($applicationRoot + '\web.Production.config') $parametersXml
$parametersXml.Save($applicationRoot + '\parameters.xml')