Getting safe–limited–meta information from a Turnkey app
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

I have stated on numerous occasions that MDriven Turnkey is not only compatible with AngularJS and MVC. MDriven Turnkey is a complete information delivery architecture that is suitable for building highly secured native apps for other platforms. In several articles and videos, I have shown this for Windows WPF and Windows Store applications.

For this to be possible, one important requirement is that the Turnkey system can expose just enough meta-information that a native client knows what the Turnkey system in question offers, but not so much that we compromise security or divulge internal details on how our system is built.

What is enough then? Well, we need to expose the main menu so that the native app knows what it should show the user. For each main menu action, we need to say what view will be brought up. For each view, we need to expose what fields, widgets, and texts the UI contains, as well as what actions are available from the view in question, based on the current selection state of the view.

In the samples below, you will want to replace the first part of the URL with the one that points to your application.

Getting the Main Menu

https://mdriventurnkey-0006.azurewebsites.net/MDriven/GlobalActionsMeta?targetgroup=

You get this:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <menugroups>
    <menugroup Name="Views" Sortkey="004"/>
    <menugroup Name="ValueStores" Sortkey="005"/>
    <menugroup Name="Edit" Sortkey="002"/>
    <menugroup Name="File" Sortkey="001"/>
    <menugroup Name="Seekers" Sortkey="003"/>
  </menugroups>
  <actions>
    <action MenuGroup="ValueStores" MenuGroupSortKey="005" DividerGroupTag="" DividerGroupTagSortKey="" Name="ShowManageBrands" Presentation="Show Manage Brands" BringUpViewModel="ManageBrands" ExecuteFrameworkRuntimeAction="None"/>
    <action MenuGroup="Views" MenuGroupSortKey="004" DividerGroupTag="" DividerGroupTagSortKey="" Name="ShowAllRentalContracts" Presentation="Show All Rental Contracts" BringUpViewModel="AllRentalContracts" ExecuteFrameworkRuntimeAction="None"/>
    <action MenuGroup="Edit" MenuGroupSortKey="002" DividerGroupTag="" DividerGroupTagSortKey="" Name="Redo" Presentation="Redo" ExecuteFrameworkRuntimeAction="Redo"/>
    <action MenuGroup="Edit" MenuGroupSortKey="002" DividerGroupTag="" DividerGroupTagSortKey="" Name="Undo" Presentation="Undo" ExecuteFrameworkRuntimeAction="Undo"/>
    <action MenuGroup="File" MenuGroupSortKey="001" DividerGroupTag="" DividerGroupTagSortKey="" Name="Save" Presentation="Save" ExecuteFrameworkRuntimeAction="Save"/>
    <action MenuGroup="Views" MenuGroupSortKey="004" DividerGroupTag="" DividerGroupTagSortKey="" Name="ShowAllCars" Presentation="Show All Cars" BringUpViewModel="AllCars" ExecuteFrameworkRuntimeAction="None"/>
    <action MenuGroup="File" MenuGroupSortKey="001" DividerGroupTag="" DividerGroupTagSortKey="" Name="Refresh" Presentation="Refresh" ExecuteFrameworkRuntimeAction="Refresh"/>
  </actions>
</root>

The MenuGroups are the ones you defined in your model and the actions are all Global Actions from your model. You get sort keys that should be treated as alphanumeric sorts to get the menu in the designed order. Sort on MenuGroupSortKey, then DividerGroupTagSortKey. Put a Divider between items that have different DividerGroupTags. Create the Menu item with presentation as stated in the presentation attribute.

When the user wants to execute, it can either be a FrameworkAction like Save, Exit, Undo, Redo, or navigation – on navigation, the BringUpViewModel attribute is set.

This information states the obvious about your application's main menu: no need to keep it safe – it is benign data.

If you want to filter away actions based on target groups – this is when your app has different audiences – you can provide the target group name in the call. TargetGroups are a special use of the AccessGroup concept that is explained here.

To implement the execution of the action, execute a REST operation like this:

https://mdriventurnkey-0006.azurewebsites.net/api/open?VMClassId=MAINMENU;ShowAllCars&ResetServerApp=false

It is the name of the action that is important - “ShowAllCars” - and the fact that you state that this is the MAINMENU. Don't worry about access control – the server will make sure the caller is authenticated if there are access-group needs for this menu action.

You will get something like this back:

"943a7840-796e-4370-bd37-d407ffa48aa9¤$null$;AllCars"

The part after the “¤” is the VMClassId : $null$;AllCars

The guid before is the VM’s server identity.

You now have enough information to pull data from the serverstream for the target view. You do this with the REST call:

https://mdriventurnkey-0006.azurewebsites.net/api/ServerStream?VMId=943a7840-796e-4370-bd37-d407ffa48aa9&cursor=0

The VMId is the guid we got back from “open”. The cursor is where in the message stream we are – since we just started for this UI it is zero.

We get json back:

[
  {
    "VMClassName": "AllCars",
    "Action": "CreateCar",
    "Enable": true,
    "Presentation": "Create Car",
    "SortKey": "000000##000000#CreateCar",
    "IsModal": false,
    "View": null,
    "PresentationOfTarget": "",
    "AreYouSureQuestion": "",
    "Class": null,
    "GroupHeader": "Car",
    "SubMenuGroup": "",
    "SubMenuGroupSortKey": "",
    "MNo": 1,
    "CType": "ServerUpdateCommand_Action",
    "IsGarbage": false
  },
  {
    "VMClassName": "AllCars",
    "Action": "ViewCarLocation",
    "Enable": false,
    "Presentation": "View Car Location",
    "SortKey": "000000##000000#ViewCarLocation",
    "IsModal": false,
    "View": "CarLocation",
    "PresentationOfTarget": "",
    "AreYouSureQuestion": "",
    "Class": null,
    "GroupHeader": "Car",
    "SubMenuGroup": "",
    "SubMenuGroupSortKey": "",
    "MNo": 2,
    "CType": "ServerUpdateCommand_Action",
    "IsGarbage": false
  },
  {
    "Attribute": "AllCar",
    "UpdateType": "Add",
    "NewValues": [ "5!1;AllCar" ],
    "OldValues": null,
    "NewValuesStartIndex": 0,
    "OldValuesStartIndex": -1,
    "VMClassId": "$null$;AllCars",
    "MNo": 3,
    "CType": "ServerUpdateCommand_UpdateCollection",
    "IsGarbage": false
  },
  {
    "Attribute": "AllCar",
    "UpdateType": "Add",
    "NewValues": [ "5!97;AllCar" ],
    "OldValues": null,
    "NewValuesStartIndex": 1,
    "OldValuesStartIndex": -1,
    "VMClassId": "$null$;AllCars",
    "MNo": 4,
    "CType": "ServerUpdateCommand_UpdateCollection",
    "IsGarbage": false
  },
  {
    "Attribute": "AllCar",
    "UpdateType": "Add",
    "NewValues": [ "5!98;AllCar" ],
    "OldValues": null,
    "NewValuesStartIndex": 2,
    "OldValuesStartIndex": -1,
    "VMClassId": "$null$;AllCars",
    "MNo": 5,
    "CType": "ServerUpdateCommand_UpdateCollection",
    "IsGarbage": false
  },
  {
    "Attribute": "AllCar",
    "UpdateType": "Add",
    "NewValues": [ "5!4;AllCar" ],
    "OldValues": null,
    "NewValuesStartIndex": 3,
    "OldValuesStartIndex": -1,
    "VMClassId": "$null$;AllCars",
    "MNo": 6,
    "CType": "ServerUpdateCommand_UpdateCollection",
    "IsGarbage": false
  },
  {
    "IsDirty": false,
    "ServerQueueLength": 6,
    "MessagesReceivedFromClient": 0,
    "SuggestCallbackInSecs": 0,
    "NumVmsInApp": "1",
    "InstanceId": 1,
    "ServersHighestMNo": 6,
    "ServerStatus": "OK-09:43:31.934",
    "RootObjectStatus": "isnull",
    "WindowHeader": "AllCars",
    "AsyncQueueCount": 0,
    "MarkersForActionsRunning": 0,
    "MNo": 7,
    "CType": "ServerUpdateCommand_AppInfo",
    "IsGarbage": false
  }
]

This Json is what builds up the data hierarchy we designed in the ViewModelEditor in MDriven Designer.

This data should be polled continuously according to the information in the object that always comes last “CType: ServerUpdateCommand_AppInfo”. In this special object you see your new cursor id == MNo=7, and when we ask the server again, is stated in SuggestCallbackInSecs=0 – in this case, we should call right back since there is probably more to fetch.

Getting a UI Definition

Once we build up the data this way, we still need to understand how to display it on the client side. We need to find the meta information of the UI-Hints that explain what kind of UI widgets we should use and where they should go on the screen in relation to each other. That is done like this:

https://mdriventurnkey-0006.azurewebsites.net/MDriven/ViewMeta?view=AllCars

And we get XML back:


<root RowHeight="20" ColWidth="50">
  <control type="grid" owner="AllCars" name="AllCar" root="vCurrent_AllCars" label="All Car" labelspan="1" colspan="3" rowspan="5" x="0" y="0" nesting="AllCar">
    <control type="textbox" name="AsString" label="As String" colspan="1"/>
    <control type="textbox" name="RegistrationNumber" label="Registration Number" colspan="3"/>
  </control>
  <control type="textbox" owner="TheCar" name="RegistrationNumber" root="vCurrent_TheCar" label="Registration Number" labelspan="1" colspan="1" rowspan="1" x="4" y="0"/>
  <control type="combobox" owner="TheCar" name="Brand" root="vCurrent_TheCar" label="Brand" labelspan="1" colspan="1" rowspan="1" x="4" y="1" picklist="BrandPickList" displaymember="Presentation"/>
  <control type="image" owner="TheCar" name="Brand_BrandImage" root="vCurrent_TheCar" label="Brand Image" labelspan="1" colspan="1" rowspan="2" x="4" y="2"/>
</root>

This XML holds enough information to put the designed widgets on a screen and set up binding to the data hierarchy that we stream in with the Json above. It even encapsulates any tagged values you have enclosed in your design.

The full-stack proof of concept implementation is in our WPF/Win10/Universal/PortableLibrary stack available now.

This page was edited 70 days ago on 02/10/2024. What links here