(It Just) Has To Be .Net
Two long-term IT techies, with a penchant for Microsoft's .NET Framework, air views on whatever topics take their fancy.

Manage Import Elements in a .csproj file

 

In my last two posts I talked about adding a macro to a Context Menu in Visual Studio and calling a custom MSBuild Target when Building a Project. I'd now like to build on those posts and create some code to manage Import elements in a .csproj file. With a view to running that code in a macro called from a Visual Studio 2010 Project's context menu to add an Import element that references a custom MSBuild Targets file.

 

So first let's create a new class Library Project in Visual Studio 2010.

  • We'll call the Project (and Solution) CSProjModifier. By the way, I always delete the Class1.cs that is added by default when you create a new Project.
  • Add a new class, for example, ImportTagChanges.cs, and make sure the class is public. Also add a 'using System.IO' statement at the top of the class file.
  • We'll add methods that manipulate Import Elements at the end of a .csproj file.
    • First lets add a 'using System.Xml.Linq' statement to the top of the file, you may also need to add a reference to the System.Xml.Linq Assembly first.
    • Now add the following three methods to Add, Remove and Get Import elements. I have not included the validation functions for brevity.

 

        public static void AddImport(string csprojFileName, string importFileName)

        {

            // Validate the supplied arguments

            ValidateArgs(csprojFileName, importFileName);

 

            // Load the contents of the csproj File into an XDocument

            XDocument xmlDoc = XDocument.Load(csprojFileName);

            // Don't forget the MSBuild namespace that decorates the Project tag in the .csproj file

            XNamespace xmlns = xmlDoc.Root.Attribute("xmlns").Value; 

 

            // look for an existing Import element for the importFileName

            var query = from el in xmlDoc.Descendants(xmlns + "Import")

                        where el.Attribute("Project").Value == importFileName

                        select el;

 

            // if one already exists, we have what we want, so exit

            if (query.Count() > 0)

            {

                return;

            }

 

            // Add new Import element as the last child of the project element

            xmlDoc.Root.Add(new XElement(xmlns + "Import", new XAttribute("Project", importFileName)));

 

            // And save the changes

            xmlDoc.Save(csprojFileName);

 

        }

 

        public static void RemoveImport(string csprojFileName, string importFileName)

        {

            // validate

            ValidateArgs(csprojFileName, importFileName);

 

            // Load csProjFile as xml

            XDocument xmlDoc = XDocument.Load(csprojFileName);

            // Don't forget the MSBuild namespace that decorates the Project tag in the .csproj file

            XNamespace xmlns = xmlDoc.Root.Attribute("xmlns").Value;

 

            // look for an existing Import element for the importFileName

            var query = from el in xmlDoc.Descendants(xmlns + "Import")

                    where el.Attribute("Project").Value == importFileName

                    select el;

            // If none exist then we exit, as we have the desired result

            if (query.Count() == 0)

            {

                return;

            }

 

            // convert query into a list to force it to be evaluated once only

            foreach (var node in query.ToList())

            {

                node.Remove();

            }

 

            xmlDoc.Save(csprojFileName);

        }

 

        public static List<string> GetImports(string csprojFileName)

        {

            // validate

            ValidateArgs(csprojFileName);

 

            var importList = new List<string>();

 

            // Load csProjFile as xml

            XDocument xmlDoc = XDocument.Load(csprojFileName);

            // Don't forget the MSBuild namespace that decorates the Project tag in the .csproj file

            XNamespace xmlns = xmlDoc.Root.Attribute("xmlns").Value;

 

            // look for an existing Import elements, but ignore the Import of Microsoft.CSharp.targets

            // which is included in every .csproj file and we never want to remove

            var query = from el in xmlDoc.Descendants(xmlns + "Import")

                    where !el.Attribute("Project").Value.EndsWith("Microsoft.CSharp.targets")

                    select el.Attribute("Project").Value;

 

            foreach (var project in query)

            {

                importList.Add(project);

            }

 

            return importList;

        }

 

 

  • Now you should add a new Windows Form to the Project to provide a form that will be displayed when the Macro runs. You will have to add a reference to the System.Windows.Forms assembly. I don't propose to show you the code here, you will see how it looks towards the end of this post.

 

  • My Form has three actions on it:
    • Show Imports - displays a list of all the Import elements in the .csproj file,
    • Add Import - allows the user to add a new Import element by providing the name of a file to be referenced, Clicking the Add Project button, adds the element and saves the .csproj file,
    • Remove Import - displays a list of all the Import elements in the .csproj file, and allows the user to select Import elements are to be removed. Clicking on the Remove Selected button removes the selected import elements and saves the file.
    • The Form also has a Exit Button, which unloads the form.

 

  • When the Project has successfully built, you will need to copy the Assembly to C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PublicAssemblies, so that you can reference it in a Macro. This will require Administrator permission.

 

  • Now lets call this code in a Macro (see my previous post about adding a macro to a Context Menu in Visual Studio).
    • In the Macro IDE, first add a reference to the assembly we just copied, so we can access the code for the Form.
    • Add a new module, place an Imports statement at the top of this module, specifying the namespace in the newly referenced assembly.
    • Then add a new sub, to show the new Form we created, say ManageProjectImports like this:

 

    Public Sub ManageProjectImports()

 

        Dim form As System.Windows.Forms.Form

        Dim itemName As String

        Dim project As EnvDTE.Project

 

        ' Check we have selected a project

        If DTE.ActiveSolutionProjects.Length <> 1 Then

            MsgBox("Select one project within the Solution Explorer, then re-run this macro.")

            Exit Sub

        End If

        project = DTE.ActiveSolutionProjects(0)

 

        ' Getting the project name'

        itemName = project.FullName()

 

        ' Check it really is a .csproj file

        If itemName.EndsWith("csproj") Then

 

            ' Show the form to show/add/remove import tags

            Try

                form = New ImportForm(itemName)

 

                form.ShowDialog()

 

            Catch err As System.Exception

                MsgBox(err.Message)

 

            End Try

        Else

            MsgBox("Don't have a csprojfile. Filename  - " + itemName)

        End If

 

    End Sub

 

  • Then, add any Project to your solution, a class library Project will do, that you can use for testing. Make sure that the .csproj file of this test Project is writable, check it out if you have already added it to source control.
  • To test your new code, right-click the test Project, select 'Manage Project Imports' from the Context Menu and you should get something like this:

 

                  

 

                 

              

                 

 

 

  • If you add or remove an Import, you will be prompted to reload the Project.

 

 

 

 


Posted Jul 12 2011, 07:10 PM by purple.kate

Add a Comment

A Note for Comment Spammers
Now, I realise that those of you who use blog comments such as these to spread your worthless spam are not the cleverest people on the face of the planet. So, I thought I'd make it clear that you're wasting your time posting your pointless garbage here. Quite simply, it will never be published. Thankfully, unlike yourselves, I'm blessed with an inherent ability to identify irrelevant content and it's every bit as easy for me to toss your contribution into the virtual waste bin as it is to publish it. So, guess what? Unless your comments are relevant (and I mean, really relevant, not some thinly veiled attempt to get me to link to your site) they're never going to appear on here. So, they're never go to appear in a search engine, never going to boost your customer's Google page rank and never going to achieve anything in terms of Search Engine Optimisation. Please, practice being a parasite elsewhere. Oh, and I'm confident that my genuine readers are perfectly well endowed and enjoy a full and healthy lifestyle without help from you. Thanks for reading.
(required)  
(optional)
(required)  
Remember Me?
Steve Morgan 2008. All rights reserved.
Powered by Community Server (Non-Commercial Edition), by Telligent Systems