You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ASP.NET MVC中实现员工层级DataTable导出至Visio(.vsd)?

Got it, let's break down how to solve this problem. Generating a .vsd (or the more modern .vsdx) file that includes both your employee hierarchy data and a visual org chart from an ASP.NET MVC app is totally feasible—you just need to work with either Microsoft's free Open XML SDK or a third-party library, since .vsd is a proprietary format. Here's what you need to know:

方案1:使用微软Open XML SDK(免费,推荐)

First, a quick note: .vsd is an older binary format, while .vsdx is the modern Open XML-based format supported by Visio 2013+. The Open XML SDK doesn't support .vsd natively, so I'll focus on .vsdx (which is more reliable for server-side generation). If you absolutely need .vsd, skip to the third-party library section below.

Step 1: Install Required NuGet Packages

Install-Package DocumentFormat.OpenXml
Install-Package DocumentFormat.OpenXml.Visio

Step 2: Convert Your DataTable to a Hierarchical Structure

First, we'll turn your flat DataTable into a nested hierarchy to make drawing the org chart easier:

public class EmployeeNode
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Position { get; set; }
    public int? ParentId { get; set; }
    public List<EmployeeNode> Children { get; set; } = new List<EmployeeNode>();
}

private List<EmployeeNode> ConvertDataTableToHierarchy(DataTable employeeTable)
{
    var allNodes = employeeTable.AsEnumerable()
        .Select(row => new EmployeeNode
        {
            Id = row.Field<int>("Id"),
            Name = row.Field<string>("Name"),
            Position = row.Field<string>("Position"),
            ParentId = row.IsNull("ParentId") ? null : (int?)row.Field<int>("ParentId")
        })
        .ToList();

    // Map parent-child relationships
    var nodeLookup = allNodes.ToDictionary(n => n.Id);
    foreach (var node in allNodes)
    {
        if (node.ParentId.HasValue && nodeLookup.ContainsKey(node.ParentId.Value))
        {
            nodeLookup[node.ParentId.Value].Children.Add(node);
        }
    }

    // Return root-level employees (no parent)
    return allNodes.Where(n => !n.ParentId.HasValue).ToList();
}

Step 3: Generate the Visio File with Chart and Data Table

Here's a method to create the Visio document, draw the org chart, and add the employee data table:

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Visio;
using System.IO;

public byte[] GenerateOrgChartVisio(List<EmployeeNode> hierarchy, DataTable employeeData)
{
    using (var memStream = new MemoryStream())
    {
        using (var visioDoc = VisioDocument.Create(memStream))
        {
            var pagePart = visioDoc.MainDocumentPart.Pages.FirstOrDefault() 
                ?? visioDoc.MainDocumentPart.AddNewPart<PagePart>();
            pagePart.Page = new Page();

            // Draw the hierarchical org chart
            DrawHierarchyNodes(pagePart, hierarchy, 100, 100, 220);

            // Add the employee data table below the chart
            AddEmployeeDataTable(pagePart, employeeData, 100, 650);
        }
        return memStream.ToArray();
    }
}

// Recursively draw employee nodes and connectors
private void DrawHierarchyNodes(PagePart pagePart, List<EmployeeNode> nodes, double x, double y, double verticalGap)
{
    int shapeId = pagePart.Page.Elements<Shape>().Max(s => int.TryParse(s.Id, out int id) ? id : 0) + 1;

    foreach (var node in nodes)
    {
        // Create employee shape
        var employeeShape = new Shape
        {
            Id = shapeId.ToString(),
            Text = new Text(new Paragraph(new Run(new Text($"{node.Name}\n{node.Position}"))))
        };
        employeeShape.Append(new ShapeLayout
        {
            PinX = new DoubleValue(x),
            PinY = new DoubleValue(y),
            Width = new DoubleValue(160),
            Height = new DoubleValue(70)
        });
        pagePart.Page.Append(employeeShape);

        // Draw child nodes and connectors
        if (node.Children.Any())
        {
            double childX = x - (node.Children.Count * 90) + 90;
            double childY = y + verticalGap;
            foreach (var child in node.Children)
            {
                shapeId++;
                var childShape = new Shape
                {
                    Id = shapeId.ToString(),
                    Text = new Text(new Paragraph(new Run(new Text($"{child.Name}\n{child.Position}"))))
                };
                childShape.Append(new ShapeLayout
                {
                    PinX = new DoubleValue(childX),
                    PinY = new DoubleValue(childY),
                    Width = new DoubleValue(160),
                    Height = new DoubleValue(70)
                });
                pagePart.Page.Append(childShape);

                // Add connector between parent and child
                shapeId++;
                var connector = new Shape
                {
                    Id = shapeId.ToString(),
                    Type = ShapeValues.Connector
                };
                connector.Append(new ShapeLayout
                {
                    BeginX = new DoubleValue(x + 80),
                    BeginY = new DoubleValue(y + 70),
                    EndX = new DoubleValue(childX + 80),
                    EndY = new DoubleValue(childY)
                });
                pagePart.Page.Append(connector);

                childX += 180;
            }
        }
        shapeId++;
        x += 200;
    }
}

// Add a data table with all employee details
private void AddEmployeeDataTable(PagePart pagePart, DataTable table, double x, double y)
{
    int shapeId = pagePart.Page.Elements<Shape>().Max(s => int.TryParse(s.Id, out int id) ? id : 0) + 1;
    const int rowHeight = 35;
    const int colWidth = 130;

    // Draw table headers
    for (int i = 0; i < table.Columns.Count; i++)
    {
        var headerShape = new Shape
        {
            Id = shapeId.ToString(),
            Text = new Text(new Paragraph(new Run(new Text(table.Columns[i].ColumnName))))
        };
        headerShape.Append(new ShapeLayout
        {
            PinX = new DoubleValue(x + (i * colWidth)),
            PinY = new DoubleValue(y),
            Width = new DoubleValue(colWidth),
            Height = new DoubleValue(rowHeight)
        });
        pagePart.Page.Append(headerShape);
        shapeId++;
    }

    // Draw table rows
    y += rowHeight;
    foreach (DataRow row in table.Rows)
    {
        for (int i = 0; i < table.Columns.Count; i++)
        {
            var cellShape = new Shape
            {
                Id = shapeId.ToString(),
                Text = new Text(new Paragraph(new Run(new Text(row[i].ToString()))))
            };
            cellShape.Append(new ShapeLayout
            {
                PinX = new DoubleValue(x + (i * colWidth)),
                PinY = new DoubleValue(y),
                Width = new DoubleValue(colWidth),
                Height = new DoubleValue(rowHeight)
            });
            pagePart.Page.Append(cellShape);
            shapeId++;
        }
        y += rowHeight;
    }
}

Step 4: Add an MVC Action to Trigger Download

Add this to your controller to let users download the generated file:

public ActionResult DownloadOrgChart()
{
    // Replace with your actual DataTable retrieval logic
    DataTable employeeTable = GetSampleEmployeeData();
    List<EmployeeNode> hierarchy = ConvertDataTableToHierarchy(employeeTable);

    byte[] visioFileBytes = GenerateOrgChartVisio(hierarchy, employeeTable);

    // Use "application/vnd.ms-visio" for .vsd (if using third-party library)
    return File(visioFileBytes, "application/vnd.ms-visio.drawing.12", "EmployeeOrgChart.vsdx");
}

// Sample data method (replace with your DB call)
private DataTable GetSampleEmployeeData()
{
    var table = new DataTable();
    table.Columns.Add("Id", typeof(int));
    table.Columns.Add("Name", typeof(string));
    table.Columns.Add("Position", typeof(string));
    table.Columns.Add("ParentId", typeof(int));

    table.Rows.Add(1, "John Doe", "CEO", DBNull.Value);
    table.Rows.Add(2, "Jane Smith", "CTO", 1);
    table.Rows.Add(3, "Bob Brown", "Engineering Manager", 2);
    table.Rows.Add(4, "Alice Lee", "Marketing Director", 1);

    return table;
}

方案2:使用第三方库(如Aspose.Diagram)

If you want to avoid the low-level Open XML code, third-party libraries like Aspose.Diagram simplify generating .vsd/.vsdx files with built-in org chart and table support. Note that this is a commercial library, but it handles layout and formatting automatically.

Sample Code with Aspose.Diagram

using Aspose.Diagram;
using System.IO;

public byte[] GenerateOrgChartWithAspose(List<EmployeeNode> hierarchy, DataTable employeeData)
{
    var diagram = new Diagram();
    var page = diagram.Pages[0];

    // Aspose has built-in org chart tools (simplified example)
    // For full org chart layout, refer to Aspose's documentation

    // Add employee data table
    var table = page.Shapes.AddTable(100, 650, employeeData.Columns.Count * 130, (employeeData.Rows.Count + 1) * 35);
    
    // Populate headers
    for (int i = 0; i < employeeData.Columns.Count; i++)
    {
        table.Cells[i, 0].Text.Value.Add(new Txt(employeeData.Columns[i].ColumnName));
    }
    
    // Populate rows
    for (int rowIdx = 0; rowIdx < employeeData.Rows.Count; rowIdx++)
    {
        for (int colIdx = 0; colIdx < employeeData.Columns.Count; colIdx++)
        {
            table.Cells[colIdx, rowIdx + 1].Text.Value.Add(new Txt(employeeData.Rows[rowIdx][colIdx].ToString()));
        }
    }

    using (var memStream = new MemoryStream())
    {
        diagram.Save(memStream, SaveFileFormat.VSD); // Generates .vsd directly
        return memStream.ToArray();
    }
}

Key Notes

  • .vsd vs .vsdx: Stick with .vsdx if possible—it's open, easier to manipulate, and compatible with modern Visio versions. .vsd requires either a third-party library or Visio Interop (which is not recommended for server environments, as it requires installing Visio on the server).
  • Layout Customization: The Open XML example uses a basic layout. For more polished auto-layout, implement a tree layout algorithm or use a library's built-in layout tools.
  • Server Environment: Avoid Visio Interop in production—it's unstable and requires elevated permissions. Open XML or third-party libraries are far more reliable.

内容的提问来源于stack exchange,提问作者Beatrix

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:45:46