RunLocal with Factory pattern

RunLocal with Factory pattern

Old forum URL: forums.lhotka.net/forums/t/11805.aspx


aheuze posted on Tuesday, January 29, 2013

Hi,

My question is related to an older post (http://forums.lhotka.net/forums/p/7260/43634.aspx#43634) about using RunLocal attribute and the factory pattern.

We use Csla 4.3.13

We are trying to have 2 factory assemblies, one for the Client (with simple Create method with RunLocal attribute) and one for the server with database calls etc...
As proposed in the older post, we have a custom ObjectFactoryAttribute and a custom ObjectFactoryLoader. In our ObjectFactoryLoader we basically try to add a suffix ("Client") on the type proposed by the attribute and try to return this Type when running on the client. It seems to work fine at first, the type is created on the client and the method (with RunLocal attribute) is found, then a local proxy is created, but when doing the proxy.Create() call, then the call is directly forwarded to the Server, without trying to call the method that was just found on the client. It is as if the RunLocal attribute was of no use, though it appears that Csla is aware the method has to be run locally...

I suspect the DataportalClient calls the DataPortalServer, but still on the client. So maybe I shouldn't use the ApplicationContext.LogicalExecutionLocation as I am using now to check if i am running on the Client or on Server, especially if everything is running on one machine during the tests...

What we are trying to achieve is to be able to call a method "New" in the library or a method "NewLocal" and to have the method "NewLocal" create the object locally and the method "New" create the object on the server. But with the factory pattern, maybe it is just not possible, without the factoryLoader knowing how the call was made. The RunLocal attribute is for the factory, not the library...

Thanks for your inputs.

JonnyBee replied on Tuesday, January 29, 2013

You should NOT make different methodnames. This will not work .

You are fine to have 2 separate assemblies - and even use different class names to be returned from the ObjectFactoryLoader - but not different method names.

In general terms - the recommendation is to have separate assemblies that contain identical class / method names (and only the method that are applicable to the client in the client assembly) - just be aware of how you distribute the assemblies with your app.

So my recommendation is rather to have

and make  sure that mydalclient.dll only contain factory classes and methods that have "RunLocal" attribute.

It is also important to implement both methods in ObjectFactory correctly. Remember - the ObjectFactoryLoader has 2 methods that return:

but NOT the method to invoke - the method to invoke is defined in the ObjectFactoryAttribute on the Business Object.

This is OK:

    public class KundeInfoListFactoryClient : ObjectFactory
    {
        [RunLocal]
        public KundeInfoList Create()
        {
            var list = (KundeInfoList)Activator.CreateInstance(typeof(KundeInfoList), true);
 
            return list;
        }
 
    public class KundeInfoListFactory : ObjectFactory
    {
        public KundeInfoList Create()
        {
            var list = (KundeInfoList)Activator.CreateInstance(typeof(KundeInfoList), true);
 
            return list;
        }

But - you cannot have diffent mehod names and both calls to the ObjectFactoryLoader MUST return the
type and instance of the XYZClient class for CSLA DataPortal to find the RunLocal attribute. You should also note 
that this is not "switchable" witin the running app as the DataPortalMethod is cached internaly for performance.

My recommendation is however to make the class names and method names the same - and only differ on the assembly name.
This will also make it easier to use an IoC container in your ObjectFactoryLoader and only differ on which assemblies to load.

 

aheuze replied on Tuesday, January 29, 2013

Thank you,

I'm actually doing exactly what you are describing with 2 assemblies myDal.dll and myDalClient.dll and myDalClient.dll only contains factories with methods tagged with RunLocal attribute. The name of the classes are the same, the name of the methods are the same. Just the dll name (and namespace names) are different. And we also are using an IoC (MEF) for factories loading.
So I think my problem is in the implementation of the ObjectFactoryLoader.

I didn't find a good way to make my FactoryLoader return the type I wanted. this is what I do in GetFactoryType:

            if (ApplicationContext.ExecutionLocation.Equals(ApplicationContext.ExecutionLocations.Client))
                assembly = string.Concat(assembly, "Client");
            factoryClass = string.Concat(assembly, ".", factoryClass);
            Type factoryType = Type.GetType(string.Concat(factoryClass, ",", assembly));
           return factoryType;

And I tried things like this in GetFactory :

            object result = null;
            if (ApplicationContext.ExecutionLocation.Equals(ApplicationContext.ExecutionLocations.Client))
            {
                try
                {
                    result = Activator.CreateInstance(this.GetFactoryType(interfaceFactoryName));
                }
                catch { }
            }
            if (result == null)
                result = MefServices<IFactoryBase>.GetObjectFactory(interfaceFactoryName);
            return result;

But obviously, this is not the way it should be implemented. I actually don't know when I should return my call to Activator or my call to MefServices (i.e. based on what should I return the client factory or the server factory). When I manage to return the client factory in myDalClient.dll, other calls to Fetch methods for example that are not implemented in the client factory will fail. If I implement everything on the server, it works fine, but then I create objects on the server...

Is there a way to know it is a "RunLocal" call in the FactoryLoader ? My tests with ApplicationContext.ExecutionLocation and ApplicationContext.LogicalExecutionLocation tell me this is not the right way to do it.

Thanks for your help.

 

JonnyBee replied on Tuesday, January 29, 2013

You might want to look at my MEF samples in http://CslaContrib.codeplex.com

Basically - I would move the initialization app/web.config

IoC class:

  /// <summary>
  /// Provides access to the IOC Container shared by all applications.
  /// </summary>
  public static class Ioc
  {
    private static readonly object _syncRoot = new object();
 
    //Container
    private static volatile CompositionContainer _container;
 
    /// <summary>
    /// Gets the container.
    /// </summary>
    public static CompositionContainer Container
    {
      get
      {
        //create and configure container if one does not yet exist
        if (_container == null)
        {
          lock (_syncRoot)
          {
            if (_container == null)
            {
              Debug.Write("Start configuring MEF Container");
 
              //create container
              var catalog = new AggregateCatalog();
 
              var parts = ConfigurationManager.AppSettings.AllKeys.Where(p => p.StartsWith("CslaContrib.Mef.DirectoryCatalog"trueCultureInfo.InvariantCulture));
              if (parts.Any())
              {
                foreach (var values in parts.Select(part => ConfigurationManager.AppSettings[part].Split(';')))
                {
                  catalog.Catalogs.Add(values.Count() > 1
                                         ? new DirectoryCatalog(values[0], values[1])
                                         : new DirectoryCatalog(values[0]));
                }
              }
              else
              {
                catalog.Catalogs.Add(new DirectoryCatalog("."));
                catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
              }
              var container = new CompositionContainer(catalog, true);
              container.ComposeParts();
              _container = container;
 
              Debug.Write("End configuring MEF Container");
            }
          }
        }
        return _container;
      }
    }
 
    /// <summary>
    /// Injects the container. Use this for unit testing where you want to control the type reasolving.
    /// </summary>
    /// <param name="container">The container.</param>
    public static void InjectContainer(CompositionContainer container)
    {
      lock (_syncRoot)
      {
        _container = container;
      }
    }
  }
 
Config:
  <appSettings>
    <add key="CslaObjectFactoryLoader" value="CslaContrib.MEF.Server.CslaFactoryLoader, CslaContrib.MEF"/>
    <add key="CslaContrib.Mef.DirectoryCatalog1" value=".;*FactoryClient.dll"/>
    <!--  Use this for Service - NTier deployment to load factories -->
    <!--<add key ="CslaContrib.Mef.DirectoryCatalog2" value="bin\.;*Factory*.dll"/>-->
  </appSettings>
ObjectFactoryLoader: 
  public class CslaFactoryLoader : IObjectFactoryLoader
  {
    private static object _syncRoot = new object();
    /// <summary>
    /// Gets the type name from the factory name.
    /// </summary>
    /// <param name="factoryName">Name of the factory.</param>
    /// <returns></returns>
    private string GetTypeName(string factoryName)
    {
      if (string.IsNullOrEmpty(factoryName)) return string.Empty;
 
      var values = factoryName.Split(',');
      return values[0];
    }
 
 
    /// <summary>
    /// Gets the factory object instance.
    /// </summary>
    /// <param name="factoryName">Name of the factory.</param>
    /// <returns></returns>
    public object GetFactory(string factoryName)
    {
 
      var typename = GetTypeName(factoryName);
 
      lock (_syncRoot)
      {
        var parts =
          Ioc.Container.Catalog.Parts.Where(part => part.ExportDefinitions.Any(item => item.ContractName == typename)).
              Select(p => p).ToArray();
 
        if (parts.Count() == 1)
        {
          var part = parts[0].CreatePart();
          Ioc.Container.SatisfyImportsOnce(part);  // compose the object
          object obj = part.GetExportedValue(part.ExportDefinitions.First(item => item.ContractName == typename));
          return obj;
        }
 
        if (parts.Count() > 1)
        {
          throw new InvalidOperationException(string.Format(Resources.MoreThanOneFactoryTypeDefinedException,
                                                            factoryName));
        }
      }
 
      throw new InvalidOperationException(
          string.Format(Csla.Properties.Resources.FactoryTypeNotFoundException, factoryName));
    }
 
    /// <summary>
    /// Gets the type of the factory.
    /// </summary>
    /// <param name="factoryName">Name of the factory.</param>
    /// <returns></returns>
    public Type GetFactoryType(string factoryName)
    {
      // return an instance of the Interface 
      // use RunLocal on the interface definition - rather than the actual class. 
      return Type.GetType(factoryName, false);
    }
  }


My preferred solution is to add RunLocal to the interface definition of my ROOT Factories like this: 

  public interface IMyRootFactory
  {
    [RunLocal]
    object Create();
 
    object Fetch(object criteria);
  }

Usage: 
  [Serializable]
  [ObjectFactory(typeof(IMyRootFactory))]
  public class MyRoot : BusinessBase<MyRoot>


Again - my preferences: 
  • [RunLocal] on the interface as part of the Contract 
    (will not have any effect on the server side as everything is run locally and simplifies
    the GetFactoryType method)
  • Then only a matter of which assembly is loaded into the IoC container and resolved in GetFactory. 

ngm replied on Tuesday, January 29, 2013

This is interesting problem, which should be solvabe without IoC.

Essentially, the issue you've got is that for client Data Portal to know whether to use local or remote proxy it has to get ObjectFactory through ObjectFactoryLoader. But for ObjectFactoryLoader to return proper ObjectFactory it needs to know whether the request will be executed through local or remote proxy.

Pretty much, you've got chicken and egg problem here ;)

If I recall correctly, there are two major points where CSLA interacts with ObjectFactoryLoader / ObjectFactory on the Data Portal. The first one is on the client Data Portal which has to retrieve appropriate method from ObjectFactory in order to distinguish between local or remote proxy. The second one is on the server Data Portal which has to retrieve the actual ObjectFactory instance in order to execute the data access.

Latter wll set both ExecutionLocation (if proxy is remote) and LogicalExecutionLocation of ApplicationContext and former will not. That's the very same reason you cannot use that context in ObjectFactoryLoader to return proper ObjectFactory type - the context is not established yet.

As long as I see there are several solutions.

You could use different ObjectFactoryLoader types on client and server, where client's loader should return factory that has only implementation for create which is marked as RunLocal and the rest of data access methods should not have an implementation at all. The server's loader should return factory that has no implementation for create but only for the rest of data access methods.

The other option should be to discover and invoke client and server data access components from within ObjectFactory. On that way you would have single ObjectFactoryLoader / ObjectFactory but where actual data access methods of the factory would create proper instances of client or server data access components. Within factory's data access methods you should have LogicalExecutionLocation established.

Also as Jonny pointed out, the full MEF approach should be very robust solution and it's variation on first option.

- ngm

aheuze replied on Tuesday, January 29, 2013

Hi! Thank you all for your inputs.

I think that my problem was the chicken and egg problem as pointed out by ngm. I found the 2 calls, the first one for the Type, to find the [RunLocal] attribute, the second to get the actual objectFactory and I couldn't understand how it was possible to do it as simply as it was described in the original post.

I think the 2 solutions for this problem, having 2 different objectfactoryLoader (so 2 different app.config) between client and server isn't really a nice option, it might be a good choice however for Mono. The other option makes the objectFactory responsible for too many things I think.

Though it is maybe possible to implement this functionnality without IoC, I believe that letting the container handle the choice is nicer.

The problem with IoC is when it has to be deployed on Mono or iOS. I believe we'll figure it out then.

Thanks again.

 

JonnyBee replied on Wednesday, January 30, 2013

Hi,

You should also remember that Silverlight, Windows Phone and Windows Store (Metro) has do database connectivity and are separate runtimes as to what the server side is running (preferrably .NET). So for many applications you MUST have separate assemblies. 

My preferred solution is to have [RunLocal] attribute on the Interface as I view this as part of the contract.. This approach also lets me use the SAME objectfactory assembly on the client and the server when the client is .NET and the DataPortal Config is the only setting that needs to change between local only or make the non-RunLoacl methods execute on the server. This could also be done by using the same assembly and add RunLocal on the methods that always run on the client.

MEF is available in MONO starting from MONO 2.8. http://www.mono-project.com/Release_Notes_Mono_2.8

There is no reason why only the "client" side class should have the [RunLocal] atribute and not the server side.  In an N-tier configuration you configure whether the DataAccess happends locally or on another serverC/machine - and the [RunLocal] atribute is checked by the DataPortal as an override to force the method to run locally (and not need any data access to initialize a Create method). 

So from my point of view - there is NO reason why the ObjectFactoryLoader should have any reponsibility beyond resolving which class / type to return.

 

aheuze replied on Wednesday, January 30, 2013

Yes, I like the idea of putting the [RunLocal] attribute on the interface. It simplifies everything and makes the code easier when using Factory interfaces and MEF.

Having a CslaContrib.Mef.DirectoryCatalog.Client or Server setting is also ok. I just feel that it could be simplified one step further, because we only need to add "Client" when we are on the client, so ideally, only one setting is actually required for the app. But it seems to be difficult to implement the server/client logic in the FactoryLoader as pointed out by ngm.

I'm happy to see that MEF is also available in MONO. That will make things (hopefully) easier !

Cheers,

JonnyBee replied on Wednesday, January 30, 2013

Hi,

That setting in app.config is to support:

 

 

So in a web/server app you MUST specify that assemblies is to be loaded from the "bin" folder.

In my simplified world there is no requirement for a specific client assembly (different assembly  name/class name/method name)  in a .NET application.

I either use the same assembly as on the server side or a client specific assembly with the same name to keep the app.config the same. 

Copyright (c) Marimer LLC