views:

236

answers:

3

Hi everybody! I want to implement multi tenancy using Windsor and i don't know how to handle this situation:

i succesfully used this technique in plain ASP.NET MVC projects and thought incorporating in a RIA Services project would be similar.

So i used IHandlerSelector, registered some components and wrote an ASP.NET MVC view to verify it works in a plain ASP.NET MVC environment. And it did!

Next step was to create a DomainService which got an IRepository injected in the constructor. This service is hosted in the ASP.NET MVC application. And it actually ... works:i can get data out of it to a Silverlight application.

Sample snippet:

public OrganizationDomainService(IRepository<Culture> cultureRepository)
{
            this.cultureRepository = cultureRepository;
}

Last step is to see if it works multi-tenant-like: it does not! The weird thing is this: using some line of code and writing debug messages in a log file i verified that the correct handler is selected! BUT this handler seems not to be injected in the DomainService. I ALWAYS get the first handler (that's the logic in my SelectHandler)

Can anybody verify this behavior? Is injection not working in RIA Services? Or am i missing something basic??

Development environment: Visual Studio 2010 Beta2

Thanks in advance

A: 

Hi Mauricio! Yes i have seen this thread and i already have implemented this. Firstly have in mind that i have used this line in Global.asax.cs to get the RIA services properly behave (hosted in an ASP.NET MVC view)

 routes.IgnoreRoute("{*allsvc}", new { allsvc = @".*\.svc(/.*)?" }); 

Here is some code:

public class HostBasedComponentSelector : IHandlerSelector
    {
        private readonly Type[] selectableTypes;

        public HostBasedComponentSelector(params Type[] selectableTypes)
        {
            this.selectableTypes = selectableTypes;
        }



        public bool HasOpinionAbout(string key, Type service)
        {
            foreach (var type in selectableTypes)
            {
                if (service == type) return true;
            }
            return false;
        }

        public IHandler SelectHandler(string key, Type service, IHandler[] handlers)
        {   
            //only for debug
            StreamWriter sw = new StreamWriter(@"c:\temp\Debug.log",true);
            sw.WriteLine(DateTime.Now + " " + service.Name + " " + GetHostname() );
            sw.WriteLine("Available handlers");
            foreach(IHandler h in  handlers )
            {
                sw.WriteLine ("Handler "+h.ComponentModel.Name);
            }

            var id = string.Format("{0}:{1}", service.Name, GetHostname());
            var selectedHandler = handlers.Where(h => h.ComponentModel.Name == id).FirstOrDefault() ??
                                  GetDefaultHandler(service, handlers);

            sw.WriteLine("Selected handler " + selectedHandler.ComponentModel.Name);
            sw.WriteLine("----------- END ----------");
            sw.Flush();
            sw.Close();


            return selectedHandler;
        }
        private IHandler GetDefaultHandler(Type service, IHandler[] handlers)
        {
            if (handlers.Length == 0)
            {
                throw new ApplicationException("No components registered for service {0} With service.Name" + service.Name);
            }
            return handlers[0];
        }

        protected string GetHostname()
        {
            return HttpContext.Current.Request.ServerVariables["SERVER_NAME"];
        }

    }

Here is the complete web.config. Notice registering the OrganizationDomainServiceFactory (it is the implementation of the article you mentioned)

<?xml version="1.0"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=152368
  -->
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler,Castle.Windsor"/>
  </configSections>
  <connectionStrings>
    <add name="ApplicationServices" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <castle>
    <properties>
      <sqlConnStr>
        <!--metadata=res://*/WebShop.csdl|res://*/WebShop.ssdl|res://*/WebShop.msl;provider=System.Data.SqlClient;provider connection string="Data Source=.\SQLEXPRESS;Initial Catalog=iWebShop;User ID=sa;Password=xxx;MultipleActiveResultSets=True"-->
      </sqlConnStr>
    </properties>
    <components>          
      <component id="CommonObjectContext" service="TestRIA1.Abstract.IObjectContext, TestRIA1" type="TestRIA1.Concrete.ObjectContextAdapter, TestRIA1" lifestyle="PerWebRequest">
      </component>

      <component id="IConnectionStringProvider:test.gammasys.gr" service="TestRIA1.Abstract.IConnectionStringProvider, TestRIA1" type="TestRIA1.Concrete.ConnectionStringProvider, TestRIA1" lifestyle="transient">
        <parameters>
          <ConnectionString>
            metadata=res://*/WebShop.csdl|res://*/WebShop.ssdl|res://*/WebShop.msl;provider=System.Data.SqlClient;provider connection string="Data Source=.\SQLEXPRESS;Initial Catalog=iWebShop;User ID=sa;Password=xxx;MultipleActiveResultSets=True"
          </ConnectionString>
        </parameters>
      </component>

      <component id="IConnectionStringProvider:test.deltasys.gr" service="TestRIA1.Abstract.IConnectionStringProvider, TestRIA1" type="TestRIA1.Concrete.ConnectionStringProvider, TestRIA1" lifestyle="transient">
        <parameters>
          <ConnectionString>
            metadata=res://*/WebShop.csdl|res://*/WebShop.ssdl|res://*/WebShop.msl;provider=System.Data.SqlClient;provider connection string="Data Source=.\SQLEXPRESS;Initial Catalog=iWebShop2;User ID=sa;Password=xxx;MultipleActiveResultSets=True"
          </ConnectionString>
        </parameters>
      </component>

      <component id="Commonrepository" service="TestRIA1.Abstract.IRepository`1, TestRIA1"   type="TestRIA1.Concrete.Repository`1, TestRIA1" lifestyle="PerWebRequest"/>

      <component id="urlbased.handlerselector" service="Castle.MicroKernel.IHandlerSelector, Castle.MicroKernel" type="TestRIA1.HostBasedComponentSelector, TestRIA1" lifestyle="transient">
        <parameters>
          <selectableTypes>
            <array>
              <item>TestRIA1.Abstract.IConnectionStringProvider, TestRIA1</item>
            </array>
          </selectableTypes>
        </parameters>
      </component>

    </components>
  </castle>
  <system.web>
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      </assemblies>
    </compilation>
    <authentication mode="Forms">
      <forms loginUrl="~/Account/LogOn" timeout="2880"/>
    </authentication>
    <membership>
      <providers>
        <clear/>
        <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="ApplicationServices" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/"/>
      </providers>
    </membership>
    <profile>
      <providers>
        <clear/>
        <add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/"/>
      </providers>
    </profile>
    <roleManager enabled="false">
      <providers>
        <clear/>
        <add name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="ApplicationServices" applicationName="/"/>
        <add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/"/>
      </providers>
    </roleManager>
    <pages>
      <namespaces>
        <add namespace="System.Web.Mvc"/>
        <add namespace="System.Web.Mvc.Ajax"/>
        <add namespace="System.Web.Mvc.Html"/>
        <add namespace="System.Web.Routing"/>
      </namespaces>
    </pages>

    <httpHandlers>
      <add verb="*" path="*.mvc" validate="false" type="System.Web.Mvc.MvcHttpHandler"/>

    </httpHandlers>
    <httpModules>
      <add name="PerRequestLifestyle" type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule,Castle.MicroKernel " />
      <add name="DomainServiceModule" type="System.Web.Ria.Services.DomainServiceHttpModule, System.Web.Ria, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />

    </httpModules>
  </system.web>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules runAllManagedModulesForAllRequests="true">
      <add name="DomainServiceModule" preCondition="managedHandler"
       type="System.Web.Ria.Services.DomainServiceHttpModule, System.Web.Ria, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      <remove name="PerRequestLifestyle"/>
      <add name="PerRequestLifestyle" preCondition="managedHandler" type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule,Castle.MicroKernel" />
      <!--to get IoC initialization of DomainService -->
      <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      <add name="OrganizationDomainServiceFactory" type="TestRIA1.OrganizationDomainServiceFactory"/>
      <!-- End of IoC initial.....  -->

    </modules>
    <handlers>
      <remove name="MvcHttpHandler" />
      <add name="MvcHttpHandler" preCondition="integratedMode" verb="*"
       path="*.mvc" type="System.Web.Mvc.MvcHttpHandler" />
    </handlers>
  </system.webServer>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
  </system.serviceModel>
</configuration>

Hope this is enough. In case you would like to have the complete project i can send you a copy (this is a pre-production test project).

Thank you very much for the time you spend!

Savvas Sopiadis
How does OrganizationDomainServiceFactory know about Windsor? Are you using a static IoC gateway? The article does a `Activator.CreateInstance<TServiceLocator>()` which doesn't seem right.
Mauricio Scheffer
A: 

So it seems i did a very weird thing in my OrganizationDomainServiceFactory. The code which did NOT work is this:

public DomainService CreateDomainService(Type domainServiceType, DomainServiceContext context  )
{
WindsorContainer container = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
IRepository<Culture> cultureRepository = container.Resolve<IRepository<Culture>>();
IRepository<Currency> currencyRepository = container.Resolve<IRepository<Currency>>();

DomainService ds = (DomainService)Activator.CreateInstance(domainServiceType, new object[] { cultureRepository,currencyRepository });

ds.Initialize(context);
return ds;
}

This is apparently not working, because of the creation of a new Container (which should not take place).

OK! So i thought i try to use ServiceLocator to get a reference to the Windsor Container (used in the WindsorControllerFactory - that's how i call it ... in the boot up of the ASP.NET MVC application), and changed the code to this:

public DomainService CreateDomainService(Type domainServiceType, DomainServiceContext context  )
{
IRepository<Culture> cultureRepository = ServiceLocator.Current.GetInstance<IRepository<Culture>>();
IRepository<Currency> currencyRepository = ServiceLocator.Current.GetInstance<IRepository<Currency>>();

DomainService ds = (DomainService)Activator.CreateInstance(domainServiceType, new object[] { cultureRepository,currencyRepository });

ds.Initialize(context);
return ds;
}

and guess what: it works(!!!) multi-tenancy as it should be!

The only thing i don't know is: is there another way to "inject" the container (constructor injection seems not to work here , the compiler complains)

BTW: moved the project from VS2010Beta2 to VS2010RC (with RIA Services support), but this should not affect the outcome!

Savvas Sopiadis
A: 

Sawas, I am acutally asking a question here : did you have to compile Castle Windsor for net 4.0, or you just used the one compiled against 3.5.

Kaveh Shirgir
Used directly the one for 3.5. Worked well without problems!
Savvas Sopiadis

related questions