Spring.NET, C#, inyección de dependencia y transacciones

Actualmente estoy iniciando el desarrollo de un proyecto ASP.NET utilizando MVC3, Spring.NET y FluentNHibernate.

Mi experiencia es principalmente Java, pero con cierta exposición a Spring Framework, por lo que Spring.NET debería ser fácil, ¿verdad?

La arquitectura de la aplicación es bastante simple, con controladores que usan servicios, servicios que usan DAO y DAO que usan entidades y asignaciones de NHibernate.

Por el momento, me estoy rascando la cabeza en el escenario actual:

Uno de mis métodos de servicio usa un Dao, que se inyecta usando spring.net. Cuando anoto el método de servicio con el atributo [Transacción], la dependencia de DAO deja de inyectarse en mi servicio, lo que provoca NullReferenceExceptions.

Cualquier idea de por qué esto podría suceder será muy apreciada.

Algo de código para ilustrar el problema. Este no es el código real, pero está bastante cerca. Primero, la clase de prueba abstracta, que extiende la clase de primavera AbstractTransactionalSpringContextTests:

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Namespace.Dao;
using Namespace.Entities;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Spring.Testing.Microsoft;

namespace Namespace.Tests.Services
{

  [TestClass]
  public class AbstractServiceTest : AbstractTransactionalSpringContextTests
  {
    public AbstractServiceTest()
    {
    }


    protected override string[] ConfigLocations
    {
      get { return new string[]
          {
             "assembly://Assembly/Namespace.Config/dao.xml",
             "assembly://Assembly/Namespace.Config/Services.xml",
             "assembly://Assembly/Namespace.Config/web.xml"
          }; 
      }
    }
  }
}

Esta clase se amplía para crear la clase de prueba real:

using System.Collections.Generic;
using Namespace.Entities;
using Namespace.Services.Assets;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Namespace.Tests.Services
{
  [TestClass]
  public class AssetsServiceTest : AbstractServiceTest
  {

    public AssetsServiceTest()
    {
    }

    private AssetsService assetsService;

    public AssetsService AssetsService
    {
      get { return assetsService; }
      set { assetsService = value; }
    }


   [TestMethod]
   public void TestGetFacilities()
   {
     Assert.IsNotNull(assetsService);

     /* THIS ASSERTION FAILS when assetsService.GetFacilities has the [Transaction] attribute */ 
     Assert.IsNotNull(assetsService.FacilityDao);  

     IList<Facility> facilities = assetsService.GetFacilities();
     Assert.IsNotNull(facilities);
   }
  }
}

Y aquí está la clase de servicio:

using System.Collections.Generic;
using Namespace.Dao;
using Namespace.Entities;
using Namespace.Models;
using Spring.Transaction;
using Spring.Transaction.Interceptor;
using Facility = Namespace.Entities.Facility;

namespace Namespace.Services.Assets
{
  public class AssetsService
  {
    public AssetsService()
    {
     System.Console.Out.WriteLine("AssetsService created");
    }

    private FacilityDao _facilityDao;

    public FacilityDao FacilityDao
    {
      get
      {
        return _facilityDao;
      }
      set
      {
        _facilityDao = value;
      }
    }

    [Transaction(TransactionPropagation.Required, ReadOnly = true)]
    public IList<Facility> GetFacilities()
    {
      return _facilityDao.FetchAll();
     }
  }
}

Y finalmente un web.xml/applicationContext condensado y combinado:

<?xml version="1.0" encoding="utf-8"?>
<objects xmlns="http://www.springframework.net">

 <db:provider id="DbProvider"
     provider="System.Data.SqlClient"
     connectionString="Data Source=localhost;...;"/>

  <object id="transactionManager"
        type="Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate32">

    <property name="DbProvider" ref="DbProvider"/>
    <property name="SessionFactory" ref="SessionFactory"/>

</object>

 <tx:attribute-driven transaction-manager="transactionManager"/>

 <object type="Namespace.Dao.FacilityDao, Namespace" id="FacilityDao">
    <property name="SessionFactory" ref="SessionFactory"/>
 </object>

 <object type="Namespace.Services.Assets.AssetsService, Namespace" id="AssetsService">
   <property name="FacilityDao" ref="FacilityDao" />
 </object>

</objects>

EDIT:

Gracias a los consejos de Max y Marijn, ahora cambié mi AssetsService para usar una interfaz en lugar de la implementación real. Sin embargo, el problema persiste. Aquí están los detalles revisados.

namespace Namespace.Services.Assets
{
  public class AssetsService
  {
    public AssetsService()
    {
      System.Console.Out.WriteLine("AssetsService created");
    }

    public IFacilityDao FacilityDao { get; set; }

    [Transaction(TransactionPropagation.Required, ReadOnly = true)]
    public IList<Facility> GetFacilities()
     {
      return FacilityDao.FetchAll();
    }

  }
}

El IFacilityDao:

namespace Namespace.Dao
{
    public interface IFacilityDao : IDao<Facility>
    {}

    public class FacilityDao : DaoBase<Facility>, IFacilityDao
    {

    }
}

Idao:

namespace Namespace.Dao
{
  public interface IDao<T>
  {
    T Fetch(int id);
    T FindByName(string name);
    IList<T> FetchAll();
    void SaveOrUpdate(T entity);
    void Delete(T entity);
  }
}

Base Dao:

namespace Namespace.Dao
{
    public abstract class DaoBase<T> : IDao<T>
    {
        public ISessionFactory SessionFactory { get; set; }

        public T Fetch(int id)
        {
            var result = SessionFactory
                      .GetCurrentSession()
                      .CreateCriteria(typeof(T))
                      .Add(Restrictions.Eq("ID", id))
                      .UniqueResult<T>();

            return result;
        }

     //.... 
}

} FacilityDao no es proxy

preguntado el 03 de mayo de 12 a las 08:05

2 Respuestas

Nota: vea la actualización a continuación

Tus AssetService.FacilityDao el miembro debe ser de un tipo de interfaz en lugar del concreto FacilityDao escribe; de lo contrario, spring.net no podrá crear un proxy de transacción para la intercepción. Intenta abstraer un IFacilityDao interfaz y cambiar su AssetService a algo como:

public class AssetsService
{
  public IFacilityDao FacilityDao { get; set; }
  // snip...
}

Noticias

El problema es que tu AssetService.GetFacilities el método no puede ser proxy; intente hacerlo virtual (spring crea un proxy basado en decorador que representa todos público virtual métodos si no hay interfaces para proxy); y si eso falla, intente introducir un IAssetService interfaz de una manera similar a la que sugerí anteriormente para el dao. e inyectarlo en un IAssetService propiedad de su prueba.

Lo siento, me lo perdí antes.

Alguna explicación adicional

el fracaso Assert.IsNotNull(assetsService.FacilityDao); proviene del hecho de que el AssetService instancia se inyecta en el accesorio de prueba (cableado automático por tipo a través de la infraestructura de prueba de primavera). Debido al atributo de transacción, un proxy aop se inyecta los objetivo de este proxy es el objeto "real" que tiene un FacilityDao inyectado en él.

Pero el assetsService de tu afirmación es el proxy aop. Inicialmente, no tenía ninguna interfaz especificada en su AssetService clase y primavera creó un proxy decorador - un proxy que hereda de AssetService, con todos los métodos virtuales anulados y delegados al objeto de destino.

La aserción devuelve nulo, porque en este proxy nunca se inyecta el dao de la instalación.

La aserción no devolvió un valor nulo sin aplicar el atributo de transacción, porque se inyectó el objeto "sin proxy".

contestado el 04 de mayo de 12 a las 08:05

Ver mi actualización; hacer GetFacilities virtual o crear IAssetService interfaz. - Marijn

Marijin tiene razón: si desea utilizar la capacidad de AOP de Spring.NET (y el uso de atributos de transacción es básicamente AOP), sus beans (en términos de Java) deben implementar una interfaz. El motivo es que Spring.NET crea un objeto proxy que técnicamente es una instancia de alguna clase que se crea durante el tiempo de ejecución. Se lanza la excepción NullReferenceException porque .NET no puede convertir esta clase de proxy en su tipo concreto. Esto solo se puede lograr mediante el uso de interfaces.

contestado el 03 de mayo de 12 a las 08:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.