Split code generated in different folders

Feb 5, 2009 at 4:06 PM
Hi, I need to redirect the source code files generated from the model (using artifact links) in specific folders, actually it seems that the output code is forced to stay under the path specified in the ProjectPath property of the PopulateProjectMappingTableAction. I tried to create an instance of the artifact link using the project unique name and item path (in place of the project mapping table) in order to force the output code destination path but it seems not work (it doesn't find the project).  I appreciate any quick advice that you might give.
Kind Regards

Developer
Feb 6, 2009 at 11:14 AM
It is not trivial to change the project output from the source code. I would suggest to stick to the mappting table and use the format specied in there.
However if you want to try, make sure you are using the "<ProjectGuid>" element value located in the proj file as the project unique id, and make sure that when you dervice the ArtifactLink class from your own artifact link, you override Path and Container (Project ID) properties and ignore the set property so you values won't be overwritten by the ArtifactLinkFactory class.
Feb 7, 2009 at 11:42 AM
I formulate better my question because my problem is a little bit more complicated.

1) First of all i need to know how to generate from a single model element more output files. Somewhere in this forum i read "Using the 1:M relationship between model elements and artifact links you should be able to generate M objects". So to the same model element i associate more artifact links (that have 1:1 relationship with a tt template) as follows:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.Modeling.CodeGeneration.Artifacts;

namespace MyCompany.MyDslModel
{
    public partial class MyModelElement : IArtifactLinkContainer
    {
        #region IArtifactLinkContainer Members

        public ICollection<IArtifactLink> ArtifactLinks
        {
            get
            {
                System.Collections.ObjectModel.Collection<IArtifactLink> links = new System.Collections.ObjectModel.Collection<IArtifactLink>();
                links.Add(ArtifactLink1);
                links.Add(ArtifactLink2);
                


                return links;
            }
        }

        #endregion

        [CLSCompliant(false)]
        public MyModelElementLink1  ArtifactLink1
        {
            get
            {
                return ArtifactLinkFactory.CreateInstance<MyModelElementLink1>(this, this.ModelRoot.ProjectMappingTable);
            }
        }

        [CLSCompliant(false)]
        public MyModelElementLink2 ArtifactLink2
        {
            get
            {
                return ArtifactLinkFactory.CreateInstance<MyModelElementLink2>(this, this.ModelRoot.ProjectMappingTable);
            }
        }
        



    }
}

When i launch generate code recipe from  "MyModelElement" on diagram it seems to execute only the first artifact link (I have only one output file), maybe is this not the right way?


2) After solving the point 1) i need to put the output files generated from "MyModelElement" in different folders under the same project. The project mapping is solved by roles but it seems not trivial to solve the mapping between the an artifactlink/tt and a specific output folder.



Developer
Feb 7, 2009 at 10:36 PM
Ok let's go first with #1.
Take a look at project "Microsoft.Practices.ServiceFactory.Extenders.ServiceContract.Wcf" and open "WCFXsdMessageContract". There you will find two artifact links that not only generate two files but also with different code gen strategies.

There is only one more thing to know about how to get the two links executed and is to bind them with the model element you want. You can also check in the designer for WCF "ServiceContractDsl" project in DslDefinition.dsl file and look for XsdMessage element. There you will have a custom attribute that points to the MessageContractRole so the link for that role will execute (XsdMessageContractLink).

Now in the Element property of that mel (XsdMessage) you will also find another role attribute that points to the DataContractRole so there will go the second link and second file generated (XsdMessageContractElementLink).

Regarding #2, since you want both files on the same project, therefore the same role, you will point the two artifacts and roles to the same role but in this case you will need to follow my first post regarding how to force the path and container overrides in your custom artifact link that derives form ArtifactLink class. That way you should drive the folder/file destination.
Feb 9, 2009 at 8:20 AM
Thank you very much for your suggestions, have been very useful. I found a more pragmatic solution without make modifications to my dsl model. I simply added a new code generation strategy that is equal to the TextTemplateGenerationStrategy except the last istruction of the "CodeGenerationResults Generate(IArtifactLink link)" in which the itemPath is provided by the Data dictionary of IArtifactLink:

[CLSCompliant(false)]
        public CodeGenerationResults Generate(IArtifactLink link)
        {
            CodeGenerationResults result = new CodeGenerationResults();

            if (link is IModelReference)
            {
                this.serviceProvider = Utility.GetData<IServiceProvider>(link);
                ProjectNode project = Utility.GetData<ProjectNode>(link);

                ModelElement modelElement = ((IModelReference)link).ModelElement;
                TextTemplateArtifactLinkWrapper textTemplateArtifactLink = new TextTemplateArtifactLinkWrapper(link);
                textTemplateArtifactLink.ResourceResolver = ResourceResolver;
                string template = GetTemplateBasedOnProject(textTemplateArtifactLink, project);

                if (modelElement != null && !string.IsNullOrEmpty(template))
                {
                    Microsoft.VisualStudio.TextTemplating.Engine engine = new Microsoft.VisualStudio.TextTemplating.Engine();
                    DomainModel model = (DomainModel)modelElement.Store.GetDomainModel(
                        modelElement.GetDomainClass().DomainModel.Id);
                    TextTemplateHost host = new TextTemplateHost(model,
                        modelElement,
                        modelElement,
                        GetService<IDslIntegrationService>(),
                        GetService<ICodeGenerationService>());

                    host.ResourceResolver = textTemplateArtifactLink.ResourceResolver;

                    string content = engine.ProcessTemplate(template, host);

                    if (host.GenerateOutput)
                    {
                        this.projectReferences = new List<Guid>(host.ProjectReferences);
                        this.assemblyReferences = GetAssemblyReferences(link);

                        if (host.CompilerErrors.Count > 0)
                        {
                            foreach (CompilerError error in host.CompilerErrors)
                            {
                                LogError(error);
                            }
                        }
                        result.Add(link.Data["ItemPath"].ToString(), content);
                    }
                }
            }

            return result;
        }


The entry Dictionary "ItemPath" contains the output file path and is setted after the ArtifactLink instance creation:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.Modeling.CodeGeneration.Artifacts;

namespace MyCompany.MyDslModel
{
    public partial class MyModelElement : IArtifactLinkContainer
    {
        #region IArtifactLinkContainer Members

        public ICollection<IArtifactLink> ArtifactLinks
        {
            get
            {
                System.Collections.ObjectModel.Collection<IArtifactLink> links = new System.Collections.ObjectModel.Collection<IArtifactLink>();
                links.Add(ArtifactLink1);
                links.Add(ArtifactLink2);
                


                return links;
            }
        }

        #endregion

        [CLSCompliant(false)]
        public MyModelElementLink1  ArtifactLink1
        {
            get
            {
                MyModelElementLink1 link = ArtifactLinkFactory.CreateInstance<MyModelElementLink1>(this, this.ModelRoot.ProjectMappingTable);
                link.Data.Add("ItemPath", "myFolder1\\myfilename1.cs");
                return link;
            }
        }

        [CLSCompliant(false)]
        public MyModelElementLink2 ArtifactLink2
        {
            get
            {
                MyModelElementLink2 link = ArtifactLinkFactory.CreateInstance<MyModelElementLink2>(this, this.ModelRoot.ProjectMappingTable);
                link.Data.Add("ItemPath", "myFolder2\\myfilename2.cs");
                return link;
            }
        }
        



    }
}

In this way I solved simultaneously issues #1#2 because I'm able from the same model element to generate two different files with different path .

Aug 19, 2010 at 2:47 PM

I've been facing a similar issue: our development team needed to add a new project to the generated solution structure and needed to generate new classes (with new T4 templates) under its path.

To solve this I've taken a slightly different (and perhaps more flexible) approach. To sum up, I've added the possibility of "redefining" the ProjectMappingRole that is assigned to the model element with one defined in my custom class derived from ArtifactLink. To make this work it was also necessary to modify the ArtifactLinkFactory class.

1) Create another role pointing to the new project.

Many things done here like adding the new custom role (ServiceDistributorRole) to ProjectMapping.xsd, ServiceFactoryRoleType.cs, create new project template, etc.

2) Decorate our custom artifact link with ProjectMappingRoleAttribute, pointing to the new role;

    [CLSCompliant(false)]
    [TextTemplate("Lib\\TextTemplates\\WCF\\CS\\ServiceDistributor.tt", TextTemplateTargetLanguage.CSharp)]
    [AssemblyReference("System.Runtime.Serialization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
    [AssemblyReference("System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
    [TypeConverter(typeof(ArtifactLinkConverter<ServiceDistributorLink>))]
    [CodeGenerationStrategy(typeof(TextTemplateCodeGenerationStrategy))]
    [Microsoft.Practices.Modeling.CodeGeneration.Metadata.ProjectMappingRole(Microsoft.Practices.ServiceFactory.RecipeFramework.Extensions.Enums.ServiceFactoryRoleType.ServiceDistributorRole)]
    public sealed class ServiceDistributorLink : ArtifactLink
    {
        // Some code ...
    }

3) Modifty ArtifactLinkFactory class to manage this new possibility, that is, first check if ProjectMappingRoleAttribute has been used at link level and if not, take it from the model element as usual.

3.a) Create a new signature for GetContainer that handles both ArtifactLink derived and ModelElement derived types (in fact ICustomAttributeProviders)

        //  XS-BEGIN: try to get container from ArtifactLink before getting from attributeProvider (model element)
        //      For that purpose overload GetContainer()
        private static Guid GetContainer(string mappingTable, ICustomAttributeProvider artifactLinkAttributeProvider, ICustomAttributeProvider modelElementAttributeProvider)
        {
            ProjectMappingRoleAttribute roleAttrib =
                ReflectionHelper.GetAttribute<ProjectMappingRoleAttribute>(artifactLinkAttributeProvider, true);

            if (roleAttrib == default(ProjectMappingRoleAttribute))
            {
                roleAttrib = ReflectionHelper.GetAttribute<ProjectMappingRoleAttribute>(modelElementAttributeProvider, true);
            }

            ServiceFactoryRoleType roleType = roleAttrib.RoleType;

            List<Role> roles = new List<Role>();
            roles.Add(new Role(roleType.ToString()));

            ReadOnlyCollection<Guid> projectGuids = ProjectMappingManager.Instance.GetProjectsInRoles(
                mappingTable,
                roles);

            if (projectGuids.Count == 0)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture,
                    Properties.Resources.ZeroProjectWithRole, roleType.ToString()));
            }
            else if (projectGuids.Count > 1)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture,
                    Properties.Resources.MoreThanOneProjectWithSameRole, roleType.ToString()));
            }

            return projectGuids[0];
        }
        //  XS-END: try to get container from ArtifactLink before getting from attributeProvider (model element)

 

3.b) Modify the CreateInstanceMethod to handle the fact that ArtifactLink could have this attribute defined.

  /// <summary>
  /// Creates an instance
  /// </summary>
  /// <param name="artifactLinkType"></param>
  /// <param name="modelElement"></param>
  /// <param name="mappingTable"></param>
  /// <returns></returns>
  public static ArtifactLink CreateInstance(Type artifactLinkType, ModelElement modelElement, string mappingTable, ICustomAttributeProvider attributeProvider)
  {
   Guard.ArgumentNotNull(artifactLinkType, "artifactLinkType");
   Guard.ArgumentNotNull(modelElement, "modelElement");
   Guard.ArgumentNotNull(attributeProvider, "attributeProvider");

            ArtifactLink link = null;
            Tuple<Type, ModelElement, string, string> key = new Tuple<Type, ModelElement, string, string>(artifactLinkType, modelElement, GetName(modelElement), mappingTable);
            if (cachedArtifactLinks.TryGetValue(key, out link))
            {
                return link;
            }
          
            link = CreateLink(artifactLinkType, modelElement);

   if (!String.IsNullOrEmpty(mappingTable))
   {
                try
                {
                    //  XS-BEGIN: allow redefinition of ProjectMappingRoleAttribute in ArtifactLink or derived type
                    link.Container = GetContainer(mappingTable, artifactLinkType, attributeProvider);
                    //  XS-END: allow redefinition of ProjectMappingRoleAttribute in ArtifactLink or derived type

                    //  ORIGINAL CODE:
                    //link.Container = GetContainer(mappingTable, attributeProvider);

                    link.Path = GetProjectPath(mappingTable, link.Container);
                    link.Project = GetProject(modelElement, link.Container);
                }
                catch (Exception e)
                {
                    Logger.Write(e);
                }
   }

            cachedArtifactLinks.TryAdd(key, link);

   return link;
  }

 Regards,

Developer
Aug 30, 2010 at 1:29 PM

Stay tunned for this week updates on 2010 refresh release and some of the memory fixes in particular with "public static ArtifactLink CreateInstance()" function updates.

Regards,

Hernan