Dependency Injection in a Web API Controller fails (null values)

Dec 1, 2012 at 6:42 PM
Edited Dec 1, 2012 at 6:43 PM

HI, 

As the title says, I'm missing something with DI for my Web API controller.

All the three following properties are null when I break into the controller code:

 

public class MyController : ApiController
{
    public SimpleRepository<Customer> CustomerRepository { set; protected get; }

    public SimpleRepository<Session> SessionRepository { set; protected get; }

    public IDomainObjectContainer Container { set; protected get; }

 

The first two services are registered in the RunWeb class, in both MenuServices and SystemServices (I also removed them from MenuServices to make sure that's not the problem, and indeed it's not).

I went through the "Debugging" section in the manual, but didn't figure this out.

What am I missing?

 

Editor
Dec 2, 2012 at 8:45 AM
I believe that NO MVC only injects into domain entities, not into controllers.


On 1 December 2012 19:42, ury <notifications@codeplex.com> wrote:

From: ury

HI,

As the title says, I'm missing something with DI for my Web API controller.

All the three following properties are null when I break into the controller code:

public SimpleRepository<Customer> CustomerRepository { set; protected get; }

public SimpleRepository<Session> SessionRepository { set; protected get; }

public IDomainObjectContainer Container { set; protected get; }

The first two services are registered in the RunWeb class, in both MenuServices and SystemServices (I also removed them from MenuServices to make sure that's not the problem, and indeed it's not).

I went through the "Debugging" section in the manual, but didn't figure this out.

What am I missing?

Read the full discussion online.

To add a post to this discussion, reply to this email (nakedobjects@discussions.codeplex.com)

To start a new discussion for this project, email nakedobjects@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Dec 2, 2012 at 10:17 AM

The application developer manual is referring to controllers (and to views) specifically:

 

7.4. How to inject Services into Controllers and Views

Any service that is registered in the Run class may be injected into a Controller and/or into a View.

Injecting into Controllers

Services are injected into Controllers in exactly the same way as services are injected into domain objects - use the injs snippet.

Dec 2, 2012 at 12:50 PM
Yes it injects into MVC controllers, but Web API controllers are something quite different in terms of the ASP.Net stack, if I remember correctly.
I don't think NO MVC supports injection into these by default. Stef or Richard would have to comment on the possibility of leveraging the NO Framework with Web API.

If suitable for your scenario, you could look at using something like the custom container I implemented for use outside the NO Framework.
http://nakedobjects.codeplex.com/discussions/403614
The zip download contains updated code to that listed in the post itself..

Coordinator
Dec 3, 2012 at 7:48 AM

Jacques is correct we do not support this currently. It would be easy to add but RO is rather a different beast to MVC so I think it would be useful to understand the underlying requirements. 

As we see it it is fully expected to customise views and or controllers in MVC which is why we support injection into them - in the case of RO we expect customisation to be done on the client side. 

Having said that there are obviously places within the RO spec where customisations are expected eg in the extensions. For the moment you would just have to modify the RO code but adding defined extensions points is something we need to do. 

So in summary could you please explain

- What your requirement is that causes you to need to add a new webapi controller that calls into the Naked Objects Framework.

- What (if any) extensions to the RO spec you anticipate so we can look to add clean extension points.

 

 

 

 

Dec 3, 2012 at 8:26 AM

While I'm implementing Jacques' (thanks Jacques!) solution, I'll try to explain the need:

My system has both a user interface (implemented by NO) and a server-to-server interface, which I'd like to implement using Web API.

The Web API controller handles requests which involve (a) scanning through the domain object tree to find the entities relevant to the request and validate the request accordingly, and (b) add new entities (sessions) to the model.

While using EF for the later function above will be replaced by direct SQL statements (as it won't be able to handle sessions memory- and performance-wise), I'd very much like to use the domain object for the first one.

If additional information is missing, please let me know and I'll provide it gladly.

BTW, I'm not using RO per-se, unless it's under the hood of NO and I'm unaware of it.  While  think what I describe above is a quite common server scenario, it's still my first NO project and I'm not that comfortable yet concluding what extensions I'd like to see.

Editor
Dec 3, 2012 at 8:35 AM
Did you look at using RO for this requirement? I detect - perhaps - you aren't clear on what RO does? Basically, it''ll get a full set of restful resources "for free", jut as NO gives you a UI "for free".

To add RO, add a new empty ASP.NET MVC webapp to your solution (alongside your NO webapp), and applying the RestfulObjects.Server package from NuGet to this new project, registering services in the RO webapp's RunWeb.

Dan

On 3 December 2012 09:27, ury <notifications@codeplex.com> wrote:

From: ury

While I'm implementing Jacques' (thanks Jacques!) solution, I'll try to explain the need:

My system has both a user interface (implemented by NO) and a server-to-server interface, which I'd like to implement using Web API.

The Web API controller handles requests which involve (a) scanning through the domain object tree to find the entities relevant to the request and validate the request accordingly, and (b) add new entities (sessions) to the model.

While using EF for the later function above will be replaced by direct SQL statements (as it won't be able to handle sessions memory- and performance-wise), I'd very much like to use the domain object for the first one.

If additional information is missing, please let me know and I'll provide it gladly.

BTW, I'm not using RO per-se, unless it's under the hood of NO and I'm unaware of it. While think what I describe above is a quite common server scenario, it's still my first NO project and I'm not that comfortable yet concluding what extensions I'd like to see.

Read the full discussion online.

To add a post to this discussion, reply to this email (nakedobjects@discussions.codeplex.com)

To start a new discussion for this project, email nakedobjects@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Coordinator
Dec 3, 2012 at 8:48 AM

I would second Dan's comments - in addition to exposing the domain objects for your first requirement via RestfulObjects you could write a service to do your direct SQL session adding work and expose that via RestfulObjects as well.

Dec 3, 2012 at 8:55 AM
Edited Dec 3, 2012 at 8:58 AM

Thanks Dan,

I'm bound to get there, but as it's the first time for me with ASP.NET MVC and EF Code-First and NO (!), I probably should leave something something for the next project :)

In addition, the remote server, a 3rd party product with a given API, requires handling about 5 HTTP GET requests.  It looks like Web API (after I spent a couple of days figuring out route mapping) supports that in a very straightforward way.  What can I gain from using RO in this scenario?

[EDIT: Maybe I'm missing the point here.  Is there a quick link to a RO-based solution which matches the above requirements?]

Coordinator
Dec 3, 2012 at 5:38 PM

At risk of sounding like we're ganging-up on you:  I agree with Dan and Stef that you should be using RO.  This is how to get a complete API over HTML to your domain model (not just CRUD but the full behaviour, including actions and enforced validation) for free.   Go to the RO project on CodePLex and watch the videos on that site  -  you'll get the idea.

Dec 4, 2012 at 7:51 AM

I've watched the AdventureWorks demo video and it looks great.

In my case, I'm working with a remote server which issues requests of the following fixed form: http://myserver/[action]?param1=value1¶m2=value2.  It expects either an "OK" (that is, the string "OK" response or no response.

The request parameters are string values (user name, session name, content name...) which I look up in my repositories (they are properties of the entities) and act upon according to the action and the internal logic of my system (a subscriber management system, if it wasn't clear).

I can't see how I "persuade" the remote server to comply with the RO spec, and that's why I think I'm forced to use extensions like the one Jacques suggested above.

Do you agree or is there a way to benefit from the RO functionality even with the imposed interface with 3rd party server?

Coordinator
Dec 4, 2012 at 8:32 AM
Edited Dec 4, 2012 at 8:32 AM

I think the following would be a reasonable thing to try

1. Create a new Naked Objects service with matching actions for each [action] in your url. Implement as you see fit.

2. Install RO as described in the video, I'd then comment out the 'RegisterRoutes' in RestfulObjectsStart:PreStart in order to add your own routing.

3. Add a function to the RestfulObjectsController with routing to match your url and parameters.

4. Within that function call up to 'GetInvokeOnService' - service name is probably hard coded, the actionName matches your passed in action in the url and you should be able to create an ArgumentMap with your passed in parameters. Based on the response from the action you can return "OK" or nothing. 

This should get you a fair bit of value from RO and gives you a clean migration path should you wish in the future.

 

 

 

 

 

Dec 4, 2012 at 1:42 PM

I got terribly mixed up trying to implement the above...

  • I "lost" my homepage, and instead I get the following:
{"links":[{"rel":"self","method":"GET","type":"application/json; profile=\"urn:org.restfulobjects:repr-types/homepage\"; charset=utf-8","href":"http://localhost:22332/"},{"rel":"urn:org.restfulobjects:rels/user","method":"GET","type":"application/json; profile=\"urn:org.restfulobjects:repr-types/user\"; charset=utf-8","href":"http://localhost:22332/user"},{"rel":"urn:org.restfulobjects:rels/services","method":"GET","type":"application/json; profile=\"urn:org.restfulobjects:repr-types/list\"; charset=utf-8; x-ro-element-type=\"System.Object\"","href":"http://localhost:22332/services"},{"rel":"urn:org.restfulobjects:rels/version","method":"GET","type":"application/json; profile=\"urn:org.restfulobjects:repr-types/version\"; charset=utf-8","href":"http://localhost:22332/version"},{"rel":"urn:org.restfulobjects:rels/domain-types","method":"GET","type":"application/json; profile=\"urn:org.restfulobjects:repr-types/type-list\"; charset=utf-8","href":"http://localhost:22332/domain-types"}],"extensions":{}}
  • I removed the RestfulObjects package and then manually deleted the modified files, but I'm still getting the above response.  I have no idea how to revert to the pre-RO status.

That's the most stressing issue at the moment...  

Other than that, assuming I could make NO and RO work together in my solution (with your help), I'm expecting the following issues:

  • How can I directly access the DB (or, in other words, inject the DbContext object) in the RO service?
  • How do build the argument map and use the return value of the GetInvokeOnService to respond the request?

Thanks,

  Ury

Coordinator
Dec 4, 2012 at 3:02 PM

What you're seeing is the RO homepage which shows that you still have the RO routing.

I would suggest the safest way to return to a absolutely clean status would be to create a new MVC4 project, add the NO MVC nuget package, copy across the config  from web.config, the services from RunWeb  and any custom views or services. That should only take a minute and makes sure you know exactly where you are. 

Then I would create another MVC Web api project and this time add the RestfulObjects Server nuget package. Then again copy across config, services etc.

Now if you run the first project you should get the MVC home page and if you run the second you should get the Json RO homepage.

At that point in the RO project go to RestfulObjectsStart: RegisterRoutes and comment out   

RestfulObjectsControllerBase.AddRestRoutes(routes);

 

 

Then add something like

  routes.MapHttpRoute(
                name: "CustomAction",
                routeTemplate: "/{actionName},
                defaults: new {controller = "RestfulObjects", action = "CustomAction"},
                constraints: new {httpMethod = new HttpMethodConstraint("GET")}
                );

On the restfulObjectsController add something like (this is all off the top of my head so may need tweaking but it should give you the right idea).

  [HttpGet]
        public override HttpResponseMessage CustomAction(string actionName, string parm1, string parm2) {

var serviceName = "Model.MyCustomService";

var jsonMap = "{ \"parm1\" : { \"value\" : " + parm1 + "} }, \"parm2\" : { \"value\" : " + parm2 + "}}"

var jo = JObject.Parse(jsonMap);

var am = ModelBinderUtils.CreateArgumentMap(jo);

 return base.GetInvokeOnService(serviceName, actionName, am);
 }

If think if your action returns a string you should be able to return the 'OK' directly. If something goes wrong you probably have to check the HttpResponseMessage (eg return "NOK")  and throw the appropriate Web Api exception.

Lastly the RO service is just a standard NO service - you can inject the Container and then the easiest way to just do something on the database is usually to grab the context from an ObjectQuery and just use some of the methods on it eg

var context = ((ObjectQuery) Container.Instances<SomePersistedType>()).Context;
context.ExecuteStoreCommand("Insert.......

 

 

 

 

 

Dec 4, 2012 at 3:10 PM
>That's the most stressing issue at the moment...
bitbucket.org really helps with this kind of stress. :P

Dec 4, 2012 at 6:32 PM

Did I say "stressing"?  I meant "pressing"...  and in any case I'm a simple guy who still uses svn.

In any case, I've removed the "old" MVC4 project, created a new one, added the NO package...  and I'm still getting the same RO homepage!

Just to be clear, other than adding a new default "Internet" MVC4 project and adding the NO package, I didn't perform any changes in the code.

Is it time to get stressed yet?

Where does RO hide?

Editor
Dec 4, 2012 at 6:48 PM
Just guess, but you might want to clear your browser's cache. The home page (and several other slow moving representations) has a long Cache-Control setting.

Dan


Dec 4, 2012 at 7:40 PM

Oh stupid!  I always forget that browser caching thing.

Back on track - thanks!

Dec 6, 2012 at 9:17 AM

Hi,

I'm getting a NullRerferenceException when calling GetInvokeOnService.  The exception details are: 

System.NullReferenceException occurred  HResult=-2147467261  Message=Object reference not set to an instance of an object.  Source=NakedObjects.Core  StackTrace:       at NakedObjects.Core.Context.HttpContextContext.InitialiseNewData()  InnerException: 

The (slightly edited) code which precedes this exception is:

 

           const string ServiceName = "Model.Services.BroadcasterService";

           var jsonMap = @"{ " +
                           "'broadcasterIpAddress' : " + "{ 'value' : '" + broadcasterIpAddressString + "' }, " +
                          "'user' : { 'value' : '" + user + "' }, " +
                          "'session' : { 'value' : '" + session + "' }, " +
                          "'channel' : { 'value' : '" + channel + "' }, " +
                          "'clienIpAddress' : { 'value' : '" + ip + "' } " +
                          "}";
           
            var argumentMap = ModelBinderUtils.CreateArgumentMap(JObject.Parse(jsonMap));
            var response = base.GetInvokeOnService(ServiceName, "authorize", argumentMap);

All the arguments are strings.

The service code is:

 

namespace Model.Services
{
    public class BroadcasterService : AbstractFactoryAndRepository
    {
        public string Authorize(string broadcasterIpAddress, string user, string session, string channel, string clientIpAddress)
        {
            ...

By the way, before this exception is thrown, I see a InitialisationException in GetInvokeOnService, saying "No entity connection strings in App.config file".  There's no App.config in this Web project, and Web.config contains my connection string (I had to replace all occurrences of "defualtConnection" though).

 

Coordinator
Dec 6, 2012 at 1:18 PM

"No entity connection strings in App.config file".

Is not good - does this work with MVC ? ie can you start the same model project with a RunMVC project ? It does sound that the framework is not finding the connection string in the web.config. If you're using EF it expects to either find a connection string or to be running code first. 

As an experiment you could change the EntityPersistorInstaller code in RunWeb to

 

   protected override IObjectPersistorInstaller Persistor
        {
            get
            {
                // Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0"); // Code First option: for in-memory database
                // Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyDbContext>()); // Code First option
                var installer = new EntityPersistorInstaller();
                installer.AddCodeFirstDbContextConstructor(() => new MyDbContext());  //Code First requirement: add each DbContext
                return installer;
            }
        }

 

Which is an empty code first context - and then put a break point in your service action to see if you can get that far.

 

 

Dec 6, 2012 at 3:24 PM

I once again removed the MVC project and recreated a new one, followed the above steps, and now the problem is gone.

Then I had NakedObjects.Surface.ServiceResourceNotFoundNOSException because I didn't register the service with the installer.

Next, I got NotAllowedNOSException, saying "action is not side-effect-free".  I read through the spec and I think that I understood that this refers to the action modifying the data (or at least not returning IQueryable and should be defined as HttpPost.  So I changed the action's attribute to [HttpPost] and no I'm getting the following HttpResponseException (in the exception's Response property:

{StatusCode: 405, ReasonPhrase: 'Method Not Allowed', Version: 1.1, Content: System.Net.Http.ObjectContent`1[[System.Web.Http.HttpError, System.Web.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]], Headers:{  Content-Type: application/json}}

AFAIK, post and put are allowed in my local IIS (checked applicationhost.config).

I tried both [HttpPut] and [HttpPost], with the same results.

Now I'm [HttpLost]

Coordinator
Dec 6, 2012 at 3:36 PM

I think you said above the request from the server is actually a GET ?

So change back to [HttpGet] but annotate the action on the service  as 'QueryOnly'

[QueryOnly]
 public string Authorize(string broadcasterIpAddress, string user, string session, string channel, string clientIpAddress)
        {}

 

 

Dec 6, 2012 at 4:10 PM
Edited Dec 6, 2012 at 4:14 PM

I changed this, but I'm still getting the same error.

This action returns a string, not a domain object or a collection, and this is imposed by the other side.

[EDIT: just wondering, should the controller action and/or service method return HttpResponseMessage with the string as the response?]

Coordinator
Dec 6, 2012 at 4:22 PM

We want to get back to the 'NotAllowedNOSException, saying "action is not side-effect-free"' error.

Ideally if the action is annotated [HttpGet] and if you remove the [QueryOnly] you should be seeing this again ? 

This error comes from a check in RO where it matches the method GET/PUT/POST to the actual action on the service. So if you can get back to that error the code knows it is a GET but that the action doesn't match. If the Action is now marked up as [QueryOnly] it will be acceptable as a GET regardless of what it returns (the annotations is a 'I know best'!). 

 

 

 

 

 

Dec 6, 2012 at 4:40 PM

Yes!  If I mark the controller action as [HttpGet] and the service action as [QueryOnly], it all gets through!

And I even got what I needed initially - I can access the model entities.

Thank you NO-RO support group :)