Using WebAPI in LINQPad?
Asked Answered
P

1

17

When I tried to use the Selfhosted WebAPI in LINQPad, I just kept getting the same error that a controller for the class didn't exist.

Do I have to create separate assemblies for the WebAPI (Controllers/Classes) and then reference them in my query?

Here's the code I'm using

#region namespaces
using AttributeRouting;
using AttributeRouting.Web.Http;
using AttributeRouting.Web.Http.SelfHost;
using System.Web.Http.SelfHost;
using System.Web.Http.Routing;
using System.Web.Http;
#endregion

public void Main()
{

    var config = new HttpSelfHostConfiguration("http://192.168.0.196:8181/");
    config.Routes.MapHttpAttributeRoutes(cfg =>
    {
        cfg.AddRoutesFromAssembly(Assembly.GetExecutingAssembly());
    });
    config.Routes.Cast<HttpRoute>().Dump();

    AllObjects.Add(new UserQuery.PlayerObject { Type = 1, BaseAddress = "Hej" });

    config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
    using(HttpSelfHostServer server = new HttpSelfHostServer(config))
    {
        server.OpenAsync().Wait();
        Console.WriteLine("Server open, press enter to quit");
        Console.ReadLine();
        server.CloseAsync();
    }

}

public static List<PlayerObject> AllObjects = new List<PlayerObject>();

public class PlayerObject
{
    public uint Type { get; set; }
    public string BaseAddress { get; set; }
}

[RoutePrefix("players")]
public class PlayerObjectController : System.Web.Http.ApiController
{
    [GET("allPlayers")]
    public IEnumerable<PlayerObject> GetAllPlayerObjects()
    {
        var players = (from p in AllObjects
                    where p.Type == 1
                    select p);
        return players.ToList();
    }
}

This code works fine when in a separate Console Project in VS2012.

I started using AttributeRouting via NuGET when I didn't get the "normal" WebAPI-routing to work.

The error I got in the browser was: No HTTP resource was found that matches the request URI 'http://192.168.0.196:8181/players/allPlayers'.

Additional error: No type was found that matches the controller named 'PlayerObject'

Pair answered 26/12, 2012 at 14:49 Comment(10)
I produced a LINQ script with your code (filling in the missing parts with some dummy classes/methods) and added config.Routes.Cast<HttpRoute>().LogTo(Console.Out);. I do see the route URL: players/allPlayers GET, HEAD, OPTIONS. So that seems to indicate the route is set up correctly. I do get No type was found that matches the controller named 'PlayerObject'. in the browser, but that seems different from your error (and likely related to my dummy classes being too dummy).Hammack
I dumped the routes, and I could see that they are registered, but when trying to go to the page I still got the error. I posted a new code that produces the error.Pair
@FrankvanPuffelen I do get the same error as you do, but only in LINQPad, when I moved this into VS2012 it started to work without trouble. I could always start to use VS, but the easiness of .Dump() in LINQPad is worth much to me. Since I use my laptop too as the "client" for this webapi. :)Pair
Could it be that the reflected type is System.Collections.Generic.IEnumerable``1[UserQuery+PlayerObject] ? Maybe the WebAPI doesn't recognize the UserQuery+PlayerObject as a normal PlayerObject-class?Pair
I added the PlayerObject to MyExtensions, but the error message remains the same. I also added the route as regular Web API without changes. I'm afraid I'm as stuck as you are. :-/Hammack
Oh well, I can live with using VS :) But thanks anyway for taking your time. :)Pair
Any lucj with this? I think it's down to not looking for the assembly in the right place...Jameson
Nope, sorry, haven't got this to work yet. And yes, it probably lies in that (even though specified) it doesn't look for the assembly in the right place, could be that it's dynamically generated.Pair
@NoLifeKing, did Filip W answer your question?Cannell
@PeteKlein It indeed did solve my problem. Just forgot to mark it as accepted. Ty for reminder. :)Pair
D
18

Web API by default will ignore controllers that are not public, and LinqPad classes are nested public, we had similar problem in scriptcs

You have to add a custom controller resolver, which will bypass that limitation, and allow you to discover controller types from the executing assembly manually.

This was actually fixed already (now Web API controllers only need to be Visible not public), but that happened in September and the latest stable version of self host is from August.

So, add this:

public class ControllerResolver: DefaultHttpControllerTypeResolver {

    public override ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver) {
        var types = Assembly.GetExecutingAssembly().GetExportedTypes();
        return types.Where(x => typeof(System.Web.Http.Controllers.IHttpController).IsAssignableFrom(x)).ToList();
    }

}

And then register against your configuration, and you're done:

var conf = new HttpSelfHostConfiguration(new Uri(address));
conf.Services.Replace(typeof(IHttpControllerTypeResolver), new ControllerResolver());

Here is a full working example, I just tested against LinqPad. Note that you have to be running LinqPad as admin, otherwise you won't be able to listen at a port.

public class TestController: System.Web.Http.ApiController {
    public string Get() {
        return "Hello world!";
    }
}

public class ControllerResolver: DefaultHttpControllerTypeResolver {
    public override ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver) {
        var types = Assembly.GetExecutingAssembly().GetExportedTypes();
        return types.Where(x => typeof(System.Web.Http.Controllers.IHttpController).IsAssignableFrom(x)).ToList();
    }
}

async Task Main() {
    var address = "http://localhost:8080";
    var conf = new HttpSelfHostConfiguration(new Uri(address));
    conf.Services.Replace(typeof(IHttpControllerTypeResolver), new ControllerResolver());

    conf.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

    var server = new HttpSelfHostServer(conf);
    await server.OpenAsync();

    // keep the query in the 'Running' state
    Util.KeepRunning();
    Util.Cleanup += async delegate {
        // shut down the server when the query's execution is canceled
        // (for example, the Cancel button is clicked)
        await server.CloseAsync();
    };
}
Derward answered 4/4, 2013 at 23:55 Comment(3)
There's a trick to force LINQPad not to nest types: start your query with #define NONESTColb
I am a bit confused though, where does this code go? I've seen your blog post and trying to figure out if this code goes in LinqPad as a 'C# Program' or does it go in my Web API application? Or is it in both?Fieldwork
One more trick, just define your namespace, then you will have no nest behavior without any tricksManuscript

© 2022 - 2024 — McMap. All rights reserved.