-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Background and motivation
System.Environment.GetFolderPath is used to query paths to known folders like the user's file folders ("Documents", "Pictures", etc.). The paths to these folders can be redirected in system configuration, which is queried by this method. To use it, a value of the System.Environment.SpecialFolder enum is passed in to query the accompanying folder:
using System;
string videosPath = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos);However, this enum only provides folders that were available in Windows XP days, and does not contain entries for nowadays commonly available personal folders, leaving a "hole" in support for those introduced in Windows Vista or other platforms like Linux and macOS (for example, the user "Downloads" folder).
It is currently required to manually p/invoke the WinAPI or rewrite the existing Linux / macOS folder retrieval logic to ensure getting correct paths. Adding support for these folders via extending the enum will provide a natural and expected way to retrieve these paths from the system configuration without having to go through all that.
Judging from forum discussions, it will also prevent users from "solving" this issue incorrectly by hardcoding and concatenating parts of the folder paths (which would not reflect any redirected paths stored in system configuration) or incorrectly p/invoking system methods themselves:
- Stack Overflow - How to programmatically derive Windows Downloads folder "%USERPROFILE%/Downloads"? and many linked / duplicate questions
- MSDN Forum - How do I determine the Windows 'Download folder' path using the KnownFolders class?
There have also been several issues requesting the missing folders in the past, but most staled or haven't been tackled since long:
- 2015-10-12 Complete Environmment.SpecialFolder #15419 (Issue)
- about extending
SpecialFolder, first mention of new (but internal) GUID based API
- about extending
- 2016-10-12 Fix SpecialFolder.MyDocuments to map to XDG properly in Unix corefx#12575
- mentions deprecating the whole
SpecialFolderAPI in favor of a new, public GUID based API:- extending the
SpecialFolderenum in any way would break code assuming it maps to deprecated Windows' CSIDLs - updating it in the future may not be flexible enough
- extending the
- mentions deprecating the whole
- 2016-10-21 Add a more flexible special folder API #19047 (API Proposal)
- proposes said API, dropped by proposer due to missing interest
- 2018-03-22 Environment.SpecialFolder should expose FOLDERID_Downloads and FOLDERID_Public values #25577 (API Proposal)
- about extending
SpecialFolderagain
- about extending
- 2018-06-13 Support other known paths dotnet-api-docs#791 (Issue)
- about extending
SpecialFolderagain
- about extending
- 2019-05-12 Environment.GetFolderPath() doesn't support all KNOWNFOLDERID's #554 (Issue)
- about extending
SpecialFolderagain
- about extending
API Proposal
I propose extending the System.Environment.SpecialFolder enum with these values:
| SpecialFolder | Windows | macOS | iOS | Linux (with XDG) | Linux |
|---|---|---|---|---|---|
| MyDownloads | FOLDERID_Downloads | ~/Downloads | NSDocumentDirectory/Downloads | XDG_DOWNLOAD_DIR | ~/Downloads |
| MySavedGames | FOLDERID_SavedGames | - | - | - | - |
| Public | FOLDERID_Public | ~/Public | - | XDG_PUBLICSHARE_DIR | ~/Public |
| PublicDesktop | FOLDERID_PublicDesktop | - | - | - | - |
| PublicDocuments | FOLDERID_PublicDocuments | - | - | - | - |
| PublicDownloads | FOLDERID_PublicDownloads | - | - | - | - |
| PublicMusic | FOLDERID_PublicMusic | - | - | - | - |
| PublicPictures | FOLDERID_PublicPictures | - | - | - | - |
| PublicVideos | FOLDERID_PublicVideos | - | - | - | - |
-
The underlying values are added starting with
0x10000due to the undocumented fact that the existing values map to Windows'CSIDLs, and0x10000is the first invalidCSIDLcombination that would not clash.- The
CSIDLAPI is deprecated, not used by .NET anymore, and does not allow querying the folders above anyway.
- The
-
~is the home directory as it is already determined. -
XDG...variables are used on Linux if they exist, otherwise the non-XDG path is used as a fallback. -
The public (sub) folder is per-system on Windows, and per-user on Linux and macOS. iOS has no such folders.
- Adding
Public...sub folders is required for Windows as they may be redirected outside of the parentPublicDirectory. Otherwise it could lead to developers looking for them to incorrectly hardcode parts of their path again by assuming they are children (the same issue currently with the Downloads and Public folders themselves).
- Adding
-
User folders are prefixed with
Myas it is currently the case for other user folders likeMyPicturesorMyDocuments, so that related folders group together more clearly in the enum.-
MySavedGamesis included as it is the only remaining Windows user folder meaningful to developers. While there are other user folders available on Windows, I do not recommend adding as they have a very special meaning only to one Windows app / component:SpecialFolder Windows only Used by / meant for ❌ MyContacts FOLDERID_Contacts Windows Contacts contact files. ❌ MyLinks FOLDERID_Links File Explorer pinned location shortcut files (< Windows 10 only). ❌ MySearches FOLDERID_Searches Parameterized Windows Search query files.
-
-
Folders that do not exist on specific platforms will return
String.Emptyas they already do for others. -
VB.NET's SpecialDirectories class will not be extended as it is understood to be a utility class for VB6-style backwards compatibility.
API Usage
The usage naturally extends the options made available through the existing enum:
using System;
string downloadsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDownloads);
string publicPath = Environment.GetFolderPath(Environment.SpecialFolder.Public);
string savedGamesPath = Environment.GetFolderPath(Environment.SpecialFolder.MySavedGames);Alternative Designs
- A completely new API available additionally to the existing
System.Environment.GetFolderPath(and possibly deprecating it) to make extending special folders more flexibly in the future was discussed at Add a more flexible special folder API #19047, but abandoned. - Alternatively, a new
enum KnownFoldercould be introduced if the existingSpecialFoldershould not be touched in any case (s. possible "Risks" below), and an overload could be provided forSystem.Environment.GetFolderPathaccepting those.
Risks
I rate the risks as Low:
- Code that enumerated over all values of the
SpecialFolderenum and depended on the undocumented fact that each underlying value is a WindowsCSIDLvalue now has to check whether the value is< 0x10000in case it wants to pass only valid values to the deprecated CSIDL Windows API. - Code that relied on the undocumented fact that the enum values could be stored in just 2 bytes now has to use a larger data type or ignore the new values with the
< 0x10000check.
The additions here should be merged after fixes to existing paths are done in #68610 to not clash with the changes.