Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In item templates, is it possible to determine things like project name / namespace? #338

Closed
tintoy opened this issue Feb 16, 2017 · 22 comments
Labels
gathering-feedback The issue requires feedback in order to be planned, please comment if the feature is useful for you triaged The issue was evaluated by the triage team, placed on correct area, next action defined.
Milestone

Comments

@tintoy
Copy link
Contributor

tintoy commented Feb 16, 2017

Hi.

I've been wanting to build item templates for MVC controllers and views, but these require knowledge of things like the current project namespace (or, for nested folders, something even more complex, like joining sub-folders onto that as sub-namespaces).

I've had a browse through the code, but haven't found anything like that (apart from perhaps replacing the entire project name with the target project name, even in source code?) so before I go any further, I wanted to check if this is supposed to be a supported scenario.

Thanks,

Adam.

@mlorbetske
Copy link
Contributor

Hi @tintoy, these values can be accepted via parameters but cannot be figured out automatically at this time. As far as the usage of these names go (file names/namespaces/literal strings/etc), we're tracking the work to make that possible with #141. For collecting the name of the project in the target directory, this could be done with implementations of IMacro (discussed how to get started with that here).

Without these, in terms of how you'd handle this if the user provided the parent namespace, let's say you had the following layout on disk

.template.config\
   template.json
Classes\
   TheClass.cs

Let's also say that TheClass.cs looks like this

namespace Company.Application.Classes
{
    public class TheClass
    {
    }
}

If you want to use the parent project's namespace rather than Company.Application, you'd add the symbol that takes the namespace like so:

"symbols": {
    "ParentProjectNamspace": {
        "type": "parameter",
        "replaces": "Company.Application",
        "dataType": "string",
        "defaultValue": "Company.Application"
    }
}

With this, if the user did something like this dotnet new TheClassTemplate --ParentProjectNamespace Project1.Stuff -n Hello, the resulting file (at Classes\Hello.cs) will look like:

namespace Project1.Stuff.Classes
{
    public class Hello
    {
    }
}

@tintoy
Copy link
Contributor Author

tintoy commented Feb 22, 2017

Thanks, that's super useful! :)

I might start experimenting with this on one of my simpler templates for now (a binary PowerShell Core module), which the above should probably cover nicely. And I'll spend this weekend taking a closer look at the extensibility stuff from #286.

@mlorbetske mlorbetske added this to the Backlog milestone Apr 27, 2017
@mlorbetske
Copy link
Contributor

Moving this to the backlog as this may be something interesting to add as a "generated" type symbol that would know how to look at existing content to try to determine the namespace. With the new fallback symbols, the user would still be able to override the value.

@sayedihashimi
Copy link
Member

Yeah this will be interesting and important. Not sure how to solve this in a good way though.

@dansiegel
Copy link
Contributor

I'm not sure how set in stone the template.json schema is. It would seem to me, if you could add a parameter to the schema that would allow the template generator to know it needs to go look for a project file and grab the root namespace that would certainly help.

@mlorbetske
Copy link
Contributor

Properties can be added as they won't be understood by older versions. However, the approach mentioned doesn't require a schema modification (it fits nicely within the existing extensibility model); it's also the approach used for port generation & has evolution steps planned to pretty up the syntax.

Moreover, for .NET Core, the namespace isn't usually defined in the project file, the tooling makes some inferences about the project name & locations of the files relative to it to determine the target namespace. Even if that were reliably present, to keep item namespaces consistent with the convention being used in the user's existing artifacts, it should really be looking at the peer code files in the target location to make the determination as to what the target namespace is. This means that, whatever the component is that gets built, it'll need to understand the target language(s) for the template & be able to reason about the computed value (thinking of "flat namespace" projects which would normally conflict with item templates with opinionated namespacing for files generated into multiple directories).

@DamianEdwards
Copy link
Member

This is currently affecting the dotnet new page item template too (tripped up a bunch of folks in our workshop). Great that we can pass the namespace in, but very easy to forget 😄

@tintoy
Copy link
Contributor Author

tintoy commented Sep 5, 2017

Having done a bunch of work with the MSBuild engine recently, I think I'm fairly confident that I know how to evaluate / compute the project namespace for a given folder location. If you guys are interested, I could take a look at implementing a symbol generator to do this.

@tintoy
Copy link
Contributor Author

tintoy commented Sep 5, 2017

$(RootNamespace) always has some sort of value in managed projects (anything that imports Microsoft.Common.CurrentVersion.targets and friends), and the rules for inferring it otherwise are pretty simple.

Root namespace (in fallback order):

  1. $(RootNamespace)
  2. $(MSBuildProjectName)

Relative namespace:

  • In project directory: RootNamespace
  • In project subdirectory: RootNamespace + '.' + TargetDirRelativeToProject.Replace(PathSeparatorChar, '.')

@tintoy
Copy link
Contributor Author

tintoy commented Sep 5, 2017

(and it's not hard to use Microsoft.Build.Evaluation to inspect the effective value of the RootNamespace property in a project)

@tintoy
Copy link
Contributor Author

tintoy commented Sep 5, 2017

to keep item namespaces consistent with the convention being used in the user's existing artifacts, it should really be looking at the peer code files in the target location to make the determination as to what the target namespace is

That'd be nice but, since Visual Studio doesn't do this either, not having it still keeps this at parity with the in-GUI experience :)

@tintoy
Copy link
Contributor Author

tintoy commented Sep 5, 2017

Ok, so here's a quick proof-of-concept:

using Microsoft.Build.Evaluation;
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;

namespace FileNSDemo
{
    static class Program
    {
        static void Main(string[] args)
        {
            try
            {
                FileInfo targetFile = new FileInfo(args[0]);
                Project owningProject = GetOwningProject(targetFile);
                if (owningProject == null)
                    return;

                string fileNamespace = owningProject.GetPropertyValue("RootNamespace");
                if (String.IsNullOrWhiteSpace(fileNamespace))
                    fileNamespace = Path.GetFileNameWithoutExtension(owningProject.FullPath);

                string relativeDirectory = Path.GetRelativePath(owningProject.DirectoryPath, targetFile.Directory.FullName);
                if (relativeDirectory != ".")
                    fileNamespace += "." + relativeDirectory.Replace(Path.DirectorySeparatorChar, '.');

                Console.WriteLine("File '{0}' is owned by project '{1}' (namespace would be '{2}').",
                    targetFile.FullName,
                    owningProject.FullPath,
                    fileNamespace
                );                
            }
            catch (Exception unexpectedError)
            {
                Console.WriteLine(unexpectedError);
            }
        }

        static Project GetOwningProject(FileInfo file)
        {
            DirectoryInfo directory = file.Directory;
            if (!directory.Exists)
            {
                Console.WriteLine("Directory '{0}' does not exist.", directory.FullName);

                return null;
            }

            while (directory != null)
            {
                FileInfo projectFile = directory.EnumerateFiles("*.*proj").FirstOrDefault();
                if (projectFile != null)
                {
                    string fileRelativePath = file.FullName.Substring(directory.FullName.Length + 1);
                    using (ProjectCollection projectCollection = MSBuildHelper.CreateProjectCollection(directory.FullName))
                    {
                        Project project = projectCollection.LoadProject(projectFile.FullName);
                        ICollection<ProjectItem> matchingFileItems = project.GetItemsByEvaluatedInclude(fileRelativePath);
                        if (matchingFileItems.Count > 0)
                            return project;
                    }
                }

                directory = directory.Parent;
            }

            return null;
        }
    }
}

(fully functional example here)

@mlorbetske
Copy link
Contributor

Thanks for the PoC @tintoy, I'm hesitant to take a dependency on MSBuild here though due to coordination concerns with all the different release vehicles for this project.

@tintoy
Copy link
Contributor Author

tintoy commented Sep 6, 2017

Yeah, I think I see what you're getting at :)

Then again, given that dotnet new generates projects files that MSBuild has to be able to parse, isn't it already dependent on a version (or version range) of MSBuild?

@tintoy
Copy link
Contributor Author

tintoy commented Sep 6, 2017

Actually, I think I now get where you're coming from - the issue is distributing the MSBuild engine alongside dotnet new, correct?

@donJoseLuis
Copy link
Contributor

This issue was last touched some years ago. We are working on a new delivery road map. Please reopen if this is something we want & we'll properly assess its' priority compared to other work aimed at improving the overall templating UX.

@donJoseLuis donJoseLuis removed this from the Backlog milestone Mar 19, 2020
@chrisfcarroll
Copy link

chrisfcarroll commented Jul 10, 2020

Yes, I'd like this re-opened, please!

Please can I have something that can evaluate anything MSBuild can evaluate?

The dotnet new template approach of letting me use compileable code as a template is a brilliant improvement on old-style templates. But this poses challenges for item templates in particular.

"symbols": {
    "anythingMSBuildCanDoICanDoToo": {
        "type": "computed-but-can-be-overriden-by-a-parameter",
        "dataType": "string",
        "replaces": "NamespaceForNewItem",
        "value": "string.Join('.' ,  $RootNamespace, $DottedRelativePathToProjectDir,  $sourceName)"
    }
}

Without some kind of evaluator able to do string manipulation and/or variables and/or everything that an msbuild expression can do, item templates for code are semi-automated-semi-manual needs editing after running the template?

@vlada-shubina vlada-shubina reopened this Jul 5, 2021
@vlada-shubina
Copy link
Member

Reviving this issue, imo it makes a lot of sense. @KathleenDollard could you please take a look?

@vlada-shubina vlada-shubina added the need-pm-discussion Need agreement from PM that the issue aligns to targeted stories for any of the next 2 releases label Jul 5, 2021
@vlada-shubina vlada-shubina added the gathering-feedback The issue requires feedback in order to be planned, please comment if the feature is useful for you label Jul 15, 2021
@bekir-ozturk bekir-ozturk added the triaged The issue was evaluated by the triage team, placed on correct area, next action defined. label Aug 13, 2021
@bekir-ozturk bekir-ozturk added the Epic Groups multiple user stories. Can be grouped under a theme. label Sep 1, 2021
@vlada-shubina vlada-shubina removed the Epic Groups multiple user stories. Can be grouped under a theme. label Sep 1, 2021
@vlada-shubina vlada-shubina removed the need-pm-discussion Need agreement from PM that the issue aligns to targeted stories for any of the next 2 releases label Dec 6, 2021
@egvijayanand
Copy link
Contributor

This is absolutely needed as I'm creating and distributing item templates for .NET MAUI, in which C# classes (both code-behind for XAML style definition and Direct C# style definition) require the C# class type a namespace.

At present, managing it as user input and that's not the ideal way to do it as the chances of making an error is high.

Unable to implicitly look up the value, my suggestion would be to expose the RootNamespace as a binding symbol quite like HostIdentifier.

That would ease out the job. Also, other MSBuild properties can also be exposed as binding symbols.

Notify to @sayedihashimi

Regards,
Vijay Anand E G

@sayedihashimi
Copy link
Member

We talked about this recently, I believe that the associated work that is needed is described at #3107.

@vlada-shubina
Copy link
Member

This will be implemented in #3829

@vlada-shubina
Copy link
Member

Closed in #3829

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
gathering-feedback The issue requires feedback in order to be planned, please comment if the feature is useful for you triaged The issue was evaluated by the triage team, placed on correct area, next action defined.
Projects
None yet
Development

No branches or pull requests