2

I'm working on my first API using MVC. I've gotten it working previously by creating an API and declaring/creating its data within the controller, like so:

public class ValuesController : ApiController
{
    private northwndEntities db = new northwndEntities();

    Product[] products = new Product[] 
    { 
        new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, 
        new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, 
        new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } 
    };

    public IEnumerable<Product> GetAllProducts()
    {
        return products;
    }

    public Product GetProduct(int id)
    {
        var product = products.FirstOrDefault((p) => p.Id == id);

        return (product);
    }
}

Here is a model I created quickly to work with this: Product.cs

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Category { get; set; }
    public decimal Price { get; set; }
}

I don't believe it was necessary, but here is the script I used to make the call, even though for earlier testing, I just navigated to the appropriate URL, rather than trying to be fancy.

 <script>
        //var apiurl = "api/values";
        var apiurl = "api/ordersapi";
        $(document).ready(function() {
            $.getJSON(apiurl).done(function(data) {
                $.each(data, function(key, item) {
                    $('<li>', { text: formatItem(item) }).appendTo($('#products'));
                });
            });
        });

        function formatItem(item) {
            return item.Name + ": $" + item.Price;
        }

        function find() {
            var pId = $('#prdId').val();
            $.getJSON(apiurl + '/' + pId)
                .done(function (data) {
                    $('#product').text(formatItem(data));
                })
                .fail( function(jqxHr, textStatus, err) {
                    $('#product').text("Error: "+err);
                });
        }
    </script>

With this in mind, a call to "api/values/2" would return the data for ID = 2 I can get this working no problem. Of course, I am also making sure to change the url variable I am using when trying to call the API that i'm about to outline below.

Next, I wanted to step up to using a separate API by calling from my pre-existing (database-first style) database.

I am using repository pattern and dependency injection so here is the code for my repository (named "repo.cs"), the API controller (named "OrdersAPI" controller), as well as my ninjectWebcommon.cs file

Repo.cs (the repository class)

public interface INorthwindRepository : IDisposable
{
    IQueryable<Order> GetOrders();
    Order GetOrderById(int id);
}

public class NorthwindRepository : INorthwindRepository
{
    private northwndEntities _ctx;

    public NorthwindRepository(northwndEntities ctx)
    {
        _ctx = ctx;
    }
    public IQueryable<Order> GetOrders()
    {        
        return _ctx.Orders.OrderBy(o => o.OrderID);
    }

    public Order GetOrderById(int id)
    {
        return _ctx.Orders.Find(id);
    }

    public void Dispose()
    {
        _ctx.Dispose();
    }
}

OrdersAPIController.cs

public class OrdersAPIController : ApiController
{

    private INorthwindRepository db;

    public OrdersAPIController(INorthwindRepository _db)
    {
        db = _db;
    }

    //api/ordersapi
    public IEnumerable<Order> Orders()
    {
        return db.GetOrders();
    }

//api/ordersapi/5
    public Order SpecificOrder(int id)
    {
        Order order = db.GetOrderById(id);

        return order;
    }
}

NinjectWebCommon.cs (*Note the comment in CreateKernel() method)

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(RAD302PracticeAPI.App_Start.NinjectWebCommon), "Start")]

[assembly: WebActivatorEx.ApplicationShutdownMethodAttribute(typeof(RAD302PracticeAPI.App_Start.NinjectWebCommon), "Stop")]

namespace RAD302PracticeAPI.App_Start
{
using System;
using System.Web;

using Microsoft.Web.Infrastructure.DynamicModuleHelper;

using Ninject;
using Ninject.Web.Common;
using RAD302PracticeAPI.Models;
using System.Web.Http;
using Ninject.Web.Mvc;
using System.Web.Mvc;
//using System.Web.Http;
//using Ninject.Web.Mvc;

public static class NinjectWebCommon 
{
    private static readonly Bootstrapper bootstrapper = new Bootstrapper();

    /// <summary>
    /// Starts the application
    /// </summary>
    public static void Start() 
    {
        DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
        DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
        bootstrapper.Initialize(CreateKernel);

        //tried code from...
        //http://haacked.com/archive/2012/03/11/itrsquos-the-little-things-about-asp-net-mvc-4.aspx/
        //but it didnt work
        //GlobalConfiguration.Configuration.ServiceResolver.SetResolver(DependencyResolver.Current.ToServiceResolver());
    }

    /// <summary>
    /// Stops the application.
    /// </summary>
    public static void Stop()
    {
        bootstrapper.ShutDown();
    }

    /// <summary>
    /// Creates the kernel that will manage your application.
    /// </summary>
    /// <returns>The created kernel.</returns>
    private static IKernel CreateKernel()
    {


        var kernel = new StandardKernel();
        try
        {
            kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
            kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

            RegisterServices(kernel);

            //this line is giving me an error saying:
            //"Cannot implicitly convert type 'Ninject.Web.Mvc.NinjectDependencyResolver' to
            //'System.Web.Http.Dependencies.IDependencyResolver'. An explicit conversion exists (are you missing a cast?)
            //However from one or two places here it has been recommended as a possible solution to solve
            //the dependency issue

            //one place is here: http://stackoverflow.com/questions/17462175/mvc-4-web-api-controller-does-not-have-a-default-constructor

            GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);

            return kernel;
        }
        catch
        {
            kernel.Dispose();
            throw;
        }
    }

    /// <summary>
    /// Load your modules or register your services here!
    /// </summary>
    /// <param name="kernel">The kernel.</param>
    private static void RegisterServices(IKernel kernel)
    {
        kernel.Bind<northwndEntities>().To<northwndEntities>();   
    }        
}
}

From the link i've posted in the commented area of my ninject.cs page, it seems the problem is that I need to set a dependency resolver for my application. I THINK that this is done for you when you're not using an API, but in this situation you must. I'm open to correction on that. So, my hunch is that I need to create a dependency resolver class, but the line I left a comment on is not working, and is supposedly the solution I need, based on other SO pages.

Thank you to anybody who takes the time to offer any advice. Getting over this hurdle will allow me to get where I want to be, for now at least. I've put some effort into researching what the problem is. Just hoping a more experienced head can spot something subtle.

*Update: When I uncomment this line

"GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);"
ie - the line that was recommended to add in the link that I included as a comment in my code, I get the error in this image http://postimg.org/image/qr2g66yaj/

And when I include that line, the error that i'm given is this: http://postimg.org/image/9wmfcxhq5/

Oisín Foley
  • 2,317
  • 2
  • 22
  • 29
  • Can you please post the exact error message / exception that you are getting? – Brendan Green Aug 20 '15 at 00:40
  • 2
    Also, you are registering the `northwndEntities` class, but not the `NorthwindRepository`? I'd guess that the resolver is falling back to looking for a parameterless constructor because it can't find a registration that matches the current constructor? – Brendan Green Aug 20 '15 at 00:43
  • It should have been NorthwindRepository that was a coding mistake. Unfortunately it still didn't solve my issue. – Oisín Foley Aug 20 '15 at 15:14

3 Answers3

2

You need to bind your repository and your interface on RegisterServices method:

kernel.Bind<INorthwindRepository>().To<NorthwindRepository>();

Also, you have to check your project configuration. You can download a Simple Ninject demo that I've created from my Github account and compare it with your project.

Hernan Guzman
  • 1,235
  • 8
  • 14
  • I incorrectly tried to bind "northwindentities" instead of "INorthwindRepository". Unfortunately making the change doesn't fix the problem. The only differences in my file to yours are ones related to the fact that you're using a normal controller as opposed to an API controller. – Oisín Foley Aug 20 '15 at 14:57
1

DI container need to know all dependencies to instantiate class. Your class requires INorthwindRepository (as it is specified in only constructore of the controller class - public OrdersAPIController(INorthwindRepository _db)) but no implementation of this interface is registered with container.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • Sorry for being stupid but do you mean in my Ninject file, have something like this? ` private static void RegisterServices(IKernel kernel) { kernel.Bind().To(); }` – Oisín Foley Aug 20 '15 at 15:15
  • @OFoley - looks reasonable. I'm not very familiar with Ninject to confirm the syntax. – Alexei Levenkov Aug 20 '15 at 17:26
1

It turns out I needed to add the following code at the top of my NinjectWebCommon.cs file

public class NinjectDependencyScope : IDependencyScope
{
    IResolutionRoot resolver;

    public NinjectDependencyScope(IResolutionRoot resolver)
    {
        this.resolver = resolver;
    }

    public object GetService(Type serviceType)
    {
        if (resolver == null)
            throw new ObjectDisposedException("this", "This scope has been disposed");

        return resolver.TryGet(serviceType);
    }

    public System.Collections.Generic.IEnumerable<object> GetServices(Type serviceType)
    {
        if (resolver == null)
            throw new ObjectDisposedException("this", "This scope has been disposed");

        return resolver.GetAll(serviceType);
    }

    public void Dispose()
    {
        IDisposable disposable = resolver as IDisposable;
        if (disposable != null)
            disposable.Dispose();

        resolver = null;
    }
}

public class NinjectDependencyResolver : NinjectDependencyScope, System.Web.Http.Dependencies.IDependencyResolver
{
    IKernel kernel;
    public NinjectDependencyResolver(IKernel kernel)
        : base(kernel)
    {
        this.kernel = kernel;
    }
    public IDependencyScope BeginScope()
    {
        return new NinjectDependencyScope(kernel.BeginBlock());
    }
} 

It brought me to a new problem where upon navigation to "http://localhost:53895/api/OrdersAPI/2" I get a message in my browser saying

{"Message":"The requested resource does not support http method 'GET'."}

Somebody may find this useful in the future.

At least i'm nearly there :)

Oisín Foley
  • 2,317
  • 2
  • 22
  • 29
  • 1
    Stick a `[HttpGet]` attribute on the `SpecificOrder()` action,. – Brendan Green Aug 20 '15 at 22:59
  • I got it all working, turned out I was trying to query an ID that didn't exist in my database. (The ids start at 10000). The [httpget] had been something I was messing around with too. Thanks for the help – Oisín Foley Aug 21 '15 at 00:51
  • Will need to add this to the global.asax for returning json rather than xml GlobalConfiguration.Configure(WebApiConfig.Register); var config = GlobalConfiguration.Configuration; config.Formatters.Remove(config.Formatters.XmlFormatter); – Oisín Foley Aug 25 '15 at 11:46