CSLA DataPortal and MEF

CSLA DataPortal and MEF

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


omel posted on Tuesday, December 14, 2010

Im using MEF to instantiate the my DataAccess (CategoryDataAccess), but the DataPortal always re-instantiate the class clearing all catalogs/information in MEF container. L

Here is the BusinessObject code

 

using System.ComponentModel.Composition;
using Csla;
using CslaMvvm.DataAccess;
 
namespace CslaMvvm.BusinessObjects
{
    [Export]
    public partial class CategoryList
    {
 
        //[ImportingConstructor]
        //public CategoryList(ICategoryDataAccess categoryDataAccess)
        //{
        //    CategoryDataAccess = categoryDataAccess;
        //}
 
        [Import(typeof(ICategoryDataAccess))]
        public ICategoryDataAccess CategoryDataAccess { get; set; }
 
        #region Data Access
 
        private void DataPortal_Fetch()
        {
            var categories = CategoryDataAccess.GetAll();
            foreach (var category in categories)
                Add(Category.GetCategory(category));
 
        }
         
        #endregion
         
        public CategoryList GetCategoryList()
        {
            return DataPortal.Fetch<CategoryList>();
        }
 
    }
}

 

 

But everytime I use this class, the DataPortal_Fetch() method reinstatiate the  CategoryList class. Here is my unit test code.

 

using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
 
namespace CslaMvvm.BusinessObjects.UnitTest
{
 
    [TestClass]
    public class CategoryUnitTest
    {
        private readonly CompositionContainer _container;
 
        public CategoryUnitTest()
        {
 
            var catalog = new AggregateCatalog();
 
            catalog.Catalogs.Add(new DirectoryCatalog("."));
            catalog.Catalogs.Add(new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly()));
            _container = new CompositionContainer(catalog);
             
            _container.ComposeParts(this);
             
        }
 
        [Import]
        private CategoryList CategoryList { get; set; }
 
        [TestInitialize]
        public void Initilialize()
        {
 
        }
 
        [TestMethod]
        public void ShouldGetAllCategory()
        {
            var items = CategoryList.GetCategoryList();
            Assert.IsTrue(items.ToList().Count() > 1);
        }
    }
}

 

Does CSLA supports DI specially for MEF

RockfordLhotka replied on Tuesday, December 14, 2010

The server-side data portal is designed to be stateless. But a lot depends on how you host the server-side components as to whether that's really true.

Normally you host the server-side data portal in IIS, running ASP.NET, which hosts WCF. So really your server-side code is subject to the rules of ASP.NET more than anything else.

As each client request to the server comes in, ASP.NET/WCF assign it a thread and go through a sequence of processing, then the data portal is called, and finally the data portal calls your code.

I don't know where MEF stores its catalog, but to use any DI or IoC container on the server, you need your container/catalog to be maintained across multiple client requests. This is just normal web server state management and has nothing to do with the data portal really. Basically you need to get the catalog stored at the AppDomain level if you want it to stick around for any length of time. You can do this by putting it in a static singleton, or in the ASP.NET Application object (as examples).

Unit test frameworks are also typically designed to isolate each test. In your test you create the catalog in the test class ctor. It is quite possible that your unit testing framework is creating a new instance of your test class to run each test - and of course that would mean your catalog would be lost in between each test because it is in an instance field.

omel replied on Tuesday, December 14, 2010

Correct me if I'm wrong but i believe it did not travel from the thread of IIS. I just reference the BusinessObjectAssembly directly in the unit test. I compose the catalog in the UnitTest constructor and only 1 unit test to be executed. (by the way i also put it on the TestInitialize but no success it's the same). 

Here is my observation during debugging, 

  1. The class are being composed for CategoryList, ICategoryDataAccess during the call of "var items = CategoryList.GetCategoryList();" from ShouldGetAllCategory test method. see the image
  2. If I enable the businessobjects constructor public CategoryList(ICategoryDataAccess categoryDataAccess) with ImportingConstructor attribute the unit test goes on this constructor and import the dataaccess successfully.
  3. Then it executes the factory method GetCategoryList from the BusinessObject, it call's the DataPortal.Fetch, after a few moment (maybe because it uses csla.reflection) it will try to call the  DataPortal_Fetch, but before calling that code it complains about the missing constructorless ctor. And because i don't have that, it throws exception. see the image
  4. And from the observation, i figure out that's the reason why the catalogs are now empty. MAYBE the csla.reflection re-instantiates the business object not in MEF way. see the image

 

They are my observation hope you have an idea how it can be solved..

 

:(

RockfordLhotka replied on Wednesday, December 15, 2010

Sorry, I thought you were talking about an actual runtime scenario.

If you are only talking about a unit test scenario, then the key lies in how the unit test framework manages setup for each test method, and what state is preserved between test method calls.

The data portal, when running in local mode, does serialize/deserialize the business object to ensure isolation of the "server-side" state. Typically this is done by the BinaryFormatter, which certainly does use a special way of creating objects - something very deep in the .NET framework actually (and very cool from a geeky perspective).

You should never put anything into a DataPortal_XYZ method that can't be stateless. The whole point of the data portal is to support stateless server models, where each data portal call is isolated from any other data portal calls.

Again, if you want shared state of some sort that exists across data portal calls, that's your responsibility within your hosting environment. So if you want your catalog to have a lifetime independent of an individual data portal call, you need to figure out how to keep the catalog in memory within your specific hosting scenario.

In your case the hosting scenario is the test runner, and various test runners work differently - and not all of them have any way to maintain state between test methods - because they too are trying to achieve isolation so tests don't collide with each other.

omel replied on Wednesday, December 15, 2010

Holy shet....

 

In that case, can you give a simple solution how can Inject the DataAcccess easily from the Business Object. Or the ObjectFactory is the only solution. I want to harness the power of DataPortal_XYZ implementation for managing the state of the BusinessObject. 

JonnyBee replied on Wednesday, December 15, 2010

Hi,

Start with reading Rockys post and understand his points and what/where you should hook in.

I'd suggest then to do the following:

1. Create a static class to hold your CompositionContainer. This will enable you to keep the CompositionContainer over DataPortal calls and remember, the DataPortal might be a WCFService and a separate intance from your application.

2. Create InjectableBase classes for Csla objects. These should override DataPortal_OnDataPortalInvoke, Child_OnDataPortalInvoke and maybe even OnDeserialized to inject dependencies into your BOs. 

3. Make sure injected properties are private backing fields and marked with both NotUndoable and NonSerialized.

4. Using intermediate base classes to hide the overrides makes a very clean implementation -  you just declare the Imports in your BOs.

See attached sample - just one single WinForm solution that uses MEF and introduces a intermediate class for BusinessBase (InjectableBusinessBase) and you can create the other classes youself.  You may share these on CslaContrib !! 

My sample /post assumes that you are using a repository pattern - if you choose to use ObjectFactory then implement your own ObjectFactoryLoader that uses MEF to get the actual ObjectFactory class.

omel replied on Wednesday, December 15, 2010

This is great idea.

RockfordLhotka replied on Wednesday, December 15, 2010

The thing is that the DataPortal_XYZ models involve the data portal creating the business object instance, so MEF won't be able to do this. If you need MEF to create the business object instance then you must use the object factory models.

But (having used MEF for a few things) I don't think you need or want MEF creating the business object instances. That way (in my experience) leads to madness. You just want MEF to create instances of the DAL objects, and so you just need to use some static singleton to contain the MEF catalog so its lifetime is at the AppDomain level, not at the business object lifetime level (since on an app server, business objects have a lifetime usually measured in fractions of a second).

JonnyBee replied on Wednesday, December 15, 2010

Hi,

And if you look at the sample attached to my previous post you will see that Export is not required on your BO in order to compose the repository data access.

The sample shows how to inject data access that will work with both SimpleDataPortal (local mode) and remote data Portals.

RockfordLhotka replied on Wednesday, December 15, 2010

Also, looking at your code it appears that you might be trying to use MEF to load business types? I very much doubt that'll work, because I doubt MEF will work across serialization boundaries (though I've never tested such a thing).

I thought you were using MEF to load your DAL types, which you'd then use to load your business objects with data.

Actually I'm just generally confused. You don't appear to have anything that is a subclass of a CSLA base class - so you have no business types at all (at least not CSLA ones)?

Let me take a different approach.

I think you should read Chapters 17 and 18 of the Expert 2008 Business Objects book to get a good understanding of what the data portal is doing. And probably Chapter 15 too.

The data portal supports 4 data access models.

If you are using DataPortal_XYZ methods (2 of the models) then the data portal creates an instance of your type and tells that instance to load itself with data. If you really are trying to get MEF to create instances of your business type, then these models won't work for you.

But there's the ObjectFactory technique (2 of the models) where the data portal creates an instance of a factory object that you create. Your factory object is responsible for creating the business object instance. I suspect you could use MEF in this case, because the factory object could just ask MEF for the instance.

omel replied on Wednesday, December 15, 2010

Also, looking at your code it appears that you might be trying to use MEF to load business types? I very much doubt that'll work, because I doubt MEF will work across serialization boundaries (though I've never tested such a thing).

I need to do this (CategoryList, needs to be attributed as Export) so that i can recompose the ICategoryDataAccess under the business object, and as you can see from the image from my previous post. Everything was composed on the first try. But after calling the DataPortal_XYZ, it was lost.

 

Actually I'm just generally confused. You don't appear to have anything that is a subclass of a CSLA base class - so you have no business types at all (at least not CSLA ones)?

Hahaha.. Sorry for that if I did not include that in the post. Actually it is located in ANOTHER PARTIAL CLASS, and it seems to be working by that design.

 

I thought you were using MEF to load your DAL types, which you'd then use to load your business objects with data.

Yes your correct with it. I need to load the DataAccess so i can easily mock this also.

 

I think you should read Chapters 17 and 18 of the Expert 2008 Business Objects book to get a good understanding of what the data portal is doing. And probably Chapter 15 too.

Yes i check this book, but i'll get another try to read it. 

 

Thanks

 

Copyright (c) Marimer LLC