I have the following in my root ResourceDictionary. The Foreground = Red part works, but the custom attached dependency property does not get set.
I can set it manually through code, but I obviously want to avoid having to set it for every textbox. Does this work in Silverlight? I have seen some posts about doing it in WPF, and my code looks right (to me).
<Style TargetType="TextBox">
<Setter Property="controls:TextBoxContextMenu.HasContextMenu" Value="True" />
<Setter Property="Foreground" Value="Red" />
</Style>
/// <summary>
/// Gets the value of the HasContextMenu attached property for a specified TextBox.
/// </summary>
/// <param name="element">The TextBox from which the property value is read.</param>
/// <returns>The HasContextMenu property value for the TextBox.</returns>
public static bool GetHasContextMenu(TextBox element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (bool)element.GetValue(HasContextMenuProperty);
}
/// <summary>
/// Sets the value of the HasContextMenu attached property to a specified TextBox.
/// </summary>
/// <param name="element">The TextBox to which the attached property is written.</param>
/// <param name="value">The needed HasContextMenu value.</param>
public static void SetHasContextMenu(TextBox element, bool value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(HasContextMenuProperty, value);
}
/// <summary>
/// Identifies the HasContextMenu dependency property.
/// </summary>
public static readonly DependencyProperty HasContextMenuProperty =
DependencyProperty.RegisterAttached(
"HasContextMenu",
typeof(bool),
typeof(TextBox),
new PropertyMetadata(false, OnHasContextMenuPropertyChanged));
/// <summary>
/// HasContextMenuProperty property changed handler.
/// </summary>
/// <param name="d">TextBoxContextMenu that changed its HasContextMenu.</param>
/// <param name="e">Event arguments.</param>
private static void OnHasContextMenuPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// code
}
I should add that the attached dependency property is defined in a class which inherits from RadContextMenu, which is a DependencyObject (I have been reading and somewhere it is suggested that this can't work if the attached property is defined in such a class, but this seems strange)
I figured it out. It was indeed due to having the attached property defined in the class I have.
To fix it, i created a new class called TextBoxContextMenuService and put the code in there instead.
Related
Imagine the following XAML:
<Page
x:Class="MyProject.MainPage"
xmlns:local="using:MyProject">
<local:MyStackPanel>
<Button Content="Go" Click="OnGo"/>
</local:MyStackPanel>
</Page>
XAML assumes that OnGois a method in the page class. Can I somehow tell it declaratively that OnGo resides in MyStackPanel instead?
I know I can assign the event handler programmatically once the page is loaded. I'm wondering if it's possible by purely XAML means. "No" is an acceptable answer :)
For your requirement, you could bind the MyStackPanel DataContext with itself. Then create BtnClickCommand that used to bind button command in the MyStackPanel class like following.
public class MyStackPanel : StackPanel
{
public MyStackPanel()
{
}
public ICommand BtnClickCommand
{
get
{
return new RelayCommand(() =>
{
});
}
}
}
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
/// <summary>
/// Raised when RaiseCanExecuteChanged is called.
/// </summary>
public event EventHandler CanExecuteChanged;
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
/// <summary>
/// Determines whether this RelayCommand can execute in its current state.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed,
/// this object can be set to null.
/// </param>
/// <returns>true if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute();
}
/// <summary>
/// Executes the RelayCommand on the current command target.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed,
/// this object can be set to null.
/// </param>
public void Execute(object parameter)
{
_execute();
}
/// <summary>
/// Method used to raise the CanExecuteChanged event
/// to indicate that the return value of the CanExecute
/// method has changed.
/// </summary>
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
Usage
<local:MyStackPanel DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
<Button Command="{Binding BtnClickCommand}" Content="ClickMe" />
</local:MyStackPanel>
We want to use swagger with in our asp.net core mvc web application. Because we use IoC to inject required instances in our controller, swagger is not able to build the swagger UI. When I remove the constructor it works fine.
Does anybody have an idea or an workaround using swagger with a constructor inside the controller?
[Route("TestNamesapce/TestService/2017/08/[controller]")]
public class TestController: FullController
{
private readonly IMaintenanceTaskProvider _provider;
private readonly MaintenanceServiceConfiguration _config;
private ILog _log;
private MessageWorker _worker;
/// <summary>
///
/// </summary>
/// <param name="log">The log.</param>
/// <param name="eventing">The eventing.</param>
/// <param name="worker">The worker.</param>
/// <param name="options">The options.</param>
/// <param name="context">The context.</param>
public TestController(ILog log, IEventingClient eventing, IFileManagementClient fileManagement, MessageWorker worker, IMaintenanceTaskProvider provider, IOptions<Configuration> options) : base(log, eventing, fileManagement)
{
_config = options.Value;
_log = log;
_provider = provider;
_worker = worker;
}
/// <summary>
/// Gets the internal license identifier.
/// </summary>
/// <returns></returns>
[HttpGet("GetInternalLicenseId")]
public string GetInternalLicenseId()
{
return "abcdefghijklmnop";
}
As a workaround, you could add a default constructor to be used for this purpose alone. Though I would also prefer to see a better solution.
I'm currently experiencing some issues with workflow services.
They work fine if I start 4, 5 in short sequence, but if I increase this value (starting from ~10) then I get the following exception:
This channel can no longer be used to send messages as the output session was auto-closed due to a server-initiated shutdown. Either disable auto-close by setting the DispatchRuntime.AutomaticInputSessionShutdown to false, or consider modifying the shutdown protocol with the remote server.
I think that the problem is in the way I create proxies. I use the following code to provide proxies, attempting to reuse existing ones:
public abstract class ProxyProvider<TService>
where TService : class
{
/// <summary>
/// Static reference to the current time provider.
/// </summary>
private static ProxyProvider<TService> current = DefaultProxyProvider.Instance;
private TService service;
/// <summary>
/// Gets or sets the current time provider.
/// </summary>
/// <value>
/// The current time provider.
/// </value>
public static ProxyProvider<TService> Current
{
get
{
return ProxyProvider<TService>.current;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
ProxyProvider<TService>.current = value;
}
}
/// <summary>
/// Resets to default.
/// </summary>
public static void ResetToDefault()
{
ProxyProvider<TService>.current = DefaultProxyProvider.Instance;
}
/// <summary>
/// Loads the proxy.
/// </summary>
/// <param name="forceNew">if set to <c>true</c> [force new].</param>
/// <returns>The instance of the proxy.</returns>
public virtual TService Provide(bool forceNew = false)
{
if (forceNew || !this.IsInstanceValid())
{
this.service = this.CreateInstance();
return this.service;
}
return this.service;
}
/// <summary>
/// Internals the load.
/// </summary>
/// <returns>The new created service.</returns>
protected abstract TService CreateInstance();
private bool IsInstanceValid()
{
var instance = this.service as ICommunicationObject;
if (instance == null)
{
return false;
}
return instance.State != CommunicationState.Faulted && instance.State != CommunicationState.Closed && instance.State != CommunicationState.Closing;
}
/// <summary>
/// Defines the default <see cref="ProxyProvider<TService>"/> which uses the System DateTime.UtcNow value.
/// </summary>
private sealed class DefaultProxyProvider : ProxyProvider<TService>
{
/// <summary>
/// Reference to the instance of the <see cref="ProxyProvider<TService>"/>.
/// </summary>
private static ProxyProvider<TService> instance;
/// <summary>
/// Gets the instance.
/// </summary>
public static ProxyProvider<TService> Instance
{
get
{
if (DefaultProxyProvider.instance == null)
{
DefaultProxyProvider.instance = new DefaultProxyProvider();
}
return DefaultProxyProvider.instance;
}
}
/// <summary>
/// Loads the specified force new.
/// </summary>
/// <returns>A non-disposed instance of the given service.</returns>
protected override TService CreateInstance()
{
var loadedService = Activator.CreateInstance<TService>();
return loadedService;
}
}
With an additional "lazy" provider:
public class CustomConstructorProxyProvider<TService> : ProxyProvider<TService>
where TService : class
{
private readonly Func<TService> constructor;
/// <summary>
/// Initializes a new instance of the <see cref="CustomConstructorProxyProvider<TService>"/> class.
/// </summary>
/// <param name="constructor">The constructor.</param>
public CustomConstructorProxyProvider(Func<TService> constructor)
{
this.constructor = constructor;
}
/// <summary>
/// Internals the load.
/// </summary>
/// <returns>The new created service.</returns>
protected override TService CreateInstance()
{
var service = this.constructor();
return service;
}
}
Used this way:
var proxy = ProxyProvider<IWorkflowService>.Current.Provide();
proxy.DoSomething();
Initialized like this:
ProxyProvider<IWorkflowService>.Current = new CustomConstructorProxyProvider<IWorkflowService>(() => new WorkflowServiceProxy("endpoint"));
Workflow services are hosted by IIS and I added the following throttling settings:
<serviceThrottling
maxConcurrentCalls="512"
maxConcurrentInstances="2147483647"
maxConcurrentSessions="1024"/>
which should be enough for my needs.
I hope that someone can help me configuring client and server to have achieve the desired scalability (a few hundreds started in sequence and running in parallel, using the WorkflowInstance sql store).
UPDATE:
I'm using NetTcpBinding for all services.
UPDATE 2:
All services are hosted and consumed by now locally.
Thanks
Francesco
I have a StackPanel in my WinRT C# Metro app that I want to use as container for a label/value pair (TextBlock and TextBox) like this:
<StackPanel Orientation="Horizontal" Width="400">
<TextBlock Text="Label" Width="150" />
<TextBox Text="Value" />
</StackPanel>
Now what I want is to let the TextBox automatically fill the remaining horizontal space of the StackPanel. Is this possible in any way? HorizontalAlignment/HorizontalContentAlignment don't work.
I know that an alternative would be to define a Grid for that. The problem is that I have this construct several times and want to use it in a Style definition. I don't want to define the Grid's definition with rows and columns x times...
Perhaps the alternative would be to define a custom user control, but I hoped there would be an easy possibility to get the TextBox to stretch.
Unfortunately the alternative (DockPanel) isnt available for Metro. You could try a WrapGrid, but I dont know if it'll solve your problem (Ive never used it).
The only real way of doing this is using a Grid as you described:
<Grid Width="400">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Label" Width="150" />
<TextBox Text="Value" Grid.Column="1" />
</Grid>
I am not sure which element you put HorizontalAlignment to, but this is what works for me:
<StackPanel>
<TextBlock Text="Title" Style="..."/>
<TextBlock Margin="0,0,0,10" Text="Description" Style="..."/>
<TextBox HorizontalAlignment="Stretch" />
<Button Margin="0" Content="Save"/>
</StackPanel>
This stretches the TextBox to the whole width of the StackPanel.
Came across this question while looking up whether a WinRT DockPanel existed.
I ended up slightly modifying the DockPanel that comes with the Silverlight Toolkit, and it seems to work.
To use, add xmlns:toolkit="using:System.Windows.Controls" to the top, and taking the question above as the scenario, we could write:
<toolkit:DockPanel Width="400">
<TextBlock Text="Label" Width="150" toolkit:DockPanel.Dock="Left" />
<TextBox Text="Value" />
</toolkit:DockPanel>
DockPanel.cs:
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
// Modified slightly for Windows Runtime support.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace System.Windows.Controls
{
public enum Dock
{
// Summary:
// Specifies that the control should be positioned on the left of the control.
Left = 0,
//
// Summary:
// Specifies that the control should be positioned on top of the control.
Top = 1,
//
// Summary:
// Specifies that the control should be positioned on the right of the control.
Right = 2,
//
// Summary:
// Specifies that the control should be positioned at the bottom of the control.
Bottom = 3,
}
/// <summary>
/// Arranges child elements around the edges of the panel. Optionally,
/// last added child element can occupy the remaining space.
/// </summary>
/// <QualityBand>Stable</QualityBand>
public class DockPanel : Panel
{
/// <summary>
/// A value indicating whether a dependency property change handler
/// should ignore the next change notification. This is used to reset
/// the value of properties without performing any of the actions in
/// their change handlers.
/// </summary>
private static bool _ignorePropertyChange;
#region public bool LastChildFill
/// <summary>
/// Gets or sets a value indicating whether the last child element
/// added to a <see cref="T:System.Windows.Controls.DockPanel" />
/// resizes to fill the remaining space.
/// </summary>
/// <value>
/// True if the last element added resizes to fill the remaining space,
/// false to indicate the last element does not resize. The default is
/// true.
/// </value>
public bool LastChildFill
{
get { return (bool)GetValue(LastChildFillProperty); }
set { SetValue(LastChildFillProperty, value); }
}
/// <summary>
/// Identifies the
/// <see cref="P:System.Windows.Controls.DockPanel.LastChildFill" />
/// dependency property.
/// </summary>
public static readonly DependencyProperty LastChildFillProperty =
DependencyProperty.Register(
"LastChildFill",
typeof(bool),
typeof(DockPanel),
new PropertyMetadata(true, OnLastChildFillPropertyChanged));
/// <summary>
/// LastChildFillProperty property changed handler.
/// </summary>
/// <param name="d">DockPanel that changed its LastChildFill.</param>
/// <param name="e">Event arguments.</param>
private static void OnLastChildFillPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DockPanel source = d as DockPanel;
source.InvalidateArrange();
}
#endregion public bool LastChildFill
#region public attached Dock Dock
/// <summary>
/// Gets the value of the
/// <see cref="P:System.Windows.Controls.DockPanel.Dock" /> attached
/// property for the specified element.
/// </summary>
/// <param name="element">
/// The element from which the property value is read.
/// </param>
/// <returns>
/// The <see cref="P:System.Windows.Controls.DockPanel.Dock" /> property
/// value for the element.
/// </returns>
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "DockPanel only has UIElement children")]
public static Dock GetDock(UIElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (Dock)element.GetValue(DockProperty);
}
/// <summary>
/// Sets the value of the
/// <see cref="P:System.Windows.Controls.DockPanel.Dock" /> attached
/// property for the specified element to the specified dock value.
/// </summary>
/// <param name="element">
/// The element to which the attached property is assigned.
/// </param>
/// <param name="dock">
/// The dock value to assign to the specified element.
/// </param>
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "DockPanel only has UIElement children")]
public static void SetDock(UIElement element, Dock dock)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(DockProperty, dock);
}
/// <summary>
/// Identifies the
/// <see cref="P:System.Windows.Controls.DockPanel.Dock" />
/// attached property.
/// </summary>
public static readonly DependencyProperty DockProperty =
DependencyProperty.RegisterAttached(
"Dock",
typeof(Dock),
typeof(DockPanel),
new PropertyMetadata(Dock.Left, OnDockPropertyChanged));
/// <summary>
/// DockProperty property changed handler.
/// </summary>
/// <param name="d">UIElement that changed its Dock.</param>
/// <param name="e">Event arguments.</param>
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Almost always set from the attached property CLR setter.")]
private static void OnDockPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Ignore the change if requested
if (_ignorePropertyChange)
{
_ignorePropertyChange = false;
return;
}
UIElement element = (UIElement)d;
Dock value = (Dock)e.NewValue;
// Validate the Dock property
if ((value != Dock.Left) &&
(value != Dock.Top) &&
(value != Dock.Right) &&
(value != Dock.Bottom))
{
// Reset the property to its original state before throwing
_ignorePropertyChange = true;
element.SetValue(DockProperty, (Dock)e.OldValue);
string message = string.Format(
CultureInfo.InvariantCulture,
"InvalidValue",
value);
throw new ArgumentException(message, "value");
}
// Cause the DockPanel to update its layout when a child changes
DockPanel panel = VisualTreeHelper.GetParent(element) as DockPanel;
if (panel != null)
{
panel.InvalidateMeasure();
}
}
#endregion public attached Dock Dock
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:System.Windows.Controls.DockPanel" /> class.
/// </summary>
public DockPanel()
{
}
/// <summary>
/// Measures the child elements of a
/// <see cref="T:System.Windows.Controls.DockPanel" /> in preparation
/// for arranging them during the
/// <see cref="M:System.Windows.Controls.DockPanel.ArrangeOverride(System.Windows.Size)" />
/// pass.
/// </summary>
/// <param name="constraint">
/// The area available to the
/// <see cref="T:System.Windows.Controls.DockPanel" />.
/// </param>
/// <returns>
/// The desired size of the
/// <see cref="T:System.Windows.Controls.DockPanel" />.
/// </returns>
[SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")]
protected override Size MeasureOverride(Size constraint)
{
double usedWidth = 0.0;
double usedHeight = 0.0;
double maximumWidth = 0.0;
double maximumHeight = 0.0;
// Measure each of the Children
foreach (UIElement element in Children)
{
// Get the child's desired size
Size remainingSize = new Size(
Math.Max(0.0, constraint.Width - usedWidth),
Math.Max(0.0, constraint.Height - usedHeight));
element.Measure(remainingSize);
Size desiredSize = element.DesiredSize;
// Decrease the remaining space for the rest of the children
switch (GetDock(element))
{
case Dock.Left:
case Dock.Right:
maximumHeight = Math.Max(maximumHeight, usedHeight + desiredSize.Height);
usedWidth += desiredSize.Width;
break;
case Dock.Top:
case Dock.Bottom:
maximumWidth = Math.Max(maximumWidth, usedWidth + desiredSize.Width);
usedHeight += desiredSize.Height;
break;
}
}
maximumWidth = Math.Max(maximumWidth, usedWidth);
maximumHeight = Math.Max(maximumHeight, usedHeight);
return new Size(maximumWidth, maximumHeight);
}
/// <summary>
/// Arranges the child elements of the
/// <see cref="T:System.Windows.Controls.DockPanel" /> control.
/// </summary>
/// <param name="arrangeSize">
/// The area in the parent element that the
/// <see cref="T:System.Windows.Controls.DockPanel" /> should use to
/// arrange its child elements.
/// </param>
/// <returns>
/// The actual size of the
/// <see cref="T:System.Windows.Controls.DockPanel" /> after the child
/// elements are arranged. The actual size should always equal
/// <paramref name="arrangeSize" />.
/// </returns>
[SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")]
protected override Size ArrangeOverride(Size arrangeSize)
{
double left = 0.0;
double top = 0.0;
double right = 0.0;
double bottom = 0.0;
// Arrange each of the Children
UIElementCollection children = Children;
int dockedCount = children.Count - (LastChildFill ? 1 : 0);
int index = 0;
foreach (UIElement element in children)
{
// Determine the remaining space left to arrange the element
Rect remainingRect = new Rect(
left,
top,
Math.Max(0.0, arrangeSize.Width - left - right),
Math.Max(0.0, arrangeSize.Height - top - bottom));
// Trim the remaining Rect to the docked size of the element
// (unless the element should fill the remaining space because
// of LastChildFill)
if (index < dockedCount)
{
Size desiredSize = element.DesiredSize;
switch (GetDock(element))
{
case Dock.Left:
left += desiredSize.Width;
remainingRect.Width = desiredSize.Width;
break;
case Dock.Top:
top += desiredSize.Height;
remainingRect.Height = desiredSize.Height;
break;
case Dock.Right:
right += desiredSize.Width;
remainingRect.X = Math.Max(0.0, arrangeSize.Width - right);
remainingRect.Width = desiredSize.Width;
break;
case Dock.Bottom:
bottom += desiredSize.Height;
remainingRect.Y = Math.Max(0.0, arrangeSize.Height - bottom);
remainingRect.Height = desiredSize.Height;
break;
}
}
element.Arrange(remainingRect);
index++;
}
return arrangeSize;
}
}
}
How about using a RelativePanel.
<RelativePanel Width="320" Height="180" BorderBrush="Black" BorderThickness="1">
<TextBox RelativePanel.AlignTopWithPanel="True"
RelativePanel.AlignBottomWithPanel="True"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"
TextWrapping="Wrap"/>
</RelativePanel>
figured out that the best native control to neast the contents is the grid look at my exmple that worked fine to me:
<Grid >
<TextBox x:Name="txtCountry" Margin="0,8,41,8" />
<Button x:Name="btnGetCountry" Content="..." Margin="0,8,0,8" HorizontalAlignment="Right" Click="btnGetCountry_Click"> </Button>
</Grid>
this will generate a nice adaptable layout with a textbox steching and a butoon on it side that maintains his aspect
I am trying to find examples of how to use WixSharp (managed code interface to WiX) to install a website and associate an AppPool.
The steps I want to achieve are:
If the website exists in IIS 6, delete it.
If the AppPool exists in IIS 6, delete it.
Delete the application artifacts from the destination directory.
Copy the new application artifacts to the destination directory.
Create the AppPool.
Create the Website, linking it to the AppPool.
I have achieved this in MSBuild but that is not as useful as an MSI. Hence I am trying to "rewrite" the above in WixSharp syntax.
WixSharp apparently supports WIXIISExtension but Google has not yielded any examples yet.
How would I code the above in WixSharp?
I am using WIX for the same purpose. The application I am trying to deploy is around 300 MB and I need to create virtual directory for same, app pool, etc.
I think your requirement is same.
I would suggest WIX is really good for this. You can have screens asking user virtual directory name, Application Pool, etc.
WIX code works perfectly for IIS 5.1, 6, 7. For 7.5 you need to create a customaction. As such you can use wix to create virtual directory even for IIS 7.5 if IIS 6 compatibility mode is installed.
Uptill now I haven't faced any errors using WIX to deploy web applications.
Good question.
I am using WixSharp for my current project and I am really happy with it. It is awesome how you can avoid writing XML files by doing all with C# syntax. By the way, I don't think you are reinventing the wheel... you are speeding up the wheel with WixSharp.
As WixSharp version 1.9.6 doesn't provide creating a WebSite with an associated WebAppPool, I did it by creating a CustomWebSite.cs file. In this way, I created my Web Application using this code:
...
var project = new ManagedProject("My Project",
new InstallDir(#"c:\my_tool",
new Dir("my_frontend",
new Files($"{frontendDir}\\app\\*.*"),
new CustomWebSite("GateKeeper", "*:31515")
{
WebApplication = new CustomWebApplication("DemoApp")
{
WebAppPool = new WebAppPool("DemoApp", "ManagedPipelineMode=Integrated;Identity=applicationPoolIdentity"),
},
InstallWebSite = true
}
)
),
...
Here is my CustomWebSite.cs file that I only used to create one WebSite and I am sure it could be better:
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using WixSharp;
using WixSharp.CommonTasks;
using static WixSharp.WebSite;
namespace ToolBox.WixSharp
{
/// <summary>
/// Defines the WebSite element to be created associated to a Dir element.
/// </summary>
///<example>The following is an example of associating a CustomWebSite to a Dir element.
///
///<code>
/// var project =
/// new Project("My Product",
/// new Dir(#"%ProgramFiles%\My Company\My Product",
/// new Dir(#"some_dir",
/// new CustomWebSite("MyApp", "*:81")
/// {
/// WebApplication = new CustomWebApplication("DemoApp")
/// {
/// WebAppPool = new WebAppPool("DemoApp", "ManagedPipelineMode=Integrated;Identity=applicationPoolIdentity")
/// }
/// InstallWebSite = true
/// }
/// ...
///
/// Compiler.BuildMsi(project);
///</code>
///
/// This code will generate something like this:
///<code>
/// <Component Id="DemoApp_WebSite" Guid="a6896bba-1818-43e0-824f-9c585b3e366b" KeyPath="yes" Win64="yes">
/// <iis:WebSite Id = "DemoApp_WebSite" Description="DemoApp_WebSite" Directory="INSTALLDIR.some_dir">
/// <iis:WebAddress Id = "WebSite_Address1" IP="*" Port="31515" />
/// <iis:WebApplication Id = "DemoApp_WebApplication" Name="DemoApp" WebAppPool="DemoApp_AppPool"/>
/// </iis:WebSite>
/// <iis:WebAppPool Id = "DemoApp_AppPool" Name="DemoApp" ManagedPipelineMode="Integrated" Identity="applicationPoolIdentity" />
///
/// <CreateFolder />
/// <RemoveFolder Id = "INSTALLDIR.some_dir" On="uninstall" />
/// </Component>
/// </code>
/// </example>
public class CustomWebSite : WixEntity, IGenericEntity
{
/// <summary>
/// Indicates if the WebSite is to be installed (created on IIS) or existing WebSite should be used to install the corresponding
/// WebApplication. The default <see cref="InstallWebSite"/> value is <c>false</c>
/// <para>Developers should be aware of the WebSite installation model imposed by WiX/MSI and use <see cref="InstallWebSite"/> carefully.</para>
/// <para>If <see cref="InstallWebSite"/> value is set to <c>false</c> the parent WebApplication (<see cref="T:WixSharp.IISVirtualDir"/>)
/// will be installed in the brand new (freshly created) WebSite or in the existing one if a site with the same address/port combination already exists
/// on IIS). The undesirable side affect of this deployment scenario is that if the existing WebSite was used to install the WebApplication it will be
/// deleted on IIS during uninstallation even if this WebSite has other WebApplications installed.</para>
/// <para>The "safer" option is to set <see cref="InstallWebSite"/> value to <c>true</c> (default value). In this case the WebApplication will
/// be installed in an existing WebSite with matching address/port. If the match is not found the installation will fail. During the uninstallation
/// only installed WebApplication will be removed from IIS.</para>
/// </summary>
public bool InstallWebSite = false;
/// <summary>
/// Initializes a new instance of the <see cref="WebSite" /> class.
/// </summary>
public CustomWebSite()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CustomWebSite"/> class.
/// </summary>
/// <param name="description">The description of the web site (as it shows up in the IIS manager console).</param>
/// <param name="addressDefinition">The address definition.</param>
public CustomWebSite(string description, string addressDefinition)
{
this.Id = $"{description}_WebSite";
this.Description = description;
this.AddressesDefinition = addressDefinition;
}
/// <summary>
/// Initializes a new instance of the <see cref="CustomWebSite"/> class.
/// </summary>
/// <param name="id">The id</param>
/// <param name="description">The description of the web site (as it shows up in the IIS manager console).</param>
/// <param name="addressDefinition">The address definition.</param>
public CustomWebSite(Id id, string description, string addressDefinition)
{
this.Id = id;
this.Description = description;
this.AddressesDefinition = addressDefinition;
}
internal void ProcessAddressesDefinition()
{
if (!AddressesDefinition.IsEmpty())
{
List<WebAddress> addressesToAdd = new List<WebAddress>();
foreach (string addressDef in AddressesDefinition.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
{
try
{
string[] tokens = addressDef.Split(":".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
string address = tokens[0];
string port = tokens[1];
if (tokens[1].ContainsWixConstants())
{
addressesToAdd.Add(new WebAddress { Address = address, AttributesDefinition = "Port=" + port });
}
else
{
addressesToAdd.Add(new WebAddress { Address = address, Port = Convert.ToInt32(port) });
}
}
catch (Exception e)
{
throw new Exception("Invalid AddressesDefinition", e);
}
}
this.addresses = addressesToAdd.ToArray();
}
}
/// <summary>
/// References a WebAppPool instance to use as the application pool for this application in IIS 6 applications.
/// </summary>
public string WebAppPool; //WebApplication element attribute
/// <summary>
/// Specification for auto-generating the <see cref="T:WebSite.WebAddresses"/> collection.
/// <para>If <see cref="AddressesDefinition"/> is specified, the existing content of <see cref="Addresses"/> will be ignored
/// and replaced with the auto-generated one at compile time.</para>
/// </summary>
/// <example>
/// <c>webSite.AddressesDefinition = "*:80;*90";</c> will be parsed and converted to an array of <see cref="T:WixSharp.WebSite.WebAddress"/> as follows:
/// <code>
/// ...
/// webSite.Addresses = new []
/// {
/// new WebSite.WebAddress
/// {
/// Address = "*",
/// Port = 80
/// },
/// new WebSite.WebAddress
/// {
/// Address = "*",
/// Port = 80
/// }
/// }
/// </code>
/// </example>
public string AddressesDefinition = "";
//// The iis:WebSite/#Directory attribute must be specified when the element has a Component as an ancestor..
//public string Directory = "";
/// <summary>
/// Reference to a WebApplication that is to be installed as part of this web site.
/// </summary>
public CustomWebApplication WebApplication = null;
/// <summary>
/// Collection of <see cref="T:WebSite.WebAddresses"/> associated with website.
/// <para>
/// The user specified values of <see cref="Addresses"/> will be ignored and replaced with the
/// auto-generated addresses if <see cref="AddressesDefinition"/> is specified either directly or via appropriate <see cref="WebSite"/> constructor.
/// </para>
/// </summary>
public WebAddress[] Addresses
{
get
{
ProcessAddressesDefinition();
return addresses;
}
set
{
addresses = value;
}
}
/// <summary>
/// This class defines WebAppPool WiX element. It is used to specify the application pool for this application in IIS 6 applications.
/// </summary>
public partial class CustomWebApplication : WixEntity
{
/// <summary>
/// References a WebAppPool instance to use as the application pool for this application in IIS 6 applications.
/// </summary>
public WebAppPool WebAppPool; //WebApplication element attribute
/// <summary>
/// Initializes a new instance of the <see cref="WebApplication"/> class.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="attributesDefinition">The attributes definition. This parameter is used to set encapsulated <see cref="T:WixSharp.WixEntity.AttributesDefinition"/>.</param>
public CustomWebApplication(string name, string attributesDefinition)
{
base.Id = $"{name}_WebApplication";
base.Name = name;
base.AttributesDefinition = attributesDefinition;
}
/// <summary>
/// Initializes a new instance of the <see cref="WebAppPool"/> class.
/// </summary>
/// <param name="name">The name.</param>
public CustomWebApplication(string name)
{
base.Id = $"{name}_WebApplication";
base.Name = name;
}
/// <summary>
/// Initializes a new instance of the <see cref="WebAppPool"/> class.
/// </summary>
public CustomWebApplication()
{
}
}
WebAddress[] addresses = new WebAddress[0];
/// <summary>
/// Primary key used to identify this particular entry.
/// </summary>
[Xml]
public new string Id
{
get
{
return base.Id;
}
set
{
base.Id = value;
}
}
/// <summary>
/// The value to set into the environment variable. If this attribute is not set, the environment variable is removed
/// during installation if it exists on the machine.
/// </summary>
[Xml]
public string Description;
/// <summary>
/// Defines the installation <see cref="Condition"/>, which is to be checked during the installation to
/// determine if the registry value should be created on the target system.
/// </summary>
public Condition Condition;
/// <summary>
/// Adds itself as an XML content into the WiX source being generated from the <see cref="WixSharp.Project"/>.
/// See 'Wix#/samples/Extensions' sample for the details on how to implement this interface correctly.
/// </summary>
/// <param name="context">The context.</param>
public void Process(ProcessingContext context)
{
// IIS namespace
XNamespace ns = WixExtension.IIs.ToXNamespace();
XElement component = this.CreateAndInsertParentComponent(context);
component.Add(this.ToXElement(ns + "WebSite"));
XElement webSiteElement = component.FindAll("WebSite")[0];
if (webSiteElement.Parent.Name == "Component" && webSiteElement.Parent.Parent.Name == "Directory")
{
// Add attributes for WebSite element
webSiteElement.AddAttributes($"Directory={webSiteElement.Parent.Parent.Attribute("Id").Value}");
}
if (Addresses != null)
{
int index = 1;
// Generates the XML fragment for WebAddress element
foreach (WebAddress address in Addresses)
{
webSiteElement.AddElement(new XElement(ns + "WebAddress",
new XAttribute("Id", $"WebSite_Address{index}"),
new XAttribute("IP", "*"),
new XAttribute("Port", address.Port)));
index++;
}
}
if (WebApplication != null)
{
// Generates the XML fragment for WebApplication element
XElement webApplicationElement = new XElement(ns + "WebApplication",
new XAttribute("Id", WebApplication.Id),
new XAttribute("Name", this.WebApplication.Name));
webSiteElement.AddElement(webApplicationElement);
if (WebApplication.WebAppPool != null)
{
WebApplication.WebAppPool.Id = $"{WebApplication.WebAppPool.Name}_WebAppPool";
webApplicationElement.SetAttribute($"WebAppPool={WebApplication.WebAppPool.Id}");
// Generates the XML fragment for WebAppPool element
webSiteElement.Parent.AddElement(new XElement(ns + "WebAppPool",
new XAttribute("Id", WebApplication.WebAppPool.Id),
new XAttribute("Name", WebApplication.WebAppPool.Name),
new XAttribute("ManagedPipelineMode", "Integrated"),
new XAttribute("Identity", "applicationPoolIdentity")));
}
}
if (Condition != null)
{
component.AddElement(new XElement("Condition", Condition.ToXValue())
.AddAttributes(Condition.Attributes));
}
}
}
}
You also have other way to solve your problem, by generating the WIX xml file following WebSite definitions and using XML injection as indicated in WixSharp IIS Sample with XMLInjection where you can subscribe to the WixSourceGenerated event.
project.WixSourceGenerated += Compiler_WixSourceGenerated;
Remember that WixSharp generates the Wix XML definitifion file, and you can modify this XML file after the WixSourceGenerated event.