11

I'm trying to create a system that will allow me to host a "WebAPI" website either through a web application or through a windows service. To this end I want all my buisness logic to be contained within one class library so that I can reference this in both my windows service and my "web" (IIS) service.

My current idea is using the self hosted options included in HttpSelfHostServer. For the web end I would just create a standard webapi website and add some reference to my class library.

What I have found is that if I have the controller in the same namespace as the HttpSelfHostServer it works correctly but as soon as the controller is within an external class library the server can no longer resolve the route to my control / action.

My code:

Windows service:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Reflection;
using System.IO;

using System.Web.Http.SelfHost;
using System.Web.Http;
using System.Web.Http.Dispatcher;

using WebApiClasses;
using WebApiClasses.Controllers;

namespace WebAPISelfHost
{
    public partial class Service1 : ServiceBase
    {
        private HttpSelfHostServer _server;
        private readonly HttpSelfHostConfiguration _config;
        public const string ServiceAddress = "http://localhost:8080";

        public Service1()
        {
            InitializeComponent();

            _config = new HttpSelfHostConfiguration(ServiceAddress);

            //AssembliesResolver assemblyResolver = new AssembliesResolver();
            //_config.Services.Replace(typeof(IAssembliesResolver), assemblyResolver);

            _config.Routes.MapHttpRoute("DefaultApi",
                "api/{controller}/{id}",
                new { id = RouteParameter.Optional });

        }

        protected override void OnStart(string[] args)
        {
            _server = new HttpSelfHostServer(_config);
            _server.OpenAsync();
        }




        protected override void OnStop()
        {
            _server.CloseAsync().Wait();
            _server.Dispose();
        }
    }

    //public class TestController : ApiController
    //{
    //    public string Get()
    //    {
    //        return "This is an internal test message.";
    //    }
    //}

    class AssembliesResolver : DefaultAssembliesResolver
    {
        public override ICollection<Assembly> GetAssemblies()
        {
            ICollection<Assembly> baseAssemblies = base.GetAssemblies();
            List<Assembly> assemblies = new List<Assembly>(baseAssemblies);

            // Add whatever additional assemblies you wish

            var controllersAssembly = Assembly.LoadFrom(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\WebApiClasses.dll");
            baseAssemblies.Add(controllersAssembly);
            return assemblies;
        }
    }
}

Controller:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Web.Http;

namespace WebApiClasses.Controllers
{
    public class TestController : ApiController
    {
        public string Get()
        {
            return "hello from class library";
        }
    }
}

When I try to navigate to: "http://localhost:8080/api/" I get:

No HTTP resource was found that matches the request URI 'http://localhost:8080/api/'. No type was found that matches the controller named 'Test'.

Any suggestions? I think I should be able to do this.

2
  • Just to be clear - you're looking to host your WebAPI project both in IIS and self-hosted in a Windows Service? Commented Dec 18, 2012 at 18:29
  • Yes basically, host in IIS or self-hosted but more importantly I only want one code base i.e. I don't want to have to have different code for the IIS hosted stuff from the windows service hosted stuff. Commented Dec 19, 2012 at 9:08

5 Answers 5

11

Have a look at this article http://www.strathweb.com/2012/06/using-controllers-from-an-external-assembly-in-asp-net-web-api/

In it they describe how to load in extra assemblies with controllers in them.

Sign up to request clarification or add additional context in comments.

Comments

2

For self hosting scenario provide a custom IAssembliesResolver implementation as other solutions suggest and add the required assemblies to the collection either by using a type in the controller's assembly or by its file path.

class AssembliesResolver : DefaultAssembliesResolver
{
    public override ICollection<Assembly> GetAssemblies()
    {
        ICollection<Assembly> baseAssemblies = base.GetAssemblies();
        List<Assembly> assemblies = new List<Assembly>(baseAssemblies);

        //Load by a type in the assembly
        var _serviceAssembly = Assembly.GetAssembly(typeof(CarsController));

        //Load from a file
        //var _serviceAssembly = Assembly.LoadFrom("abc.dll");

        //Check if the assembly is already in the list
        //if you are getting the assembly reference by a type in it,
        //The assembly will probably be in the list
        if (!assemblies.Contains(_serviceAssembly))
            assemblies.Add(_serviceAssembly);

        return assemblies;
    }

Then in a console application's Main method or in a service class's OnStart method, replace the assembly resolver service.

var config = new HttpSelfHostConfiguration("http://localhost:3000");
config.Services.Replace(typeof(IAssembliesResolver), new AssembliesResolver());
config.Routes.MapHttpRoute("default", "api/{controller}/{action}/{id}", new { id = RouteParameter.Optional });
var server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();

Comments

1

I have a similar problem and i do the following actions and i solve it.

  1. First of all add the reference from the external class library in the api project.
  2. The controller must inherit from ApiController
  3. The controller class must have public acess modifier and must the controller name to end with Controller.For example TestController ,EmployeeController etc
  4. And the very important the nuget packages must have the same version.

I also sugget the using of attributes like [HttpGet],[HttpPost]

1 Comment

This is the best Answer :)
0

I eventually found out how to do this I had to re-arrange my code as such:

Windows Service:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Reflection;
using System.IO;

using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Dispatcher;
using System.Web.Http.SelfHost;

namespace WebAPISelfHost
{
    public partial class Service1 : ServiceBase
    {
        static HttpSelfHostServer CreateHost(string address)
        {
            // Create normal config
            HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(address);

            // Set our own assembly resolver where we add the assemblies we need
            AssembliesResolver assemblyResolver = new AssembliesResolver();
            config.Services.Replace(typeof(IAssembliesResolver), assemblyResolver);

            // Add a route
            config.Routes.MapHttpRoute(
              name: "default",
              routeTemplate: "api/{controller}/{id}",
              defaults: new { controller = "Home", id = RouteParameter.Optional });

            HttpSelfHostServer server = new HttpSelfHostServer(config);
            server.OpenAsync().Wait();

            return server;
        }

        public Service1()
        {
            InitializeComponent();

        }

        protected override void OnStart(string[] args)
        {
            HttpSelfHostServer server = CreateHost("http://localhost:8080");
        }

        protected override void OnStop()
        {
        }
    }

    class AssembliesResolver : DefaultAssembliesResolver
    {
        public override ICollection<Assembly> GetAssemblies()
        {
            ICollection<Assembly> baseAssemblies = base.GetAssemblies();
            List<Assembly> assemblies = new List<Assembly>(baseAssemblies);

            assemblies.Add(Assembly.LoadFrom(@"C:\Users\david.kolosowski\Documents\Visual Studio 2010\Projects\WebAPISelfHost\WebAPISelfHost\bin\Debug\ClassLibrary1.dll"));

            return assemblies;
        }
    }
}

Class library:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Web.Http;

namespace ClassLibrary1
{
    public class TestController : ApiController
    {
        public string Get()
        {
            return "hello from class library2";
        }
    }
}

I think there where various configuration problems with the solution so I deleted all my reference and added only the ones required for each project.

Thanks to all who posted suggestions.

2 Comments

Your answer is nearly identical to the solution in the article I linked in my answer. Did you use it?
Sorry forgot to respond here. I had already read that article, but thanks anyway.
0

For future readers.

Here is (one way) how you use a different assembly and use IIS.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            //routeTemplate: "api/{controller}/{id}",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

and

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Http.Dispatcher;

public class MyCustomAssemblyResolver : DefaultAssembliesResolver
{
    public override ICollection<Assembly> GetAssemblies()
    {
        ICollection<Assembly> baseAssemblies = base.GetAssemblies();
        List<Assembly> assemblies = new List<Assembly>(baseAssemblies);
        Type myType = typeof(MyControllerInAnotherAssembly);
        var controllersAssembly = Assembly.GetAssembly(myType );
        baseAssemblies.Add(controllersAssembly);
        return assemblies;
    }
}

and (Global.asax)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Http.Dispatcher;
using System.Web.Security;
using System.Web.SessionState;


public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        WebApiConfig.Register(GlobalConfiguration.Configuration);    
        GlobalConfiguration.Configuration.Services.Replace(typeof(IAssembliesResolver), new MyCustomAssemblyResolver());
    }
}

1 Comment

For self hosting scenario adding the controller assembly by an implementing type causes duplicate assembly entry in the assemblies collection. So you need to load the assembly by its file path.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.