公司的网站项目在用户上传文件时出现异常,查看发现是文件路径过长造成的。
异常如下:
System.IO.PathTooLongException
The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.
这句话也很好理解,就是目录不能超过248字符,整个路径不能超过260字符。这是Windows系统的限制,我们可以自己新建一个文件夹试一试。
这是我新建的文件夹,当超过248字符时继续输入是无效的。加上文件名刚好就是260个字符,就是说文件名输入12个字符后便不能在输入了。
我自己在电脑上测试的时候,是报这个异常 System.IO.DirectoryNotFoundException。微软的文章《More on new .NET path handling》中也给出了解释:
Note that in many cases the OS will return DirectoryNotFound for paths that are too long. The primary case is with creating files. We don't have a way to programmatically check whether paths are actually too long so we can't translate DirectoryNotFound into PathTooLong. We're looking at updating the exception text in the future to say it "may also be caused by a path that is too long" where such a thing is possible.
带着问题搜索一下,很容易就能发现这篇微软官方的文档 Naming Files, Paths, and Namespaces ,这篇文章和上面的那篇里面都给出了差不多的解决方案,我先把我测试的结论用红色字体标注了。
1、加前缀 各种项目类型(Web、控制台、WinForm)都适用
\\?\ 例如:"\\?\D:\very long path"。
如果是UNC,就用 \\?\UNC\
加前缀为什么可以呢?大概就是Windows API具有许多功能,这些功能也具有Unicode版本,允许使用扩展长度的路径,最大总路径长度为32,767个字符,那加个前缀就能指定成使用扩展长度的路径了。具体可以去文章里看看。
很多第三方的库为我们封装好了新的IO方法,也是利用的前缀来解决:
- Delimon.Win32.IO Library
- NuGet:Microsoft.Experimental.IO 官网 用到的地方把系统的File和Directory替换为LongPathFile和LongPathDirectory即可
- NuGet:ZetaLongPaths GitHub
2、同时配置两个地方(要求Windows 10, Version 1607 以上)ASP.NET Web应用程序 不成功!(我不知道原因啊,如果有大神成功了留言告诉我一下,谢谢),控制台应用和WinForm 成功:
1)系统配置,配置策略或者修改注册表。
运行gpedit.msc,进入配置编辑器,然后 计算机配置 > 管理模板 > 系统 > 文件系统
找到启用Win32长路径,设置为已启用就行了,然后最好才重启下电脑。
这个策略就是帮我们在注册表里面加了一项。
HKLM\SYSTEM\CurrentControlSet\Control\FileSystem LongPathsEnabled (Type: REG_DWORD) 值为 1
如果选择已禁用,就是把只设置为 0
不知道你注意到上面那个图里面的“注意”没有,意思是说你把它改回未配置是没用的,并不会删除注册表。
2)添加应用程序清单文件(application manifest)。
官方文档告诉我们把这个加进去就行了
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
<ws2:longPathAware>true</ws2:longPathAware>
</windowsSettings>
</application>
另一篇文档里面又是这样的,命名空间的事儿,都差不多。
VS帮我们创建的manifest会默认加入一些东西,只用把下图这个地方注释去掉,修改成上面那样就行了。
改完之后,去项目的属性里面看一眼,这个地方是不是选择正确。
好了,官方的文章到这就结束了。然后WinForm应该是OK的,控制台程序有的人成功,有的人不成功~~~
你就搜索吧,微软的文章需要我们慢慢探索~~~~~
也许你能找到这一篇文章:.NET 4.6.2 and long paths on Windows 10
这里出现了一个关键字.NET 4.6.2 , .NET 4.6.2以下版本还要在App.config里配置
<runtime>
<AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" />
</runtime>
就像这样
设置了两个值为false,因为文章里说了:The defaults for these two values are true if the target framework is 4.6.1 or earlier.
到这里OK,大功告成。
大大的疑惑?
加前缀的方案似乎对系统版本和.NET版本没什么要求,但是要改代码啊,那么多地方,对我这个懒人来说这太累了。
公司的项目其实用的是.NET 4.7.2的版本。我最开始用WinForm和控制台测试没问题,我就新建了一个Web项目测试,发现始终不行,微软官方也没有对Web项目做特别说明。
第一个遇到的问题是怎么加manifest,因为你打开Web项目的属性会发现无法设置manifest,我在stackoverflow上看到也有老外在问,但没有答案。
后来我发现,通过右键项目添加app.manifest是有效的(暂且认为吧,这块我不是太懂),因为我用VS打开生成的dll看了下,如图。
然而并没有什么卵用~我就死马当活马医了,又在Web.config中加入了配置,根据官网描述4.6.2以上是不需要的。
还是不行,我感觉是不是Web不是这样配置的,毕竟官方的例子是控制台程序,又是一顿搜,找到了这篇 <AppContextSwitchOverrides> element
告诉我要这么配置
好!那我就加上。不过事实证明没用。
有个外国哥们说,我也试过了,if条件都进不去,因为那个值默认就是false,配置不配置没差别。
bool legacyPaths;
if (AppContext.TryGetSwitch("Switch.System.IO.UseLegacyPathHandling", out legacyPaths) && legacyPaths)
{
var switchType = Type.GetType("System.AppContextSwitches");
if (switchType != null)
{
AppContext.SetSwitch("Switch.System.IO.UseLegacyPathHandling", false);
var legacyField = switchType.GetField("_useLegacyPathHandling", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
legacyField?.SetValue(null, (Int32)0);
AppContext.TryGetSwitch("Switch.System.IO.UseLegacyPathHandling", out legacyPaths);
Assert.IsFalse(legacyPaths, "Long pathnames are not supported!");
}
}
在搜索研究了半天之后,最终我放弃了,还是用第一种解决方法吧。
希望这篇文章可以帮助到同样问题的兄弟,也希望第二个方法有成功的大神告诉我下。
附加:
2020-07-13
今天用VS发布一个网站的时候报了个错误:
The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.
解决方案如下:
- Unload your web/webjob project
- Select “Edit [yourprojectfilename].csproj“
- Add the following entry under the first <PropertyGroup> that exists in the xml file:
- <IntermediateOutputPath>..\Temp</IntermediateOutputPath>
- Reload the project
- Clean the project
- Try the deployment/publish again – it should work now.