Scaffold MVC Template Editing. How to get project reference in Solution Folder?

Asked

Viewed 254 times

3

I am editing the T4 templates from ASP.NET MVC’s Scaffold and I need to get some extra class information. At first, for example, of the attribute DisplayName class.

I found some examples:

var env = (DTE)((IServiceProvider)this.Host).GetService(typeof(EnvDTE.DTE));
var proj = env.Solution.Projects.OfType<Project>()
    .Where(p => p.Name == assemblyName)
    .FirstOrDefault();

var codeType = proj.CodeModel.CodeTypeFromFullName(classFullName);

var attr = codeType != null ? codeType.Attributes
    .OfType<EnvDTE.CodeAttribute>()
    .FirstOrDefault(x => x.Name == "DisplayName") : null;

var modelName = attr != null ? 
    attr.Value.Replace("\"", "") : 
    ViewDataTypeShortName;

So far, so good. But I have in my Solution two projects that are from another Solution (and are in different Solutions directories), but which were added normally via Add / Existing Project.

So these other two assemblies I can’t get reference using:

var proj = env.Solution.Projects.OfType<Project>()
    .Where(p => p.Name == assemblyName)
    .FirstOrDefault();

In fact, I did a re-interview on env.Solution.Projects.OfType<Project>() to see what would be printed:

<# foreach (var proj in env.Solution.Projects.OfType<Project>()) { #>
// <#= proj.Name #>
<# } #>

And I got something I didn’t understand. Among the Projects that were really created, like:

// Common
// Projeto.Domain
// Projeto.Service
// Projeto.WebMVC

Where that Common is a Solution Folder created for the projects of the other Solution. But the projects that are within it.

So, another way I found to perform a Reflection in the assemblies was to add them directly:

<#@ assembly name="C:\Projects\SolutionA\Projeto.Domain\bin\Debug\Projeto.Domain.dll" #>
<#@ include namespace="Projeto.Domain.Entities" #>
<#@ assembly name="C:\Projects\SolutionA\Projeto.Service\bin\Debug\Projeto.Service.dll" #>
<#@ include namespace="Projeto.Service.ViewModels" #>

This way the Reflection is already made in the traditional way known.

The problem with this type of addition is that the sources will not be in the same directories, which will result in assemblies not being in the specified directories.

So I tried with, for example $(SolutionDir), but it seems that in Scaffold templates it does not work.

My question is how to add the references logically or how to read the other assemblies via env.Solution.Projects.

For those who have had experience, another way to solve the problem is also welcome!

  • I don’t think it was a good idea for you to layer your project and try to use Scaffolding after. In the tests I do here, the only separation that is reasonable is to separate Models in a Class Library and Controllers and Views continue in the ASP.NET MVC project. Anyway, I can direct the response to the use of EnvDTE, if you want.

  • My Controllers Views are in the ASP.NET MVC project, only the ViewModels go to another layer. Yes, I would like you to give an example of solution if you have. Thank you!

2 answers

2


Just continue reading the other levels to locate the user classes you want to locate. The following code may be useful:

var proj = env.Solution.Projects.OfType<Project>()
    .Where(p => p.Name == assemblyName)
    .FirstOrDefault();

foreach (EnvDTE.CodeElement element in proj.CodeModel.CodeElements)
{
    if (element.Kind == EnvDTE.vsCMElement.vsCMElementClass)
    {
        var myClass = (EnvDTE.CodeClass)element;
        // MyClass será uma classe de usuário
    }
}

To iterate the class properties, this code can be interesting:

public IEnumerable<CodeProperty> GetProperties(CodeClass @class)
{
    if (@class == null) 
        return new List<CodeProperty>();

    var baseProperties = GetProperties(@class.Bases.Cast<CodeClass>().FirstOrDefault());

    return baseProperties.Concat(@class
        .Members
        .Cast<CodeElement>()
        .Where(ce => ce.Kind == vsCMElement.vsCMElementProperty)
        .Cast<CodeProperty>()
        .Where(p => p.Access == vsCMAccess.vsCMAccessPublic));
    }
}

I took it from here.

  • I just don’t know why, I’m not getting anything from codeClass.Attributes and not even with codeClass.GetType().GetProperties(). Have you seen it? Thank you!

  • @Jamestk I put one more method. See if it helps you.

1

Solution found in: Envdte : Getting all Projects (the Solutionfolder PITA)

Well, I created an additional file where I put my functions, called Morefunctions.cs.include.T4 and added in the template folders I want to use, for example in: Codetemplates Project Mvcview

At the end of the template files I add the following line:

<#@ include file="MoreFunctions.cs.include.t4" #>

Archive content, all tested on Visual Studio 2015 Community:

<#+
DTE2 GetServiceEnvironment() {      
    return (DTE2)((IServiceProvider)this.Host).GetService(typeof(EnvDTE.DTE));
}

Solution GetSolution() {
    return GetServiceEnvironment().Solution;
}

List<Project> GetProjects() {
    Projects projects = GetSolution().Projects;
    var projectList = new List<Project>();
    foreach (Project project in projects) {
        if (project.Kind == ProjectKinds.vsProjectKindSolutionFolder)
            projectList.AddRange(GetProjectsInSolutionFolder(project));
        else
            projectList.Add(project);
    }  
    return projectList;
}

List<Project> GetProjectsInSolutionFolder(Project solutionFolder) {
    var projectList = new List<Project>();
    for (var i = 1; i <= solutionFolder.ProjectItems.Count; i++) {
        var subProject = solutionFolder.ProjectItems.Item(i).SubProject;
        if (subProject == null)
            continue;
        if (subProject.Kind == ProjectKinds.vsProjectKindSolutionFolder)
            projectList.AddRange(GetProjectsInSolutionFolder(subProject));
        else
            projectList.Add(subProject);
    }
    return projectList;
}

Project GetProject(string projectName) {
    return GetProjects().OfType<Project>().Where(p => p.Name == projectName).FirstOrDefault();
}

Project GetThisProject() {
    return GetSolution().FindProjectItem(this.Host.TemplateFile).ContainingProject as EnvDTE.Project;
}

CodeType GetCodeType(string classFullName) {
    var projects = GetProjects();
    foreach (var project in projects) {
        if (classFullName.StartsWith(project.Name)) {
            CodeType codeType = project.CodeModel.CodeTypeFromFullName(classFullName);
            return codeType;
        }
    }
    return null;
}

CodeClass GetCodeClass(CodeElements elements, string className) {
    if (elements == null)
        return null;
    foreach (EnvDTE.CodeElement element in elements) {
        if (element.Kind == vsCMElement.vsCMElementClass) {
            var codeClass = (EnvDTE.CodeClass)element;
            if (codeClass != null && codeClass.FullName == className)
                return codeClass;
        }

        if (element.Kind == vsCMElement.vsCMElementNamespace) {
            var result = GetCodeClass(((CodeNamespace)element).Members, className);
            if (result != null)
                return result;
        }

        if (element.IsCodeType) {
            var result = GetCodeClass(((CodeType)element).Members, className);
            if (result != null)
                return result;
        }
    }
    return null;
}
#>

I needed to add the following references in Imports.include.T4:

<#@ assembly name="EnvDTE" #>
<#@ assembly name="EnvDTE80" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="EnvDTE80" #>

Finally, an example of use to get the attribute value DisplayName:

var codeType = GetCodeType(ViewDataTypeName);

var attr = codeType != null ? 
    codeType.Attributes.OfType<EnvDTE.CodeAttribute>()
        .FirstOrDefault(x => x.Name == "DisplayName") : null;

var modelDisplayName = attr != null ? 
    attr.Value.Replace("\"", "") : 
    ViewDataTypeShortName;

PREVIOUS ATTEMPT - IT DIDN’T WORK !!!

I implemented it as follows:

CodeClass GetCodeClass(CodeElements elements, string className) {
    if (elements == null)
        return null;
    foreach (EnvDTE.CodeElement element in elements) {
        if (element.Kind == vsCMElement.vsCMElementClass)
        {
            var codeClass = (EnvDTE.CodeClass)element;
            if (codeClass != null && codeClass.FullName == className)
                return codeClass;
        }

        if (element.Kind == vsCMElement.vsCMElementNamespace)
        {
            var result = GetCodeClass(((CodeNamespace)element).Members, className);
            if (result != null)
                return result;
        }

        if (element.IsCodeType)
        {
            var result = GetCodeClass(((CodeType)element).Members, className);
            if (result != null)
                return result;
        }
    }
    return null;
}

That I used to:

var project = GetProject();
var codeType = GetCodeClass(project.CodeModel.CodeElements, ViewDataTypeName);

var attr = codeType != null ?    
    codeType.Attributes.OfType<EnvDTE.CodeAttribute>()
        .FirstOrDefault(x => x.Name == "DisplayNameAttribute") : null;
modelName = attr != null ? 
    attr.Value.Replace("\"", "") : 
    ViewDataTypeShortName;

But unfortunately it does not return my attributes.

I even made a "Reflection", but did not print anything.

<#
    foreach (var prop in codeType.GetType().GetProperties()) {
#>
//      <#= prop.Name #>
<#
    }
#>

Nothing, and nothing either:

foreach (CodeAttribute attribute in codeType.Attributes) {
    #>
//  <#= attribute.Name #>
    <#
}

But the class name printed...

#>
// <#= codeType.Name #>

<#

You’ll understand..

Let’s go on the next journey!!

  • What you brought back here? foreach (CodeAttribute attribute in codeType.Attributes) {? Empty enumerator?

  • Didn’t print anything, didn’t check if it was null... I’ll see you now!

  • Not null, therefore.. empty enumerator!!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.