-
Notifications
You must be signed in to change notification settings - Fork 0
Writing a Custom Aspect in Code
Writing your aspects in Visual Studio (usually with C#, but this is not an obligation) is very interesting since you can benefit from Visual Studio's autocompletion and you can debug it at run time. However, compared to writing an aspect in XML, you're less impervious to change since you're not referencing a specific version of the CodeModeler API, plus the code is less accessible to other developers using your aspect (unless you make it part of the whole solution).
For instance, say we developed a custom aspect named Sample.Tracking. This aspect will be a class in a standard .NET class library project. In fact, this class library project can be part of the same Visual Studio solution than the CodeModeler project. The assembly must reference at least CodeModeler.Runtime.dll, CodeModeler.Model.dll, and CodeModeler.Producers.dll assemblies (add NuGet packages SoftFluent.CodeModeler.Runtime and SoftFluent.CodeModeler.Model)
Here is the C# code for the TrackingAspect.cs file:
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using CodeModeler.Model;
using CodeModeler.Runtime.Utilities;
namespace Sample.Tracking
{
public class TrackingAspect : IProjectTemplate
{
public static readonly XmlLineInfoDocument Descriptor;
public const string NamespaceUri = "http://www.mycompany.com/aspects/myaspect/2013/1";
static TrackingAspect()
{
Descriptor = new XmlLineInfoDocument();
Descriptor.LoadXml(
@"<cm:project xmlns:cm='" + Constants.CodeModelerNamespaceUri + @"' defaultNamespace='MyAspect'>
<cm:pattern name='My Aspect' namespaceUri='" + NamespaceUri + @"' preferredPrefix='mya' step='Tables'>
<cm:message class='_doc'>This is some description for our custom aspect.</cf:message>
<cm:descriptor name='enable' typeName='boolean' category='My Custom Aspect' targets='Entity' defaultValue='false' displayName='Add the MyCustomProperty' description='Description for our custom descriptor.' />
</cm:pattern>
</cm:project>");
}
public Project Project { get; private set; }
public XmlElement Element { get; private set; }
public XmlLineInfoDocument Run(IDictionary context)
{
if (context == null || !context.Contains("Project"))
{
// we are probably called for meta data inspection only
return Descriptor;
}
// the dictionary contains at least these two entries
Element = (XmlElement)context["Element"];
Project = (Project)context["Project"];
// if defined on project, do it on all entities. this is more a debug option
bool allEntities = XmlUtilities.GetAttribute(Project.Element, "trackable", NamespaceUri, true);
IEnumerable<XmlElement> elements;
if (allEntities)
{
elements = Project.Package.RootModelPart.SelectElements("//*");
}
else
{
elements = Project.Package.RootModelPart.SelectElements("trackable", NamespaceUri, true);
}
foreach (var element in elements)
{
// use a CodeModeler utility method to get entity .NET type full name
var entityFullName = Entity.GetClrTypeName(Project, Project.Package.RootModelPart, element);
if (entityFullName == null) // attribute was probably not placed on an entity property
continue;
var entity = Project.Entities[entityFullName];
if (entity == null || entity.GetData(NamespaceUri + ":done", false))
continue;
// TODO: implement logic
entity.Data[NamespaceUri + ":done"] = true;
}
// Xml sent back here can be merged into M(x), but this is not recommended.
// Instead, since we have no specific Xml to send back, but aspect description
return Descriptor;
}
}
}
A compiled aspect must implement the IProjectTemplate interface:
public interface IProjectTemplate
{
XmlLineInfoDocument Run(IDictionary context);
}
This interface requests an Xml document from the aspect. How it works is this Xml document will be merged into the Mx model at inference time.
Although it’s possible to inject concepts like entities or enumerations, expressed as Xml elements, directly from this interface, the best practice is to only return descriptors, and act on the in-memory model when it’s possible (when there’s a project instance in the context). That’s what the sample does.
Once the aspect has been compiled, you can select it using the “Add Existing Aspect” Aspects folder node context menu:
You can see the descriptor is presented here.
Once the aspect has been added, you will see it in the Aspects folder node and in the References folder node (as it’s a .NET assembly):
Now, if we use that simple model:
We can set specific aspect attributes using the Visual Property grid (“Aspects and Producers Properties” tab), according to the descriptors declared by the aspect C# code:
- Introduction
- Architect Guide
- Concepts
- Using Visual Studio
- Overview
- Creating a CodeModeler Project
- Visual Environment
- Project Hierarchy
- Design Surface
- Customizing Design Surfaces
- Ribbon Bar
- Property Grid
- Member Format Expressions
- Model Grid
- Method Editor
- View Editor
- Instance Editor and Grid
- Resources Editor
- Inferred Model Viewer
- Building
- Project Physical Layout
- Source Control Support
- Generating
- Aspect Oriented Design (AOD)
- Developer Guide
- The Business Object Model (BOM)
- CodeModeler Query Language (CMQL)
- Starting Guide - Tutorial
- Upgrade From CFE