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

ASP.Net Core API查询Active Directory遇空值时请求失败

ASP.Net Core AD查询空属性导致请求失败的解决方案

问题场景

开发的ASP.Net Core API在执行Active Directory用户查询时,若返回的属性(如homeDirectory)为空值,API请求会失败并返回首页HTML。代码编译无错误,尝试过if-else语句处理空值但问题仍未解决。

原代码

using Microsoft.AspNetCore.Mvc;
using System.DirectoryServices;
using System.Security.Principal;
using Newtonsoft.Json;

namespace SDAdmConsole.Controllers;

[ApiController]
[Route("api/[controller]")]
public class SearchUsrController : Controller
{
    public IActionResult  Index()
    {
        // List of Domains to Check users
        string[] domains = { "tst-ads-001", "tst-ads-001." };
        
        // Active Directory Properties we need to bring the results for
        string[] propertiesToLoad = { "samAccountName", "displayName", "objectSid", "department", "employeeNumber", "manager", "userPrincipalName","badPwdCount", "lockoutTime", 
                                    "lastLogon", "lastLogonTimestamp", "pwdLastSet", "homeDirectory", "extensionAttribute1", "distinguishedName", "userWorkstations" 
                                    };
        
        // Master Credentials for Active Directory LDAP search
        string domainUser = "redacted";
        string domainPassword = "redacted";

        // Filter used for Active Directory Search
        string searchQuery = "(&(objectCategory=user)(objectClass=user)(samAccountName=*steven.test*))";
        
        // New empty Dictionary to put all the results returned into.
        List<Dictionary<string, object>> resultList = new List<Dictionary<string, object>>();

        // Search each domain in the list of Domains
        foreach (string domain in domains)
        {
            // New Search builder
            DirectorySearcher searcher = new DirectorySearcher(new DirectoryEntry("LDAP://" + domain, domainUser, domainPassword), searchQuery, propertiesToLoad);

            // Results returned as a collection
            SearchResultCollection results = searcher.FindAll();

            foreach (SearchResult result in results)
            {
                // Create Dictionary to store all the values from each user found
                Dictionary<string, object> resultDict = new Dictionary<string, object>();

                // Convert raw SID in to a readable value
                byte[] sidBytes = (byte[])result.Properties["objectSid"][0];
                SecurityIdentifier sid = new SecurityIdentifier(sidBytes, 0);
                string sidString = sid.Value;               
            
                resultDict.Add("displayName", result.Properties["displayName"][0]);
                resultDict.Add("sAMAccountName", result.Properties["sAMAccountName"][0]); 
                resultDict.Add("department", result.Properties["department"][0]);   
                resultDict.Add("employeeNumber", result.Properties["employeeNumber"][0]);   
                resultDict.Add("manager", result.Properties["manager"][0]);   
                resultDict.Add("userPrincipalName", result.Properties["userPrincipalName"][0]);   
                resultDict.Add("badPwdCount", result.Properties["badPwdCount"][0]);
                resultDict.Add("lockoutTime", result.Properties["lockoutTime"][0]);
                resultDict.Add("lastLogon", result.Properties["lastLogon"][0]);   
                resultDict.Add("lastLogonTimestamp", result.Properties["lastLogonTimestamp"][0]);   
                resultDict.Add("pwdLastSet", result.Properties["pwdLastSet"][0]);
                resultDict.Add("homeDirectory", result.Properties["homeDirectory"][0]); 
                resultDict.Add("objectSid", sidString);
                //resultDict.Add("extensionAttribute1", result.Properties["extensionAttribute1"][0]);   
                //resultDict.Add("distinguishedName", result.Properties["distinguishedName"][0]); 
                //resultDict.Add("userWorkstations", result.Properties["userWorkstations"][0]); 
                //resultDict.Add("extensionAttribute1", result.Properties["extensionAttribute1"][0]);   
                //resultDict.Add("mail", result.Properties["mail"][0]);
                //resultDict.Add("memberof", result.Properties["memberof"][0]);   */                      
                resultList.Add(resultDict);
            }
        }

        // Convert Dictionary in to JSON array
        string json = JsonConvert.SerializeObject(resultList);

        // Return JSON List for the API request
        return Ok(json);
    }
}

问题根源

直接通过result.Properties["属性名"][0]取值的方式,在AD属性为空时会触发索引越界异常——因为空属性对应的集合长度为0,访问索引0必然报错。而ASP.Net Core默认会将未捕获的异常重定向到首页HTML,这就是请求失败的核心原因。

之前的if-else未生效,大概率是没有先判断属性集合是否存在且包含元素,仅对取值结果做了判断,根本没走到空值处理逻辑就报错了。

解决方案

1. 封装安全取值方法

新增一个工具方法,先判断属性是否存在且有值,再返回对应内容,否则返回null或空字符串:

private object GetAdPropertyValue(SearchResult result, string propertyName)
{
    if (result.Properties.Contains(propertyName) && result.Properties[propertyName].Count > 0)
    {
        return result.Properties[propertyName][0];
    }
    // 如需返回空字符串而非null,替换为return "";
    return null;
}

2. 替换所有直接取值的代码

将原代码中所有result.Properties["xxx"][0]的调用,替换为上述方法:

// 替换后的属性赋值逻辑
resultDict.Add("displayName", GetAdPropertyValue(result, "displayName"));
resultDict.Add("sAMAccountName", GetAdPropertyValue(result, "sAMAccountName"));
resultDict.Add("department", GetAdPropertyValue(result, "department"));
resultDict.Add("employeeNumber", GetAdPropertyValue(result, "employeeNumber"));
resultDict.Add("manager", GetAdPropertyValue(result, "manager"));
resultDict.Add("userPrincipalName", GetAdPropertyValue(result, "userPrincipalName"));
resultDict.Add("badPwdCount", GetAdPropertyValue(result, "badPwdCount"));
resultDict.Add("lockoutTime", GetAdPropertyValue(result, "lockoutTime"));
resultDict.Add("lastLogon", GetAdPropertyValue(result, "lastLogon"));
resultDict.Add("lastLogonTimestamp", GetAdPropertyValue(result, "lastLogonTimestamp"));
resultDict.Add("pwdLastSet", GetAdPropertyValue(result, "pwdLastSet"));
resultDict.Add("homeDirectory", GetAdPropertyValue(result, "homeDirectory"));

3. 安全处理必填属性(如objectSid)

虽然objectSid是AD用户的必填属性,但为了避免意外,也添加判断逻辑:

// 替换原有的objectSid处理代码
if (result.Properties.Contains("objectSid") && result.Properties["objectSid"].Count > 0)
{
    byte[] sidBytes = (byte[])result.Properties["objectSid"][0];
    SecurityIdentifier sid = new SecurityIdentifier(sidBytes, 0);
    resultDict.Add("objectSid", sid.Value);
}
else
{
    resultDict.Add("objectSid", null);
}

4. 优化返回逻辑并添加异常捕获

  • 不要手动序列化JSON,直接返回Ok(resultList),让ASP.Net Core框架自动处理序列化(如需使用Newtonsoft.Json,可在Program.cs中配置)。
  • 添加try-catch捕获异常,返回标准错误JSON而非首页HTML:
public IActionResult Index()
{
    try
    {
        // 原有代码逻辑(已替换安全取值方法)
        // ...
        return Ok(resultList);
    }
    catch (Exception ex)
    {
        // 可添加日志记录逻辑
        return StatusCode(500, new { Error = "AD用户查询失败", Details = ex.Message });
    }
}

5. 可选:禁用默认错误页(全局配置)

如果不想让ASP.Net Core在异常时返回首页,可在Program.cs中添加全局配置:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add(new ProducesAttribute("application/json"));
})
.ConfigureApiBehaviorOptions(options =>
{
    options.SuppressMapClientErrors = true;
    options.SuppressModelStateInvalidFilter = true;
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 05:17:55