Why does object factory type is required to be found on the client side?

Why does object factory type is required to be found on the client side?

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


markell posted on Sunday, July 12, 2009

Hi.
The ObjectFactory attribute receives the object factory name and not its type for a good reason - not to couple the client side with the server side. However, inside the data portal there is an attempt to access the object factory type on the client side. Granted, it does not attempt to instantiate it on the client side, but still it requires that the object factory assembly be found on the client side. All of this is in order to check if the respective factory method is attributed with the RunLocal attribute.
It seems to be a bug. I suggest adding a new parameter to the ObjectFactoryAttribute - CheckFactoryType, which is false by default. Only if it is set will the client side data portal try to reflect on the actual factory type, otherwise it just assumes RunLocal as false and everything is fine.
What do you think?

RockfordLhotka replied on Monday, July 13, 2009

I almost can't believe I'm going to say this - but this is a feature not a bug :)

As you note, this behavior is necessary to make RunLocal work, and so it is intentional - I did think through this issue.

I am open to debate on alternative solutions. I'm a bit skeptical about a CheckFactoryType property, because that seems very cryptic and someone trying (and failing) to use RunLocal would probably never discover that they also need to apply CheckFactoryType to a totally different class.

markell replied on Monday, July 13, 2009

Well, it looks like a bug, because if the type is actually required and there is no way around it, then why does ObjectFactoryAttribute receives a string and not a Type parameter? This way everything is clear and the intentions are well communicated. But, it receives a string after all.

As of now, the need to examine the RunLocal attribute prevents clear separation of the client from the server - the client side demands server side assemblies, which is sometimes unacceptable. When I weigh the benefits of RunLocal versus client/server separation this is not even an issue - RunLocal is totally beaten.

However, the way is immaterial here. I will gladly adopt any other solution, which will allow one to achieve the aforementioned separation.
Thanks.

RockfordLhotka replied on Monday, July 13, 2009

RunLocal is invaluable, though, because many (probably most) Create() operations end up as RunLocal.

 

In Silverlight I used a different approach, where the data portal methods accept a parameter to indicate whether the code should run locally – which means the factory method has the knowledge of running local or remote. So no RunLocal attribute and no messy stuff with ObjectFactory, because this knowledge is centralized in the factory.

 

But even that’s not really perfect, because the data method itself (DataPortal_Create() or whatever) is obviously coded to run local or not – so technically the knowledge is still in two places: the factory and the data method itself.

 

Which is why RunLocal is the best overall answer – because that attribute is applied to the method, and the method is implemented to run locally (or not). So all the knowledge is centralized in that one location.

 

Rocky

 

rxelizondo replied on Saturday, January 09, 2010

RockfordLhotka:

... the data method itself (DataPortal_Create() or whatever) is obviously coded to run local or not ...


I am not sure its really that obvious. I mean, it is possible that code on a method such "DataPortal_Create()" to be location agnostic right?

For example:

private void DataPortal_Create()
{
if(ApplicationContext.ExecutionLocation == ApplicationContext.ExecutionLocations.Client)
{
// We are here because the User has very slow internet connection. So
// lets get place-holder values from some file cached in the local machine.
}
else
{
// The user has fast internet connection, we can go to the
// the server and get the real values.
}

In a case such as the one mentioned above, you would need to let the DataPortal know to go local or remote somewhere else other than with an attribute on the method. You would need to do something like what I think is done in CSLA for SilverLight.

RockfordLhotka replied on Sunday, January 10, 2010

If you have some RunLocal methods and some server-only methods, you'd need two DAL assemblies to contain these different methods.

You'd then deploy the RunLocal assembly to both client and server, and the server-only assembly to the server, not the client.

The data portal would succeed in finding the RunLocal attribute on the assemblies that do exist on the client, and would run that code on the client. It would obviously fail to find the types for the server-only code, and so RunLocal defaults to false and the server call is made.

The Silverlight model is different. I'm not sure it is better. It was an experiment of a sort, to try a different solution to the problem. I dislike it, because one concept is split into different places in the code.

You write your method, and you'll write it to be location neutral or not. So as you write the method, you know whether the method can run locally or only on the server - and putting a RunLocal attribute on the method makes sense, because it keeps that knowledge contained to the method.

In the SL model you write the method. But it is the method caller that says whether the method is location neutral or not. So the knowledge is split - the method is implemented correctly, and then in an entirely different part of your app you decide how the method is to be invoked. A developer can't look just one place to find how the method works and is invoked.

On the other hand, the SL model is easy to use and understand, and it completely avoids the need to reflect to find the RunLocal attribute. But I'm not convinced that the loss of elegance and maintainability is actually worth it.

Kevin Fairclough replied on Friday, July 09, 2010

This problem will also bite me in the future.

How about we have two attributes RemoteObjectFactory and LocalObjectFactory.  If the class is decorated with LocalObjectFactory then creations are done locally by either a specified factory or a default implementation.  RemoteObjectFactory is a rename of ObjectFactory (not essential but clearer).

[LocalObjectFactory(ProductFactory)]

[RemoteObjectFactory("Server.ProductFactory,Server")]

public class Product...

{

}

Then instead of having to touch the RemoteObjectFactory type you could just check for the existance of LocalObjectFactory decoration. 

 

However, it only really makes sense to Create locally nothing else.  So the attribute could be:

LocalCreation().  No parameters looks for DataPortal_Create in the BO or just creates an instance after security check.

LocalCreation(Type)

 

My Thoughts, I haven't looked at the Silverlight way.

Regards

Kevin

RockfordLhotka replied on Friday, July 09, 2010

You are absolutely not required to have the object factory assembly on the client.

The only times you need to have the object factory assembly on the client are:

  1. You are using a local (client-side) data portal, so the object factory code is running on the client
  2. You are using RunLocal on one or more object factory methods, so that code is running on the client

If you are using a remote data portal configuration, and you are not using RunLocal, then there is no need at all to have the object factory assembly on the client.

Kevin Fairclough replied on Monday, July 12, 2010

Well I must be doing something wrong then,  Is the FactoryLoader required on the client?  I'm using a specific loader.

Regards

Kevin

Kevin Fairclough replied on Monday, July 12, 2010

Well I don't get this at all.

Ok, FactoryLoader needs to be on the client, i'll have to split my data assembly for this.

I've looked at the source (including latest) and the factory type is required on the client.   The client DataPortal reflects it.

Unless I'm looking in the wrong place, which I probably am!

 

Regards

Kevin

JonnyBee replied on Monday, July 12, 2010

Hi Kevin,

Yes -  the factory loader should be in a separate assembly and available on the client.

But your ObjectFactory classes are not required to be present on the client, only those classes that contain methods tagged with RunLocal must be on the client in order to be executed there.

Kevin Fairclough replied on Tuesday, July 13, 2010

Thanks for the reply.

This is only true if the loader has the smarts to determine running location and not return the type that only exists on the server.  If we always return that type from GetFactoryType then a FileLoadException(see below) is raised because the type is not on the client when it scans for RunLocal attribute.

None of my factories have RunLocal.

The best solution for now seems to be configuring the client to use a different loader than the server.

Regards

Kevin

--Stack Trace--

System.IO.FileLoadException

   at System.RuntimeTypeHandle._GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark, Boolean loadTypeFromPartialName)
   at System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark)
   at System.RuntimeType.PrivateGetType(String typeName, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark)
   at System.Type.GetType(String typeName)
   at Csla.Server.ObjectFactoryLoader.GetFactoryType(String factoryName)
   at Csla.Server.DataPortalMethodCache.GetFetchMethod(Type objectType, Object criteria)
   at Csla.DataPortal.Fetch(Type objectType, Object criteria)
   at Csla.DataPortal.Fetch[T](Object criteria)

JonnyBee replied on Tuesday, July 13, 2010

 Kevin,

Have a look at the SimpleNTier (WinForms sample) and PagedList demo (Silverlight) for CSLA4.

Both samples use ObjectFactory and the ObjectFactory assemblies are NOT required to exist on the client.

Just so we can understand your code:

  1. What type of client are you using?
  2. Which version of CSLA are you using?
  3. Are you using the default ObjectFactoryLoader or you own?
  4. What is the Objectfactory attibute on your class?

And check that your application is configured to use remote dataportal.

Note that the default objet factory loader (or rather the .Net Type.GetType does not accept any white spaces in the class name)
So this one will fail: [ObjectFactory("Namespace.Type , Assembly")]  (due to whitespace before comma).

If you are using the SimpleDataPortal then the ObjectFactory assemblies must be in the bin folder of your client (and the would normally not be present as the Business assembly should not have any references to the object factories).

Kevin Fairclough replied on Tuesday, July 13, 2010

Solved!

In my custom factory loader there was a call to the following which needed removing:

return Type.GetType(parser.FactoryType, true);

This is throwing exceptions if the type is not found, Doh!  I'll have to check the rest of my factory as generic types are built to form a generic factory type. 

This method (GetFactoryType in ObjectFactory) must return null on the client, or a specific client factory.

Thanks for your help

Kevin

rfcdejong replied on Wednesday, July 14, 2010

Just to confirm that the assembly's with objectfactories are not needed at the client. A (large) project with 2000+ fte hours of work in it is running on a 3 tier setup where the client's don't have the server assemblies (containing the factory's)

And yes, our custom FactoryLoader is indeed available on the client, but there isn't an registration of it using appSettings CslaObjectFactoryLoader or in-code

Kevin Fairclough replied on Wednesday, July 14, 2010

Not sure I follow.  Why have a custom FactoryLoader on the client if you're not actually using it on the client?  Surely if it's not registerd in AppSettings the default ObjectFactoryLoader will be being used, therefore you don't need any loader on the client.

Kevin

rfcdejong replied on Wednesday, July 14, 2010

I didn't check page 2, sorry for the reply in that case.. didn't read the message that u solved it. Anyway the custom factoryloader is only available because it's in our own business layer assembly framework.business and the clients are using that. no big deal.

I'm glad u solved it.

Kevin Fairclough replied on Wednesday, July 14, 2010

No probs, thanks.  I was intrigued there for a second, you must be doing something magic!!

RockfordLhotka replied on Monday, July 13, 2009

Now that I've had my morning coffee, I should also point out that the "type name" parameter you provide to the ObjectFactory attribute is not actually a type name. It is just a string.

The default interpreter for that string (something called a factory loader) takes that string value and looks for the corresponding type.

But you can provide your own interpreter for that string, which can use that string in other ways.

One solution to the issue we're discussing here, is for you to create your own factory loaded for the client, that alters the string value slightly - possibly by altering the assembly name. So this:

[ObjectFactory("Namespace.Type, Assembly")]

would load this type: "Namespace.Type, AssemblyClient" because the factory loader just adds "Client" to the end of the assembly.

This allows you to have a client-only assembly with the RunLocal methods, and a server-only assembly with the rest of the methods. You end up with the separation of concerns you are looking for, and with all the local/remote functionality supported by the data portal.

In other words, you just have two DAL assemblies: Dal and DalClient. And you have a factory loader that is used only on the client (or only on the server - you could do it either way) that alters the assembly name as appropriate.

markell replied on Monday, July 13, 2009

OK. Let me get it straight. The alternative to the obscure boolean flag is to have another assembly and if I never use RunLocal in the first place, the second assembly is just sitting there to satisfy type resolution on the client side.
The factory loader will have to determine on which side it is running in order to mangle the given string on the client side and leave it as is on the server. Or there should be two factory loaders and the code returning one will decide which factory loader to return.

I guess I will prefer to stick with the obscure boolean flag. Its default value may leave the current semantics unchanged, but once client is separated from server (thus making local runs impossible) and the type resolution failures manifest themselves, one can simply turn the flag off (or on, depending on the perspective).

Thanks.

RockfordLhotka replied on Monday, July 13, 2009

LOL!

 

If we’re looking for architectural purity, using the IoC scheme supported by ObjectFactory with a factory loader is far more pure than a limited API scheme to address one narrow issue.

 

The primary motivation for people to use the more difficult ObjectFactory scheme in the first place is architectural purity and separation of concerns – so my laughter comes through the irony of using a more complex pure model, and then putting a hack in the API to simplify its use.

 

Barring others joining the discussion in favor of some sort of API change, my inclination is to leave things as they are. A lot of work went into making the object factory and factory loader concept super-flexible to support many different scenarios (including ones we can’t foresee), and I’m not ready just yet to implement a solution that feels like a one-off hack.

 

Rocky

 

markell replied on Monday, July 13, 2009

It is indeed a hack. I will keep it until a real solution arrives.
Thanks for the prompt replies.

Copyright (c) Marimer LLC