Dave Bouwman writes about how ArcGIS 9.2 ADF does not support curves. We've actually bumped into this issue a long time ago, resorting into converting the curves to a series of points (as Dave suggests).
This is but another huge issue with the ADF. As I've said before, this is one horrific development platform. I've started to seriously think about coding a replacement that will work against ArcObjects (at least something that will satisfy my team's needs), but it would take a lot of time which we currently do not posses. One day, though, one day.
I've been looking for the easiest possible way to deploy a web.config file to several different environments. My web.config looks something like this in development:
<configuration>
<appSettings>
<add key="LogEveryRequest" value="true" />
<add key="EnableCaching" value="false" />
<add key="DefaultGisServer" value="127.0.0.1" />
</appSettings>
<connectionStrings>
<add connectionString="UserName=scott,Password=tiger" name="DBConnection" />
</connectionStrings>
<system.web>
<compilation debug="true">
</compilation>
</configuration>
I need the ability to change specific values in the test and production (release) environments. For instance, the connection string is obviously different, and I would also like to turn off logging and debugging in production.
I'm using a Web Deployment Project, so I have MSBuild at my disposal. Now, the WDP comes with an option to replace specific configuration sections, but that option is not powerful enough, as it requires you to replace an entire configuration section with another. For example, in the above configuration file I might want to change only the EnableCaching property. I don't want to keep 3 versions of the same appSettings section, only to be able to change just that single property.
What I needed turned out to be in the MSBuild Community Tasks project. Specifically, it contains a very powerful task called XmlMassUpdate. This task allows me to specify a substitution file that looks like this:
<configuration xmlns:xmu="urn:msbuildcommunitytasks-xmlmassupdate">
<substitutions>
<Debug>
</Debug>
<Test>
<appSettings>
<add xmu:key="key" key="GisServer" value="134.122.34.3" />
<add xmu:key="key" key="EnableCaching" value="true" />
</appSettings>
<connectionStrings>
<add connectionString="UserName=TestUser;Password=Testpass" name="DBConnection" />
</connectionStrings>
<system.web>
<compilation debug="false"/>
</system.web>
</Test>
<Release>
<appSettings>
<add xmu:key="key" key="GisServer" value="181.3.12.123" />
<add xmu:key="key" key="EnableCaching" value="true" />
<add xmu:key="key" key="LogEveryRequest" value="false" />
</appSettings>
<connectionStrings>
<add connectionString="UserName=ProdUser;Password=Prodpass" name="DBConnection" />
</connectionStrings>
</Release>
</substitutions>
</configuration>
As you can see, for debug I'm leaving everything as it is. For Test and Release I'm changing some of the properties. I want logging on in the Test environment, so I don't even mention it in my substitutions file, but for production it has to be changed to false. Other than that, everything here is pretty straight forward. I change only what I need to.
One thing to notice, though, is that the appSettings values have an attribute called xmu:key. This attribute is needed for the replacement engine to know by which attribute to locate the value to replace. For example, look at the first substitution in the test environment:
<add xmu:key="key" key="GisServer" value="134.122.34.3" />
We are telling XmlMassUpdate to find the GisServer value in the original web.config file, and replace it with this one. But XmlMassUpdate needs to know how to match our substitution value with the original value, so by saying xmu:key=key we're telling it to compare them by the key attribute. This way, it will know that it should look for an appSettings setting with a key="GisServer" attribute.
Once the substitution file is ready, we have to edit the MSBuild project file to call XmlMassUpdate. For that you would first need to right click your Web Deployment project and hit "Open Project File". Than you have to add the following lines to it:
1 <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
2 <PropertyGroup>
3 <BuildDependsOn>
4 $(BuildDependsOn);
5 MyAfterBuild
6 </BuildDependsOn>
7 <SubstitutionsFilePath>$(SourceWebPhysicalPath)\substitutions.xml </SubstitutionsFilePath>
8 </PropertyGroup>
9 <ItemGroup>
10 <ExcludeFromBuild Include="$(SubstitutionsFilePath)"/>
11 </ItemGroup>
12 <Target Name="MyAfterBuild">
13 <XmlMassUpdate
14 ContentFile="$(OutputPath)web.config"
15 SubstitutionsFile="$(SubstitutionsFilePath)"
16 ContentRoot="/configuration"
17 SubstitutionsRoot="/configuration/substitutions/$(Configuration)"/>
18 </Target>
First we import the MSBuild Community Task target file. This will allow us to use the XmlMassUpdate task (of course you have to install the community tasks project first for this to work).
In lines 3-6 we're adding a build event called MyAfterBuild. In fact the WDP project already contains a target called AfterBuild, but I found out that you can't really use it. If you specify an AfterBuild target, you will override the WDP AfterBuild target, which actually copies the web site files to the target directory. If you override that, well, your build won't work. So I'm specifying that the build should also depend on my MyAfterBuild target.
In line 7 I'm specifying that my substitution file resides in the original source web, in a file called substitutions.xml.
In line 10 I'm making sure the substitution file will not be deployed together with the web site (might be a bad idea to deploy all these connection strings).
In lines 12-18 we're finally calling XmlMassUpdate.
- ContentFile tells it where to find the web.config to edit (in the output location, usually).
- SubstitutionsFile tells it where to find our substitutions.xml.
- ContentRoot tells it that in the original web.config, the xml path to start replacing stuff at is the configuration node.
- SubstitutionsRoot tells it that in our substitutions.xml, the task should take values to replace from the specified xml path. For example, when we compile in debug it will take values from /configuration/substitutions/debug, and when we compile in release it will be taken from /configuration/substitutions/release. In order to enable our Test environment substitutions, we will have to add another build configuration. That's as easy as going to Build->Configuration Manager and adding a new solution configuration called Test.
And there you go. Save and recompile your project, and you will find that the web.config in your output folder has the correct values, depending on the build configuration you chose.
Go ahead and download the MSBuild Community Tasks now. They have a pretty good documentation, which helped me a lot in finding this elegant solution and writing this post.
I've been going over this week's updates, and ran across this post in CodeBetter, that talks about why classes should be sealed by default, only to be unsealed by the 'unsealed' keyword.
I couldn't disagree more. I think that you need a hell of a good reason to seal a class, so claiming that most classes should be sealed is a bit seems outrageous to me. I believe it's absurd to prevent extensibility for your classes, and if classes are sealed by default, the result will be, obviously, a lot more sealed classes around. After all, unless you plan your class as some sort of base-class, you probably won't even think about adding that 'unsealed' word. And you know that at some point it will make absolute sense for you to extend a class in a way the creator did not consider, and you will be rather screwed.
The reasons to leave a class sealed, taken from the great CLR via C# book, seem to pale in comparison with this issue. I think that is a very rare case in which your performance bottleneck is unsealed classes, and in these special circumstances you'll probably consider this beforehand.
About security and predictability - well, I don't agree that you have to baby-sit inheritors. It is a good idea to create a design that supports correct extensibility points, but if you have to default to anything, default to unsealed. It can be impossible for you to predict all the cases in which your class is used, and if the inheritor breaks anything, well, that's his problem. If it were up to me, I would make all the methods virtual by default as well (as it is in Java). Quite often it happened to me that I really wanted to override protected methods which were not marked as virtual, and I had to hack around that.
About versioning, well, again, the case in which you would want to change a class to be sealed seems extremely rare to me, at best.