8

I am trying to create my first SXA compatible component, and I can't seem to get everything setup quite right. I decompiled the Maps component, and am using it as my guide. I understand that I need to use an MVC pattern, and so here's what I've got. Here's my controller:

public class OoyalaController : StandardController
{
    private readonly IOoyalaRepository _repository;
    public OoyalaController(IOoyalaRepository repository)
    {
        _repository = repository;
    }

    protected override object GetModel()
    {
        return _repository.GetModel();
    }
}

Here's my Interface:

public interface IOoyalaRepository : IModelRepository
{
}

And here's my repository class:

public class OoyalaComponentRepository: ModelRepository, IOoyalaRepository
{
    public override IRenderingModelBase GetModel()
    {
        OoyalaComponentModel model = new OoyalaComponentModel();
        FillBaseProperties(model);
        return model;
    }
}

Finally, here's my model class:

public class OoyalaComponentModel : RenderingModelBase
{
    public string MediaID { get; set; }
}

I've got my .cshtml looking like this:

@using Sitecore.XA.Foundation.SitecoreExtensions.Extensions
@model Components.Models.OoyalaComponentModel

<div class="component simple-component @Model.CssClasses.Aggregate()">
    <div class="component-content">
        <h1>You are on @Model.MediaID page</h1>
    </div>
</div>

In my content editor view, I believe I have the new rendering added to the right category and setup in the same way that the Map component is setup, as you can see in this screenshot: Ooyala Rendering

I also setup the Parameters template to include the IComponentVariant and Styling base templates: Parameters template

I seem to be missing something, however, as whenever I try to add my component to the page, I get the following error:

Exception: System.InvalidOperationException
Message: An error occurred when trying to create a controller of type 'Components.Controllers.OoyalaController'. Make sure that the controller has a parameterless public constructor.
Source: System.Web.Mvc
   at System.Web.Mvc.DefaultControllerFactory.DefaultControllerActivator.Create(RequestContext requestContext, Type controllerType)
   at System.Web.Mvc.DefaultControllerFactory.CreateController(RequestContext requestContext, String controllerName)
   at Sitecore.Mvc.Controllers.SitecoreControllerFactory.CreateController(RequestContext requestContext, String controllerName)

Nested Exception

Exception: System.MissingMethodException
Message: No parameterless constructor defined for this object.
Source: mscorlib
   at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
   at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.Activator.CreateInstance(Type type, Boolean nonPublic)
   at System.Activator.CreateInstance(Type type)
   at System.Web.Mvc.DefaultControllerFactory.DefaultControllerActivator.Create(RequestContext requestContext, Type controllerType)

I understand that the system is looking for a parameterless constructor, however, the map component doesn't have a parameterless constructor.

What do I have configured/coded incorrectly?

11

To inject something via constructor or get types with Service Locator you need to register your services first.

public class RegisterOoyalaServices : IocProcessor
{
    public override void Process(IocArgs args)
    {
        args.ServiceCollection.AddTransient<IOoyalaRepository, OoyalaComponentRepository>();
    }
}
<pipelines>
  <ioc>
    <processor type="NAMESPACE.RegisterOoyalaServices, NAMESPACE" />
  </ioc>
</pipelines>

You will have to reference following DLLs:

  • Sitecore.XA.Foundation.IoC
  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.DependencyInjection.Abstractions
  • 1
    Almost - you need to register the controller too - without that the controller factory will try to create the controller with a parameterless constructor. – Richard Seal Nov 29 '16 at 23:32
  • 4
    You don't have to register controllers explicitly while working with SXA. ExperienceAcceleratorDependencyResolver will take care of that. – Alan Płócieniak Nov 29 '16 at 23:45
6

Ok - please don't use ServiceLocator to "fix" this issue. The problem looks to be because you have not registered your OoyalaController with your IoC container.

If you are using the standard Sitecore 8.2 container you can do this by creating a configurator for your project. You should also register your dependencies that the controller is requesting.

using Microsoft.Extensions.DependencyInjection;
using Sitecore.DependencyInjection;

namespace MyProject
{
    public class MyConfiguratorClass : IServicesConfigurator
    {
        public void Configure(IServiceCollection serviceCollection)
        {
            serviceCollection.AddScoped<OoyalaController>();
            serviceCollection.AddTransient<IOoyalaRepository, OoyalaComponentRepository>();
        }
    }
}

Register the configurator in your config like this:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <services>
            <configurator type="MyProject.MyConfiguratorClass, MyProject" />
        </services>
    </sitecore>
</configuration>

That will enable the IoC Container to resolve the dependencies for the controller when it is created.

Check out this pose on Dependency Injection in Sitecore 8.2 by Kam - there are some more details there about how to register all controllers in your project in a single call to make it easier.

  • +1 You're completely right - I fixed my solution accordingly. I was trying to help to guide the OP towards the answer and Service Locator was only an example, albeit a bad one. I only didn't suggest Sitecore DI because the OP didn't mention Sitecore 8.2. – Zachary Kniebel Nov 30 '16 at 0:17
2

A quick fix could be to do this (using ServiceLocator as SXA does in the StandardController):

private readonly IOoyalaRepository _repository;
public OoyalaController()
{
    _repository = ServiceLocator.Current.Resolve<IOoyalaRepository>();
}

For now I have no answer to why the maps controller does work with a constructor with parameters...

  • 1
    When I try to use the ServiceLocator.Current, Visual Studio complains that it doesn't know what Current is. Do you now what assembly I need to reference in order to make that resolve? – Keith VanderVeen Nov 29 '16 at 22:20
  • 2
    The StandardController gets it from Sitecore.XA.Foundation.IoC – Gatogordo Nov 29 '16 at 22:22
  • 1
    +1 for the namespace in SXA. I couldn't remember if SXA had it's own container. Also, +1 for the answer, since I wasn't going to include the instantiation of the Repository in my answer, originally – Zachary Kniebel Nov 29 '16 at 22:24
  • 2
    Still wondering why the SXA feature controllers works with a constructor with parameters.. could be something with the ExperienceAcceleratorDependencyResolver but it's getting too late to think clearly (for me..) – Gatogordo Nov 29 '16 at 22:31
  • 1
    Nooooo - don't service locate it, the controller just needs to be registered with the IoC container. That is why the SXA controllers work. – Richard Seal Nov 29 '16 at 23:08
1

The problem is that you don't have a parameterless constructor for your controller. It's not the component that is the issue. The code is expecting to be able to instantiate your controller without parameters, but because you defined a constructor for your controller that does have a parameter you now need to explicitly define a parameterless constructor, as well.

Changing your controller to the following help to highlight the issue by induction:

public class OoyalaController : StandardController
{
    private readonly IOoyalaRepository _repository;

    public OoyalaController(IOoyalaRepository repository)
    {
        _repository = repository;
    }

    public OoyalaController()
    {
       ...instantiate your Repository here, or resolve it or...
    }

    // as mentioned in comments, need to instantiate the Repository or this will fail
    protected override object GetModel()
    {
        return _repository.GetModel();
    }
}

To verify that this resolves your issue, try changing the parameterless constructor to the following temporarily:

    public OoyalaController()
    {
       _repository = new OoyalaRepository(); // replace with actual constructor, if different
    }

So long as the above works, we can verify that this controller was your issue. The next thing to do is fix your IoC issue.

As mentioned by @RichardSeal, you should not Service Locate (I previously used this as a DI example, even though Service Locator is not a(n) (anti-)pattern that I recommend for use), but rather you should be using IoC to register your controller, so that your code can instantiate it with parameters. I won't repeat his answer here, but the first thing that you should do is remove the new parameterless constructor from your controller and register the class per Richard or Alan's solution.

  • When I do that, then my GetModel() call fails, because the _repository variable has not been instantiated. How do I get the proper reference to the model defined in the component if I don't pass it in? – Keith VanderVeen Nov 29 '16 at 22:13
  • Yes, you are still going to need to instantiate your repository. You can do so directly (don't tell the DI police ;), using a resolver, or using something else. You can also create your own controller factory that instantiates your controllers using a parameterless constructor and sets up the repository for them. There are a bunch of options that are DI friendly, but a little out of scope for this answer. – Zachary Kniebel Nov 29 '16 at 22:15
  • I updated my post to show an example that remains DI-friendly and shoudl resolve the errors with the parameterless constructor and the repository being null – Zachary Kniebel Nov 29 '16 at 22:20
  • Thank you for your help in getting this figured out! I think that I'm close now, but when I made the change to use the ServiceLocator class, I'm getting a Exception: System.NullReferenceException Message: Object reference not set to an instance of an object. Source: Components at Components.Controllers.OoyalaController.GetModel() at Sitecore.XA.Foundation.Mvc.Controllers.StandardController.Index() exception. I'm not sure, but it looks like maybe that resolver isn't finding my new class. Any ideas what else I could try? – Keith VanderVeen Nov 29 '16 at 22:48
  • When you debug at that line, is your _repository initialized? – Zachary Kniebel Nov 29 '16 at 22:51
0

Just to summarize the discussion - you don't need to register controllers in the SXA container. Just like Alan mention above they will be automatically resolved by ExperienceAcceleratorDependencyResolver.

But please remember to register all of the services which you want to inject into your controller!

I would suggest usage of ServiceLocator just in the places where constructor injection isn't possible.

@Richard Seal: Thanks to SXA you are able to use Dependency Injection also on Sitecore 8.1! (we are using .NET Core container, just like in the 8.2)

-1

I think the following statement will work out best for you:

_repository = ServiceLocator.Current.Resolve<ISimpleComponentRepository >();
  • I'm trying to understand what this answer means. What class does this code go in? There is no ISimpleComponentRepository given in the example, and so I'm not sure how this helps to answer the question. – Keith VanderVeen Jan 12 '17 at 17:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.