That darn cat
That darn cat

Reputation: 3

C# MVC, Dependency Injection And Child Objects

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.

Upvotes: 0

Views: 1047

Answers (1)

JDBennett
JDBennett

Reputation: 1505

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;

    }
}

}

Upvotes: 1

Related Questions