Dev Azure Pipeline Web Config Transform

The web.config transform appears at first glance to be somewhat orphaned in Azure DevOps. If you are deploying to a WebApp then the pipeline wizard will probably have given you this snippet for your Build step:

- task: [email protected]
inputs:
solution: '$(solution)'
msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactStagingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'

To run config transforms you can add the additional parameter /p:TransformConfigFile=true to your msbuildArgs.

The replacement, of course, is the independent FileTransform task (which can also work within zip files). But once your site is packaged the Web.Release.Config is left behind; so you have to run the FileTransform task before, not after, the build step. (Similarly the WebAppDeployment task can’t transform a Web.Release.config that isn’t inside the package).

Slightly magically, the default parameter for the FileTransform is which is exactly what you typically want for web.Release.config transforms:

#xmlTransformationRules: '-transform **\*.Release.config -xml **\*.config-transform **\*.$(Release.EnvironmentName).config -xml **\*.config' # Optional

FileTransform can also do variable substitution, and you can have multiple FileTransform steps in your pipeline. I also like to see some diagnostic feedback so I include script step to show the xml transform result. But I put it before variable substitution, because the variables often include secrets:

- task: [email protected]
displayName: Transform Release.Config
inputs:
folderPath: '$(System.DefaultWorkingDirectory)/Ui/'
enableXmlTransform: true
xmlTransformationRules: -transform **/*.Release.config -xml **/*.config

- task: [email protected]
displayName: "Confirm web.config transform"
inputs:
targetType: 'inline'
script: 'cat $env:System_DefaultWorkingDirectory/Ui/web.config'
errorActionPreference: 'continue'

- task: [email protected]
displayName: Variable substitution *.config
inputs:
folderPath: '$(System.DefaultWorkingDirectory)/Ui/'
fileType: 'xml'
targetFiles: '**/*.config'

- task: [email protected]
...etc..

Reference

An Asp.Net MVC HtmlHelper.RadioButtonsFor helper

These overloads will do the hopefully-obvious thing with a Model => Model.Property and a List of SelectListItems (or a single SelectListItem)

Use the RadioButtonLabelLayout setting to control whether you nest the radio button inside its label, or lay them out as siblings; and whether you like text before button or vice versa.

    public static class HtmlHelperRadioButtonExtensions
    {
        /// <summary>
        /// Returns radio buttons for the property in the object represented by the specified expression.
        /// A radio button is rendered for each item in <paramref name="listOfValues"/>.
        /// Use <paramref name="labelLayout"/> to control whether each label contains its button, or is a sibling, 
        /// and whether button precedes text or vice versa.
        /// <list type="bullet">
        /// <item>
        ///     <term>Example result for the default labelLayout= RadioButtonLabelLayout.LabelTagContainsButtonThenText:</term>
        ///     <description>
        ///     &lt;label for="Object_Property_Red"&gt;&lt;input id="Object_Property_Red" name="Object.Property" type="radio" value="Red" /&gt; Red&lt;/label&gt; 
        ///     &lt;label for="Object_Property_Blue"&gt;&lt;input id="Object_Property_Blue" name="Object.Property" type="radio" value="Blue" /&gt; Blue&lt;/label&gt; 
        ///     </description>
        /// </item>
        /// <item>
        ///     <term>Example result for labelLayout= RadioButtonLabelLayout.SiblingBeforeButton:</term>
        ///     <description>
        ///     &lt;label for="Object_Property_Red"&gt;Red&lt;/label&gt; &lt;input id="Object_Property_Red" name="Object.Property" type="radio" value="Red" /&gt;
        ///     &lt;label for="Object_Property_Blue"&gt; Blue&lt;/label&gt; &lt;input id="Object_Property_Blue" name="Object.Property" type="radio" value="Blue" /&gt;
        ///     </description>
        /// </item>
        /// </list>
        /// </summary>
        /// <param name="listOfValues">Used to generate the Value and the Label Text for each radio button</param>
        /// <param name="labelLayout">One <see cref="RadioButtonLabelLayout"/> to control the layout of the rendered button and its label.</param>
        /// <returns>
        /// An MvcHtmlString for the required buttons 
        /// 
        /// </returns>
        public static MvcHtmlString 
            RadioButtonsFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                    Expression<Func<TModel, TProperty>> expression,
                                                    IEnumerable<SelectListItem> listOfValues,
                                                    RadioButtonLabelLayout labelLayout = RadioButtonLabelLayout.LabelTagContainsButtonThenText)
        {
            if (listOfValues == null) { return null; }
            var buttons= listOfValues.Select(item => RadioButtonFor(htmlHelper, expression, item, labelLayout));
            return MvcHtmlString.Create(buttons.Aggregate(new StringBuilder(), (sb, o) => sb.Append(o), sb => sb.ToString()));
        }

        /// <summary> Create an <see cref="IEnumerable{T}"/> list of radio buttons ready for individual processing before rendering</summary>
        public static IEnumerable<MvcHtmlString> RadioButtonListFor<TModel, TProperty>(
                    this HtmlHelper<TModel> htmlHelper,
                    Expression<Func<TModel, TProperty>> expression,
                    IEnumerable<SelectListItem> listOfValues,
                    RadioButtonLabelLayout labelLayout)
        {
            if (listOfValues == null) { return new MvcHtmlString[0]; }
            return listOfValues.Select( item =>  RadioButtonFor(htmlHelper, expression, item, labelLayout) );
        }

        public static MvcHtmlString RadioButtonFor<TModel, TProperty>(
                                HtmlHelper<TModel> htmlHelper, 
                                Expression<Func<TModel, TProperty>> expression, 
                                SelectListItem item, 
                                RadioButtonLabelLayout labelLayout)
        {
            var id = htmlHelper.IdFor(expression) + " " + item.Value;
            var radio = htmlHelper.RadioButtonFor(expression, item.Value, new {id}).ToHtmlString();
            var labelText = HttpUtility.HtmlEncode(item.Text);
            TagBuilder nestingLabel = null;
            switch (labelLayout)
            {
                case RadioButtonLabelLayout.LabelTagContainsTextThenButton:
                    nestingLabel = new TagBuilder("label") {InnerHtml = labelText + " " + radio};
                    return MvcHtmlString.Create(nestingLabel.ToString());
                case RadioButtonLabelLayout.LabelTagContainsButtonThenText:
                    nestingLabel = new TagBuilder("label") {InnerHtml = radio + " " + labelText};
                    return MvcHtmlString.Create(nestingLabel.ToString());
                case RadioButtonLabelLayout.SiblingBeforeButton:
                    return MvcHtmlString.Create(radio + " " + htmlHelper.Label(id, labelText));
                case RadioButtonLabelLayout.SiblingAfterButton:
                    return MvcHtmlString.Create(htmlHelper.Label(id, labelText) + " " + radio);
                default:
                    throw new ArgumentOutOfRangeException("labelLayout", labelLayout, 
                                "This is not a valid RadioButtonLayoutStyle for rendering a radio button");
            }
        }
    }

    public enum RadioButtonLabelLayout
    {
        LabelTagContainsButtonThenText = 0,
        LabelTagContainsTextThenButton = 1,
        SiblingBeforeButton = 2,
        SiblingAfterButton = 3,
    }

Asp.Net MVC 4 on Mono and Windows

Having at last got an Asp.Net Mvc 4 project template working on the Mac/Xamarin/Mono, I came to grief again when deploying it to a Windows hosted server. The problem is that whereas mono works without Microsoft.Web.Infrastructure.dll (and indeed is likely to give errors if you have it), for hosting on Windows you must re-include it.
So my deploy process now includes putting Microsoft.Web.Infrastructure.dll back in the web application bin/ directory; and now I can develop on the Mac and deploy to a Windows Server.