0

I've been following the Pro ASP.NET MVC5 book by Adam Freeman and whilst it's been fantastic for uncovering the basics of MVC and DI it's a bit lacking on the details to apply the learning to real-world examples.

I want to return a view for an Article, and show all the comments for that article. Using web-forms this is straightforward, however I want to use MVC and DI and I'm sot sure how to proceed.

I have the Article class:

public class Article
{
    //[DataMember]
    [HiddenInput(DisplayValue = false)]
    public int ID { get; set; }
    //[DataMember]
    [Required(ErrorMessage = "Please enter a headline")]
    [StringLength(100, ErrorMessage = "Headline cannot be longer than 100 characters.")]
    public virtual string Headline { get; set; }

    [...]

    //TO DO: Find a way to use DI for this
    //[NotMapped]
    private IEnumerable<Comment> comments;
    [NotMapped]
    public IEnumerable<Comment> Comments
    {
        get
        {
            if (null == comments)
            {
                ICommentRepository cRepository = new CommentRepository();
                comments = cRepository.Comments.Where(c => c.ArticleID == this.ID).ToList();
            }
            return comments;
        }
    }
}

The problem I have is with the lines:

        ICommentRepository cRepository = new CommentRepository();
        comments = cRepository.Comments.Where(c => c.ArticleID == this.ID).ToList();

The above lines mean that I'm now relying on an interface and a concrete repository.

The Controller is as follows:

public class ArticleController : Controller
{
    private IArticleRepository repository;


    public ArticleController(IArticleRepository articleRepository)
    {
        repository = articleRepository;
    }

    public PartialViewResult SnippetView(int id)
    {
        return PartialView(repository.GetByID(id));
    }
}

I don't see a way to add the CommentRepository to it - unless I add a Property Injection, which seems like a bodge, as I would have to do this for all child objects for the parent class. Some of the child objects also have child objects - in this case Comments have a Member object:

    public PartialViewResult SnippetView(int id)
    {
        Article a = repository.GetByID(id);
        a.CommentReposiory = ICommentRepository;

        return PartialView(repository.GetByID(id));
    }

I have updated the classes as per responses provided by @JDBennett

The article class has an added parameterised constructor to a comment service:

public class Article
{
    private ICommentService _commentService;

    //public Article()
    //{

    //    IKernel ninjectKernel = new StandardKernel();
    //    ninjectKernel.Bind<ICommentService>().To<CommentService>();
    //    ninjectKernel.Bind<ICommentRepository>().To<CommentRepository>();
    //    _commentService = ninjectKernel.Get<ICommentService>();

    //}

    public Article(ICommentService commentService)
    {
        _commentService = commentService;
    }

    //[DataMember]
    [HiddenInput(DisplayValue = false)]
    public int ID { get; set; }

    [...]

    [NotMapped]
    public IEnumerable<Comment> Comments
    {
        get
        {
            //return _commentService.Comments.ToList();

            return _commentService.Comments.Where(c => c.ID == this.ID).ToList();
        }
    }
}

The comment Service interface and class are as follows:

Comment service Interface:

public interface ICommentService
{
    IEnumerable<Comment> Comments { get; }
}

Comment Service class: public class CommentService : ICommentService { private ICommentRepository cRepository;

    public CommentService(ICommentRepository repo)
    {
        cRepository = repo;
    }

    public IEnumerable<Comment> Comments
    {
        get
        {
            return cRepository.Comments.ToList();
        }
        /*set not yet implemented*/   
    }
}

The ninject resolver code is:

public class NinjectDependencyResolver : IDependencyResolver
{
    private IKernel kernel;

    public NinjectDependencyResolver(IKernel kernelParam)
    {
        kernel = kernelParam;
        AddBindings();
    }

    public object GetService(Type serviceType)
    {
        return kernel.TryGet(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return kernel.GetAll(serviceType);
    }

    private void AddBindings()
    {
        //put bindings here

        //comment service
        kernel.Bind<ICommentService>().To<CommentService>();

        //Community db
        kernel.Bind<IBlogRepository>().To<BlogRepository>();
        kernel.Bind<ICommentRepository>().To<CommentRepository>();
        //news db
        kernel.Bind<IArticleRepository>().To<ArticleRepository>();
        kernel.Bind<IMemberRepository>().To<MemberRepository>();
        //registration db
        kernel.Bind<IEventRepository>().To<EventRepository>();
        kernel.Bind<IRegistrationRepository>().To<RegistrationRepository>();


    }
}

The ninjectwebcommon file, located in the app_start folder:

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(Nordics.App_Start.NinjectWebCommon), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethodAttribute(typeof(Nordics.App_Start.NinjectWebCommon), "Stop")]

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

    using Microsoft.Web.Infrastructure.DynamicModuleHelper;

    using Ninject;
    using Ninject.Web.Common;

    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);
        }

        /// <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);
                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)
        {
            System.Web.Mvc.DependencyResolver.SetResolver(new Nordics.Infrastructure.NinjectDependencyResolver(kernel));
        }        
    }
}

The code compiles, but when I attempt to run the code I get an error:

System.Reflection.TargetInvocationException was unhandled by user code.
Message=Exception has been thrown by the target of an invocation.
[...]
InnerException: System.InvalidOperationException
Message=The class 'Domain.Entities.Article' has no parameterless constructor.

If I was to update the Article class to remove the parameterised constructor, then add the remm'd out parameterless one, the code compiles and runs, which is fine except that it feels to be a bit of a bodge, that makes the code less testable.

  • 4
    Your `Comments` property should have a simple `{ get; set; }` and you inject both `IArticleRepository` and `ICommentRepository` into the controller. But you should be using a view model anyway, not data models with `[NotMapped]` properties –  Jul 05 '16 at 12:46
  • Do you really even need to inject a ICommentRepository? If they are in the same DbContext, Why not just map the Comments to an article using a navigation property. And like Stephen said, split your view model from your ef model. – Fran Jul 05 '16 at 13:57
  • @Fran The Comments Table is in a separate dbContext to the Articles table. – That darn cat Jul 05 '16 at 14:49
  • How does this statement in the Article class public IEnumerable Comments work if they are in different contexts – Fran Jul 05 '16 at 14:52
  • hi @Fran, I'm not sure I understand the question but: I have 2 connection string values in the web.config file, one for each database/dbcontext. – That darn cat Jul 05 '16 at 15:09
  • I see, you marked the Comments [NotMapped]. What I was trying to explain was this answer to this question http://stackoverflow.com/questions/30608266/entity-framework-relationships-between-different-dbcontext-and-different-schemas – Fran Jul 05 '16 at 15:30

1 Answers1

1

The IoC container of your choosing will resolve the components.

You are correct in that you do not want property injection nor do you want to instantiate the object directly (as in your example). Instead I would provide a CommentService to be resolved by the Dependency Resolver for your article class.

Below I define a simple interface. The implementation of the interface is important because I also use the dependency resolver to resolve the Repository.

public interface ICommentService
{

      IEnumerable<Comment> Comments {get;set;}

 }


public class CommentService : ICommentService
{
    //Using the repository pattern - I assume the comments come from a Database somewhere
    private IRepository _repository;

    public CommentService(IRepository repository)
    {

         _repository = repository;

    }

    IEnumerable<Comments> 
    {
        get
        {
             return _repository.Comments().ToList();    

        }
        set
        {
               _repository.Comments.AddRange(val);
               _repository.SaveChanges();
        }        

}


public class Article
{
     private ICommentService _commentService;

     public Article (ICommentService commentService)
     {
         _commentService = commentService;

      }


    //[DataMember]
   [HiddenInput(DisplayValue = false)]
   public int ID { get; set; }

   //[DataMember]
   [Required(ErrorMessage = "Please enter a headline")]
   [StringLength(100, ErrorMessage = "Headline cannot be longer than 100 characters.")]
   public virtual string Headline { get; set; }



[NotMapped]
public IEnumerable<Comment> Comments
{
    get
    {
        //This is all you need 
        return _commentService.Comments;

    }
}

}

JDBennett
  • 1,323
  • 17
  • 45
  • Hi @JDBennett, I've updated my files to reflect your suggestions. After compiling and running, I'm now getting an issue from the Controller class. The code fails when I try to return the above Partial View result. When I drill down, the issue arises in the article repository: public Article GetByID(int id) { return context.Article.FirstOrDefault(a => a.ID == id); } I receive an error message, The class 'Domain.Entities.Article' has no parameterless constructor. I feel like I'm close, but not quite there...! – That darn cat Jul 06 '16 at 14:39
  • What type of dependency injection component are you using? That looks like your controllers are not getting registered? – JDBennett Jul 06 '16 at 18:44
  • Actually it sounds like the DependencyResolver has not been registered correctly. Can you post what your Startup code looks like as it pertains to the DI components? – JDBennett Jul 06 '16 at 23:40
  • Hi @JDBennett, I have a NinjectDependencyResolver class, and I've added the CommentService to the AddBindings Method: "kernel.Bind().To();">. Other classes resolve/bind correctly, so I'm assuming that it's been resolved correctly. – That darn cat Jul 07 '16 at 09:36
  • Bizarrely, when I added a parameterless constructor with the following code, it worked: public Article() { IKernel ninjectKernel = new StandardKernel(); ninjectKernel.Bind().To(); ninjectKernel.Bind().To(); _commentService = ninjectKernel.Get(); } . It's like I'm close to a solution, but not quite there....! – That darn cat Jul 07 '16 at 11:04
  • What problem do you see now? – JDBennett Jul 07 '16 at 13:38
  • It works with the above parameterless constructor, but when I replace it with the Article (ICommentService commentService) constructor I receive an error message, The class 'Domain.Entities.Article' has no parameterless constructor. – That darn cat Jul 07 '16 at 14:21
  • Did you register the Article class for Ninject to resolve? I.e Kernel.Bind
    ().To
    ();can you update your question with your Ninject registration code so I can see what you are doing there?
    – JDBennett Jul 07 '16 at 18:51
  • I don't see you setting the DependencyResolver to the NinjectDependencyResolver? – JDBennett Jul 27 '16 at 23:29
  • Apologies if I'm being a bit dense, but haven't I done that on: private static void RegisterServices(IKernel kernel)? – That darn cat Aug 09 '16 at 10:53
  • Looking closer at this. I see you have IArticleRepository in the Bindings. Where are you registering the Article class? I also do not see an interface that the Article class implements? Ninject needs to know what types to resolve (hence the convention binding). Modify the Article class so that it implements IArticleRepository and then bind the Article class to IArticleRepository in the AddBindings method. When you run- put a breakpoint on the constructor of the article class to see the ICommentService being resolved by Ninject. – JDBennett Aug 09 '16 at 17:04