Archive for February, 2011
Configuration Handling Reloaded
Configuration handling of web projects is a well discussed topic. There are lots of examples on how to configure web.config for different environments; using configSections, naming conventions, T4 transformations, web deployment projects etc.
Until now, this have been quite painful. Visual Studio 2010, however, has a new feature which opens up a lot of new possibilities; Config Transformations. This is a very cool feature and triggers when you publish your web application within Visual Studio.
Here is an example on how to handle advanced configurations and make them available locally too for testing and debugging purposes.
The challenge:
- I want different config files for different environments.
- I want to test different configurations locally and be able to debug them on my machine with a simple change. This means I must be able to compile my web.config file dynamically when building
- I want my own settings which does not affect the configurations for my teammates.
XmlTransformation task to the rescue!
With the new XmlTransformation task for MSBuild, it is possible to manually trigger config transformations. This is great news because then we can control which files will be transformed and when they will be transformed. I can make more complex configurations and also have my own transformations which are personal and not checked into the version control system.
First, I would like to be able to test the different configurations. Lets say I have a web application which will behave different in different environments. This could be controlled in a database or it could be controlled in a config file.
As an example, I have created a simple solution with 2 themes, one red and one blue theme. Changing the configuration, should also change the theme on the web site. In order to achieve this I have created 2 themes in my solutions with a stylesheet for each theme that will set the background color of the header for the default ASP.NET site that comes with Visual Studio:
In order to be able to change this, I create some configurations in the Configuration manager in Visual Studio:
With the configurations, I am able to create additional transformation files for web.config:
Also notice that I have a Web.master.config file and a Web.mysettings.config file. The master file is the base file that will be used for all transformations. This should contain the settings which is most common for the different configurations. The mysettings file is my personal config file which I use for my personal configurations. For instance, I like to use Fiddler and this contains transformations for adding Fiddler proxy to the web.config file.
The content of the Web.mysettings.config file:
<?xml version="1.0"?> <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <system.net> <defaultProxy xdt:Transform='Insert'> <proxy proxyaddress="http://127.0.0.1:8888"/> </defaultProxy> </system.net> </configuration>
Selecting the debug – blue configuration should result in the standard blue color for the default ASP.NET site when running/debugging the web application:
Selecting the debug – red configuration should result in having a red header in the web site:
In real life, a lot of different appsettings could be different for the different configurations. In this example, the Web.debug – red.config file only contains
<?xml version="1.0"?> <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <system.web> <pages xdt:Transform="SetAttributes" theme="Red" /> </system.web> </configuration>
which will change the theme of the pages element.
Now, here comes the real magic. This is the code you have put into the project file of the web application to be able to modify the Web.config file. Edit the project file and paste this before the </Project> closing element
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" /> <!-- This is a hack to perform the master => transform without mysettings of web.config when publishing the website. It will only trigger on --> <!-- publish within Visual Studio. It actually overrides the TransformWebConfig Target in the original Microsoft.Web.publishing.targets file, --> <!-- but since it is empty by default, it should work fine! --> <Target Name="TransformWebConfig"> <Message Text="%09 ------------- Transforming web.config.master => web.config with $(Configuration) transformations --------------" /> <TransformXml Source="web.master.config" Transform="web.$(Configuration).config" Destination="web.config" /> </Target> <Target Name="BeforeBuild"> <Message Text="%09 ------------- Transforming web.config with $(Configuration) transformations --------------" /> <TransformXml Condition="Exists('web.mysettings.config')" Source="web.master.config" Transform="web.$(Configuration).config" Destination="web.transformed.config" /> <TransformXml Condition="Exists('web.mysettings.config')" Source="web.transformed.config" Transform="web.mysettings.config" Destination="web.config" /> <TransformXml Condition="!Exists('web.mysettings.config')" Source="web.master.config" Transform="web.$(Configuration).config" Destination="web.config" /> </Target> <Target Name="AfterBuild"> <!--<Delete Condition="Exists('web.mysettings.config')" FiThe BeforeBuoilles="web.transformed.config" />--> </Target>
Some comments on the code:
- The TransformXml task comes with Visual Studio 2010, so all you have to do is make a reference to it with the using statement.
- In the BeforeBuild Target, this is where the transformations take place. This makes it possible to dynamically create Web.config every time a build triggers.
- Using Source and Destination, it is possible to do transformations to all kind of XML file types, not only Web.config.- You could have appSettings.config, connectionsTrings.config etc. It would also be possible to make more hierarchical transformations by doing more than one transformation. Lets say there are some common settings for the red configuration which should be transformed in all environments, it would be possible to have this in a separate config file that will be transformed into the final web.config file.
- The AfterBuild target should clean up the temporary config files. However, there seems to be a bug with the TransformXml task locking the files, so lets hope Microsoft fixes this in a future version.
- The Web.mysettings.config transformation only triggers if there is a mysettings file available. This means not all teammembers have to create this file if they don't need to.
- The target TransformWebConfig is a minor hack. In order to prevent the mysettings transformation when doing a Publish… within Visual Studio, I had to find a target in the Publish… event which triggers only when publishing. This target actually does nothing in the default Microsoft.Web.Publishing.targets file, so that's the reason for overriding this particular target. I wish it would be possible to actually hook into the publish event in Visual Studio through MSBuild, but that seems impossible. And using a target in MSBuild also overrides the target without having the option to call the base target which is overridden, so anything you do in that target will not be continued in the Microsoft.Web.Publishing.targets file. Lets hope MS don't put anything into that target in the future. I also tried to use DependsOnTargets, but that seems to trigger the target depending on, not listening to wheter the target has been run. That's a big difference.
Conclusion
Using the TransformXml task is a pretty simple solution to a complex problem. It comes with Visual Studio 2010 and makes environmental and application specific configurations much simpler to handle in real life. For Visual Studio 2008 and earlier, you still need to think different. Have a look at Andreas post on how to make complex transformations using T4 templates.
You can download the sample solution here: ConfigExample.zip.
True dynamic controls
This is something I have been struggeling with so many times, and there are a lot of examples on how to programatically add controls to a page. In this example I add dynamically created controls which remembers their state on postbacks.
The problem:
- I want to add new controls to a page. Clicking a button adds a new control to the page.
- The new control should remember its state through ViewState and postback events should work as normal.
The solution:
- I can add the controls to a Panel or Placeholder and I have to do it in an early stage in the page lifecycle.
- Some experiments and thoughts
- The dynamically added controls needs to be added to the page on every postback. It is not possible to add a new control and forget it. This means I need to have a loop where the controls are added to the page.
- The ID of the control need to be the same on every postback in order for the control to remember its state through ViewState.
- Doing some testing, I tried adding the controls in PreInit. This is where Microsoft recommends to add dynamically created controls. The only problem here is that the ViewState is not available at this stage. So I am not able to track how many controls that have been added to the page. I have a property NumberOfDynamicControls which holds how many dynamically controls that have been added. The same goes with Init, no luck. The ViewState is loaded between Init and Load.
- You can add controls to the page in almost any event (PreInit, Init, Load, postback event etc.) and it will render fine. However, the ViewState will not be tracked and it is not possible to add and render more than one control.
- The only solution I found was to override the LoadViewState event and add the controls there. This seems like a hack, and probably is, but everything seems to work fine regarding ViewState tracking and postback events.
- I need to have the dynamic control available as an invisible "ghost" control before it is "added" to the page. This is to keep track of the state of the control. When clicking the "Add" button, the ghost control is set visible, although it's already there.
Here is the code:
This is the HTML markup. I'm having a simple Panel and a button. Everytime I click the Add button, a new dynamically created button is added to the page.
<asp:Panel runat="server" ID="Panel1" > <asp:Button Text="Add" runat="server" OnClick="AddControlToPanel" /> </asp:Panel>
I have to initialize a "ghost" control. The dynamically created button is actually added to the page before it is visible. This is to track ViewState. Clicking the Add button makes the ghost control visible:
protected void AddControlToPanel(object sender, EventArgs e)
{
Button newButton = Panel1.FindControl("ButtonDynamic" + (NumberOfDynamicControls - 1)) as Button;
if (newButton != null)
{
newButton.Visible = true;
newButton.Text = "New button";
NumberOfDynamicControls++;
}
}
Here is the tracking of number of dynamically created controls added to the page. The value is stored in the ViewState.
protected int NumberOfDynamicControls
{
get { return ViewState["numberOfDynamicControls"] == null ? 1 : (int)ViewState["numberOfDynamicControls"]; }
set { ViewState["numberOfDynamicControls"] = value; }
}
Here comes the real magic. By overriding the LoadViewState and rebuilding the dynamically added controls, everything works as expected.
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
if (IsPostBack)
{
InitializeDynamicButtons(NumberOfDynamicControls);
}
}
protected void InitializeDynamicButtons(int numberOfButtons)
{
for (int i = 0; i < numberOfButtons; i++)
{
Button dynamicButton = new Button();
Panel1.Controls.Add(dynamicButton);
dynamicButton.ID = "ButtonDynamic" + i;
dynamicButton.Click += DynamicButton_Click;
dynamicButton.Visible = false;
}
}
The dynamically created button also has a click event:
void DynamicButton_Click(object sender, EventArgs e)
{
Button button = (Button) sender;
button.Text = "Clicked!";
}
Before:
After:
Here is the complete code:
If you know some other good ways to achieve this, let me know!
Search
Knut Hamang
Recent Posts
Recent Comments
- Lori on Html to Pdf in .NET
- Susan on Html to Pdf in .NET
- Susan on Html to Pdf in .NET
- Ananth on Html to Pdf in .NET
- Tim M on Passing an object to ObjectDataSource






