Laden...

[solved] ASP.NET MVC - IoC und DI mit Castle windsor

Erstellt von squadwuschel vor 10 Jahren Letzter Beitrag vor 10 Jahren 1.559 Views
S
squadwuschel Themenstarter:in
406 Beiträge seit 2007
vor 10 Jahren
[solved] ASP.NET MVC - IoC und DI mit Castle windsor

Hallo,

also ich versuche gerade ein MVC Projekt aufzusetzten, welches Castle Windsor und DI verwendet. Soweit so gut. Ich würde kurz den aktuellen "Quellcode" Posten der für das Grundverständnis meines Problems notwendig ist.

Global.asax -->


      //Erstellen unserer Castle Windsor Instanz die wir im gesamten Projekt benutzten
            Container = new WindsorContainer();
            //Hinzufügen unserer Konfigurationen zum Kontainer.
            Container.Install(new WebInstaller(), new LoggingInstaller());

            //Erstellen unserer CustomControllerFactory zum Instanziieren der Passenden Controller
            var customControllerFactory = new CustomControllerFactory(Container);
            //Unsere CustomControllerFactory als Standard Factory zum Erstellen von Controllern setzten, damit diese auch von MVC verwendet wird.
            ControllerBuilder.Current.SetControllerFactory(customControllerFactory);

WebInstaller zum Reg. der Interfaces und Klassen für Castle -->


            string connectionStr = GetConnectionString("MainModel");
            //Unseren ModelContainer Registrieren mit dem passenden Connectionstring dazu.
            container.Register(Component.For(typeof(MainModelContainer)).DependsOn(new { connectionString = connectionStr }).LifestylePerWebRequest());
            //Alle Queries Registrieren die sich in der Assembly "User.EF.Queries" befinden und jeweils ein passendes Interface enthalten wie 
            //"IMemberQueries/MemberQueries" siehe auch: http://docs.castleproject.org/Windsor.Registering-components-by-conventions.ashx
            container.Register(Classes.FromAssemblyContaining<MemberQueries>().InNamespace("User.EF.Queries").WithService.DefaultInterfaces().LifestylePerWebRequest());

            //Alle Controller Registrieren die IController beinhalten/nutzen.
            container.Register(Classes.FromAssemblyContaining<HomeController>().BasedOn<IController>().LifestylePerWebRequest());

//PROBLEM funktioniert so nicht!
            container.Register(Classes.FromAssemblyContaining<HomeController>().BasedOn<IAddMessage>().LifestylePerWebRequest());

HomeController -->


        //Sollte automatisch von Windsor gesetzt werden, wird aber nicht gemacht, sondern bleibt null
        public IAddMessage AddMessage { get; set; }

        private readonly IMemberQueries _memberContract;

        public HomeController(IMemberQueries memberContract)
        {
           _memberContract = memberContract;
        }
      
        public ActionResult Index()
        {
            //PROBLEM AddMessage ist Null und wird nicht automatisch per Property Resolve von
            //Castle gesetzt
            AddMessage.Add("Test", MessageTypes.message);
            return View("Index");
        }

Die Implementierung von IAddMessage


  public class CustomMessage : IAddMessage
    {
        private IController Controller { get; set; }

        public CustomMessage(IController controller)
        {
            Controller = controller;
        }

        public void Add(string text, MessageTypes messageTypes)
        {
            //TODO
        }
    }

Das IAddMessage Interface -->


    public interface IAddMessage
    {
        void Add(string text, MessageTypes messageTypes);
    }

So, mein Problem ist, das er im Controller das "IAddMessage" Property nicht setzten kann, da er die Abhängigkeit für IController im Konstruktor von CustomMessage nicht auflösen kann. Ich bin aktuell leider am Ende meines "Lateins" und weiß nicht was ich "falsch" mache, denn den Controller kann er ja Instanzieren da er diesen ja auch problemlos lädt.

Ich muss dazu sagenbin noch recht neu was Windsor angeht und bin auch noch nicht so recht dahinter gekommen wann man "Component" und wann "Classes" verwendet. Evtl. kann mir jemand einen Tip geben wie ich mein Problem angehen kann. 😕

Fehlermeldung die ich aus dem Windsor Objekt auslesen kann im Debug modus:
Some dependencies of this component could not be statically resolved.
'User.Web.UI.Helper.Messages.CustomMessage' is waiting for the following dependencies:

  • Service 'System.Web.Mvc.IController' which was not registered.
    --> habe auch schon danach im netzt gesucht, aber nichts nützliches gefunden 😕

thx much
SquadWuschel

Mein Blog über .NET und MVC / EF | Meine kostenlose Onlinearbeitszeitverwaltung My:Worktime

S
417 Beiträge seit 2008
vor 10 Jahren

Hallo,

CustomMessage sieht etwas seltsam aus. Was für einen Controller willst du denn injekten bzw. woher soll Castle Windsor wissen, welchen Controller es übergeben soll, wenn du nur das Interface angibst?
Generell finde ich die Abhängigkeit zu einem Controller unpassend für Komponenten.
Was genau machst du denn mit dem Controller in CustomMessage?

W
955 Beiträge seit 2010
vor 10 Jahren

Hi,

versuch's doch mal mit constructor injection. Ansonsten würde ich auch die cross dependency auflösen.
circular-interfaces-dependencies-and-castle-windsor

S
squadwuschel Themenstarter:in
406 Beiträge seit 2007
vor 10 Jahren

Hallo,

erst einmal Danke für die Hinweise, dem werde ich gleich morgen Früh nachgehen.

In customMessage soll eigentlich der aktuelle Controller übergeben werden, denn ich will auf den
Controller direkt zugreifen (nicht das Interface) um auf TempData zugreifen zu können in dem ich meine Meldungen ablege. Ich habe dann einen HTML Helper der die Meldungen auf der Seite ausgibt. (Ob das im Moment die perfekte Lösung ist, sei dahin gestellt) (CustomMessage ist quasi meine Implementierung für die Ausgabe der Nachrichten für ASP.NET MVC)

Controller.TempData["Messages"]

Wie gebe ich denn an das er die Controller Instanz Injecten soll in der er sich qausi gerade befindet bzw. erstellt. Ich Stehe hier ein wenig auf dem Schlauch 😕 nen Stichwort wäre schon cool nach dem man Googlen kann.

bzw. habe inzwischen auch mal nach dem Circular References geschaut, muss hier aber sagen das verstehe ich nicht, bzw. weiß nicht wie ich das auf mein Problem anwenden soll 😕

thx much
SquadWuschel

Mein Blog über .NET und MVC / EF | Meine kostenlose Onlinearbeitszeitverwaltung My:Worktime

M
402 Beiträge seit 2005
vor 10 Jahren

Hi...

"TempData" wäre eigentlich dafür gedacht Daten von einem Conroller-Aufruf zum nächsten zu "übergeben".

Um Daten aus dem Controller in das View zu bekommen gibt's "ViewBag" oder "ViewData".

Hier ist ein erklärender Artikel dazu:
http://www.codeproject.com/Articles/476967/WhatplusisplusViewData-2cplusViewBagplusandplusTem

Am Besten wäre es allerdings wenn du überhaupt mit ViewModels arbeiten würdest. Dann würdest du statt des Controllers einfach nur das ViewModel an dein IAddMessage-Objekt übergeben und dann an das View. Vorteil wäre auch dass du ordentlich testen könntest.

lg

S
squadwuschel Themenstarter:in
406 Beiträge seit 2007
vor 10 Jahren

Hio,

Jojo kenn ich soweit 😃 - Da ich die Messages die ich anzeigen möchte auch Controllerübergreifend funktionieren sollen und ich mich nicht um das Leeren des Inhalts kümmern möchte verwende ich hier TempData. Um Daten in meinen View zu bekommen verwende ich immer Strongly Typed Views die an einem Model hängen.

Das sollte aber hier erst einmal egal sein, denn das Problem was ich oben beschreiben habe bleibt weiterhin bestehen.

thx much
SquadWuschel

Mein Blog über .NET und MVC / EF | Meine kostenlose Onlinearbeitszeitverwaltung My:Worktime

S
squadwuschel Themenstarter:in
406 Beiträge seit 2007
vor 10 Jahren

So ich habe eine funktionierende Lösung gefunden. Das Problem ist hier, das Windsor ja nicht weiß welchen Controller er erstellen soll.

Daher habe ich die "DefaultControllerFactory" erweitert um mir meinen aktuellen Controller im aktuellen Request zu merken.


public class CustomControllerFactory : DefaultControllerFactory
{
    private readonly IWindsorContainer _container;
    private const string CurrentControllerIndexName = "CurrentControllerItem";

    /// <summary>
    /// Den Windsor Container für unser Projekt übergeben
    /// </summary>
    /// <param name="container">Unser aktueller Windsor Kontainer</param>
    public CustomControllerFactory(IWindsorContainer container)
    {
        if (container == null)
        {
            throw new NullReferenceException("IWindsorContainer is Null");
        }

        _container = container;
    }

    /// <summary>
    /// Erstellen der passenden Controller Instanzen anhand unseres Containers werden die Instanzen automatisch aufgelöst.
    /// </summary>
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", requestContext.HttpContext.Request.Path));
        }

        //Unseren Controller auflösen
        var controller = (IController)this._container.Resolve(controllerType);
        //Prüfen ob bereits ein Controller im HttpContext enthalten ist, wenn ja wird dieser entfernt um den aktuellen Hinzufügen zu können.
        if(HttpContext.Current.Items.Contains(CurrentControllerIndexName))
        {
            HttpContext.Current.Items.Remove(CurrentControllerIndexName);
        }

        //Unseren aktuellen Context hinzufügen
        HttpContext.Current.Items.Add(CurrentControllerIndexName, controller);
        
        return controller;
    }

    /// <summary>
    /// Gibt den aktuellen Controller zurück der angezeigt wird.
    /// </summary>
    public static IController GetCurrentController()
    {
        if(HttpContext.Current.Items.Contains(CurrentControllerIndexName))
        {
            return (IController) HttpContext.Current.Items[CurrentControllerIndexName];
        }

        throw  new Exception("No Controller found in the Current HttpContext");
    }

    public override void ReleaseController(IController controller)
    {
        this._container.Release(controller);
    }
}

Dann habe ich meine Message Klasse angepasst und hier wird jetzt kein Konstruktor oder Property vom Typ IController mehr benötigt.


public void Add(string text, MessageTypes messageTypes)
{
    if (text != null)
    {
        Controller controller = (Controller)CustomControllerFactory.GetCurrentController();
        //Eine Liste erstellen, damit wird jetzt jede Meldung hinzugefügt und alle können angezeigt werden.
        List<IMessage> messages = (List<IMessage>)controller.TempData[TempMessageString];
        //Wenn noch keine Nachrichten hinterlegt wurden, muss die Liste initialisiert werden
        if (messages == null)
        {
            messages = new List<IMessage>();
        }

        messages.Add(new Message(text, messageTypes));

        //Die Message im TempData hinterlegen, damit diese dann von der Passenden Helper Methode dargestellt werden kann.
        controller.TempData[TempMessageString] = messages;
    }
}

Ich finde die Lösung soweit sehr cool muss ich sagen. Evtl. gibts noch Anmerkungen und Hinweise.

thx much
SquadWuschel

Mein Blog über .NET und MVC / EF | Meine kostenlose Onlinearbeitszeitverwaltung My:Worktime

W
955 Beiträge seit 2010
vor 10 Jahren

Ich finde die Lösung soweit sehr cool muss ich sagen. Hast Du geprüft das das thread-safe ist? Das Du auch wirklich Dein Controller bekommst wenn zwei Requests parallel erfolgen?

S
squadwuschel Themenstarter:in
406 Beiträge seit 2007
vor 10 Jahren

jojo das sollte soweit passen, habe ich aber noch nicht genauer nachgelesen.

Mein Blog über .NET und MVC / EF | Meine kostenlose Onlinearbeitszeitverwaltung My:Worktime