Parsing file solusi Visual Studio

109

Bagaimana cara mengurai file solusi Visual Studio (SLN) di .NET? Saya ingin menulis aplikasi yang menggabungkan beberapa solusi menjadi satu sambil menyimpan urutan pembuatan relatif.

Filip Frącz
sumber

Jawaban:

113

Versi .NET 4.0 dari rakitan Microsoft.Build berisi kelas SolutionParser di ruang nama Microsoft.Build.Construction yang mem-parsing file solusi Visual Studio.

Sayangnya kelas ini bersifat internal, tetapi saya telah membungkus beberapa fungsionalitas itu di kelas yang menggunakan refleksi untuk mendapatkan beberapa properti umum yang mungkin berguna bagi Anda.

public class Solution
{
    //internal class SolutionParser
    //Name: Microsoft.Build.Construction.SolutionParser
    //Assembly: Microsoft.Build, Version=4.0.0.0

    static readonly Type s_SolutionParser;
    static readonly PropertyInfo s_SolutionParser_solutionReader;
    static readonly MethodInfo s_SolutionParser_parseSolution;
    static readonly PropertyInfo s_SolutionParser_projects;

    static Solution()
    {
        s_SolutionParser = Type.GetType("Microsoft.Build.Construction.SolutionParser, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);
        if (s_SolutionParser != null)
        {
            s_SolutionParser_solutionReader = s_SolutionParser.GetProperty("SolutionReader", BindingFlags.NonPublic | BindingFlags.Instance);
            s_SolutionParser_projects = s_SolutionParser.GetProperty("Projects", BindingFlags.NonPublic | BindingFlags.Instance);
            s_SolutionParser_parseSolution = s_SolutionParser.GetMethod("ParseSolution", BindingFlags.NonPublic | BindingFlags.Instance);
        }
    }

    public List<SolutionProject> Projects { get; private set; }

    public Solution(string solutionFileName)
    {
        if (s_SolutionParser == null)
        {
            throw new InvalidOperationException("Can not find type 'Microsoft.Build.Construction.SolutionParser' are you missing a assembly reference to 'Microsoft.Build.dll'?");
        }
        var solutionParser = s_SolutionParser.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First().Invoke(null);
        using (var streamReader = new StreamReader(solutionFileName))
        {
            s_SolutionParser_solutionReader.SetValue(solutionParser, streamReader, null);
            s_SolutionParser_parseSolution.Invoke(solutionParser, null);
        }
        var projects = new List<SolutionProject>();
        var array = (Array)s_SolutionParser_projects.GetValue(solutionParser, null);
        for (int i = 0; i < array.Length; i++)
        {
            projects.Add(new SolutionProject(array.GetValue(i)));
        }
        this.Projects = projects;
    }
}

[DebuggerDisplay("{ProjectName}, {RelativePath}, {ProjectGuid}")]
public class SolutionProject
{
    static readonly Type s_ProjectInSolution;
    static readonly PropertyInfo s_ProjectInSolution_ProjectName;
    static readonly PropertyInfo s_ProjectInSolution_RelativePath;
    static readonly PropertyInfo s_ProjectInSolution_ProjectGuid;
    static readonly PropertyInfo s_ProjectInSolution_ProjectType;

    static SolutionProject()
    {
        s_ProjectInSolution = Type.GetType("Microsoft.Build.Construction.ProjectInSolution, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);
        if (s_ProjectInSolution != null)
        {
            s_ProjectInSolution_ProjectName = s_ProjectInSolution.GetProperty("ProjectName", BindingFlags.NonPublic | BindingFlags.Instance);
            s_ProjectInSolution_RelativePath = s_ProjectInSolution.GetProperty("RelativePath", BindingFlags.NonPublic | BindingFlags.Instance);
            s_ProjectInSolution_ProjectGuid = s_ProjectInSolution.GetProperty("ProjectGuid", BindingFlags.NonPublic | BindingFlags.Instance);
            s_ProjectInSolution_ProjectType = s_ProjectInSolution.GetProperty("ProjectType", BindingFlags.NonPublic | BindingFlags.Instance);
        }
    }

    public string ProjectName { get; private set; }
    public string RelativePath { get; private set; }
    public string ProjectGuid { get; private set; }
    public string ProjectType { get; private set; }

    public SolutionProject(object solutionProject)
    {
        this.ProjectName = s_ProjectInSolution_ProjectName.GetValue(solutionProject, null) as string;
        this.RelativePath = s_ProjectInSolution_RelativePath.GetValue(solutionProject, null) as string;
        this.ProjectGuid = s_ProjectInSolution_ProjectGuid.GetValue(solutionProject, null) as string;
        this.ProjectType = s_ProjectInSolution_ProjectType.GetValue(solutionProject, null).ToString();
    }
}

Perhatikan bahwa Anda harus mengubah kerangka kerja target Anda ke ".NET Framework 4" (bukan profil klien) untuk dapat menambahkan referensi Microsoft.Build ke proyek Anda.

John Leidegren
sumber
3
Ini bagus, namun ini menunjukkan grup "Item Solusi" sebagai "Proyek" yang salah.
Doug
3
Berikut adalah pernyataan "menggunakan" untuk ditambahkan: using System; menggunakan System.Reflection; menggunakan System.Collections.Generic; menggunakan System.Diagnostics; menggunakan System.IO; menggunakan System.Linq;
NealWalters
1
@Kiquenet - Anda dapat memeriksa properti "ProjectType" dari objek s_ProjectInSolution dengan cara yang sama seperti properti lain yang terbuka. Ini mengembalikan enum tetapi hanya ToString () itu. Saya mencoba mengedit posting untuk memasukkan ini dua kali tetapi baru saja ditembak jatuh setiap kali.
oasten
3
@oasten Meskipun niat baik komunitas SO tidak menyukai hasil edit tersebut, Anda harus terlibat dalam diskusi meta jika Anda ingin mempelajari lebih lanjut tentang ini. Saya pribadi terkadang merasa agak gila. Harap perhatikan bahwa saya sama sekali tidak ada hubungannya dengan pengeditan Anda yang ditutup. Cara yang tepat meskipun menurut saya untuk menambahkan hasil edit sebenarnya adalah dengan memposting ulang semuanya sebagai jawaban lain. Dengan cara ini, jelaslah bahwa Anda adalah kontributor di atas yang semula saya berikan. Ini masuk akal tetapi alat moderasi tidak benar-benar memberikan mekanisme umpan balik yang baik.
John Leidegren
18
Ada kelas publik baru yang SolutionFilediperkenalkan di Microsoft.Build.dll yang diinstal dengan Visual Studio 2015 (lihat msdn.microsoft.com/en-us/library/… )
Phil
70

Dengan Visual Studio 2015 sekarang ada SolutionFilekelas yang dapat diakses publik yang dapat digunakan untuk mengurai file solusi:

using Microsoft.Build.Construction;
var _solutionFile = SolutionFile.Parse(path);

Kelas ini ditemukan di rakitan Microsoft.Build.dll 14.0.0.0 . Dalam kasus saya, itu terletak di:

C:\Program Files (x86)\Reference Assemblies\Microsoft\MSBuild\v14.0\Microsoft.Build.dll

Terima kasih kepada Phil karena telah menunjukkan hal ini !

Maciej Kucia
sumber
1
Sangat berguna ... inilah yang saya gunakan untuk konsumsi PowerShell ...Add-Type -Path "C:\Program Files (x86)\Reference Assemblies\Microsoft\MSBuild\v14.0\Microsoft.Build.dll" $slnFile = [Microsoft.Build.Construction.SolutionFile]::Parse($slnPath); $slnFile.ProjectsInOrder
SliverNinja - MSFT
2
Saya tidak dapat menemukan kelas seperti itu di Microsoft.Build.dll v4.0.0.0 yang dikirimkan dengan Visual Studio 2017.
Jeff G
@JeffG Coba instal msbuild sendiri.
Maciej Kucia
1
@JeffG Saya juga menggunakan VS 2017. Jika saya menambahkan Mircosoft.Build dari tab Assemblies di Add Reference, maka saya tidak memiliki akses ke SolutionFile. Namun, jika saya menelusuri dan mereferensikan dll yang terletak di folder di atas, maka tampaknya berfungsi.
Inrego
3
@JeffG Paket ini sekarang tersedia di NuGet nuget.org/packages/Microsoft.Build
Robert Hardy
16

Saya tidak tahu apakah ada yang masih mencari solusi untuk masalah ini, tetapi saya menemukan proyek yang tampaknya melakukan apa yang diperlukan. https://slntools.codeplex.com/ Salah satu fungsi alat ini adalah menggabungkan beberapa solusi bersama.

WiredWiz
sumber
Pada file solusi yang saya uji, slntools ini benar-benar memberikan lebih banyak detail daripada ReSharper libs.
Răzvan Flavius ​​Panda
14

JetBrains (pencipta Resharper) memiliki kemampuan parsing sln publik di rakitan mereka (tidak perlu refleksi). Ini mungkin lebih kuat daripada solusi sumber terbuka yang ada yang disarankan di sini (apalagi peretasan ReGex). Yang perlu Anda lakukan hanyalah:

  • Unduh Alat Baris Perintah ReSharper (gratis).
  • Tambahkan berikut ini sebagai referensi untuk proyek Anda
    • JetBrains.Platform.ProjectModel
    • JetBrains.Platform.Util
    • JetBrains.Platform.Interop.WinApi

Perpustakaan tidak didokumentasikan, tetapi Reflector (atau memang, dotPeek) adalah teman Anda. Sebagai contoh:

public static void PrintProjects(string solutionPath)
{
    var slnFile = SolutionFileParser.ParseFile(FileSystemPath.Parse(solutionPath));
    foreach (var project in slnFile.Projects)
    {
        Console.WriteLine(project.ProjectName);
        Console.WriteLine(project.ProjectGuid);
        Console.WriteLine(project.ProjectTypeGuid);
        foreach (var kvp in project.ProjectSections)
        {
            Console.WriteLine(kvp.Key);
            foreach (var projectSection in kvp.Value) 
            {
                Console.WriteLine(projectSection.SectionName);
                Console.WriteLine(projectSection.SectionValue);
                foreach (var kvpp in projectSection.Properties)
                {
                    Console.WriteLine(kvpp.Key); 
                    Console.WriteLine(string.Join(",", kvpp.Value));
                }
            }
        }
    }
}
Ohad Schneider
sumber
4
CATATAN: Pada posting ini namespace sedikit berbeda [mungkin JB memfaktorkan ulang barang-barang mereka :)]: ~ JetBrains.Platform.ProjectModel ~ JetBrains.Platform.Util ~ JetBrains.Platform.Interop.WinApi
lewiSnort
9

Saya tidak bisa benar-benar menawarkan Anda perpustakaan dan saya kira tidak ada perpustakaan yang ada di luar sana. Tapi saya telah menghabiskan banyak waktu bermain-main dengan file .sln dalam skenario pengeditan batch dan saya telah menemukan Powershell menjadi alat yang sangat berguna untuk tugas ini. Format .SLN cukup sederhana dan hampir dapat diurai seluruhnya dengan beberapa ekspresi cepat dan kotor. Sebagai contoh

File Proyek yang disertakan.

gc ConsoleApplication30.sln | 
  ? { $_ -match "^Project" } | 
  %{ $_ -match ".*=(.*)$" | out-null ; $matches[1] } | 
  %{ $_.Split(",")[1].Trim().Trim('"') }

Ini tidak selalu cantik, tetapi ini adalah cara efektif untuk melakukan pemrosesan batch.

JaredPar
sumber
Ya, itulah yang saya lakukan sejauh ini
Filip Frącz
Untuk mengecualikan folder solusi, ini berfungsi untuk saya: (Get-Content MySolution.sln) | Where-Object {$ _ -match '(? = ^ Project (?! \ ("\ {2150E333-8FDC-42A3-9474-1A3956D46DE8 \}" \))) ^ (\ w +)'} | ForEach-Object {$ _ -match ". * = (. *) $" | out-null; $ pertandingan [1]} | ForEach-Object {$ _. Split (",") [1] .Trim (). Trim ('"')}
David Gardiner
6

kami memecahkan masalah yang sama dalam menggabungkan solusi secara otomatis dengan menulis plugin Visual Studio yang membuat solusi baru kemudian mencari file * .sln dan mengimpornya ke yang baru menggunakan:

dte2.Solution.AddFromFile(solutionPath, false);

Masalah kami sedikit berbeda karena kami ingin VS memilah urutan pembuatan untuk kami, jadi kami kemudian mengonversi referensi dll apa pun ke referensi proyek jika memungkinkan.

Kami kemudian mengotomatiskan ini menjadi proses pembuatan dengan menjalankan VS melalui otomatisasi COM.

Solusi ini sedikit Heath Robinson, tetapi memiliki keuntungan bahwa VS melakukan pengeditan sehingga kode kami tidak bergantung pada format file sln. Yang sangat membantu ketika kami pindah dari VS 2005 ke 2008 dan sekali lagi ke 2010.

Andy Lowry
sumber
Apakah Anda mengalami masalah kinerja saat menggunakan DTE dan jika demikian, bagaimana Anda mengatasinya? stackoverflow.com/questions/1620199/…
Chris Moutray
@mouters Kami menemukan bahwa menggunakan DTE dan menggunakan gui memiliki kinerja yang hampir sama. Menginstal hanya opsi VS minimal membantu sedikit, tetapi tidak banyak. Karena ini otomatis dan berjalan dalam semalam, kinerja bukanlah sesuatu yang menjadi perhatian kami.
Andy Lowry
Jika sln memiliki folder yang menyertakan proyek, Anda tidak bisa mendapatkan proyek yang termasuk dalam folder.
lindexi
5

Semuanya bagus, tetapi saya juga ingin mendapatkan kemampuan pembuatan sln - dalam snapshot kode di atas Anda hanya mem-parsing file .sln - Saya ingin membuat hal serupa kecuali untuk dapat menghasilkan kembali sln dengan sedikit modifikasi kembali ke file .sln . Kasus seperti itu dapat menjadi contoh proyek yang sama untuk porting .NET platform yang berbeda. Untuk saat ini hanya sln re-generation, tapi nanti saya akan kembangkan ke project juga.

Saya rasa saya juga ingin mendemonstrasikan kekuatan ekspresi reguler dan antarmuka asli. (Jumlah kode lebih kecil dengan lebih banyak fungsi)

Pembaruan 4.1.2017 Saya telah membuat repositori svn terpisah untuk mem -parsing solusi .sln: https://sourceforge.net/p/syncproj/code/HEAD/tree/

Di bawah ini adalah cuplikan sampel kode saya sendiri (pendahulu). Anda bebas menggunakan salah satunya.

Ada kemungkinan bahwa di masa depan kode parsing solusi berbasis svn akan diperbarui dengan kemampuan generasi juga.

Pembaruan 4.2.2017 Kode sumber di SVN mendukung pembuatan .sln juga.

using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Text;


public class Program
{
    [DebuggerDisplay("{ProjectName}, {RelativePath}, {ProjectGuid}")]
    public class SolutionProject
    {
        public string ParentProjectGuid;
        public string ProjectName;
        public string RelativePath;
        public string ProjectGuid;

        public string AsSlnString()
        { 
            return "Project(\"" + ParentProjectGuid + "\") = \"" + ProjectName + "\", \"" + RelativePath + "\", \"" + ProjectGuid + "\"";
        }
    }

/// <summary>
/// .sln loaded into class.
/// </summary>
public class Solution
{
    public List<object> slnLines;       // List of either String (line format is not intresting to us), or SolutionProject.

    /// <summary>
    /// Loads visual studio .sln solution
    /// </summary>
    /// <param name="solutionFileName"></param>
    /// <exception cref="System.IO.FileNotFoundException">The file specified in path was not found.</exception>
    public Solution( string solutionFileName )
    {
        slnLines = new List<object>();
        String slnTxt = File.ReadAllText(solutionFileName);
        string[] lines = slnTxt.Split('\n');
        //Match string like: Project("{66666666-7777-8888-9999-AAAAAAAAAAAA}") = "ProjectName", "projectpath.csproj", "{11111111-2222-3333-4444-555555555555}"
        Regex projMatcher = new Regex("Project\\(\"(?<ParentProjectGuid>{[A-F0-9-]+})\"\\) = \"(?<ProjectName>.*?)\", \"(?<RelativePath>.*?)\", \"(?<ProjectGuid>{[A-F0-9-]+})");

        Regex.Replace(slnTxt, "^(.*?)[\n\r]*$", new MatchEvaluator(m =>
            {
                String line = m.Groups[1].Value;

                Match m2 = projMatcher.Match(line);
                if (m2.Groups.Count < 2)
                {
                    slnLines.Add(line);
                    return "";
                }

                SolutionProject s = new SolutionProject();
                foreach (String g in projMatcher.GetGroupNames().Where(x => x != "0")) /* "0" - RegEx special kind of group */
                    s.GetType().GetField(g).SetValue(s, m2.Groups[g].ToString());

                slnLines.Add(s);
                return "";
            }), 
            RegexOptions.Multiline
        );
    }

    /// <summary>
    /// Gets list of sub-projects in solution.
    /// </summary>
    /// <param name="bGetAlsoFolders">true if get also sub-folders.</param>
    public List<SolutionProject> GetProjects( bool bGetAlsoFolders = false )
    {
        var q = slnLines.Where( x => x is SolutionProject ).Select( i => i as SolutionProject );

        if( !bGetAlsoFolders )  // Filter away folder names in solution.
            q = q.Where( x => x.RelativePath != x.ProjectName );

        return q.ToList();
    }

    /// <summary>
    /// Saves solution as file.
    /// </summary>
    public void SaveAs( String asFilename )
    {
        StringBuilder s = new StringBuilder();

        for( int i = 0; i < slnLines.Count; i++ )
        {
            if( slnLines[i] is String ) 
                s.Append(slnLines[i]);
            else
                s.Append((slnLines[i] as SolutionProject).AsSlnString() );

            if( i != slnLines.Count )
                s.AppendLine();
        }

        File.WriteAllText(asFilename, s.ToString());
    }
}


    static void Main()
    {
        String projectFile = @"yourown.sln";

        try
        {
            String outProjectFile = Path.Combine(Path.GetDirectoryName(projectFile), Path.GetFileNameWithoutExtension(projectFile) + "_2.sln");
            Solution s = new Solution(projectFile);
            foreach( var proj in s.GetProjects() )
            {
                Console.WriteLine( proj.RelativePath );
            }

            SolutionProject p = s.GetProjects().Where( x => x.ProjectName.Contains("Plugin") ).First();
            p.RelativePath = Path.Combine( Path.GetDirectoryName(p.RelativePath) , Path.GetFileNameWithoutExtension(p.RelativePath) + "_Variation" + ".csproj");

            s.SaveAs(outProjectFile);

        }
        catch (Exception ex)
        {
            Console.WriteLine("Error: " + ex.Message);
        }
    }
}
TarmoPikaro
sumber
3

Saya menjelaskan, menentukan bahwa kelas MSBuild dapat digunakan untuk memanipulasi struktur yang mendasarinya. Saya akan memiliki kode lebih lanjut di situs web saya nanti.

// VSSolution

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.IO;
using AbstractX.Contracts;

namespace VSProvider
{
    public class VSSolution : IVSSolution
    {
        //internal class SolutionParser 
        //Name: Microsoft.Build.Construction.SolutionParser 
        //Assembly: Microsoft.Build, Version=4.0.0.0 

        static readonly Type s_SolutionParser;
        static readonly PropertyInfo s_SolutionParser_solutionReader;
        static readonly MethodInfo s_SolutionParser_parseSolution;
        static readonly PropertyInfo s_SolutionParser_projects;
        private string solutionFileName;
        private List<VSProject> projects;

        public string Name
        {
            get
            {
                return Path.GetFileNameWithoutExtension(solutionFileName);
            }
        }

        public IEnumerable<IVSProject> Projects
        {
            get
            {
                return projects;
            }
        }

        static VSSolution()
        {
            s_SolutionParser = Type.GetType("Microsoft.Build.Construction.SolutionParser, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);
            s_SolutionParser_solutionReader = s_SolutionParser.GetProperty("SolutionReader", BindingFlags.NonPublic | BindingFlags.Instance);
            s_SolutionParser_projects = s_SolutionParser.GetProperty("Projects", BindingFlags.NonPublic | BindingFlags.Instance);
            s_SolutionParser_parseSolution = s_SolutionParser.GetMethod("ParseSolution", BindingFlags.NonPublic | BindingFlags.Instance);
        }

        public string SolutionPath
        {
            get
            {
                var file = new FileInfo(solutionFileName);

                return file.DirectoryName;
            }
        }

        public VSSolution(string solutionFileName)
        {
            if (s_SolutionParser == null)
            {
                throw new InvalidOperationException("Can not find type 'Microsoft.Build.Construction.SolutionParser' are you missing a assembly reference to 'Microsoft.Build.dll'?");
            }

            var solutionParser = s_SolutionParser.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First().Invoke(null);

            using (var streamReader = new StreamReader(solutionFileName))
            {
                s_SolutionParser_solutionReader.SetValue(solutionParser, streamReader, null);
                s_SolutionParser_parseSolution.Invoke(solutionParser, null);
            }

            this.solutionFileName = solutionFileName;

            projects = new List<VSProject>();
            var array = (Array)s_SolutionParser_projects.GetValue(solutionParser, null);

            for (int i = 0; i < array.Length; i++)
            {
                projects.Add(new VSProject(this, array.GetValue(i)));
            }
        }

        public void Dispose()
        {
        }
    }
}

// VSProject

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.IO;
using System.Xml;
using AbstractX.Contracts;
using System.Collections;

namespace VSProvider
{
    [DebuggerDisplay("{ProjectName}, {RelativePath}, {ProjectGuid}")]
    public class VSProject : IVSProject
    {
        static readonly Type s_ProjectInSolution;
        static readonly Type s_RootElement;
        static readonly Type s_ProjectRootElement;
        static readonly Type s_ProjectRootElementCache;
        static readonly PropertyInfo s_ProjectInSolution_ProjectName;
        static readonly PropertyInfo s_ProjectInSolution_ProjectType;
        static readonly PropertyInfo s_ProjectInSolution_RelativePath;
        static readonly PropertyInfo s_ProjectInSolution_ProjectGuid;
        static readonly PropertyInfo s_ProjectRootElement_Items;

        private VSSolution solution;
        private string projectFileName;
        private object internalSolutionProject;
        private List<VSProjectItem> items;
        public string Name { get; private set; }
        public string ProjectType { get; private set; }
        public string RelativePath { get; private set; }
        public string ProjectGuid { get; private set; }

        public string FileName
        {
            get
            {
                return projectFileName;
            }
        }

        static VSProject()
        {
            s_ProjectInSolution = Type.GetType("Microsoft.Build.Construction.ProjectInSolution, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);

            s_ProjectInSolution_ProjectName = s_ProjectInSolution.GetProperty("ProjectName", BindingFlags.NonPublic | BindingFlags.Instance);
            s_ProjectInSolution_ProjectType = s_ProjectInSolution.GetProperty("ProjectType", BindingFlags.NonPublic | BindingFlags.Instance);
            s_ProjectInSolution_RelativePath = s_ProjectInSolution.GetProperty("RelativePath", BindingFlags.NonPublic | BindingFlags.Instance);
            s_ProjectInSolution_ProjectGuid = s_ProjectInSolution.GetProperty("ProjectGuid", BindingFlags.NonPublic | BindingFlags.Instance);

            s_ProjectRootElement = Type.GetType("Microsoft.Build.Construction.ProjectRootElement, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);
            s_ProjectRootElementCache = Type.GetType("Microsoft.Build.Evaluation.ProjectRootElementCache, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);

            s_ProjectRootElement_Items = s_ProjectRootElement.GetProperty("Items", BindingFlags.Public | BindingFlags.Instance);
        }

        public IEnumerable<IVSProjectItem> Items
        {
            get
            {
                return items;
            }
        }

        public VSProject(VSSolution solution, object internalSolutionProject)
        {
            this.Name = s_ProjectInSolution_ProjectName.GetValue(internalSolutionProject, null) as string;
            this.ProjectType = s_ProjectInSolution_ProjectType.GetValue(internalSolutionProject, null).ToString();
            this.RelativePath = s_ProjectInSolution_RelativePath.GetValue(internalSolutionProject, null) as string;
            this.ProjectGuid = s_ProjectInSolution_ProjectGuid.GetValue(internalSolutionProject, null) as string;

            this.solution = solution;
            this.internalSolutionProject = internalSolutionProject;

            this.projectFileName = Path.Combine(solution.SolutionPath, this.RelativePath);

            items = new List<VSProjectItem>();

            if (this.ProjectType == "KnownToBeMSBuildFormat")
            {
                this.Parse();
            }
        }

        private void Parse()
        {
            var stream = File.OpenRead(projectFileName);
            var reader = XmlReader.Create(stream);
            var cache = s_ProjectRootElementCache.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First().Invoke(new object[] { true });
            var rootElement = s_ProjectRootElement.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First().Invoke(new object[] { reader, cache });

            stream.Close();

            var collection = (ICollection)s_ProjectRootElement_Items.GetValue(rootElement, null);

            foreach (var item in collection)
            {
                items.Add(new VSProjectItem(this, item));
            }

        }

        public IEnumerable<IVSProjectItem> EDMXModels
        {
            get 
            {
                return this.items.Where(i => i.ItemType == "EntityDeploy");
            }
        }

        public void Dispose()
        {
        }
    }
}

// VSProjectItem

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.IO;
using System.Xml;
using AbstractX.Contracts;

namespace VSProvider
{
    [DebuggerDisplay("{ProjectName}, {RelativePath}, {ProjectGuid}")]
    public class VSProjectItem : IVSProjectItem
    {
        static readonly Type s_ProjectItemElement;
        static readonly PropertyInfo s_ProjectItemElement_ItemType;
        static readonly PropertyInfo s_ProjectItemElement_Include;

        private VSProject project;
        private object internalProjectItem;
        private string fileName;

        static VSProjectItem()
        {
            s_ProjectItemElement = Type.GetType("Microsoft.Build.Construction.ProjectItemElement, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);

            s_ProjectItemElement_ItemType = s_ProjectItemElement.GetProperty("ItemType", BindingFlags.Public | BindingFlags.Instance);
            s_ProjectItemElement_Include = s_ProjectItemElement.GetProperty("Include", BindingFlags.Public | BindingFlags.Instance);
        }

        public string ItemType { get; private set; }
        public string Include { get; private set; }

        public VSProjectItem(VSProject project, object internalProjectItem)
        {
            this.ItemType = s_ProjectItemElement_ItemType.GetValue(internalProjectItem, null) as string;
            this.Include = s_ProjectItemElement_Include.GetValue(internalProjectItem, null) as string;
            this.project = project;
            this.internalProjectItem = internalProjectItem;

            // todo - expand this

            if (this.ItemType == "Compile" || this.ItemType == "EntityDeploy")
            {
                var file = new FileInfo(project.FileName);

                fileName = Path.Combine(file.DirectoryName, this.Include);
            }
        }

        public byte[] FileContents
        {
            get 
            {
                return File.ReadAllBytes(fileName);
            }
        }

        public string Name
        {
            get 
            {
                if (fileName != null)
                {
                    var file = new FileInfo(fileName);

                    return file.Name;
                }
                else
                {
                    return this.Include;
                }
            }
        }
    }
}
Ken
sumber
Tahu dari mana AbstractX berasal?
gunr2171
Ini tampaknya salah menafsirkan situs web ASP.Net di mana relativepathmenjadi URL situs yang harus dijalankan di IISExpress dll di bawah.
Doug
1

Jawaban oleh @ john-leidegren bagus. Untuk pra-VS2015, ini sangat berguna. Tetapi ada kesalahan kecil, karena kode untuk mengambil konfigurasi hilang. Jadi ingin menambahkannya, jika seseorang kesulitan menggunakan kode ini.
Peningkatannya sangat sederhana:

    public class Solution
{
    //internal class SolutionParser
    //Name: Microsoft.Build.Construction.SolutionParser
    //Assembly: Microsoft.Build, Version=4.0.0.0

    static readonly Type s_SolutionParser;
    static readonly PropertyInfo s_SolutionParser_solutionReader;
    static readonly MethodInfo s_SolutionParser_parseSolution;
    static readonly PropertyInfo s_SolutionParser_projects;
    static readonly PropertyInfo s_SolutionParser_configurations;//this was missing in john's answer


    static Solution()
    {
        s_SolutionParser = Type.GetType("Microsoft.Build.Construction.SolutionParser, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);
        if ( s_SolutionParser != null )
        {
            s_SolutionParser_solutionReader = s_SolutionParser.GetProperty("SolutionReader", BindingFlags.NonPublic | BindingFlags.Instance);
            s_SolutionParser_projects = s_SolutionParser.GetProperty("Projects", BindingFlags.NonPublic | BindingFlags.Instance);
            s_SolutionParser_parseSolution = s_SolutionParser.GetMethod("ParseSolution", BindingFlags.NonPublic | BindingFlags.Instance);
            s_SolutionParser_configurations = s_SolutionParser.GetProperty("SolutionConfigurations", BindingFlags.NonPublic | BindingFlags.Instance); //this was missing in john's answer

            // additional info:
            var PropNameLst = GenHlp_PropBrowser.PropNamesOfType(s_SolutionParser);
            // the above call would yield something like this:
            // [ 0] "SolutionParserWarnings"        string
            // [ 1] "SolutionParserComments"        string
            // [ 2] "SolutionParserErrorCodes"      string
            // [ 3] "Version"                       string
            // [ 4] "ContainsWebProjects"           string
            // [ 5] "ContainsWebDeploymentProjects" string
            // [ 6] "ProjectsInOrder"               string
            // [ 7] "ProjectsByGuid"                string
            // [ 8] "SolutionFile"                  string
            // [ 9] "SolutionFileDirectory"         string
            // [10] "SolutionReader"                string
            // [11] "Projects"                      string
            // [12] "SolutionConfigurations"        string
        }
    }

    public List<SolutionProject> Projects { get; private set; }
    public List<SolutionConfiguration> Configurations { get; private set; }

   //...
   //...
   //... no change in the rest of the code
}

Sebagai bantuan tambahan, menyediakan kode sederhana untuk menjelajahi properti a System.Typeseperti yang disarankan oleh @oasten.

public class GenHlp_PropBrowser
{
    public static List<string> PropNamesOfClass(object anObj)
    {
        return anObj == null ? null : PropNamesOfType(anObj.GetType());
    }
    public static List<String> PropNamesOfType(System.Type aTyp)
    {
        List<string> retLst = new List<string>();
        foreach ( var p in aTyp.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance) )
        {
            retLst.Add(p.Name);
        }
        return retLst;
    }
}
elimad
sumber
0

Terima kasih @John Leidegren dia menawarkan cara yang efektif. Saya menulis kelas hlper karena saya tidak dapat menggunakan kodenya yang tidak dapat menemukan s_SolutionParser_configurationsdan proyek tanpa FullName.

Kode ada di github yang bisa mendapatkan proyek dengan Nama Lengkap.

Dan kode tidak bisa mendapatkan SolutionConfiguration.

Tetapi ketika Anda mengembangkan vsx, vs akan mengatakan tidak dapat menemukan Microsoft.Build.dll, jadi Anda dapat mencoba menggunakan dte untuk mendapatkan semua proyek.

Kode yang menggunakan dte untuk mendapatkan semua proyek ada di github

lindexi
sumber
@ NP83 Terima kasih dan saya menghapus tautan
lindexi