4

I will up the question at second time. Do not blame me please.

Situation:

I have a form

TfrmMain = class(TForm)
private
   [Inject('IniFileSettings')]
   FSettings: ISettings;
public
end;

I have container initialization procedure:

procedure BuildContainer(const container: TContainer);
begin
  container.RegisterType<TIniSettings>.Implements<ISettings>('IniFileSettings');

  container.RegisterType<TfrmMain, TfrmMain>.DelegateTo(
    function: TfrmMain
    begin
      Application.CreateForm(TfrmMain, Result);
    end);

  container.Build;
end;

So I initialize both TfrmMain as well as TIniSettings via container.

in .DPR I have:

begin
  BuildContainer(GlobalContainer);
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TfrmMain, frmMain);
  Application.Run;
end.

Also I have a helper for TApplication:

procedure TApplicationHelper.CreateForm(InstanceClass: TComponentClass; var Reference);
var
  locator: IServiceLocator;
begin
  locator := TServiceLocatorAdapter.Create(GlobalContainer);
  if locator.HasService(InstanceClass.ClassInfo) then
    TObject(Reference) := GlobalContainer.Resolve(InstanceClass.ClassInfo).AsObject
  else
    inherited CreateForm(InstanceClass, Reference);
end;

Problem: when I try to

procedure TfrmMain.FormCreate(Sender: TObject);
begin
   s := FSettings.ReadString('Connection', 'Server', 'localhost');
end;

I get AV exception because FSettings currently is NIL.

What is correct way to get FSettings object from the container?

UPDATE:

FSettings := GlobalContainer.Resolve<ISettings>;

This row works perfectly... As in last time I have problem to use [Inject] attribute. Even with solution from Stefan I can make the method working:

How to initialize main application form in Spring4D GlobalContainer?

Community
  • 1
  • 1
mad
  • 1,029
  • 3
  • 17
  • 38
  • Why do you feel that `FSettings` should be other than `nil`? – David Heffernan Oct 23 '14 at 15:52
  • @David looks like I apply [Inject] attribute for it... Please look at first code snippet in the post. Do I do it wrong way? – mad Oct 23 '14 at 15:55
  • why are you registering your main form to the container? Try to do one thing at a time. Forget about your form and concentrate on FSettings. Move the buildcontainer call to the FormCreate event, does it work then? – whosrdaddy Oct 23 '14 at 15:59
  • I cannot see a definition of `Inject`. Is it part of Spring? In which case I guess I'll back away from the Q, being out of my depth. – David Heffernan Oct 23 '14 at 16:00
  • @David yes, it is part of the Spring4D Framework... Thanks in any case – mad Oct 23 '14 at 16:02
  • @whosrdaddy That is why: http://stackoverflow.com/questions/24284401/how-to-initialize-main-application-form-in-spring4d-globalcontainer – mad Oct 23 '14 at 16:02
  • Your `TApplicationHelper` differs somehow from the solution provided by Stefan Glienke. – Sir Rufo Oct 23 '14 at 20:01
  • That is why GlobalContainer has no HasService() method. But in Stefan's example it has... – mad Oct 23 '14 at 20:05
  • my version has it via TServiceLocatorAdapter... or via ServiceLocator.HasService – mad Oct 23 '14 at 20:13
  • My version is 1.1 (2014-09-12) – mad Oct 23 '14 at 20:15
  • Sorry, my eyes were just making jokes to my mind, you are right – Sir Rufo Oct 23 '14 at 20:16
  • From last try I had no time to try Stefan's approach... Now I have a little time to make a Spring4D live in my project but have the same difficulties. – mad Oct 23 '14 at 20:19
  • Old question, but I had a similar issue and bumped into it. In my case, I didn't use the unit `Spring.Container.Common`, causing the `[Inject]` attribute to not be recognised. The compiler gives a nice warning about this, though, but if, like me, you google before you read compiler hints, you might end up here as well. ;-) – GolezTrol Dec 30 '19 at 20:11

1 Answers1

6

First the reason why the container does not have a HasService anymore is because that method has been removed. You can access it as follows:

if container.Kernel.Registry.HasService(...) then  // yeah yeah, I know LoD is crying right now ;)

I would avoid mixing using ServiceLocator and GlobalContainer. While they should point to the same instance it might not be the case because actually someone could point one of them to another instance. If you really want to use ServiceLocator in this case then also resolve from ServiceLocator. But keep in mind that there is nothing on it that the container does not know (even if you have to call some different parts of the kernel.

But that is not the problem you are facing here with your settings injection. The problem you have is the timing. The FormCreate method (I just guess it is attached to the OnCreate event). So the container instantiates the TfrmMain, the event gets called and then returns to the container code which afterwards does all the injections. So calling something that has not been injected via constructor in some code being called during construction is a temporal coupling.

There are different approaches to this problem:

  • moving your access to the FSettings to some event that gets triggered later (like OnShow or OnActivate)
  • don't use field injection it can be nice but that couples your code to the container because "traditional" code cannot do that. Use property injection and a setter that executes the code.

When you consider constructor injection as for dependencies that are mandatory and property injection for those that are optional I would say go for the constructor injection. But knowing that you working with a TComponent descendant I probably would use the property injection in that case although that dependency is not optional.

Stefan Glienke
  • 20,860
  • 2
  • 48
  • 102
  • Overriding constructor should be just fine. Application.CreateForm still calls the virtual constructor. AfterConstruction is another obvious choice. – David Heffernan Oct 23 '14 at 21:56
  • That cannot work at all. No way to get a parameter in via CreateForm. I didn't read closely enough. You can scratch that bullet point completely. No need to be unsure whether or not it works. It doesn't. – David Heffernan Oct 24 '14 at 05:47
  • 1
    @Stefan Thanks a lot! You have saved my morning. The correct answer was: Timing. Now I use injected objects in OnShow. Works fine for me. – mad Oct 24 '14 at 06:49
  • CreateForm is the only way to make the main form – David Heffernan Oct 24 '14 at 19:37