project-system icon indicating copy to clipboard operation
project-system copied to clipboard

Change default namespace associated to a folder when creating new class

Open vsfeedback opened this issue 6 years ago • 43 comments

This issue has a corresponding ticket on Developer Community. Please vote and comment there to make sure your voice is heard.


It would be nice if there is a feature to set a default namespace for each folder when creating a new class. For example, if we have the following file structure:

MyProject
     MyFolder
          MyClass1

Then if we create MyClass2 under MyFolder, the created class will be under the MyProject namespace instead of MyProject.MyFolder.

Additional references: https://stackoverflow.com/questions/1317901/change-default-namespace-when-creating-class-in-folder-visual-studio

vsfeedback avatar Aug 26 '19 17:08 vsfeedback

To paraphrase, the request is to be able to mark a folder with metadata to stop it from participating in namespace construction.

drewnoakes avatar Aug 26 '19 23:08 drewnoakes

Yep interesting and reasonable request. We don't have the ability to associate metadata with a folder in CPS - other than empty folders, they don't have a item representation, so we'd need to introduce that.

davkean avatar Aug 27 '19 01:08 davkean

@davkean You might want to look at this feedback post I created. Admittedly, this was before I realized that ReSharper was doing part of this... https://developercommunity.visualstudio.com/content/problem/621033/adding-a-class-does-not-honor-namespace-provider-s.html

So somehow ReSharper is keeping meta data on the folder to determine if the folder name should be part of the namespace. If I add a new class via ReSharper (which I never do) it's honored. If I use VS to add a class, it's not honored (not surprisingly in hindsight).

Here is a small screenshot of a folder being selected and ReSharper's added property "Namespace Provider"

Folder Properties

Now, I am not expecting you to code to ReSharper's standard, but when you start working on this you may let them know so they can take it into account in their code. I would hate to see double folder properties and have to set them both not knowing which one is which.

JimF42 avatar Oct 04 '19 14:10 JimF42

ReSharper stores this data in their own resource file, outside of the project file. We would want to store it in the project file directly.

For example:

<ItemGroup>
  <Folder Include="Models\" IsNamespaceProvider="False" />
</ItemGroup>

Currently this Folder item is removed as soon as a class is added to that folder. We'd need to ensure Folder items with custom metadata are not removed in such cases.

drewnoakes avatar Oct 06 '19 23:10 drewnoakes

This is useful when we want to add all of our class extensions in an Extensions folder but want them all to be in the main namespace.

I think the best option would be to allow us to bring up the Properties window and edit a DefaultNameSpace property on the folder.

mrpmorris avatar Jan 01 '20 19:01 mrpmorris

@jinujoseph Are you tracking anything similar on your side?

davkean avatar Jan 17 '20 00:01 davkean

This is useful when we want to add all of our class extensions in an Extensions folder but want them all to be in the main namespace.

Agree, this is quite common case when some folder need to be in parent namespace. My thought:

  1. Create Virtual Folder in project (like solution virtual folder) with unique icon.
  2. Or ignore folder with special prefix like maybe @Extensions or _Extensions. both way are very clear to indicate all items in the folder are using parent namespace, which I think is better then ReSharper way.

Codentale avatar Jun 15 '20 08:06 Codentale

Any news on this? Is it being considered for a future update?

thomaslevesque avatar Oct 01 '20 21:10 thomaslevesque

This would be really useful!

Adding metadata to any C# Project folder to override default folder-structure based namespace with:

  • default namespace: (empty) would follow default functionality
  • absolute namespace: /Parent/Child allows full namespace override
  • relative namespace: Child or ./Child or ../../Child that would resolve based on parent Namespace (which obviously can be overridden itself).
  • no namespace: - this would use parent namespace without child participating in the name construction

And any file added to that folder will have the namespace based on the override when file is created. And this should only apply to new files. If you change the folder's virtual namespace, there should be no sync for existing files with the old namespace. That's the developer's job to rename.

This would really help because often, you find yourself wanting to structure (1 or multiple folders deep) files but need them all in the same namespace. It's quite a headache.

CodeAngry avatar Oct 15 '20 11:10 CodeAngry

In our case we have a project where we structure functionality under a seperate folder. Like everything for Customer is being filed under the Customer folder. But since there's also a DTO called Customer in another project we use the CustomerModule namespace for the classes in the Customer folder.

Now each time you add a new class it's being added in the Customer namespace. It would be handy if you could have a folderconfig or maybe projectconfig file (one in the entire project) where you could specify the options for class files for a specific folder/project

PieterjanDeClippel avatar Oct 15 '20 16:10 PieterjanDeClippel

In addition to specifying that a folder does not participate in namespace construction, I can see it being useful to also specify the full namespace for a given folder, such that it ignores any project-level namespace and ancestor folder names.

drewnoakes avatar Oct 15 '20 22:10 drewnoakes

This is not flexible

<ItemGroup>
  <Folder Include="Models\" IsNamespaceProvider="False" />
</ItemGroup>

Should be

<ItemGroup>
  <Folder Include="Models\" Namespace="whatever I want or empty" />
</ItemGroup>

MhAllan avatar Nov 17 '20 13:11 MhAllan

@MhAllan both have merits. Specifying the full namespace on folders is harder to maintain when moving folders or renaming ancestors.

drewnoakes avatar Nov 18 '20 00:11 drewnoakes

It should be, If I set a Namespace, that won't change by changing the hierarchy or renaming ancestors.

MhAllan avatar Nov 22 '20 02:11 MhAllan

@drewnoakes that's the whole point of the request, to break the link between namespace and folder location.

mrpmorris avatar Nov 22 '20 10:11 mrpmorris

@drewnoakes that's the whole point of the request, to break the link between namespace and folder location.

Indeed. But there are multiple potential implementations, each with strengths and weaknesses.

Edit: I can see why my comment could be confusing out of context. I was referring to the idea of specifying the full path on each folder, rather than just opting a particular folder out of namespace construction.

drewnoakes avatar Nov 22 '20 10:11 drewnoakes

I like these Examples:

1.Dynamic with folder name:

<ItemGroup>
  <Folder Include="Models\" Namespace="Example.Sale.Core.[FolderName]" />
</ItemGroup>

2.Static namespace:

<ItemGroup>
  <Folder Include="Models\" Namespace="Example.Sale.Core.Models" />
</ItemGroup>

3.Default from project namespace:

<ItemGroup>
  <Folder Include="Models\"/>
</ItemGroup>

Ali-YousefiTelori avatar Dec 10 '20 06:12 Ali-YousefiTelori

I think * would be better than [FolderName]

mrpmorris avatar Dec 10 '20 08:12 mrpmorris

Hi,

Any idea on when this will be implemented?

wouterroos avatar Jul 19 '21 11:07 wouterroos

what do you think about basing the new namespace off whatever the majority of existing files have in that folder? that would avoid the need to store metadata with the folder and is completely automatic.

danielchalmers avatar Aug 25 '21 10:08 danielchalmers

IMO, the flexibility of the Namespace="..." option is more advantageous than the simplicity of the IsNamespaceProvider="false" option.

Handling folder moves, changes, etc would be fairly easy to maintain for such special cases if variables or template placeholders can be provided to refer to the folder name/relative folder path.

<ItemGroup>
    <!-- These use the root/default namespace despite being in folders -->
    <Folder Include="Common\" Namespace="$(DefaultNamespace)" />
    <Folder Include="Root\" Namespace="$(DefaultNamespace)" />
    
    <!-- These use a special namespace prefix + the folder names -->
    <Folder Include="Special\" Namespace="Some.Special.Namespace.$(FolderNameVarOrTemplateId)" />
</ItemGroup>

Another nice to have would be a way to define/overwrite the namespace pattern for new files at a higher level, like a project wide setting. Something like this maybe?

<PropertyGroup>
    <!-- Default behavior of my.namespace.folder.names -->
    <NamespaceGeneration Pattern="$(DefaultNamespace).$(RelativeFolderPath)" NamespacePathSeparator="." />
 </PropertyGroup>
 
 <PropertyGroup>
     <!-- In this project always use the root/default namespace regardless of folder path
     <NamspaceGeneration Pattern="$(DefaultNamespace)" />
 </PropertyGroup>
 
 <PropertyGroup>
    <!-- Default behavior except in lieu of my.namespace.some._folder it would be my.namespace.some.folder -->
     <NamespaceGeneration>
         <Pattern>$(DefaultNamespace).$(RelativeFolderPath.Replace("_", "")</Pattern>
     </NamespaceGeneration>
 </PropertyGroup>

RyanThomas73 avatar Sep 17 '21 23:09 RyanThomas73

Could this also be done with editorconfig? Would love to know thoughts on that. Here's my immediate thinking on for/against

Pros:

  • editorconfig already supports hierarchy in file system, and could even do file globbing to apply namespace
  • Could easily be plugged in on the Roslyn side (each document would be able to lookup property associated with it and just work)
  • Puts setting close to files if it differs from defined root

Cons:

  • Could be confusing since root/default namespace are project level settings
  • How would variables work? Could it be something like $(DefaultNamespace).Services?
    • Might be able to provide multiple settings like dotnet_namespace_prefix, dotnet_namespace, dotnet_namespace_affix with special values for current_folder_name etc.

ryzngard avatar Oct 06 '21 05:10 ryzngard

Editorconfig is a bad idea, because:

  1. Editorconfig is designed for code style rules, not code itself
  2. Final solution must control naming fules for every folder in one project - for editorconfig you need place new editorconfig file in every folder.

AlexeiScherbakov avatar Oct 06 '21 08:10 AlexeiScherbakov

Editorconfig is designed for code style rules, not code itself

Yes. Namespace enforcement is a codestyle in csharp. I'll get into the details a bit below, but the only place RootNamespace has any meaning for a compiler (in dotnet) is in Visual Basic (maybe F#? not sure). Setting a property in csproj for namespaces will not change the symbol information for the project, nor will it break the compilation without extra tooling (IDE or Analyzers in this case). The output dll will always be the same regardless of what you set that property to.

Long Explanation

I'm assuming we're talking about Default Namespace and not Root Namespace. Just work with me on this definition, because I know it contradicts the naming convention in csproj

  • Default Namespace is a namespace that exists as the default. It has no impact beyond specifying what namespace a file is when created. Arguably could be the default if no namespace is specified, but I didn't check that yet (it may actually be)
  • Root Namespace is a namespace that precedes all namespaces.

A good example is the following code in VB:

Imports System

Namespace MyNamespace

    Module Program
        Sub Main(args As String())
            Console.WriteLine(GetType(Program).FullName)
        End Sub
    End Module
End Namespace

with the following vbproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <RootNamespace>TestVB.Changed</RootNamespace>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

</Project>

The output of this is TestVB.Changed.MyNamespace.Program.

A similar C# example looks different:

namespace Test
{
    class Program 
    {
        public static void Main(string[] args)
        {
            Console.WriteLine(typeof(Program).FullName);
        }
    }
}

with csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <RootNamespace>MyNamespace</RootNamespace>
  </PropertyGroup>

</Project>

The output is Test.Program. For C#, root namespace is purely a coding style convention and not impactful on the compilation of the code. It's not a root at all, it's just a hint to developer tools to "do the right thing".

Even in the original ask it was pointed out that this would impact new file creation:

Then if we create MyClass2 under MyFolder, the created class will be under the MyProject namespace instead of MyProject.MyFolder.

Code style is applied on new file creation. That's how we get the default namespace to start with. it also impacts things like file header. Even dotnet new doesn't have a single csharp class template, which is probably good considering that it would likely differ from the behavior of whatever IDE is being used.

Enforcement of namespace styling already exists for csharp in editorconfig with dotnet_style_namespace_match_folder , which uses a codestyle analyzer to indicate if a namespace is "correct". For better or worse, csharp has no real concept of a compile time "root" for namespaces beyond "global::". It's unfortunate the csproj property is called RootNamespace. In project property pages we even reword to Default Namespace

ryzngard avatar Oct 06 '21 09:10 ryzngard

Example: You have namespace ViewModel, and folders inside it: Train,Truck,Plane, Utils. Classes inside Train,Truck,Plane folders must be in *.ViewModel namespace. Classes inside Utils must be in *.ViewModel.Utils namepspace. How can this be achived with editorconfig?

AlexeiScherbakov avatar Oct 06 '21 09:10 AlexeiScherbakov

Classes inside Train,Truck,Plane folders must be in *.ViewModel namespace. Classes inside Utils must be in *.ViewModel.Utils namepspace. How can this be achived with editorconfig?

It can't currently, which is why I bring this up. But also it can't be achieved with the current proposal afaik. Current proposal only addresses new files being created. There's no enforcement in csharp as a language that namespaces must be something specific. There's two parts to this:

  • dotnet_style_namespace_match_folder : this enforces namespace names based on folder structure. This is still under that purview, even if it doesn't let you customize the expectation currently. This results in IDE0130 diagnostic, which can be set to info/warning/error as desired.
  • If we added another setting in editorconfig such as dotnet_root_namespace and dotnet_namespace_suffix (naming is hard late at night) then the scenario would be as follows.

Root .editorconfig

dotnet_style_namespace_match_folder = true
dotnet_root_namespace = "MyRoot"

Root/{Train, Truck, Plane} Folder .editorconfig

[Utils/*.cs]
dotnet_namespace_suffix = "ViewModel.Utils"

[*.cs]
dotnet_namespace_suffix = "ViewModel"

  • assume my glob pattern is correct. I didn't look up editorconfig globs and it's late here.

I will also concede that this could be done with proposal + analyzer, if we update the analyzer to use the project properties proposed. I'm just not sure it's the best approach and think being explicit about namespace requirements as a style belongs in editorconfig. The downside of current proposal is that it's nontrivial for an analyzer to determine what the correct namespace is. Analyzers are how we would enforce this, so it should be considered for the proposal.

ryzngard avatar Oct 06 '21 09:10 ryzngard

The design here should also factor in how we tool this from within Visual Studio. For folder level constructs, it would feel natural to surface this preference in the Properties pane when the folder in question is focussed in Solution Explorer. The default place to store metadata on folders would be on a <Folder> item in the project file. The Project System could pass these items to Roslyn.

drewnoakes avatar Oct 06 '21 12:10 drewnoakes

Maybe it would it would best just to have virtual folders. I also think adding the ability to just nest files would be great.

TwoPintOhh avatar Dec 24 '21 04:12 TwoPintOhh

Maybe it would it would best just to have virtual folders.

I'm also starting to think that this might be the cleaner approach here: have a different type of structure (virtual folder instead of "normal folder") that could also be potentially shown using a different graphic in the solution, and that would naturally not contribute to namespace segments.

The only caveat that I'm not sure how to solve, would be to allow this while not bloating the csproj files with those folders... Ideally, there should be a way to configure the logic used to automatically include files and folders to also include these "virtual folders" based on some criteria that the user can manipulate.

My use case is as follows (cropped sample): image

I use the folders inside "Operations" as an organization tool alone, and I don't want those folders to exist in the namespaces of the classes inside. This structure has as many subfolders as there are "operations" in the system, which are modeled using command handlers.

It would be nice if there was a way to configure that "I want operation subfolders to be virtual" and then every time I add a folder in there, it is automatically set to virtual without further csproj modifications.

The proposal here, of adding metadata to a Folder element to either set a specific namespace or to not participate in namespaces, would solve my problem but would still require a high maintenance level since every new operation would require an extra change in the csproj.

Another way to support my scenario would be to have an option to "stop participating in namespaces from here", which would extend the "don't participate in namespaces" logic to all subfolders. That might be a little too specific of an option however.

julealgon avatar Mar 23 '22 21:03 julealgon

The proposal here, of adding metadata to a Folder element to either set a specific namespace or to not participate in namespaces, would solve my problem but would still require a high maintenance level since every new operation would require an extra change in the csproj

Maybe something like this could avoid that maintenance:

<Folder Update="Operations/**/" ContributesToNamespace="False" />

We'd need to verify whether MSBuild's globbing can match folders, or only files.

drewnoakes avatar Mar 23 '22 21:03 drewnoakes