2012-08-22 12 views
15

¿Es posible tener una API web genérica que admita cualquier modelo en su proyecto?Controlador genérico de API web para admitir cualquier modelo

class BaseApiController<T> :ApiController 
{ 
    private IRepository<T> _repository; 

    // inject repository 

    public virtual IEnumerable<T> GetAll() 
    { 
     return _repository.GetAll(); 
    } 

    public virtual T Get(int id) 
    { 
     return _repositry.Get(id); 
    } 

    public virtual void Post(T item) 
    { 
     _repository.Save(item); 
    } 
    // etc... 
} 

class FooApiController : BaseApiController<Foo> 
{ 
    //.. 

} 

class BarApiController : BaseApiController<Bar> 
{ 
    //.. 
} 

¿Sería este un buen enfoque?

Después de todo, ¿estoy repitiendo los métodos CRUD? ¿Puedo usar esta clase base para hacer el trabajo por mí?

¿está bien? harías esto? alguna mejor idea?

+0

Hola, sé que esta es una pregunta antigua, pero ¿podría explicar cómo logró llamar a sus métodos de acción genéricos? No parece que pueda lograr esto solo con las reglas de enrutamiento. Me sería de gran aprecio. – Brett

Respuesta

22

Hice esto para un proyecto pequeño para poner algo en marcha y ejecutar una demostración para un cliente. Una vez que ingresé a los detalles de las reglas comerciales, la validación y otras consideraciones, terminé teniendo que anular los métodos CRUD de mi clase base, por lo que no funcionó como una implementación a largo plazo.

me encontré con problemas con el enrutamiento, ya que no todo usa un ID del mismo tipo (que estaba trabajando con un sistema existente). Algunas tablas tenían claves primarias int, algunas tenían strings y otras tenían guids.

Terminé teniendo problemas con eso también. Al final, aunque me pareció astuto cuando lo hice por primera vez, usarlo realmente en una implementación en el mundo real resultó ser una cuestión diferente y no me acercó más.

+6

así que podría utilizar una interfaz llamada 'IIdentifier ' para representar diferentes tipos de id. – DarthVader

+0

Es cierto, pero en retrospectiva, todavía no creo que me haya beneficiado al tratar de refactorizar preventivamente las operaciones CRUD a un controlador genérico. Causó demasiados dolores de cabeza más adelante en el desarrollo. –

+0

Si está en un terreno nuevo, no hay ninguna razón por la cual no debería tener todos los "Id" del mismo tipo. – LastTribunal

5

Definitivamente es posible. Nunca tuve una razón para hacer eso antes, pero si funciona para tu situación, debería ser bueno.

Si todos sus modelos se pueden guardar y recuperar de la misma manera, ¿deberían estar todos en el mismo controlador?

+0

bien ¿por qué no funciona? esencialmente la mayor parte del tiempo, todo lo que hacemos es usar las mismas operaciones para todos los modelos de dominio y repetirlos muchas veces. – DarthVader

+1

Me preguntaste si era posible, así que respondí que :) Debo admitir que me quedé corto con el resto de la pregunta, puedo eliminar mi respuesta ... – eouw0o83hf

+0

te daré +1 por no eliminar tu respuesta :) – DarthVader

0

Es absolutamente posible como se dijo en la respuesta anterior. Este es un buen enfoque y definitivamente una buena arquitectura. Pero no entiendo por qué tus controladores no son públicos. ¿Puede ser tu problema y por eso tu código no funcionó?

+0

oh, acabo de tipear aquí rápidamente. no prestó demasiada atención a los modificadores de acceso. – DarthVader

+0

Entonces, ¿qué es los síntomas de no funcionar? –

+0

¿quién dijo que no funcionaba? Pregunté si es factible o un buen diseño/ – DarthVader

1

No hay nada de malo en esto mientras manejes todo el trabajo pesado en tus repositorios. Es posible que desee envolver/manejar excepciones de estado de modelo en su controlador base.

De hecho, estoy haciendo algo similar para un proyecto grande donde los usuarios pueden definir sus propias entidades y API, es decir: un usuario puede querer tener usuarios y cuentas mientras que otro puede querer rastrear coches y todo lo demás. Todos usan el mismo controlador interno, pero cada uno tiene sus propios puntos finales.

No estoy seguro de cuán útil es nuestro código ya que no usamos genéricos (cada objeto se mantiene como metadatos y se manipula/pasa de un lado a otro como diccionarios JObject) pero aquí hay un código para darle una idea de que estamos haciendo y tal vez sirven de alimento para el pensamiento:

[POST("{primaryEntity}", RouteName = "PostPrimary")] 
public async Task<HttpResponseMessage> CreatePrimary(string primaryEntity, JObject entity) 
{ 
    // first find out which params are necessary to accept the request based on the entity's mapped metadata type 
    OperationalParams paramsForRequest = GetOperationalParams(primaryEntity, DatasetOperationalEntityIntentIntentType.POST); 

    // map the passed values to the expected params and the intent that is in use 
    IDictionary<string, object> objValues = MapAndValidateProperties(paramsForRequest.EntityModel, paramsForRequest.IntentModel, entity); 

    // get the results back from the service and return the data to the client. 
    QueryResults results = await paramsForRequest.ClientService.CreatePrimaryEntity(paramsForRequest.EntityModel, objValues, entity, paramsForRequest.IntentModel); 
     return HttpResponseMessageFromQueryResults(primaryEntity, results); 

} 
+0

Buena suerte :) se ve muy complicado. Evitaría todo eso :) – DarthVader

+1

Por lo que estás haciendo, sí, leve exageración;). Sin embargo, el código anterior permite múltiples cuentas de clientes con múltiples aplicaciones en cada cuenta para administrar múltiples entidades usando un controlador genérico (en realidad lo hemos dividido en un controlador por tipo de acción) por lo que no es tan complejo cuando se considera el problema que tiene que resolver . Y si podemos usar el código anterior y admitir miles de clientes con un tiempo de respuesta inferior a 100 ms, está listo para hacerlo;). De todos modos, solo intento mostrar otra forma de lograrlo como alimento para el pensamiento. – AlexGad

+0

genial :) buena suerte :) – DarthVader

1

Si tiene clases en tiempo de diseño predefinidas, como uno que ha generado a partir del modelo de EF o Código primero, entonces esto es demasiado complicado para su sistema. Esto es genial si no tiene clases predefinidas (como en mi proyecto donde las clases de entidad de datos se generan en tiempo de ejecución).

Mi solución (aún no implementada correctamente) era crear IHttpControllerSelector personalizado que selecciona mi controlador genérico para todas las solicitudes, allí puedo establecer el tipo de descriptor del controlador en concreto desde genérico a través de la reflexión parámetro genérico dependiendo de la ruta de solicitud.

también un buen punto de partida es http://entityrepository.codeplex.com/ (He encontrado esto en algún lugar aquí en stackoverflow)

1

Lo que se hace es definitivamente posible como otros han dicho. Pero para las dependencias del repositorio, debe usar la inyección de dependencia. Mi controlador típico (Api o MVC) sería el siguiente.

public class PatientCategoryApiController : ApiController 
{ 

    private IEntityRepository<PatientCategory, short> m_Repository; 
    public PatientCategoryApiController(IEntityRepository<PatientCategory, short> repository) 
    { 
     if (repository == null) 
      throw new ArgumentNullException("entitiesContext is null"); 

     m_Repository = repository; 
    } 
} 

Este es el patrón típico de inyección de constructor. Debe tener una sólida comprensión de DI y contenedores como NInject o Autofac. Si no sabes DI, entonces tienes un largo camino por delante. Pero este es un excelente enfoque. Echa un vistazo a este libro. https://www.manning.com/books/dependency-injection-in-dot-net

Cuestiones relacionadas