22

Se non si sa di cosa sto parlando o passare attraverso the tutorial e provare ad aggiungere dipendenza Injection da soli o tentare la fortuna con la mia spiegazione del problema.Esiste un modo corretto/corretto per risolvere il problema del ciclo di iniezione delle dipendenze nell'esercitazione ContactsManager di ASP.NET MVC?

Nota: Questo problema non rientra nell'ambito del tutorial originale su ASP.NET. Il tutorial suggerisce solo che i pattern utilizzati sono dipendenti dall'iniezione.

Il problema è fondamentalmente che esiste un ciclo di dipendenza tra il controller, ModelStateWrapper e ContactManagerService.

  1. Il conduttore ContactController accetta un IContactManagerService.
  2. Il costruttore ContactManagerService prende un IContactManagerRepository (non importante) e un IValidationDictionary (che implementa ModelStateWrapper).
  3. Il costruttore ModelStateWrapper accetta un ModelStateDictionary (che è una proprietà denominata "ModelState" sul controller).

così il ciclo di dipendenza è questa: controller> Servizio> ModelStateWrapper> Regolatore

Se si tenta di aggiungere l'iniezione di dipendenza a questo, fallirà. Quindi la mia domanda è; cosa dovrei fare al riguardo? Altri hanno postato questa domanda, ma le risposte sono poche, diverse e sembrano tutte un po '"hack-ish".

La mia soluzione attuale è quella di rimuovere l'IModelStateWrapper dal costruttore IService e aggiungere un metodo Initialize invece in questo modo:

public class ContactController : Controller 
{ 
    private readonly IContactService _contactService; 

    public ContactController(IContactService contactService) 
    { 
     _contactService = contactService; 
     contactService.Initialize(new ModelStateWrapper(ModelState)); 
    } 

    //Class implementation... 
} 

public class ContactService : IContactService 
{ 
    private IValidationDictionary _validationDictionary; 
    private readonly IContactRepository _contactRepository; 

    public ContactService(IContactRepository contactRepository) 
    { 
     _contactRepository = contactRepository; 
    } 

    private void Initialize(IValidationDictionary validationDictionary) 
    { 
     if(validationDictionary == null) 
      throw new ArgumentNullException("validationDictionary"); 

     _validationDictionary = validationDictionary; 
    } 

    //Class implementation... 
} 

public class ModelStateWrapper : IValidationDictionary 
{ 
    private readonly ModelStateDictionary _modelState; 

    public ModelStateWrapper(ModelStateDictionary modelState) 
    { 
     _modelState = modelState; 
    } 

    //Class implementation... 
} 

Con questo costrutto posso configurare il mio contenitore di unità in questo modo:

public static void ConfigureUnityContainer() 
{ 
    IUnityContainer container = new UnityContainer(); 

    // Registrations 
    container.RegisterTypeInHttpRequestLifetime<IContactRepository, EntityContactRepository>(); 
    container.RegisterTypeInHttpRequestLifetime<IContactService, ContactService>(); 

    ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory(container)); 
} 

Sfortunatamente ciò significa che il metodo "Inizializza" sul servizio deve essere chiamato manualmente dal costruttore del controllore. C'è un modo migliore? Forse dove in qualche modo includo il IVidationDictionary nella mia configurazione di unità? Devo passare a un altro contenitore DI? Mi sto perdendo qualcosa?

+0

Devo ammettere che ho fatto questo tutorial 3 volte e non abbia mai incontrato questo? Il problema è nell'origine o segue manualmente il tutorial, se è quest'ultimo che probabilmente manca un passaggio? – BinaryMisfit

+0

La domanda va oltre lo scopo del tutorial originale che ha solo accennato a Dependency Injection, ma non è mai arrivato a mostrare come dovrebbe essere fatto. Chiarirò la domanda – JohannesH

risposta

11

Come considerazione generale, le dipendenze circolari indicano un difetto di progettazione - Credo di poter tranquillamente dire questo dal momento che non sei l'autore originale del codice :)

Non vorrei considerare un metodo Initialize una buona soluzione . A meno che tu non abbia a che fare con uno scenario aggiuntivo (che non sei), Method Injection non è la soluzione giusta. L'hai già capito, dal momento che ritieni insoddisfacente che sia necessario richiamarlo manualmente perché il tuo contenitore DI non può.

A meno che non sia completamente errato, ContactController non ha bisogno dell'istanza di IValidationDictionary prima che i metodi di azione vengano richiamati?

Se ciò è vero, la soluzione più semplice sarebbe probabilmente quella di definire un'interfaccia IValidationDictionaryFactory e rendere il costruttore ContactController un'istanza di questa interfaccia.

Questa interfaccia può essere definito in questo modo:

public interface IValidationDictionaryFactory 
{ 
    IValidationDictionary Create(Controller controller); 
} 

Qualsiasi metodo d'azione sul controller che ha bisogno di un'istanza IValidationDictionary possono poi invocare il metodo Create per ottenere l'istanza.

L'implementazione di default sarebbe simile a questa:

public class DefaultValidationDictionaryFactory : IValidationDictionaryFactory 
{ 
    public IValidationDictionary Create(Controller controller) 
    { 
     return controller.ModelState; 
    } 
} 
+0

Perché il downvote anonimo? –

+6

Mark, non sono il downvoter anonimo ma credo che il problema qui sia come ottenere elegantemente il controller.ModelState nell'istanza IContactService per essere popolato. Con la tua soluzione, se dovessi ottenere un'istanza di IValidationDictionaryFactory nell'istanza IContactService, avremmo comunque bisogno di un'istanza del controller all'interno del servizio, per chiamare il metodo Create. – Ben

+0

come ottenere IValidationDictionaryFactory in ContactService? dovrebbe essere in ContactService – 1AmirJalali

1

Ogni controller ha un metodo virtuale Initialize di fare cose del genere.

Penso che non ci sia un modo migliore perché IValidationDictionary è un livello di astrazione tra la richiesta corrente/controller/modelstate e IContactService. Iniettare i controller nello stato di modelstate nel servizio e quindi iniettare il servizio nel controller è semplicemente impossibile utilizzare l'iniezione del costruttore. Uno deve essere il primo

Può esserci un modo utilizzando l'iniezione di proprietà? Ma penso che anche questo sarà complicato.

Problemi correlati