Sunday, February 21, 2010

Strongly typed thank you!

This weekend I set out for doing a Rational Rose file import plugin for Modlr. I wrote the code that successfully traversed the rather strange file format that they use. Rational Rose is a pre-xml product:

   1:  
   2: (object Petal
   3:     version        47
   4:     _written       "Rose 8.0.0303.1400"
   5:     charSet        0)
   6:  
   7: (object Design "Logical View"
   8:     is_unit        TRUE
   9:     is_loaded      TRUE
  10:     quid           "4B7FCC8D02EF"
  11:     defaults       (object defaults
  12:     rightMargin     0.250000
  13:     leftMargin     0.250000
  14:     topMargin      0.250000
  15:     bottomMargin     0.500000
  16:     pageOverlap     0.250000
  17:     clipIconLabels     TRUE
  18:     autoResize     TRUE
  19:     snapToGrid     TRUE
  20:     gridX          0
  21:     gridY          0
  22:     defaultFont     (object Font
  23:         size           12
  24:         face           "Arial"
  25:         bold           FALSE
  26:         italics        FALSE
  27:         underline      FALSE
  28:         strike         FALSE
  29:         color          0
  30:         default_color     TRUE)
  31:     showMessageNum     3
  32:     showClassOfObject     TRUE
  33:     notation       "Unified")
  34:     root_usecase_package     (object Class_Category "Use Case View"
  35:     quid           "4B7FCC8D02F1"

So when I had all that done I thought that it would an easy and quick task to wrap that information into a model designed in eco so that I could populate it with objects from the file. Once that was done I planned to write the code that transformed from that rose model to the eco modellayer model…

But I immediately got super tired when I realized that I had to create some 30-40 classes with a lot of attributes and relations all by hand in order to hold the rational rose data from the file…

It seemed silly that I would need to do stuff by hand at all. After all we are the tool making species! So I started to think that this is a common scenario – I have a description, or data adhering to a description, in one format, and I want to have that as a model so I can code towards it with strongly typed classes ( the older (and wiser?) I get, I get increasingly more allergic to scripting ).

So if this is a common problem that at least I come across from time to time maybe I should solve that first?

I asked myself what format the data I need to handle usually come in : XML.

So to avoid losing focus of my initial problem all together I used my Rational Rose File decoder logic and made it spit out the same information as XML:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <root>
   3:   <Petal>
   4:     <name></name>
   5:     <key></key>
   6:     <version>47</version>
   7:     <_written>"Rose 8.0.0303.1400"</_written>
   8:   </Petal>
   9:   <Design>
  10:     <name>Logical View</name>
  11:     <key></key>
  12:     <is_unit>TRUE</is_unit>
  13:     <is_loaded>TRUE</is_loaded>
  14:     <quid>"4B7FCC8D02EF"</quid>
  15:     <defaults>
  16:       <defaults>
  17:         <name></name>
  18:         <key></key>
  19:         <rightMargin>0.250000</rightMargin>
  20:         <leftMargin>0.250000</leftMargin>
  21:         <topMargin>0.250000</topMargin>
  22:         <bottomMargin>0.500000</bottomMargin>
  23:         <pageOverlap>0.250000</pageOverlap>
  24:         <clipIconLabels>TRUE</clipIconLabels>
  25:         <autoResize>TRUE</autoResize>
  26:         <snapToGrid>TRUE</snapToGrid>
  27:         <gridX>0</gridX>
  28:         <gridY>0</gridY>
  29:         <defaultFont>
  30:           <Font>
  31:             <name></name>

The general idea was to write a plugin that given this XML, or a similar XML, or preferably any XML, could derive a model for me, saving me from doing stuff manually.

I know that some of you are thinking “oh boy this guy is writing a plugin in order to write a plugin in order to convert a rose file?”, and yes you are correct. But one plugin will be very generic – taking almost any XML and turning it into a modlr model.

So nothing fancy:

   1: XDocument xd = XDocument.Load(file);
   2: foreach (XElement xe in xd.Root.Elements())
   3: {
   4:     Analyze(xe);
   5: }

And the magic of the plugin:

   1: private Eco.ModelLayer.Class Analyze(XElement xe)
   2: {
   3:     Eco.ModelLayer.Class c = EnsureClass(GetNameFromXE(xe));
   4:     foreach (XAttribute xa in xe.Attributes())
   5:     {
   6:         EnsureAttribute(c, xa.Name.ToString());
   7:     }
   8:     foreach (XElement subxe in xe.Elements())
   9:     {
  10:  
  11:         if (subxe.FirstNode is XText || subxe.FirstNode==null)
  12:         {
  13:             // treat as model attribute
  14:             EnsureAttribute(c, GetNameFromXE(subxe));
  15:         }
  16:         else
  17:         {
  18:             // treat as model association
  19:             /*
  20:                 We treat to types of linking:
  21:                  single link may look like this:
  22:                      obj1
  23:                         attr1
  24:                         link
  25:                             attr1
  26:              
  27:              *   multilink often looks like this:
  28:                      obj1
  29:                         attr1
  30:                         link
  31:                             obj2
  32:                                attr1
  33:              */

34: if (subxe.Elements().Count() > 0 &&

(subxe.Elements().First().FirstNode is XText || subxe.Elements().First().FirstNode == null))

  35:             {
  36:                 Eco.ModelLayer.Class targetclass = Analyze(subxe);
  37:                 AssociationEnd ae = EnsureAssociation(c, targetclass, GetNameFromXE(subxe));
  38:                 if (ae!=null)
  39:                     ae.Multiplicity = "0..1";
  40:             }
  41:             else
  42:             {
  43:                 List<Eco.ModelLayer.Class> classesfoundinrelation = new List<Eco.ModelLayer.Class>();
  44:                 foreach (XElement xeObjectInAssoc in subxe.Elements())
  45:                 {
  46:                     Eco.ModelLayer.Class targetclass = Analyze(xeObjectInAssoc);
  47:                     if (classesfoundinrelation.IndexOf(targetclass) == -1)
  48:                         classesfoundinrelation.Add(targetclass);
  49:                 }
  50:  
  51:                 if (classesfoundinrelation.Count > 0)
  52:                 {

53: // if classesfoundinrelation.Count>1 the association is most likely

pointing to a super class of the classes found, but we dont have enough info - pick first

  54:                     AssociationEnd ae = EnsureAssociation(c, classesfoundinrelation[0], GetNameFromXE(subxe));
  55:                     if (ae!=null)
  56:                         ae.Multiplicity = "0..*";
  57:                 }
  58:             }
  59:         }
  60:     }
  61:     return c;
  62: }
  63:  

The logic to actually create a Class, an Attribute or a Relation looks like this:

   1: private AssociationEnd EnsureAssociation(Eco.ModelLayer.Class cfrom, Eco.ModelLayer.Class cto, string xName)
   2: {
   3:  
   4:     if (_esp != null)
   5:     {
   6:         var res = (from x in cfrom.AssociationEnd where x.OtherEnd != null && x.OtherEnd.Name == xName select x);
   7:         if (res.Count() == 0)
   8:         {
   9:             AssociationEnd ae1=new AssociationEnd(_esp);
  10:             AssociationEnd ae2 = new AssociationEnd(_esp);
  11:             Association a = new Association(_esp);
  12:             a.AssociationEnd.Add(ae1);
  13:             a.AssociationEnd.Add(ae2);
  14:             a.Package_ = _package;
  15:             ae2.Name = xName;
  16:             ae2.IsNavigable = true;
  17:             cfrom.AssociationEnd.Add(ae1);
  18:             cto.AssociationEnd.Add(ae2);
  19:             return ae2;
  20:         }
  21:         return res.First().OtherEnd;
  22:     }
  23:     return null;
  24: }
  25:  
  26: private void EnsureAttribute(Eco.ModelLayer.Class c, string xName)
  27: {
  28:     if (_esp != null)
  29:     {
  30:         foreach (ModelElement me in c.AllFeatures)
  31:         {
  32:             if (me is Eco.ModelLayer.Attribute)
  33:             {
  34:                 if (xName == (me as Eco.ModelLayer.Attribute).Name)
  35:                 {
  36:                     return;
  37:                 }
  38:             }
  39:         }
  40:         // not found
  41:  
  42:         Eco.ModelLayer.Attribute a=new Eco.ModelLayer.Attribute(_esp);
  43:         a.Name = xName;
  44:         if (_stringDataType==null)

45: _stringDataType=(from x in _esp.GetEcoService<IExtentService>().AllInstances<Datatype>()

where x.Name.ToLower()=="string" select x).First<Datatype>();

  46:         a.Type = _stringDataType;
  47:         c.Feature.Add(a);
  48:  
  49:     }
  50: }
  51:  
  52: private Eco.ModelLayer.Class EnsureClass(string xName)
  53: {
  54:     if (_esp != null)
  55:     {
  56:         string completename = _prefix + xName;
  57:         var result = (from x in _esp.GetEcoService<IExtentService>().AllInstances<Eco.ModelLayer.Class>() 
  58:                       where (x.Name == completename && x.OwningPackage == _package) select x).ToArray<Eco.ModelLayer.Class>();
  59:         if (result.Count() == 0)
  60:         {
  61:             Eco.ModelLayer.Class c = new Eco.ModelLayer.Class(_esp);
  62:             c.Name = completename;
  63:             c.Package_=_package;
  64:             return c;
  65:         }
  66:         else
  67:             return result[0];                
  68:     }
  69:     return null;
  70: }
  71:     }

I tried the logic on several different xml files that I found in my temp folder and found some special cases. This lead me to the conclusion that I needed at least two different strategies: one where the xml element has a generic name and the real name is given in some attribute. One example of this is the EcoMdl file format:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <contents>
   3:   <object type="Diagram" href="ClassDiagram_Diagram1">
   4:     <doc><![CDATA[]]></doc>
   5:     <modlrdiagram>diagramimages/efcd3388-264f-469f-b6e2-5e0a1dc68839.jpg</modlrdiagram>
   6:     <attribute name="ColorOnNew">0</attribute>
   7:     <attribute name="Id">efcd3388-264f-469f-b6e2-5e0a1dc68839</attribute>
   8:     <attribute name="SnapGridSize">15</attribute>
   9:     <attribute name="PresentationName">Diagram1</attribute>
  10:     <attribute name="ShowMethodSignatures">False</attribute>
  11:     <attribute name="ShowAssociationNames">DimDefaults</attribute>
  12:     <attribute name="ShowVisibility">False</attribute>
  13:     <attribute name="SquareNewAssociations">False</attribute>
  14:     <attribute name="SquareNewGeneralizations">False</attribute>
  15:     <attribute name="ShowMethodReturnTypes">False</attribute>
  16:     <attribute name="Name">Diagram1</attribute>
  17:     <link name="PlacedClass">
  18:       <object type="PlacedClass" href="Class1">
  19:         <attribute name="RenderedWidth">200,8</attribute>
  20:         <attribute name="id">0aac4fb8-5dd6-4082-b16b-73c07565fc57</attribute>
  21:         <attribute name="Color">1692800256</attribute>
  22:         <attribute name="Size">1</attribute>
  23:         <attribute name="LastKnownName">Class1</attribute>
  24:         <attribute name="RenderedHeight">49,25</attribute>

But the more common scenario is the format where the xml elements have specific names. Like the pad format:

   1: <?xml version="1.0" encoding="UTF-8"?>
   2: <XML_DIZ_INFO>
   3:   <MASTER_PAD_VERSION_INFO>
   4:     <MASTER_PAD_VERSION>3.01</MASTER_PAD_VERSION>
   5:     <MASTER_PAD_EDITOR>PADManager 2.0.46</MASTER_PAD_EDITOR>
   6:     <MASTER_PAD_INFO>Portable Application Description, or PAD for short, is a data set that is used by shareware authors to disseminate information to anyone interested in their software products. To find out more go to http://www.asp-shareware.org/pad</MASTER_PAD_INFO>
   7:   </MASTER_PAD_VERSION_INFO>
   8:   <RoboSoft>
   9:     <Company_UIN>CMP-009701CA593</Company_UIN>
  10:     <Company_Description></Company_Description>
  11:     <Product_UIN>APP-009701FB886</Product_UIN>
  12:     <Publish_on_CD>FALSE</Publish_on_CD>
  13:     <Search_String></Search_String>
  14:     <Press_Release_Search_String></Press_Release_Search_String>
  15:     <NewsFeed_Search_String></NewsFeed_Search_String>
  16:     <Search_Engine_Search_String></Search_Engine_Search_String>
  17:     <Web_Directories_Search_String></Web_Directories_Search_String>
  18:     <Search_String_Unique></Search_String_Unique>
  19:     <Comments_For_Reviewer></Comments_For_Reviewer>
  20:     <Additional_Categories></Additional_Categories>
  21:     <Backlink></Backlink>
  22:   </RoboSoft>
  23:   <Company_Info>
  24:     <Company_Name>CapableObjects.com</Company_Name>
  25:     <Address_1>Box 1200</Address_1>
  26:     <Address_2>Huddinge</Address_2>
  27:     <City_Town>Stockholm</City_Town>

 

 

I added a window that pops up when executing the plugin, it lets you pick the xml file, choose strategy, choose target package and GO:

image

And it gave me this (I dragged them onto the diagram myself)

image

Puh, now when I have a good starting point for the rational rose object model I can back to my original task… But that will be some other day…

(the plugin is in the demos folder for the next available eco build and it is called PluginXmlToModel)

Wednesday, February 17, 2010

Modlr plugins

We added a plugin interface for Gaffr and ECO. We want to enable you to extend Modlr so that you can implement a special file format import, or export, or what have you.

This is how it works:

Given these two interfaces…

   1: namespace Modlr.Plugins
   2: {
   3:     public interface IModlrPlugin
   4:     {
   5:         string GetName();
   6:         List<IModlrPluginMenuOperation> ProvideOperations();
   7:     }
   8:  
   9:     public interface IModlrPluginMenuOperation
  10:     {
  11:         string GetName();
  12:         void CheckEnableOrExecute(IEcoServiceProvider esp, bool execute, 
                        IEcoObject maincontext, IEcoObject subcontext,out bool enabled );
  13:     }
  14: }

…you can implement a plugin in an assembly like this:

   1: public class Class1 : IModlrPlugin
   2: {
   3:     #region IModlrPlugin Members
   4:  
   5:     public string GetName()
   6:     {
   7:         return "Demo plugin";
   8:     }
   9:  
  10:  
  11:     public List<IModlrPluginMenuOperation> ProvideOperations()
  12:     {
  13:         List<IModlrPluginMenuOperation> ops = new List<IModlrPluginMenuOperation>();
  14:         ops.Add(new OperationShowDialog());
  15:         ops.Add(new OperationActOnClass());
  16:  
  17:         return ops;
  18:     }   
  19:  
  20:     #endregion
  21: }
  22:  

The code above installs two Operations; OperationShowDialog and OperationActOnClass.

Looking closer on the operation ActOnClass:

   1: public class OperationActOnClass : IModlrPluginMenuOperation
   2: {
   3:     #region IModlrPluginMenuAction Members
   4:  
   5:     public void CheckEnableOrExecute(IEcoServiceProvider esp,
                      bool execute, Eco.ObjectRepresentation.IEcoObject maincontext,
                      Eco.ObjectRepresentation.IEcoObject subcontext, out bool enabled)
   6:     {
   7:         enabled = false;
   8:         if (maincontext != null && maincontext.AsIObject().AsObject is Eco.ModelLayer.Class)
   9:         {
  10:             enabled = true;
  11:             if (execute)
  12:             {
  13:                 (maincontext.AsIObject().AsObject as Eco.ModelLayer.Class).Name += "X";
  14:             }
  15:         }
  16:     }
  17:  
  18:     public string GetName()
  19:     {
  20:         return "Operation on class";
  21:     }
  22:  
  23:     #endregion
  24: }

The code checks that the context is a class, and if it is the code says that the operation should be enabled. If this was an “execute” call we perform the desired logic – in this case I just add an “X” to the back of the class name.

You can do a lot of stuff in a plugin. You get access to the IEcoServerProvider for the EcoModelLayer. The Modlr application is a standard ECO-built-application so you can use the IOCLService to get to any object, of any class that builds up your model content. You can access Diagrams, PlacedClasses, StateMachines etc. You can inspect and set TaggedValues, you can run thru every Attribute and check for spelling errors. You can create reports that you share with other ECO/Gaffr users…

So even if the interface is very small and simple, it is very powerful.

When you build your Plugin you will want to reference the following assemblies (found in the eco or gaffr install directory):

image

Compile your plugin and put the assembly in the folder %ProgramData%/CapableObjects/Plugins

Restart Gaffr.net or VisualStudio and you will find your plugin operations in the context menu:

image

Simple and powerful.

Sunday, February 14, 2010

ViewModels in ASP.NET

I got a couple of questions about ViewModels in ASP.NET; and my conscience has stirred me to action. I was aiming to finish the immensely cool  project that we call GaffrServer and I want to use the ASP.NET viewmodels to make that happen.

Side note on what GaffrServer is: Imagine that you prototype in Gaffr.net using the upcoming Application WPF framework WECPOF (oh I just love inventing code names for our projects: WECPOF - ECO + WPF – get it?), and you want persistence. You can write to a XML file, or set up a database… But you will also be able to connect to GaffrServer – GaffrServer is cloud service that you upload your model to, then you get back an url that is the up and running server side persistence mapper with the synchronization service running – WOW! I WANT IT NOW.

Anyway the GaffrServer project is sort of dragging its feet so I am better of just showing ASP.NET ViewModels.

I wrapped up a sample that looks like this:

image

 

Notice the derived attributes on OrderItem and Order:

   1: CostForRow = (self.Quantity*self.Article.Cost)+(self.Quantity*self.Article.Weight*self.Order.DeliveryType.CostPerKiloGram)
   2:  
   3: TotalCost = self.OrderItem->Collect(a|a.CostForRow)->sum()

(Note that the TotalCost actually use the derivation in CostForRow – super cool imho)

Then I did a ViewModel:

image

I know some of you might initially frown at the complexity, but trust me, you just click away and use the suggested columns that are available on the context menu – I seldom have a specific plan when I start – I think and do at the same time. That is the hallmark of a tool that is helping you…

The basic idea was to fill in an order, add OrderLines, set the correct Article on the OrderLine, see the price per row, and the total price, taking into account what delivery type you use…

There you go, a neat little list of requirements so easily said – and easily implemented.

I created a ECO ASP.NET application from the ECO Solution Wizard, Added the scriptmanager and an UpdatePanel to get Ajax behavior.

Then I add the ViewModelASPNET component, hook it up to the EcoSpaceManager, and tell it to use ViewModel ViewModelOrder:

image

I need to write some code to get test data and to hook up the ViewModelASPNET control to an order:

   1: private void Page_Load(object sender, System.EventArgs e)
   2: {
   3:  
   4:     if (!Page.IsPostBack)
   5:     {
   6:         Order order;
   7:         if (EcoSpace.GetEcoService<IExtentService>().AllInstances<Order>().Count == 0)
   8:         {
   9:             order = InitSomeTestData();
  10:         }
  11:         else
  12:             order = EcoSpace.GetEcoService<IExtentService>().AllInstances<Order>()[0];
  13:         ViewModelASPNET1.SetObject(order);
  14:     }
  15: }
  16:  
  17: private Order InitSomeTestData()
  18: {
  19:     Order order=new Order(EcoSpace);
  20:     order.CustomerName = "SomeName";
  21:     order.CustomerAddress = "SomeAddress";
  22:     order.DeliveryType = new DeliveryType(EcoSpace) { Name = "UPS", CostPerKiloGram = 20.5 };
  23:     new DeliveryType(EcoSpace) { Name = "Schenker", CostPerKiloGram = 19.5 };
  24:     new DeliveryType(EcoSpace) { Name = "DHL", CostPerKiloGram = 29.5 };
  25:  
  26:     new Article(EcoSpace) { Name = "Knife", Cost=127.5, Weight=0.25 };
  27:     new Article(EcoSpace) { Name = "Vobbler", Cost = 17.1, Weight = 0.15 };
  28:     new Article(EcoSpace) { Name = "Fishing rod", Cost = 527.5, Weight = 1.05 };
  29:  
  30:     EcoSpace.UpdateDatabase();
  31:     return order;
  32:  
  33: }

I also must init the viewmodels before they are used:

   1: protected void ScriptManager1_Init(object sender, EventArgs e)
   2: {
   3:     ViewDefinitionsInApplication.Init(EcoSpace);
   4:         
   5: }

Then I get something that looks like this:

image

Notice the crazy style on CostForRow, the style on TotalCost and on Selected orderitem – I get these by providing a css stylesheet that has styles using the names I set in the ViewModelColumns StyleRef property…

As you change deliverytype or anything else the TotalCost will be updated on the next postback.

In this demo I chose to use the setting ViewModelASPNET.PostbackOnComboboxChange="true"
I also chose to use the setting ViewModelASPNET.GridSizeFixedWithScrollbar="True"

The bits to run this is in the eco build after this date, it is in the demos called EcoASPNETWithViewModel

 
Contact Us | Terms of Use | Privacy Statement © 2009 CapableObjects