Creating Custom Attributes in Visual Studio 2010 Project Template? - c#

I'm attempting to create a custom named attribute in my project template at the XML level to store a boolean value, that I can use to track the success of some imports I'm doing in the project template file.
I'd like to be able to declare an attribute for the project template file like so:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" InitialTargets="TestImports" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Attributes>
<Attribute Name="ImportedProp" Value="False" />
</Attributes>
<Import Condition="Exists('..\SomeProj.props')" ImportedProp="true" Project="..\SomeProj.props" />
<Target Name="TestImports">
<Error Text="Unable to find Common Properties." Condition="'$(ImportedProp)' == true" />
</Target>
The above method of declaring an Attribute is sadly not valid. Visual Studio doesn't like it at all. It compile fine, but errors when you go to use the template to create a new project in Visual Studio.
So, I want to create an attribute name "ImportedProp" (e.g. Imported Properly) and be able to set it during the Import calls as being true if the import succeeded.
Then I can error out if the attribute "ImportedProp" is still false after all the import calls.
The problem is, as far as I can tell, there is no way to declare your own attributes solely in the XML of the vstemplate or csproj template. Does anyone know how to create a custom attribute for a Visual Studio 2010 template that I can use like I have suggested?
Declaring the attribute in the vstemplate or csproj template XML simply yields an error where Visual Studio says it doesn't recognize the Attributes tag in the vstemplate or csproj XML file.
I normally wouldn't bother someone about something that should be as simple as this, but either I'm blind, or typing the wrong keywords into Google, but I can't find any solutions or examples of how to do this.

It turns out, there is another way to do this that will check for those imports. You can't create a custom attribute, but you can create a value in your Project Properties (.props) or your project file like so.
<PropertyGroup>
<SettableVal> *Value* </SettableVal>
</PropertyGroup>
Then you can use it however you would like. Throughout the project file or template to control your imports, reference includes, or whatever.

Related

Importing proto files from different directories

I'm struggling writing the right configuration for grpc imports.
So the .net solution structure is like:
\Protos\Common\common.proto
\Protos\Vehicle\car.proto
\CarMicroservice
Inside car.proto I have: import "common.proto"
What I want is the generated grpc code to be inside the project CarMicroservice.
Inside CarMicroservice.csproj I have written the path to the protos:
<Protobuf Include="common.proto" ProtoRoot="..\Protos\Common\" />
<Protobuf Include="car.proto" ProtoRoot="..\Protos\Vehicle\" />
But getting error: 'common.proto' no such file or directory
The question is:
How do I correctly import common.proto inside the car.proto?
Note: I already looked at the similar issue, but couldn't make it work for my case
Importing .proto files from another project
Ok, I finally solved the issue. Also #DazWilkin pointed it out.
You can't use relative paths in the import, so you should use absolute path of the project. In my case it was: import "Common/common.proto"
Use the project root for the location of proto files. So instead of ProtoRoot="..\Protos\Common\" use ProtoRoot="../Protos/"
Now comes the interesting part.
For some reason when I used backslashes for the ProtoRoot path as "..\Protos\
I was getting errors as 'file not found'. So don't use the backslashes for paths.
The final code in CarMicroservice.csproj is like the following:
<Protobuf Include="Common/common.proto" ProtoRoot="../Protos/" />
<Protobuf Include="Vehicle/car.proto" ProtoRoot="../Protos/" />
Alternatively, there are two other ways to specify include directories for protoc.
First, there is a global property named Protobuf_AdditionalImportsPath. If you specify this property in your .csproj/.fsproj, it will be passed on to protoc for every .proto in that project. You can use it like this:
<PropertyGroup>
<Protobuf_AdditionalImportsPath>../../my/other/directory</Protobuf_AdditionalImportsPath>
</PropertyGroup>
There is also a AdditionalImportDirs property you can specify directly on <Protobuf> elements. This will likely only be valid for the .protos you specify it on:
<Protobuf Include="MyProto.proto" Link="MyProto.proto" AdditionalImportDirs="../../my/other/directory" />
Keep in mind that in both cases, you can specify any directory you want, regardless of whether it is a parent of the .proto you're compiling.
Also keep in mind that the import path you specify in your .proto needs be relative to one of the import directories you specify.

Telling *.csproj formats apart

I have an application that needs to parse the ProjectReference elements from *.csproj files. It could do this well with the old (.net) format where I used the Name element to get the name of a project:
<ProjectReference Include="..\MyProject\MyProject.csproj">
<Project>{guid..}</Project>
<Name>MyProject</Name>
</ProjectReference>
The new format however (.net-core) makes it crash now because there is no Name element anymore.
I found a few differences between both files but I'm not sure which one I should use tell that I'm working with the new core-file. The differences are:
the new format does not contain xml declaration
the root element starts with the <Project Sdk= attribute and does not contain any default namespace wheres the old one has xmlns=" declared
the ProjectReference element does not contain any children
Which property would be the most reliable way to recognize the core-file like Visual Studio does? Am I on the right track or is there any other criteria I should use to tell the file formats apart?
If you are using MSBuild to evaluate the project file, the sdk sets the UsingMicrosoftNETSdk property to true.
If you are only use XML based tooling to read the file, you can check if a <TargetFramework> or <TargetFrameworks> (plural) property (inside a <PropertyGroup>).
This is the same mechanism that visual studio uses to determine whether the new or classic project system is used for the project (see Opening With CPS document).

Add VSIX features to C# Class Library

I have an existing Single-File Generator (housed in a C# Class Library). How do you add the VSIX project-level features to this project? The end goal is to compile my class library project and get a VSIX.
(I'm actually answering my own question. This is in relation to SIngle-file generator changes in Visual Studio 2017 - but that question wasn't asking what I'm answering here.)
First off,
your Single-File Generator class needs to have the appropriate class-level attributes:
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell;
using VSLangProj80;
[ComVisible(true)]
[Guid("your-unique-identifier")]
[CodeGeneratorRegistration(
typeof(MyCustomTool),
"MyCustomTool",
vsContextGuids.vsContextGuidVCSProject,
GeneratesDesignTimeSource = true,
GeneratorRegKeyName = "MyCustomTool"
)]
[ProvideObject(
typeof(MyCustomTool),
RegisterUsing = RegistrationMethod.CodeBase
)]
public sealed class MyCustomTool : IVsSingleFileGenerator {
All of these attributes will ensure that a .pkgdef file is correctly generated within your VSIX. The .pkgdef file contains the registry entries that are used to register your single-file generator with Visual Studio.
Second,
add a text file "source.extension.vsixmanifest" to your project. Its "Build Action" should be "None." Give it some default text of:
<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
<Metadata>
<Identity Id="MyCustomTool.MyCompany.another-random-guid" Version="1.0" Language="en-US" Publisher="MyCompany" />
<DisplayName>MyCustomTool</DisplayName>
<Description>Helpful information</Description>
</Metadata>
<Installation>
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[15.0]" />
</Installation>
<Dependencies>
<Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.5,)" />
</Dependencies>
<Prerequisites>
<Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[15.0,16.0)" DisplayName="Visual Studio core editor" />
</Prerequisites>
</PackageManifest>
Most of this stuff is pretty esoteric. In the next step, we'll make it so you can edit this file with a designer.
Third
(and to answer the original question), you need to manhandle the .csproj file (your C# Class Library file). Specifically, you need to add the following:
<PropertyGroup>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ProjectTypeGuids>{82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
</PropertyGroup>
<ItemGroup>
<None Include="source.extension.vsixmanifest">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<PropertyGroup>
<UseCodebase>true</UseCodebase>
</PropertyGroup>
<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />
So, what have we done here? Let's break it down.
First, we setup a path to the location of the Visual Studio toolset. Afterall, this is now an "extensions project." So, it needs to know where the VS-SDK is located.
Then, we changed the "ProjectTypeGuids" (which probably wasn't in your project file to begin with). Originally, it just included the guid for C# (which is the "FAE04EC0-..." guid). Now, it also includes the guid for VSIX (which is the "82b43b9b-..." guid).
We also made sure the "source.extension.vsixmanifest" file uses its new, fancy designer (instead of editing the file by hand).
The "UseCodeBase" element is important. This element prevents you from being forced to register your Generator with the system's COM registry. Instead, Visual Studio will simply load up your Generator from its installation location.
At the bottom, we import the MsBuild stuff for the VS-SDK.
Fourth,
Load your project back up. Go to the Project Properties screen and you'll see a new "VSIX" section at the bottom. Open that section and check the "Create VSIX Container during build" checkbox.
At this point, you can also double-check the "source.extension.vsixmanifest" file. Depending on how fancy your Generator is, you can change whatever you need. (The contents of the file that I pasted above is pretty much exactly what I used for my project.)
And finally,
you can compile your project. In the bin folder, you'll find MyCustomTool.dll and MyCustomTool.vsix. The .vsix file is simply a zip file. Inside the .vsix, you'll find MyCustomTool.pkgdef.
If we've done everything correctly, the .pkgdef file should look something like this:
[$RootKey$\Generators\{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}\MyCustomTool]
#="MyCustomTool"
"CLSID"="{your-unique-identifier}"
"GeneratesDesignTimeSource"=dword:00000001
[$RootKey$\CLSID\{your-unique-identifier}]
#="MyCustomTool"
"InprocServer32"="$WinDir$\SYSTEM32\MSCOREE.DLL"
"Class"="MyCustomTool"
"CodeBase"="$PackageFolder$\MyCustomTool.dll"
"ThreadingModel"="Both"
And, I think this is the longest SO answer I've written. And probably, only 5 people will ever read this :)
If, instead of implementing the IVsSingleFileGenerator on your class, your class inherits from the abstract class BaseCodeGeneratorWithSite that inherits from the abstract class BaseCodeGenerator that implements IVsSingleFileGenerator you need to add the following atribute to your class, to avoid the error message "Cannot find custom tool '...' on this system":
[ClassInterface(ClassInterfaceType.None)]
The reason is that the abstract class BaseCodeGeneratorWithSite is not COM visible.

MSBuild handling circular dependencies

I am new to MSBuild. Just started trying it two days ago, and now I am just testing it. I have run into a problem where I get this error:
"c:\Users\martinslot\Documents\Visual Studio 2010\Projects\MultifileAssembly\SpecializedBuild.xml" (BuildNumberUtil target) (1) ->
c:\Users\martinslot\Documents\Visual Studio 2010\Projects\MultifileAssembly\SpecializedBuild.xml(4,34): error MSB4006: There is a circular dependency in t
he target dependency graph involving target "BuildNumberUtil".
My MSBuild script look like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="BuildNumberUtil" DependsOnTargets="BuildStringUtil" >
<Message Text="=============Building modules for NumberUtil============="/>
<Csc TargetType="Module" Sources="NumberUtil/DoubleUtil.cs; NumberUtil/IntegerUtil.cs" AddModules="/StringUtil/StringUtil"/>
<Copy SourceFiles="#(NetModules)" DestinationFolder="../Output/Specialized"/>
</Target>
<Target Name="BuildStringUtil" DependsOnTargets="BuildNumberUtil" >
<Message Text="=============Building modules for StringUtil============="/>
<Csc TargetType="Module" Sources="StringUtil/StringUtil.cs;" AddModules="/NumberUtil/IntegerUtil;/NumberUtil/DoubleUtil"/>
<Copy SourceFiles="#(NetModules)" DestinationFolder="/Output/Specialized"/>
</Target>
</Project>
I understand the problem, actually I created this small example to see if MSBuild understood and could somehow correct the problem. How do I solve this?
My problem is that the two targets compile modules that rely on eachother. Does someone here have a solution on how to handle this kind of problem with MSBuild? Maybe I am constructing this in the wrong way?
You simply cannot build projects with circular dependencies. How could you? Which do you build first? There may be some esoteric, convoluted, incorrect way of doing so, but why do it? Circular dependencies usually indicate a design flaw. Fix the design, and you no longer have a circular dependency issue.
It is possible to construct Circular Modules within the scope of MSBuild and Visual Studio; however, doing so has a very limited set of situations where it would be valid to do so.
One key way to do this, if you're planning on using Xaml within your code, is to remove the Sources aspect of the Csc tag and generate your own .response file which actually points to the code you wish to inject. Within the Csc tag attributes you'd specify this file yourself in the ResponseFiles attribute.
Within your .response file, you would then break your application down into its assembly and netmodule components, making sure to include the core assembly's files first at all times. Typically the Csc tag's attributes are directly translated into Csc.exe command line parameters. The parameter names do not always match up. For the sake of resolution it's best to use full, non-relative, paths when referring to files (example, partial, .response below):
"X:\Projects\Code\C#\Solution Name\InternalName\ProjectName - InternalName\SearchContexts\StringSearchType.cs"
"X:\Projects\Code\C#\Solution Name\InternalName\ProjectName - InternalName\UI\Themes\Themes.cs"
/target:module /out:bin\x86\Debug\InternalName.UI.dll
"X:\Projects\Code\C#\Solution Name\InternalName\ProjectName - InternalName\UI\EditDatabaseImageControl.xaml.cs"
"X:\Projects\Code\C#\Solution Name\InternalName\ProjectName - InternalName\obj\x86\Debug\UI\EditDatabaseImageControl.g.cs"
You'll notice that this will end up with merging your multiple sets of Targets into one, and that I've included the xaml generated code myself. This is partly why you remove the Sources aspect, as the Xaml Page generator part of the MSBuild task automatically injects information into the #(Compile) set. Since there's a Debug/Release configuration, in the area where you define the response file to use, I create two versions of the response (since I'm using a T4 template):
ResponseFiles="$(CompilerResponseFile);InternalName.$(Configuration).response"
If you intended to include more than one platform in your code you'd likely need C*P response files where C is the number of configurations (Debug|Release) and P is the number of platforms (x86, x64, AnyCpu). This kind of solution would likely only be a sane method by using a generator.
The short version of this: it is possible to create circular modules so long as you can guarantee that you'll compile it all in one step. To ensure that you maintain the build functionality that is afforded to you with the Xaml build step, your best bet is to start with a normal C# project, and create your own .Targets file from the $(MSBuildToolsPath)\Microsoft.CSharp.targets in the <Import ... tag near the bottom. You'll also likely need a secondary csproj for design purposes since a large portion of intellisense is lost by using this workaround (or use a csproj Condition attribute where the target is selected by some flag you set). You'll also notice certain Xaml editors don't seem to like the binding to netmodule namespaces, so if you bind to types in a netmodule you'll likely have to do them in codebehind (I haven't tested workarounds for this since there's usually ways around static namespace binding)
For some reason within all this, the .baml compiled .xaml files are implicitly understood by the Csc compiler, I haven't been able to figure out where it's deriving this from a command argument, or if it's just implicit by design. If I had to guess they're inferred by the g.cs files associated to what you include in your list of included files.
Observe that this is occurred for web application (either ASP.NET standard web application or ASP.NET MVC application) and fix for this problem is to be removed the below line in ".csproj" file.
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
Package
</BuildDependsOn>
</PropertyGroup>

How to set the output path of several visual C# projects

I have a solution that contains several c# projects and I would like to be able to set the output path and other properties on all the projects together in a single place. Property Sheets (vsprops) do not seem to be able available for C# projects and the $(SolutionDir) variable is ignored. Are there any other methods to set properties across several C# projects?
Update
By Following the information in the answer by Bas Bossink I was able to set the output path of several projects by creating a common csproj and importing it into the individual project. A few other points:
When building in Visual Studio if changes are made to the common project it is necessary to touch/reload any projects that reference it for the changes to be picked up.
Any properties which are also set in a individual project will override the common properties.
Setting $(SolutionDir) as the output path via the Visual Studio UI does not work as expected because the value is treated as a string literal rather than getting expanded. However, Setting $(SolutionDir) directly into the csproj file with a text editor works as expected.
A csproj file is already an msbuild file, this means that csproj files can also use an import element as described here. The import element is
exactly what you require. You could create a Common.proj that contains something like:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5"xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputPath>$(SolutionDir)output</OutputPath>
<WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
You can import this Common.proj in each of your csprojs, for instance like so:
<Import Project="..\Common.proj" />
The import statement should precede any tasks that depend on the properties defined in Common.proj
I hope this helps. I can't confirm your problems with the $(SolutionDir) variable I've used it many times. I do know however that this variable does not get set when you run an msbuild command via the commandline on a specific project that is contained in a solution. It will be set when you build your solution in Visual Studio.
Unfortunately, these bits of information such as output path are all stored inside the individual *.csproj files. If you want to batch-update a whole bunch of those, you'll have to revert to some kind of a text-updating tool or create a script to touch each of those files.
For things like this (apply changes to a bunch of text files at once) I personally use WildEdit by Helios Software - works like a charm and it's reasonably priced.
But I'm sure there are tons of free alternatives out there, too.
I would suggest you to use a build tool such as MSBuild or NAnt which would give you more flexibility on your builds. Basically the idea is to kick off a build using (in most cases) a single configurable build file.
I would personally recommend NAnt.
You could find an awesome tutorial on NAnt on JP Boodhoo's blog here
Set the $(OutputPath) property in a common property sheet. Then delete that entry in all the project files you want to it to affect. Then import that property sheet into all your projects.
For hundreds of projects that can be very tedious. Which is why I wrote a tool to help with this:
https://github.com/chris1248/MsbuildRefactor

Categories