Sunday, November 21, 2010

Straight forward, easy even, Silverlight applications with ECO that saves, loads and discovers changed objects on the server

This is the beta of the Silverlight persistence – I want feedback on how it works and how hard/easy it is to get along with.

Yes, I have promised this for a long time but we got caught up in STUFF and then there was more STUFF and you know how it goes…

Anyway I have spent the last couple of days wrapping the final things of the Silverlight persistence up.

The first thing that got me stumped was that Silverlight does not accept WCF-interfaces that does not follow the Async pattern (with BeginOp/EndOp). The WCF interface we had for server persistence did not follow that so it had to be changed.

The second thing that got me stumped was that even when having the Async-pattern-WCF-interface, all Silverlight applications will hang if any communication is done over a WCF channel on the Main thread.

So I had to figure out a practical way to call things on a non-MainThread-thread without getting into the total chaos of an ecospace that is read and written simultaneously from multiple threads.

I am not saying that I have “solved” every thread issue in the book with this beta – but I think that I have the strategy laid out.

The biggest challenge is Lazy Load: Lazy Load is a super function of ECO that works really well and removes a ton of work for the developer – it can be dangerous too of course – creating too many server round trips – but ECO has strategies to handle that. The problem with Lazy Load in Silverlight is that the loading cannot be done on the main thread – because the app will hang. And UI-binding cannot be done in a background thread because UI is main thread.

So Lazy Load has to go? Well yes – in Silverlight it has to go, at least on the main thread. We cannot stall a main thread operation that discovers the need for loading – if we do the app will hang for ever.

Introducing IAsyncSupportService

It is really easy to get things to execute on a background thread:

ThreadPool.QueueUserWorkItem(new WaitCallback(
(obj) =>
{
    GetInToTroubleQuick;    
}));

The code above gets a free thread from the thread pool – the thing is that we want to avoid doing stuff out of sequence so we do not want just any thread. For example we do not want a multilink to become fetched AFTER we get the result of Count – we want things to happen in sequence because they depend on each other.

Threading is best used on things that does not have dependencies – in an ecospace objects have dependencies – that is 90% of the point – sure you can have some group of objects that does not depend on some other group of objects but that will not be the normal case – and if so consider 2 ecospaces.

The Service I added to handle things in the background but still in sequence is called IAsyncSupportService. It is used like this:

EcoServiceHelper.GetAsyncSupportService(_ecoSpace).PerformTaskAsync(
() =>
{
    _ecoSpace.UpdateDatabase();
});

The IAsyncSupportService does a couple of things:
1. Ensures that tasks are executed in the sequence they are added
2. Delays the Subscriber events that a Task will emit until the Task is done then it emits them on the Main thread

The IAsyncSupportService also has a way to sync something from a Task back to the MainThread using a method called DispatchTaskToMainThread:

ass.PerformTaskAsync(
() =>
{
    c.VMClassDescriptor.ViewModelClass.ViewModel.EnsureSpanFetch();
    ass.DispatchTaskToMainThread(() =>
     {
         c.PauseDisplay = false;
     });
});

Make sure your ecospace objects are fully fetched from server

The thing is that you will want to ensure that you are “fully fetched” while in the Async Task and that you leave nothing to lazy load when you are back on the main thread as your UI pulls the data for the bindings – if you do not, WCF will be used on the mainthread as soon as lazy load kicks in and the app will hang… To ensure that everything is fetched can be a chore… You can do this by calling Evaluate on the expressions you know will be fetched:

IOclService ocl = EcoServiceHelper.GetOclService(EcoSpace);
result=ocl.Evaluate(elem, Expression, vars);

Or you can do it by navigating the associations, and attributes that your UI use with Linq or code… Still a chore…

But if you use ViewModels – Code generated – or not, with the ViewModelSLUserControl or not we will do that for you automatically – go ViewModels!

The ViewModel has a new Method called EnsureSpanFetch – you can call it yourself but it is called when you assign a Root object to a ViewModel. The one time you want to call it is when you call Refresh – there is no telling what other clients have changed so we do not know if there is new data that we will be forced to load after a Refresh:

EcoServiceHelper.GetAsyncSupportService(_ecoSpace).PerformTaskAsync(
() =>
{
    EcoServiceHelper.GetPersistenceService(_ecoSpace).Refresh(true);
    ViewModelUserControl.ViewModel.EnsureSpanFetch();
});

The example

A simple enough model:

image

And a ViewModel:

image

Add it to the silverlight form:

<Button Click="Button_Click">
    <TextBlock Text="Add C1"></TextBlock></Button>
<Button Click="Button_Click_1">
    <TextBlock Text="Add C2"></TextBlock>
</Button>
<Button Click="Button_Click_2">
    <TextBlock Text="UpdateDatabase"></TextBlock>
</Button>
<Button Click="Button_Click_3">
    <TextBlock Text="Refresh"></TextBlock>
</Button>
<StackPanel Margin="20">
    <TextBlock FontSize="24" FontFamily="Georgia" 
           Text="Below the viewmodel rendered from UI hints">
    </TextBlock>
    <ecovm:ViewModelSLUserControl 
           x:Name="ViewModelUserControl" 
           ViewModelName="ViewModel1" >
    </ecovm:ViewModelSLUserControl>
</StackPanel>

image

And some codebehind

public partial class MainPage : UserControl
{
    private EcoSpaceAndModel.EcoSpace1 _ecoSpace;
    public MainPage()
    {
        InitializeComponent();
        _ecoSpace = new EcoSpaceAndModel.EcoSpace1();
        _ecoSpace.Active = true;
        DequeuerSL.Active = true;
        ViewModelDefinitionsInApplication.Init(_ecoSpace);
        ViewModelUserControl.SetEcoSpace(_ecoSpace);
        (Resources["ViewModel1"] as ViewModel1).SetObject(_ecoSpace,null);
       
    }
 
    private int x = 0;
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        // This code could execute in the main thread (without PerformTaskAsync)
        // but it is safer to run it on the same thread as the persistence
        EcoServiceHelper.GetAsyncSupportService(_ecoSpace).PerformTaskAsync(() =>
        {
            x++;
            new EcoProject1.Class1(_ecoSpace) { Attribute1 = "c1" + x.ToString() };
        });
 
    }
 
    private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        // This code could execute in the main thread (without PerformTaskAsync)
        // but it is safer to run it on the same thread as the persistence
        EcoServiceHelper.GetAsyncSupportService(_ecoSpace).PerformTaskAsync(() =>
        {
            x++;
            new EcoProject1.Class2(_ecoSpace) { Name = "c2" + x.ToString() };
        });
    }
 
    private void Button_Click_2(object sender, RoutedEventArgs e)
    {
        // This code MUST be executed in a TaskAsync since it use the
        // WCF channel
        EcoServiceHelper.GetAsyncSupportService(_ecoSpace).PerformTaskAsync(() =>
        {
            _ecoSpace.UpdateDatabase();
        });
    }
 
    private void Button_Click_3(object sender, RoutedEventArgs e)
    {
        // This code MUST be executed in a TaskAsync since it use the
        // WCF channel
        EcoServiceHelper.GetAsyncSupportService(_ecoSpace).PerformTaskAsync(() =>
        {
            EcoServiceHelper.GetPersistenceService(_ecoSpace).Refresh(true);
            // The refresh could have found new objects - make sure these
            // are fully fetched to avoid lazy load on main thread -> hang
            ViewModelUserControl.ViewModel.EnsureSpanFetch();
        });
    }
}

Deploying to a server

This example has a persistence server hosted in IIS. Getting everything to work properly can be a challenge. To help you smoking issues out I recommend the IE-plugin called Web Development Helper; http://projects.nikhilk.net/WebDevHelper

This tool installs in IE and when activated it shows like this and reveal all kinds of errors like a missing clientaccesspolicy file – it is a life saver :

image

I have published this demo server here THE SERVER

I assign this url to the PersistenceMapperClient in the Silverlight client:

public EcoSpace1(string theurl)
    : base()
{
    this.persistenceMapperClient1 = new Eco.Wcf.Client.PersistenceMapperWCFClient();
    this.persistenceMapperClient1.Uri = theurl;
    this.PersistenceMapper = this.persistenceMapperClient1;
}

The client I published here THE CLIENT , now the interesting thing will be when you open two or more of the clients, change data in them, update the database and then refresh the other.

The full sample code is in the latest eoc6 build demos folder SilverlightWCFPersistenceDemo.

(The persistence server uses a MemoryMapper – so it will clear when IIS swaps out my server – could have used any other persistence mapper available to eco of course but that is not what this post is about )

Read this far? Thanks! Now write some comment below:

Tuesday, November 9, 2010

Eco Silverlight–client and server side–how is it supposed to work

The question on the forum was “So what is on the Eco Service side? What are the steps going to be to make a SL Eco project work? Is there a description anywhere of what the plans are for this to work?”

And this is the answer as it stands today.

There is no Silverlight wizard available (yet) so you do need some hacking to get started – just copy stuff from here…

(Everything here is for Silverlight 4)

#1 Pick New Solution and create a new EcoProject in dll using the standard wizard, also choose to have a Server for persistence – I took Windows application below:

Enterprise core objects

#2 Right-click the solution and pick “Add new Project”  - Pick a Silverlight Application (standard on all options you need to set):

image

#3 Right—click solution and pick “Add new project” again – this time pick a Silverlight Class Library – Name it SLEcoSpaceAndModel

#4 Delete the default Class1 in SLEcoSpaceAndModel and add “Existing Item” – we are going to add a link to the EcoProject1.ecomdl file. Make sure it is a link and not a copy:

image

When we have gotten this far you can test that the “Modlr plugin – CodeGen function” has been extended to find ecomdl file links in Silverlight projects. This shows by modeling a few classes and clicking update code:

Enterprise core objects Silverlight and server side

You will find that the generated code is also maintained as links in the SLEcoSpaceAndModel project – this is only due to the fact that this project contains a file link to the EcoProject1.ecomdl file.

We want to have it this way since we cannot reuse the EcoProject1.Model assembly in a Silverlight application since it is NOT a Silverlight project (EcoProject1.Model is not SL) – and we do not want it to be a Silverlight project since we want to let our server side code – and possibly other consumers – share a normal .net assembly (not Silverlight).

#5 We now need an EcoSpace instance in Silverlight – I will add it in the SLEcoSpaceAndModelAssembly – you could add it in an assembly of its own – but there is not much point unless you will have multiple Silverlight frontends that all are going to use a common model.

I suggest that you just copy much of this code into a new file:

   1: namespace SLEcoSpaceAndModel
   2: {
   3:     using System;
   4:     using System.Collections;
   5:     using System.Collections.Generic;
   6:     using System.Linq;
   7:     using Eco.Handles;
   8:     using Eco.Linq;
   9:     using Eco.Services;
  10:     using Eco.UmlCodeAttributes;
  11:     using EcoProject1;
  12:  
  13:     [EcoSpace]
  14:     [UmlTaggedValue("Eco.InitializeNullableStringsToNull", "true")]
  15:     [UmlTaggedValue("Eco.GenerateMultiplicityConstraints", "true")]
  16:     public partial class EcoSpace1 : Eco.Handles.DefaultEcoSpace
  17:     {
  18:         private static ITypeSystemService typeSystemProvider;
  19:  
  20:         /// <summary>
  21:         /// THIS IS HOW YOU CONTROL WHAT PACKAGES TO USE, Add more if needed
  22:         /// protected PackageType IncludeEcoPackage_NAMESPACE_PackageType
  23:         /// </summary>
  24:         protected EcoProject1Package IncludeEcoPackage_EcoProject1_EcoProject1Package;
  25:  
  26:         private Eco.Wcf.Client.PersistenceMapperWCFClient persistenceMapperClient1;
  27:  
  28:         public EcoSpace1()
  29:             : base()
  30:         {
  31:             this.persistenceMapperClient1 = new Eco.Wcf.Client.PersistenceMapperWCFClient();
  32:             this.persistenceMapperClient1.Uri = "http://localhost:8000/EcoProject1WinFormServer";
  33:             //  this.PersistenceMapper = this.persistenceMapperClient1; --- This does not work yet
  34:         }
  35:  
  36:         /// <summary>
  37:         /// Persist all changes to the domain objects.
  38:         /// </summary>
  39:         /// <remarks>
  40:         /// This function persists all changes to the eco space, including object creation,
  41:         /// object manipulation, changed associations and object deletions. After invoking this method
  42:         /// all undo information is removed.
  43:         /// If the application does not have any persistence layer defined the operation does nothing.
  44:         /// </remarks>
  45:         public void UpdateDatabase()
  46:         {
  47:             if ((Persistence != null) && (DirtyList != null))
  48:             {
  49:                 Persistence.UpdateDatabaseWithList(DirtyList.AllDirtyObjects());
  50:             }
  51:         }
  52:  
  53:         public static new ITypeSystemService GetTypeSystemService()
  54:         {
  55:             if (typeSystemProvider == null)
  56:             {
  57:                 lock (typeof(EcoSpace1))
  58:                 {
  59:                     if (typeSystemProvider == null)
  60:                         typeSystemProvider = MakeTypeService(typeof(EcoSpace1));
  61:                 }
  62:             }
  63:             return typeSystemProvider;
  64:         }
  65:  
  66:         protected override ITypeSystemService GetTypeSystemProvider()
  67:         {
  68:             return EcoSpace1.GetTypeSystemService();
  69:         }
  70:     }
  71: }

You need to add references to ECO Silverlight assemblies, to all the Silverlight assemblies that will use ECO:

image

And The SilverlightApplication assembly needs also a reference to the SLEcoSpaceAndModel assembly.

Then we can initialize an EcoSpace instance, I choose to do it in the MainPage.xaml.cs:

   1: private SLEcoSpaceAndModel.EcoSpace1 _ecoSpace;
   2: public MainPage()
   3: {
   4:     InitializeComponent();
   5:     _ecoSpace = new SLEcoSpaceAndModel.EcoSpace1();
   6:     _ecoSpace.Active = true;
   7:     DequeuerSL.Active = true;

And to test things out I use a ViewModelUserControl:

   1: <UserControl x:Class="SilverlightApplication2.MainPage"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:     xmlns:ecovm="clr-namespace:Eco.ViewModel.Silverlight;assembly=Eco.Silverlight"
   7:     mc:Ignorable="d"
   8:     d:DesignHeight="300" d:DesignWidth="400">
   9:  
  10:     <Grid x:Name="LayoutRoot" Background="White">
  11:         <StackPanel>
  12:             <Button Click="Button_Click">
  13:                 <TextBlock Text="Add C1"></TextBlock>
  14:             </Button>
  15:             <Button Click="Button_Click_1">
  16:                 <TextBlock Text="Add C2"></TextBlock>
  17:             </Button>
  18:             <ecovm:ViewModelSLUserControl x:Name="ViewModelUserControl" 
  19:                                           ViewModelName="ViewModel1" ></ecovm:ViewModelSLUserControl>
  20:         </StackPanel>
  21:     </Grid>
  22: </UserControl>

And when using rendered ViewModels we also need to init them:

   1: ViewModelDefinitionsInApplication.Init(_ecoSpace);
   2: ViewModelUserControl.SetEcoSpace(_ecoSpace);

Implement the button event handlers:

   1: private int x = 0;
   2: private void Button_Click(object sender, RoutedEventArgs e)
   3: {
   4:     x++;
   5:     new Class1(_ecoSpace) { Attribute1 = "c1" + x.ToString() };
   6: }
   7:  
   8: private void Button_Click_1(object sender, RoutedEventArgs e)
   9: {
  10:     x++;
  11:     new Class2(_ecoSpace) { Name = "c2" + x.ToString() };
  12: }

And the result is a silverlight app that shares model and user code implementations with the server:

image

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