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.
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
RockfordLhotka:
... the data method itself (DataPortal_Create() or whatever) is obviously coded to run local or not ...
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.
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
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:
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.
Well I must be doing something wrong then, Is the FactoryLoader required on the client? I'm using a specific loader.
Regards
Kevin
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
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.
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)
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:
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).
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
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
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
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.
No probs, thanks. I was intrigued there for a second, you must be doing something magic!!
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.
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
Copyright (c) Marimer LLC