Service not injected

Mar 31, 2013 at 6:44 PM
Edited Mar 31, 2013 at 6:44 PM
Hi guys,
I'm having one of those "it worked just a second ago" moments.

In my RestfulObjects project, I have a service (inherited from AbstractFactoryAndRepository), which is installed as a system service in RubWeb:
        protected override IServicesInstaller SystemServices {
            get { return new ServicesInstaller(new BroadcasterService(), new SearchableCustomerRepository(), new SimpleRepository<Broadcaster>()); }
        }
(BroadcasterService, the first one in the list).

I'm trying to have it injected to the RestfulObjectsController -
        #region Injected services

        public BroadcasterService BroadcasterService { set; protected get; }

        #endregion
but it's null when I break into an action inside this controller.

The above is a workaround I tried for a solution which previous worked for me - invoking the service using GetInvokeOnService, as follows:
        [HttpGet]
        [ActionName("authorize")]
        public HttpResponseMessage Authorize(string user, string session, string channel, string ip, int device, int app)
        {
            ...
            var jsonMap = @"{ " +
                          "'broadcasterIpAddress' : " + "{ 'value' : '" + broadcasterIpAddressString + "' }, " +
                          "'user' : { 'value' : '" + HttpUtility.HtmlDecode(user) + "' }, " +
                          "'session' : { 'value' : '" + HttpUtility.HtmlDecode(session) + "' }, " +
                          "'channel' : { 'value' : '" + HttpUtility.HtmlDecode(channel) + "' }, " +
                          "'clientIpAddress' : { 'value' : '" + ip + "' } " +
                          "}";
            
            var argumentMap    = ModelBinderUtils.CreateArgumentMap(JObject.Parse(jsonMap));
            var response       = GetInvokeOnService(ServiceName, "Authorize", argumentMap);
The value returned from GetInvokeOnService is:
{StatusCode: 404, ReasonPhrase: 'Not Found', Version: 1.1, Content: System.Net.Http.ObjectContent`1[[RestfulObjects.Snapshot.Utility.RestSnapshot, RestfulObjects.Snapshot, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Headers:
{
  Cache-Control: no-cache
  Pragma: no-cache
  Date: Sun, 31 Mar 2013 18:37:12 GMT
  Warning: 199 RestfulObjects "No such service SmsWebApi.BroadcasterService"
  Expires: Sun, 31 Mar 2013 18:37:12 GMT
}}  System.Net.Http.HttpResponseMessage
The ServiceName parameter passed to GetInvokeOnService is a string of the form [namespace.service-class] - "SmsWebApi.BroadcasterService" as the warning above says.

This code used to work perfectly, and I have no idea what could have changed! I've made many changes to other parts of the solution (which I think are not related), but didn't touch this one.

I've tested this with the same parameters previously passed to the service, I've called different actions, with the same result (I've seen status code 422 with some parameters, if it helps).
I've even recreated the Web projects (rather than nuget-update them) with the new versions of NO/RO (I needed a good excuse), once again with the same results.

Any idea regarding any of the above issues?

Thanks,
Ury
Coordinator
Mar 31, 2013 at 8:18 PM
It's Easter break here but I'll investigate and get back to you when I'm back in the office on Tuesday
Apr 1, 2013 at 7:18 AM
Edited Apr 1, 2013 at 7:50 AM
It's Passover over here, but some people just don't care :)
(I mean, I do, my evil customers don't)
Happy holidays!
Coordinator
Apr 2, 2013 at 9:00 AM
Ury,
  1. We don't support injection of services into the RestfulObjectsController so yes that won't work.
  2. System services are deliberately not exposed to the outside world. If you add your service to the 'ContributedActions' services in RunWeb instead you should be able to see it. If the actions on the service show up inappropriately as contributed actions as a result, use the NotContributedActionAttribute to hide again.
Stef
Apr 2, 2013 at 9:40 AM
Thanks Stef,
So just to make sure I got it right: I should register the service in the contributed actions installer (and optionally hide it using the attribute) and then the service WILL be injected to the RestfulObjectsController?
Coordinator
Apr 2, 2013 at 10:27 AM
Sorry I wasn't clear.

We never inject services into the RestfulObjectsController.

In your workaround code snippet you call 'GetInvokeOnService' - this will not see 'System' services only Menu services or contributed action services. So by changing the installer for your BroadcasterService the workaround should now see the service and not return a 404.

'GetInvokeOnService' is only called through the REST api so it deliberately ignores System services - the rationale is that System Services are purely for internal use in the code and have functionality that you almost certainly would not want to expose. You can of course always inject it into a Menu/Contributed actions service and then expose functionality indirectly through that.
Apr 2, 2013 at 10:56 AM
Edited Apr 2, 2013 at 11:08 AM
Now I got it :)

The only reason I'm still stuck on injection is performance (we discussed that before here). The action which contains 'GetInvokeOnService' may be called "a lot of times" (I wish I had a number, but let's take the extreme and say a 1000 time EDIT: per second max for the sake of the discussion). I wonder regarding the extra overhead of 'GetInvokeOnService'. Within the service I'm not calling the DB only directly and not via any framework, so the service implementation hopefully won't act as a bottleneck.
Any thoughts on that?
Coordinator
Apr 2, 2013 at 11:32 AM
There's a variety of different things you could do -
  • Create a new controller - say 'AuthorizeController' and route to that. If you're running under NakedObjects.MVC that will support service injection. That's probably the cleanest approach.
Much more 'hacky' but you can access services from the NO Framework via the static NakedObjectsContext which is in NakedObjects.Core eg
NakedObjectsContext.ObjectPersistor.GetServices()
Which will return you an array of INakedObjects - you can get the actual service through the 'Object' property. You could do something like
 var s = NakedObjectsContext.ObjectPersistor.GetServices().Select(no => no.Object).OfType<BroadcasterService>().Single();
I'd advise using NO.MVC and injecting into a controller. Other approaches are very much 'at your own risk' .
Apr 2, 2013 at 11:59 AM
I'm a risk lover :)

In the meantime I used the "contributed action approach", and after getting a great result for the first request, I keep getting response 422:
{StatusCode: 422, ReasonPhrase: 'Unprocessable Entity', Version: 1.1, Content: System.Net.Http.ObjectContent`1[[RestfulObjects.Snapshot.Utility.RestSnapshot, RestfulObjects.Snapshot, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Headers:
{
  Cache-Control: no-cache
  Pragma: no-cache
  Date: Tue, 02 Apr 2013 11:54:50 GMT
  Warning: 199 RestfulObjects "Mandatory"
  Expires: Tue, 02 Apr 2013 11:54:50 GMT
}}
While I tend to take your advice regarding the NO.MVC solution, it would take me a while to implement it, and it would be great if I have "working" solution in the meantime.

So... any ideas regarding the 422 response?
Coordinator
Apr 2, 2013 at 1:46 PM
I think you're returning an object from the 'Authorize' method on the service that has a null or empty value for one of its properties. By default properties are mandatory so try annotating any properties that may be null on the returned object '[Optionally]'.
Apr 2, 2013 at 4:19 PM
Strange, as the service method returns an enum value.

I've set up an NO.MVC project with with an NO controller and my service. Injection works and the service handles the request nicely.

The thing is, and it brings me all the way back to our previous discussion, is that I just don't know how to return a response containing a string content - which is trivial with WebAPI controllers, only that those can be injected into...

Is there a solid way of returning string content from the NO controller request handler?
Coordinator
Apr 3, 2013 at 8:04 AM
As it's just your custom controller I would have thought you could just return a string using
return Content("mystring"); 
Apr 3, 2013 at 11:08 AM
You're right of course.
I think the problem is routing, only that I'm not sure where it is.
I'm sending the following request:

http://localhost/SmsAPI/__Broadcaster__/authorize?...

In the route debugger, I see the following:
Image

The Ajax thing confuses me, because I'm not sure where it's coming from, why is it taking the place of the "Broadcaster" controller (which does handle the request in effect!). I suspect that this may be the reason for the peer server not getting my response.

Is it by any chance related to NO?
Coordinator
Apr 3, 2013 at 12:46 PM
Quite possibly - try commenting out
RunMvc.RegisterGenericRoutes(routes);
in
NakedObjectsStart : RegisterRoutes
And add your own routing instead.
Apr 3, 2013 at 2:45 PM
Edited Apr 3, 2013 at 2:46 PM
Removing RunMvc.RegisterGenericRoutes made all requests fail on their routes, which led me to the problem and its solution:
Per your good advice above, I moved from a ASP.NET MVC WebAPI / RO.MVC project to a ASP.NET MVC / NO.MVC project, I copied the logic and even copied all the route mapping to the right (different) location, but I left them as WebAPI route mapping (MapHttpRoute) rather than MVC routes (MapRoute). It seems like this type of mappings, though it appears in the route mapping table (!?), is not taken into consideration.

Thanks for your patience and valuable support!