Phân tích cú pháp tệp Giải pháp Visual Studio


109

Làm cách nào để phân tích cú pháp tệp giải pháp Visual Studio (SLN) trong .NET? Tôi muốn viết một ứng dụng kết hợp nhiều giải pháp thành một trong khi lưu thứ tự xây dựng tương đối.

Câu trả lời:


113

Phiên bản .NET 4.0 của hợp ngữ Microsoft.Build chứa lớp SolutionParser trong không gian tên Microsoft.Build.Construction phân tích cú pháp tệp giải pháp Visual Studio.

Thật không may, lớp này là nội bộ, nhưng tôi đã gói một số chức năng đó trong một lớp sử dụng phản xạ để có được một số thuộc tính chung mà bạn có thể thấy hữu ích.

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();
    }
}

Lưu ý rằng bạn phải thay đổi khung mục tiêu của mình thành ".NET Framework 4" (không phải hồ sơ máy khách) để có thể thêm tham chiếu Microsoft.Build vào dự án của bạn.


3
Điều này là tốt, tuy nhiên nó hiển thị các nhóm "Mục Giải pháp" là "Dự án" là không chính xác.
Doug

3
Dưới đây là các câu lệnh "using" để thêm: using System; sử dụng System.Reflection; sử dụng System.Collections.Generic; sử dụng System.Diagnostics; sử dụng System.IO; sử dụng System.Linq;
NealWalters

1
@Kiquenet - bạn có thể kiểm tra thuộc tính "ProjectType" của đối tượng s_ProjectInSolution theo cách giống như các thuộc tính khác được hiển thị. Điều này trả về một enum nhưng chỉ là ToString () nó. Tôi đã cố gắng chỉnh sửa bài đăng để bao gồm điều này hai lần nhưng mỗi lần đều bị bắn chìm trong biển lửa.
oasten

3
@oasten Trong khi có ý định tốt, cộng đồng SO cau mày về những chỉnh sửa như vậy, bạn nên tham gia vào các cuộc thảo luận meta nếu bạn muốn tìm hiểu thêm về điều này. Cá nhân tôi thấy rằng nó đôi khi hơi điên rồ. Xin lưu ý rằng tôi hoàn toàn không liên quan gì đến việc chỉnh sửa của bạn bị đóng. Mặc dù tôi nghĩ rằng cách thích hợp để thêm bạn chỉnh sửa là thực sự đăng lại mọi thứ như một câu trả lời khác. Bằng cách này, rõ ràng rằng bạn là người đóng góp trên những gì tôi đã cung cấp ban đầu. Nó có ý nghĩa nhưng các công cụ kiểm duyệt không thực sự cung cấp một cơ chế phản hồi tốt.
John Leidegren

18
Có một lớp công khai mới được SolutionFilegiới thiệu trong Microsoft.Build.dll được cài đặt với Visual Studio 2015 (xem msdn.microsoft.com/en-us/library/… )
Phil

70

Với Visual Studio 2015 hiện có một SolutionFilelớp có thể truy cập công khai có thể được sử dụng để phân tích cú pháp các tệp giải pháp:

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

Lớp này được tìm thấy trong hội đồng Microsoft.Build.dll 14.0.0.0 . Trong trường hợp của tôi, nó được đặt tại:

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

Nhờ Phil cho chỉ ra này !


1
Rất hữu ích ... Đây là những gì tôi sử dụng cho tiêu dùng 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
Tôi không thể tìm thấy một lớp như vậy trong Microsoft.Build.dll v4.0.0.0 được xuất xưởng cùng với Visual Studio 2017.
Jeff G

@JeffG Hãy thử cài đặt msbuild một mình.
Maciej Kucia

1
@JeffG Tôi cũng đang sử dụng VS 2017. Nếu tôi thêm Mircosoft.Build từ tab Assemblies trong Add Reference, thì tôi không có quyền truy cập vào SolutionFile. Tuy nhiên, nếu tôi duyệt và tham chiếu dll nằm trong thư mục ở trên, thì nó dường như hoạt động.
Inrego

3
@JeffG Gói này hiện đã có trên NuGet nuget.org/packages/Microsoft.Build
Robert Hardy

16

Tôi không biết liệu có ai vẫn đang tìm kiếm giải pháp cho vấn đề này không, nhưng tôi đã xem qua một dự án dường như chỉ làm những gì cần thiết. https://slntools.codeplex.com/ Một trong những chức năng của công cụ này là hợp nhất nhiều giải pháp với nhau.


Trên tệp giải pháp mà tôi đã thử nghiệm, slntools này thực sự cung cấp nhiều chi tiết hơn so với các libs của ReSharper.
Răzvan Flavius ​​Panda,

14

JetBrains (những người tạo ra Resharper) có khả năng phân tích cú pháp sln công khai trong các tập hợp của họ (không cần phản ánh). Nó có lẽ mạnh mẽ hơn các giải pháp mã nguồn mở hiện tại được đề xuất ở đây (hãy để một mình các bản hack ReGex). Tất cả những gì bạn cần làm là:

  • Tải xuống Công cụ dòng lệnh ReSharper (miễn phí).
  • Thêm phần sau làm tài liệu tham khảo cho dự án của bạn
    • JetBrains.Platform.ProjectModel
    • JetBrains.Platform.Util
    • JetBrains.Platform.Interop.WinApi

Thư viện không có tài liệu, nhưng Reflector (hoặc thực sự, dotPeek) là bạn của bạn. Ví dụ:

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));
                }
            }
        }
    }
}

4
LƯU Ý: Kể từ bài đăng này, các không gian tên hơi khác một chút [có lẽ JB đã cấu trúc lại nội dung của họ :)]: ~ JetBrains.Platform.ProjectModel ~ JetBrains.Platform.Util ~ JetBrains.Platform.Interop.WinApi
lewiSnort

9

Tôi thực sự không thể cung cấp cho bạn một thư viện và tôi đoán là không có cái nào tồn tại ở đó. Nhưng tôi đã dành rất nhiều thời gian với các tệp .sln trong các kịch bản chỉnh sửa hàng loạt và tôi thấy Powershell là một công cụ rất hữu ích cho tác vụ này. Định dạng .SLN khá đơn giản và có thể được phân tích cú pháp gần như hoàn toàn với một vài biểu thức nhanh và khó hiểu. Ví dụ

Các tệp Dự án bao gồm.

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

Nó không phải lúc nào cũng đẹp, nhưng nó là một cách hiệu quả để xử lý hàng loạt.


Vâng, đó là những gì tôi đang làm cho đến nay
Filip Frącz

Để loại trừ các thư mục giải pháp, điều này phù hợp với tôi: (Get-Content MySolution.sln) | Where-Object {$ _ -match '(? = ^ Project (?! \ ("\ {2150E333-8FDC-42A3-9474-1A3956D46DE8 \}" \))) ^ (\ w +)'} | ForEach-Object {$ _ -match ". * = (. *) $" | out-null; $ đối sánh [1]} | ForEach-Object {. $ _ Split (", ") [1] .Trim () Trim ( '"').}
David Gardiner

6

chúng tôi đã giải quyết một vấn đề tương tự về việc hợp nhất các giải pháp tự động bằng cách viết một plugin Visual Studio tạo giải pháp mới, sau đó tìm kiếm tệp * .sln và nhập chúng vào tệp mới bằng cách sử dụng:

dte2.Solution.AddFromFile(solutionPath, false);

Vấn đề của chúng tôi hơi khác ở chỗ chúng tôi muốn VS sắp xếp thứ tự xây dựng cho chúng tôi, vì vậy chúng tôi sau đó chuyển đổi bất kỳ tham chiếu dll nào thành tham chiếu dự án nếu có thể.

Sau đó, chúng tôi tự động hóa điều này thành một quy trình xây dựng bằng cách chạy VS thông qua tự động hóa COM.

Giải pháp này hơi giống Heath Robinson, nhưng có lợi thế là VS đang thực hiện việc chỉnh sửa nên mã của chúng tôi không phụ thuộc vào định dạng của tệp sln. Điều này rất hữu ích khi chúng tôi chuyển từ VS 2005 sang 2008 và một lần nữa sang 2010.


Bạn có gặp sự cố hiệu suất khi sử dụng DTE không và nếu có thì bạn giải quyết nó như thế nào? stackoverflow.com/questions/1620199/…
Chris Moutray

@mouters Chúng tôi nhận thấy rằng sử dụng DTE và sử dụng gui đều có cùng hiệu suất. Chỉ cài đặt các tùy chọn VS tối thiểu đã giúp một chút, nhưng không nhiều. Vì điều này được tự động hóa và chạy qua đêm nên hiệu suất không phải là điều chúng tôi quan tâm.
Andy Lowry

Nếu sln có thư mục bao gồm dự án, bạn không thể lấy dự án có trong thư mục.
lindexi

5

Mọi thứ đều tuyệt vời, nhưng tôi cũng muốn có được khả năng tạo sln - trong ảnh chụp nhanh mã ở trên, bạn chỉ phân tích cú pháp các tệp .sln - tôi muốn làm điều tương tự ngoại trừ việc có thể tạo lại sln với các sửa đổi nhỏ trở lại tệp .sln . Những trường hợp như vậy có thể là ví dụ chuyển cùng một dự án cho nền tảng .NET khác nhau. Hiện tại, nó chỉ là sự tái tạo lại sln, nhưng sau này tôi sẽ mở rộng nó sang các dự án.

Tôi đoán rằng tôi cũng muốn chứng minh sức mạnh của biểu thức chính quy và giao diện gốc. (Số lượng mã nhỏ hơn với nhiều chức năng hơn)

Cập nhật 4.1.2017 Tôi đã tạo kho lưu trữ svn riêng biệt để phân tích cú pháp .sln: https://sourceforge.net/p/syncproj/code/HEAD/tree/

Dưới đây là đoạn mã mẫu của riêng tôi (tiền thân). Bạn có thể tự do sử dụng bất kỳ ứng dụng nào trong số chúng.

Có thể trong tương lai mã phân tích cú pháp dựa trên giải pháp svn cũng sẽ được cập nhật với các khả năng tạo.

Cập nhật 4.2.2017 Mã nguồn trong SVN cũng đang hỗ trợ tạo .sln .

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);
        }
    }
}

3

Tôi giải thích, xác định rằng các lớp MSBuild có thể được sử dụng để thao tác các cấu trúc bên dưới. Tôi sẽ có thêm mã trên trang web của tôi sau.

// 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;
                }
            }
        }
    }
}

Bạn có biết AbstractX đến từ đâu không?
gunr2171,

Điều này dường như hiểu sai các trang web ASP.Net nơi relativepathtrở thành URL mà trang web sẽ chạy trong IISExpress, v.v.
Doug

1

Câu trả lời của @ john-leidegren thật tuyệt. Đối với trước VS2015, điều này rất hữu ích. Nhưng có một lỗi nhỏ, vì mã để truy xuất cấu hình bị thiếu. Vì vậy, muốn thêm nó, trong trường hợp ai đó đang gặp khó khăn để sử dụng mã này.
Cải tiến rất đơn giản:

    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
}

Như trợ giúp bổ sung, cung cấp mã đơn giản để duyệt qua các thuộc tính của a System.Typenhư được đề xuất bởi @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;
    }
}


0

Cảm ơn @John Leidegren, anh ấy đã đưa ra một cách hiệu quả. Tôi viết một lớp hlper vì tôi không thể sử dụng mã của anh ấy mà không thể tìm thấy s_SolutionParser_configurationsvà các dự án không có FullName.

Mã nằm trong github có thể tải các dự án với FullName.

Và mã không thể nhận được SolutionConfiguration.

Nhưng khi bạn phát triển một vsx, vs sẽ thông báo không thể tìm thấy Microsoft.Build.dll, vì vậy bạn có thể thử sử dụng dte để nhận tất cả các dự án.

Mã sử ​​dụng dte để nhận tất cả các dự án có trong github


@ NP83 Cảm ơn bạn và tôi đã xóa liên kết
lindexi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.