Adding custom information to CSPROJ files - c#

As part of our development life cycle we have a number of process that we run against the C# source in our projects.
The processes are driven off a GUI that currently reads the *.csproj file to find the source files used within the project. This works fine.
We now have a new requirement to provide some validation processes that require a call out to a web-service. The web-service needs to be provided with some credentials that are project specific. Ideally we could enter and store these credentials within the *.csproj file but I don't see a means of extending it - is there?
We don't really want to introduce a new config. file just for these settings if we can help it. Is it possible to store information like this is the *.csproj file, if not is there any other place to put it.
thanks

The .csproj file is basically an MSBuild file, as such you can extend it with custom values. If you right-click on a project in Visual Studio and choose "Unload Project", the project will "grey out" and you can then right-click again and choose Edit [ProjectFileName].csproj. You can then add something similar to the following:
<PropertyGroup Label="Custom">
<Badger>1</Badger>
</PropertyGroup>
This should be persisted when the project is modified (i.e. files added/removed) and you can retrieve the values from the file, using the method of your choice.

VS projects support "project extensions". These are custom data stored directly in csproj/vbproj files. You can very easily read and write them even from VS. For example, the following VS macro writes such custom setting:
Dim proj As Project = DirectCast(DTE.ActiveSolutionProjects(0), Project)
proj.Globals.VariableValue("MySettingName1") = "My value1"
proj.Globals.VariablePersists("MySettingName1") = True
The following reads it back:
proj.Globals.VariableValue("MySettingName1").ToString
And the code in csproj file looks like:
<ProjectExtensions>
<VisualStudio>
<UserProperties MySettingName1="My value1" />
</VisualStudio>
</ProjectExtensions>
Of course, this is persisted and will not be overwritten by VS.

I know you dismiss it but the most obvious, and probably recommended, place is in the config file. Albeit encrypted.
One config file per project does for most cases and is not a large overhead imho.

Related

WPF Clickonce publish with Microsoft.Net.Sdk

I can successfully build a WPF application with the new csproj format using the Sdk="Microsoft.Net.Sdk".
However, it is a bit of a challenge to publish the said app. The option is definitely not available from the IDE. But what I find a bit puzzling is that the Publish target doesn't seem to be available when you call msbuild directly.
These are some of the top-level properties I set:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<LanguageTargets>$(MSBuildExtensionsPath)\$(VisualStudioVersion)\Bin\Microsoft.CSharp.targets</LanguageTargets>
<OutputType>WinExe</OutputType>
<PlatformTarget>x86</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
<!--<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>-->
</PropertyGroup></Project>
I also set the typical ones associated with the ClickOnce eg PublishUrl, etc. What can I do to get at/expose the Publish target the same way the LanguageTarget above enables "CoreBuild" for the other legacy C# build tasks outside Console, Web and plain libraries.
Further Thoughts:
So, it turns out that on further inspection, there is actually a Publish target. But it does a simple folder/xcopy deployment to a subfolder called Publish rather than creating an app.publish folder and doing the ClickOnce thing.
How does one work around this?
You can manually publish ClickOnce using the Mage.exe (command line) or MageUI.exe (gui) tools. It's not very convenient but it does seem to work if you get everything right. I'll outline what worked for me using MageUI.exe.
Choose the correct version of the utility for the .NET version you're using from:
C:\Program Files (x86)\Microsoft SDKs\Windows\
First publish your application files to a folder. Normally this would be something like:
\\server\share\MyApplication\Application Files\MyApplication_1_0_0_25\
NOTE: I had issues with the space in Application Files, where it would be converted to %20, but I don't think UNC paths support that value. I had to remove the space and renamed the folder to ApplicationFiles. (This will probably break previously published versions though.)
Then use MageUI.exe to create a new application manifest:
On the Name page, give it a name, version, and choose a processor architecture (x86).
On the Files page, enter the directory you published the files to, and then hit populate. It should load all the program files into the DataGridView below.
On the Permissions Required page, I was not able to get it working with anything less than FullTrust. Without FullTrust, when the application was run, nothing happened.
Save the manifest file as MyApplication.exe.manifest to the application folder. (You will be able to sign the manifest when you save it.)
Now create a new Deployment manifest:
On the Name page, enter the same name and version and choose the right processor architecture.
On the Description page, enter Publisher and Product.
On the Deployment Options page, I chose Online Only. I did not include a Start Location.
On the Application Reference page, choose Select Manifest and browse to the application manifest file you previously created.
Save the deployment manifest as \\server\share\MyApplication\MyApplication.application; (you can sign it when you save.)
NOTE: A glitch here seems to be that it will have inferred the wrong relative path when you select the application manifest file. After you've saved the deployment manifest the first time, go select the application manifest file again, and it will now infer the correct relative path. Then hit save again and you should be ok.
There are a lot of things that can go wrong and a lot of ways that the procedure can differ, but these are the steps that worked for me.
(Another thing I had to do during these steps was clear my ClickOnce Application Cache, by deleting the contents of c:\users\username\AppData\Local\Apps\2.0\. But that was probably just because of all the mistakes I made. I would only do this if you get stuck.)
Microsoft is finally adding ClickOnce functionality to SDK Style Winforms and WPF projects in .NET 5.
I was able to put ClickOnce in a WPF net48 project with new SDK style as before of moving to the new SDK.
It was necessary just to put this block in the end of my .csproj file:
<PropertyGroup>
<PublishProtocol>ClickOnce</PublishProtocol>
</PropertyGroup>
<Import Sdk="Microsoft.NET.Sdk.WindowsDesktop" Project="Sdk.props" />
<Import Sdk="Microsoft.NET.Sdk.WindowsDesktop" Project="Sdk.targets" />
<Target Name="ComputeAndCopyFilesToPublishDirectory" />
It does the following:
set SDK to not skip the import of Microsoft.NET.ClickOnce.targets. That is achieved by setting <PublishProtocol>ClickOnce</PublishProtocol>
set SDK to avoid bin/debug/**/* files to be copied to publish directory.
That is achieved by skipping the "ComputeAndCopyFilesToPublishDirectory" target execution. To do so, we've overriden it to an empty implementation.

Multiple .NET Configuration Files and Setup Project Problem

In order to handle settings for different deployment targets, I moved application settings from app.config to its own file and included that file in app.config via configSource. I also created a settings file for each target.Here is an illustration:
Project A
app.config (references settings.config)
settings.config
settings.Release.config
settings.Debug.config
During post-build, I copy the appropriate settings.{configuration}.config to the output directory. This is working fine so far and I can see settings.config file in the project output directory containing settings for the current build configuration: Release, Debug, etc.
However, I am having a problem with the setup project that I have for this project (Project A). Initially, it was not including settings.config file. So I set the build action for settings.config file as Content and I added content files from Project A to the setup project. This ensured that settings.config file was included in the setup. However, since the setup project appears to be picking settings.config file from the project directory instead of the output directory, settings.config file included in the setup is not what it should be. I want the one from the output directory to be included in the setup program since that one is the correct one for the current build configuration. I tried the following:
Added settings.config as a file to the setup project. However, it seems like I can only specify absolute path. So when I add it from the output directory of a particular build configuration (..bin\debug\settings.config), it does not work in other build configuration since (..bin\debug\settings.config) does exist in the directory specified. I looked into using relative paths or dynamic paths in the setup project where the build configuration could be specifed as part of the path but I could not find anything.
I considered using pre-build event to actually modify settings.config file in the project directory and then have it copied over the output directory by setting its 'Copy to Output Directory' to copy always or copy if newer. This should ensure that the appropriate settings.config is copied to the output directory just like the post-build based solution and should also ensure that the contents of settings.config file is updated before the setup project includes it. However, I don't like this solution because I would have to make sure settings.config file is writeable before I can make any changes since it is source controlled. If it is readonly, then I need to flip it to writeable, make changes, and then set it to readonly again. It is adding extra complexity.
I was wondering if anyone has a better idea or knows a setup project trick that allows me to include settings.config file appropriate for the current build configuration in the setup program.
Thanks
If I had to approach this problem, I'd start by asking the following question:
Why does settings.config have to be under source code control if settings.Debug.config or settings.Release.config provide the same information?
The answer, if I read your question correctly, is because you needed to force a settings.config file to appear as part of the build output. I'm guessing this is because your setup project is using the built in "Primary output" choice.
What you can do instead is add that file to your setup project as an explicit file reference. Right-click on the setup project and choose add / file, then select the file you want to include. As you'll notice (unless it's been fixed in VS2008 which sadly I'm not yet allowed to use at work), there is a very annoying limitation placed on manually added files - there is no way to make the path build configuration aware. You can work around that by copying the appropriate settings.config file to a common location (e.g. bin/Configuration) and picking it up from there. This does limit you to building Debug and Release versions sequentially, rather than in parallel, but for many this probably isn't a huge problem.
If you aren't required to use VS setup projects, I strongly encourage you to take a look at WiX (Windows Installer XML - see http://wix.sourceforge.net/ for more information). That will easily allow you to accomplish what is necessary, although if you are unfamiliar with the internal workings of Microsoft Installer the initial learning curve could be a little steep. Microsoft use WiX themselves for some pretty significant setup tasks (e.g. Office 2007, SQL Server, etc.). It had been hoped that WiX would become part of Visual Studio (for VS 2010), but sadly that is no longer the case.
I decided to go about achieving the same result (being able to have different configuration settings for different target environments) in a different way. So here is how I implemented it and it is working great. I read some of the posts here at SO about XmlMassUpdate task from MSBuild Community Tasks and decided to utilize it. Here is what I did:
1) For each project that needs to have different settings depending on the target environment, I added an xml file called app.config.substitutions.xml or web.config.substitutions.xml to the project. So, the project looked like
Project A
app.config
app.config.substitutions.xml
app.config.substitutions.xml file has the settings substitutions that XmlMassUpdate will process and apply to app.config file. Below is a sample substitution file that I use:
<configuration xmlns:xmu="urn:msbuildcommunitytasks-xmlmassupdate">
<substitutions>
<Development>
<appSettings>
<add xmu:key="key" key="SomeSetting" value="DevValue" />
</appSettings>
</Development>
<Test>
<appSettings>
<add xmu:key="key" key="SomeSetting" value="TestValue" />
</appSettings>
</Test>
<Release>
<appSettings>
<add xmu:key="key" key="SomeSetting" value="ReleaseValue" />
</appSettings>
</Release>
</substitutions>
</configuration>
For details on how to specify substitutions, take a look at the documentation for XmlMassUpdate or just do a search on it.
2) Now I need to run XmlMassUpdate as part of build automation (TeamBuild/MSBuild). So in BeforeCompile in TeamBuild build definition file (basically a proj file), I added the following to run XmlMassUpdate on config files that have a corresponding .substitution.xml file
<PropertyGroup>
<SubstitutionFileExtension>.substitutions.xml</SubstitutionFileExtension>
<TargetEnvironment>Test</TargetEnvironment>
</PropertyGroup>
<Target Name="BeforeCompile" Condition="'$(IsDesktopBuild)'!='true'">
<CreateItem Include="$(SolutionRoot)\**\app.config;$(SolutionRoot)\**\web.config">
<Output ItemName="ConfigurationFiles" TaskParameter="Include"/>
</CreateItem>
<CreateItem Include="#(ConfigurationFiles)" Condition="Exists('%(FullPath)$(SubstitutionFileExtension)')">
<Output ItemName="ConfigFilesWithSubstitutions" TaskParameter="Include"/>
</CreateItem>
<Message Text="Updating configuration files with deployment target specific settings..."/>
<XmlMassUpdate
ContentFile="%(ConfigFilesWithSubstitutions.FullPath)"
SubstitutionsFile="%(ConfigFilesWithSubstitutions.FullPath)$(SubstitutionFileExtension)"
ContentRoot="/configuration"
SubstitutionsRoot="/configuration/substitutions/$(TargetEnvironment)"/>
</Target>
Note that config files are read-only during the build, I make sure to set them writeable before running this task. I actually have another custom MSBuild task that runs before XmlMassUpdate that handles common settings throughout all of the config files such as connection strings. That task makes the config files writeable. I also don't check modified config files back to the source control. They're (appropriate config file for the deployment target) included in the installer.

Getting Content Files and Primary Output programmatically

For some reason, we have a script that creates batch files to XCOPY our compiled assemblies, config files, and various other files to a network share for our beta testers. We do have an installer, but some don't have the permissions required to run the installer, or they're running over Citrix.
If you vomited all over your desk at the mentions of XCOPY and Citrix, use it as an excuse to go home early. You're welcome.
The code currently has hundreds of lines like:
CreateScripts(basePath, "Client", outputDir, FileType.EXE | FileType.DLL | FileType.XML | FileType.CONFIG);
It used to be worse, with 20 int parameters (one per file type) representing whether or not to copy that file type to the output directory.
These hundreds of lines create upload/download batch files with thousands of XCOPY lines. In our setup projects, we can reference things like "Primary output from Client" and "Content Files from Client". I'd love to be able to do that programmatically from a non-setup project, but I'm at a loss.
Obviously MS does it, either using an API or by parsing the .csproj files. How would I go about doing this? I'm just looking for a way to get a list of files for any of the setup categories, i.e.:
Primary Output
Localized Resources
Content Files
Documentation Files
EDIT:
I have a setup project like Hath suggested, and it's halfway to what I'm looking for. The only problem keeping that from being a perfect solution is that multiple projects depend on the same assemblies being in their own folder, and the setup will only copy the file once.
Example:
Projects Admin, Client, and Server all rely on ExceptionHandler.dll, and Admin and Client both rely on Util.dll, while Server does not. This is what I'm looking for:
Admin
Admin.exe
Admin.exe.config
ExceptionHandler.dll
Util.dll
Client
Client.exe
Client.exe.config
ExceptionHandler.dll
Util.dll
Server
Server.exe
Server.exe.config
ExceptionHandler.dll
Since the referenced assemblies are all the same, what I get is this:
Admin
Admin.exe
Admin.exe.config
ExceptionHandler.dll
Util.dll
Client
Client.exe
Client.exe.config
Server
Server.exe
Server.exe.config
This causes a FileNotFoundException when either Client or Server can't find one of the two DLLs it's expecting.
Is there a setup property I'm missing to make it always copy the output, even if it's duplicated elsewhere in another project's output?
EDIT AGAIN: All referenced DLLs are set to "Copy Local", and always have been. I found a decent article on using NAnt and XSLT to grab the list of files, so that may be a possible solution as well, as neouser99 suggested.
ACCEPTED SOLUTION: I'm pretty much back where I started. All .exe and .dll outputs are put into a "bin" directory in the setup project, loosely packed. The other per-application folders contain shortcuts to the executable in that directory.
The difference now is, I'm going to add a custom action to the installer to use reflection, enumerate the dependencies for each executable output, and copy the .exe and .dll files to the separate directories. Bit of a pain, as I just assumed there was a way to programmatically detect what files would be included via some setup library.
why not use another setup project and just set the 'Package files' setting to As Loose uncompressed files (setup project->properties)? then share the folder.. or something.
edit:
I see, you have 3 folders for your outputs. but the setup project only detects the ExceptionHandler.dll and Util.dll once, so it will just pick the first folder and put it in there.
You could do a setup project for each project - bit annoying maybe..
You could manually add in the dll's to the projects that are missing the assembly's
either by adding in the File by 'add file' or 'add assembly' or 'add project output' if you have those projects in the same solution.. (I doubt that's the case though).
or just dump all of them into one output directory...
Although it's designed as a build tool, you might find NAnt to be extremely useful in what you are talking about. The tasks (build, copy, move, delete, etc.) that you can define allow for very fine-grained file lookups, up to general, full folders. If you also incorporate NAnt into your build process, I think you could find that it helps out in more ways then one.
Another approach that has worked for me in the past is to add the shared resource (Assembly, DLL or project) as a reference to each of the Admin, Server and Client projects. Then open the properties panel for the referenced item in each project and set "Copy Local" to true.
Now when you build the projects, each will have its own instance of the Assembly copied into its output folder.
This should also cause the shared components added in this manner to be replicated in each of the output folders in the setup package.
A completely different approach could be to set them up as symbolic links on the network share. A symbolic link is basically a short-cut where the file-system hides the fact that it is a short-cut, so all other applications actually believes that the file has been copied (http://en.wikipedia.org/wiki/NTFS_symbolic_link).
One advantage of this approach is that the file is updated immediately as the file changes and not only when you build your projects. So when you for instance save one of the config-files with a text-editor the update is applied immediately.
The following MSBuild script part can build your SLN file (you can replace it with .csproj) and will report a list of all projects that were build (Dlls, EXEs).
<MSBuild Projects="MySolution.sln" Targets="Clean; Rebuild" Properties="Configuration=$(BuildMode);">
<Output TaskParameter="TargetOutputs"
ItemName="AssembliesBuilt" />
</MSBuild>
Now, this doesn't really solve your problem, but it gets you a list of everything that was build. You also have copylocal, so you could probably just take AssembiesBuild and copy all DLL and .CONFIG files from there.
Example:
AssembliesBuild = c:\myproj\something1\build.dll
you'd go to c:\myproj\something1\ and simply search for all *.dll and *.config files and include them. You can do this pretty easily with MSBuild or powershell, if you have it installed. To output a XCOPY script from MSBuild, I think you'll need MSBuild contrib projct installed.

Where is "Set as Startup" defined and persisted in C# winforms solutions?

I don't see it in the .sln file, which is what I expected.
Which project is the "startup" project only has any relevance for debugging, which means it's user metadata from the point of the solution and the projects. Regardless of which project is the "startup" project, the compiled code is the same.
Because of this, the information is stored as a user setting in the Solution User Options file (solution.suo) which accompanies the Solution file (solution.sln). The .suo file "Records all of the options that you might associate with your solution so that each time you open it, it includes customizations that you have made" according to MSDN.
The .suo file is a binary file. If you want to read or change it programatically, you have to use IVsPersistSolutionOpts.LoadUserOptions from the Microsoft.VisualStudio.Shell.Interop namespace.
It's in the Solutions User Options (.suo) file, which gets created next to the .sln file.
It seems that the first item in the solution's sln file is, by default, the startup project. So, you could manually edit the sln file to make your project the first project in the solution. Then, a user can override that by selecting a different project to be startup.

Manage multiple app config files during development

I'm building an application that is used by several different customers. Each customer has a fair amount of custom business logic, which I have cleverly refactored out into an assembly that gets loaded at runtime. The name of that assembly, along with a number of other customer-specific settings, are stored in the application's configuration file.
Right now, here's what I have to do in order to debug the application for customer foo:
Go to the filesystem in my project directory and delete app.config
Copy app.config.foo to app.config.foo - Copy.
Rename app.config.foo - Copy as app.config.
Tell Windows that yes, I want to change the file's extension.
Switch back to Visual Studio.
Open the Settings.settings item in my project.
Click "Yes" 13 or 14 times as VS asks me if I want to use the new settings that have been changed in app.config.
Close Settings.settings.
Okay! Now I'm ready to debug!
It seems to me that the rigamarole of opening Settings.settings is, or ought to be, unnecessary: I don't need the default values in Settings.cs to be regenerated, because I don't use them. But it's the only way I know of to make VS aware of the fact that the app.config file has changed, so that the build will copy it to the output directory.
There's got to be an easier way of doing this. What is it?
You can also let Visual Studio automate Robert`s approach by:
Define a Build Configuration for each client
In the post build event, simply xcopy app.config.xxx to your bin folder. Where XXX is the name of a Build Config accessible in VS. Something like: xcopy app.config.$(ConfigurationName) $(OutDir)/app.config
VS will drop a distinct build for your clients in separate folders, aolong with the proper config file.
bin/Client1/
bin/Client2/
You can refer this post for some good practices : Managing Multiple Configuration File Environments with Pre-Build Events
Thinking about the mess of managing multiple configuration files I made this tool: http://envride.codeplex.com/
Its purpose its exactly to make it easier to manage multiple configuration files in an automated way. I would be very pleased if you would take a look at it.
A couple of people suggested using multiple VS configurations, which I think would have worked, except that it would require me to rebuild the solution every time I switched between configurations.
What I did instead seemed a little stupid while I was doing it, but I've been using it for nearly a year now and it works extremely smoothly. In my project directly, I create a separate app.config.XXX file for each customer. The actual app.config file is used solely to generate Settings.cs - it has all of the correct setting names and their default values. It doesn't get copied to the build directories, ever.
Then I wrote a little program that lets me select a customer, and that simply goes through the directories for each project and, if I selected customer XXX, copies app.config.XXX to bin\debug\myprogram.exe.config and bin\release\myprogram.exe.config. As long as this program knows where the root of the solution is (I have to be a little careful whenever I branch the code), it works like a charm.
This thread is too old to represent current tools in VS.
You can use an addon that acts similar to web.debug.config but for app.config.
https://marketplace.visualstudio.com/items?itemName=GolanAvraham.ConfigurationTransform
And for the same app.config transformations without addon.
https://www.linkedin.com/pulse/multi-appconfig-visual-studio-2017-benjamin-davis/
You may opt to define multiple Visual Studio solution configurations, one for each customer, and have customised MSBuild targets for your Windows app project.
I have documented the steps of how I handled this here. Multiple app.config files for deploying to different environments
After a little digging and work around I got my Test project working with multiple configurations,
In the Configuration Manager, create the configurations you need
Copy paste your app.config and add the name of the configuration, in my case is AHI, FIV, MGC, so my config files look like: App.AHI.config, App.MGC.config, App.FIV.Config. You can name it how ever you wanted, but keep the same convention
Add a Post-Build event. In my case it would look like: xcopy $(ProjectDir)app.$(ConfigurationName).config $(TargetDir)$(TargetName).dll.config /y
here is my post, so you can read it with more details
Running a Test Project with Multiple Configurations

Categories