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

IIS部署后无法从远程服务器下载文件(本地运行正常)

问题:C# Web应用部署到IIS后无法通过NetworkCredential下载远程文件

问题描述

开发的C# Web应用通过NetworkCredential从远程文件服务器下载文件,本地主机运行时功能完全正常,但部署到IIS后,下载操作失败,前端提示“无法访问此网站”。IIS上的上传功能运行正常,且无错误日志生成,事件查看器中也无异常条目。

相关代码

C# 下载方法

public HttpResponseMessage DownloadFile(string networkPath, string documentFullPath, NetworkCredential credentials, string documentPath, string contentType)
{
    FileStream stream;
    if (isServer.ToLower() == "true")
    {
        logger.Error("ClinicalEventControll:4710");
        using (new ConnectToSharedFolder(networkPath, credentials))
        {
            logger.Error(":4712");
            stream = new FileStream(networkPath + @"\\" + documentFullPath, FileMode.Open);
            httpResponseMessage.Content = new StreamContent(stream);
            httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            httpResponseMessage.Content.Headers.ContentDisposition.FileName = documentPath;
            httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
            httpResponseMessage.Content.Headers.ContentLength = stream.Length;
        }
    }
    return httpResponseMessage;
}

网络连接类(ConnectToSharedFolder)

public class ConnectToSharedFolder : IDisposable
{
    private readonly string _networkName;
    private static readonly ILog logger = LogManager.GetLogger(typeof(ClinicalEventdetailsApiController));
    
    public ConnectToSharedFolder(string networkName, NetworkCredential credentials)
    {
        _networkName = networkName;
        var netResource = new NetResource
        {
            Scope = ResourceScope.GlobalNetwork,
            ResourceType = ResourceType.Disk,
            DisplayType = ResourceDisplaytype.Share,
            RemoteName = networkName
        };

        var userName = string.IsNullOrEmpty(credentials.Domain)? credentials.UserName : $@"{credentials.Domain}\{credentials.UserName}";

        var result = WNetAddConnection2(netResource, credentials.Password, userName, 0);
        if (result != 0)
        {
            logger.Error(result + " UNC path error status code");
            throw new Win32Exception(result, "Error connecting to remote share");
        }
    }

    ~ConnectToSharedFolder()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        WNetCancelConnection2(_networkName, 0, true);
    }

    [DllImport("mpr.dll")]
    private static extern int WNetAddConnection2(NetResource netResource, string password, string username, int flags);

    [DllImport("mpr.dll")]
    private static extern int WNetCancelConnection2(string name, int flags, bool force);

    [StructLayout(LayoutKind.Sequential)]
    public class NetResource
    {
        public ResourceScope Scope;
        public ResourceType ResourceType;
        public ResourceDisplaytype DisplayType;
        public int Usage;
        public string LocalName;
        public string RemoteName;
        public string Comment;
        public string Provider;
    }

    public enum ResourceScope : int
    {
        Connected = 1,
        GlobalNetwork,
        Remembered,
        Recent,
        Context
    };

    public enum ResourceType : int
    {
        Any = 0,
        Disk = 1,
        Print = 2,
        Reserved = 8,
    }

    public enum ResourceDisplaytype : int
    {
        Generic = 0x0,
        Domain = 0x01,
        Server = 0x02,
        Share = 0x03,
        File = 0x04,
        Group = 0x05,
        Network = 0x06,
        Root = 0x07,
        Shareadmin = 0x08,
        Directory = 0x09,
        Tree = 0x0a,
        Ndscontainer = 0x0b
    }
}

JavaScript 前端调用代码

function downloadSourceDoc(sourceDocID) {
    showLoader();
    var form = $('form');
    form.attr('action', '../DownloadSourceDoc?SourceDocID=' + sourceDocID + '&RoleID=' + $('#hdnUserRoleID').val());
    form.attr('method', 'POST');
    form.submit();
    closeLoader();
}

已排查内容

  • 网络凭证验证正确
  • 服务器可正常访问目标文件路径
  • 已为相关账号授予必要权限
  • 未生成任何错误日志
  • 事件查看器中无异常条目

回答

核心问题分析

本地运行正常但IIS部署后失败,核心原因集中在权限隔离和资源生命周期管理:

  1. IIS应用池身份限制:本地运行用当前登录用户身份(通常有远程共享访问权限),但IIS默认用ApplicationPoolIdentity或Network Service内置账户,这类账户默认无远程文件服务器访问权限,且WNetAddConnection2的会话隔离会导致代码传入的凭证无法生效。
  2. 资源释放时机错误:原代码中FileStream在ConnectToSharedFolder的using块外部引用,共享连接断开后,流会失去文件访问权限,导致响应失效。
  3. 未显式实例化响应对象:原代码中httpResponseMessage未提前实例化,可能触发空引用异常。

解决方案

1. 修复代码资源生命周期问题

将文件流的创建、响应构建完全放在共享连接的using块内部,确保连接在流读取完成前不释放:

public HttpResponseMessage DownloadFile(string networkPath, string documentFullPath, NetworkCredential credentials, string documentPath, string contentType)
{
    if (string.Equals(isServer, "true", StringComparison.OrdinalIgnoreCase))
    {
        logger.Error("ClinicalEventControll:4710");
        using (new ConnectToSharedFolder(networkPath, credentials))
        {
            logger.Error(":4712");
            var fullPath = Path.Combine(networkPath, documentFullPath);
            using (var stream = new FileStream(fullPath, FileMode.Open))
            {
                var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK);
                httpResponseMessage.Content = new StreamContent(stream);
                httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                {
                    FileName = documentPath
                };
                httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
                httpResponseMessage.Content.Headers.ContentLength = stream.Length;
                return httpResponseMessage;
            }
        }
    }
    return new HttpResponseMessage(HttpStatusCode.BadRequest);
}

2. 调整IIS应用程序池身份

将应用程序池身份改为具有远程共享访问权限的域账户:

  • 打开IIS管理器,找到目标应用程序池
  • 右键选择「高级设置」,在「进程模型」→「标识」中选择「自定义账户」
  • 输入有权访问远程文件服务器的域账号和密码,保存后重启应用程序池

3. 替换WNetAddConnection2(推荐)

避免依赖Windows会话的API,直接用用户模拟访问文件:

public HttpResponseMessage DownloadFile(string networkPath, string documentFullPath, NetworkCredential credentials, string documentPath, string contentType)
{
    if (string.Equals(isServer, "true", StringComparison.OrdinalIgnoreCase))
    {
        logger.Error("ClinicalEventControll:4710");
        var fullPath = Path.Combine(networkPath, documentFullPath);
        using (var impersonationContext = credentials.Impersonate())
        {
            using (var stream = new FileStream(fullPath, FileMode.Open))
            {
                var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK);
                httpResponseMessage.Content = new StreamContent(stream);
                httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                {
                    FileName = documentPath
                };
                httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
                httpResponseMessage.Content.Headers.ContentLength = stream.Length;
                return httpResponseMessage;
            }
            impersonationContext.Undo();
        }
    }
    return new HttpResponseMessage(HttpStatusCode.BadRequest);
}

该方式无需建立共享连接,直接通过模拟指定用户访问文件,彻底规避会话隔离问题。

4. 检查IIS超时设置

若下载文件较大,需调整站点「连接超时」和应用程序池「闲置超时」时间,避免因请求超时导致前端报错。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 01:45:52