Search code examples
c#xamarinxamarin.formsninjectninject-extensions

Custom Instance Provider doesn't trigger in Xamarin.Forms App


I've read the "Mastering Ninject for Dependency Injection" book, and I made the Telecom exercise where it teaches you to use Custom Instance Providers to scenarios where 2 or more classes share the same interface.

I tried to apply the same concept to DesignTime Data and Runtime and if I don't use Factories or conventions my app works fine, however, as soon as I try to use Custom Instance Providers the code seems fine, but when it reaches the Factory Implementation it doesn't trigger my Instance provider to get the proper implementation for runtime or design time data.

This is my Ninject Module:

public class WhyMvvmModule : NinjectModule
{
    public override void Load()
    {
        Bind<IFriendServiceFactory>().ToFactory(() => new NameAttributeInstanceProvider());
        Bind<IDialogService>().To<DialogService>();
        Bind<INavigationServiceExtension>().To<NavigationService>();
        Bind<IFriendService>().To<FriendService>().NamedLikeFactoryMethod((IFriendServiceFactory f) => f.GetFriendService());
        Bind<IFriendService>().To<DesignFriendService>().NamedLikeFactoryMethod((IFriendServiceFactory f) => f.GetDesignFriendService());
        Bind<IFileStorageService>().To<FileStorageService>();
    }
}

This is the shared interface:

public interface IFriendService
{
    Task<IEnumerable<Friend>> Refresh();
    Task<string> Save(Friend updatedFriend);
}

This is my Factory Interface with BindingName Attributes to choose which Service to apply:

public interface IFriendServiceFactory
{
    [BindingName("DesignFriendService")]
    IFriendService GetDesignFriendService();
    [BindingName("FriendService")]
    IFriendService GetFriendService();
}

These are the service implementations:

public class DesignFriendService : IFriendService
{
    public Task<IEnumerable<Friend>> Refresh()
    {
        var result = new List<Friend>();
        for (int i = 0; i < 10; i++)
        {
            result.Add(new Friend { Id = i, FirstName = $"First Name {i}", LastName = $"Last Name {i}", PictureUrl = "LinkedInOriginalLogo256x256.png" });
        }
        return Task.FromResult<IEnumerable<Friend>>(result);
    }

    public Task<string> Save(Friend updatedFriend)
    {
        throw new NotImplementedException();
    }
}

and the runtime service:

public class FriendService : IFriendService
{
    public async Task<IEnumerable<Friend>> Refresh()
    {
        var result = await App.FriendRepo.GetAllFriendsAsync();

        return result.Data;
    }

    public async Task<string> Save(Friend updatedFriend)
    {
        string result = string.Empty;
        result = (await App.FriendRepo.UpdateFriendAsync(updatedFriend)).ToString();

        return result;
    }
}

This is my Custom Instance Provider:

public class NameAttributeInstanceProvider : StandardInstanceProvider
{
    protected override string GetName(MethodInfo methodInfo, object[] arguments)
    {
        if (methodInfo.GetCustomAttributes(typeof(BindingNameAttribute), true)
            .FirstOrDefault() is BindingNameAttribute nameAttribute)
            return nameAttribute.Name;
        return base.GetName(methodInfo, arguments);
    }
}

and this is my attribute the one where the custom instance provider will get the name of the attribute or class which is applied at the IFriendServiceFactory:

public class BindingNameAttribute : Attribute
{
    public BindingNameAttribute(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
}

Finally, this is where the code breaks as soon as I run the app. It doesn't even run, it throws me an exception at my Refresh method at my ViewModel that calls the SwitchFriendService class:

ViewModel Method:

async Task Refresh()
{
    Friends.Clear();

    var friends = await _dataService.Refresh();

    foreach (var friend in friends)
    {
        Friends.Add(friend);
    }
}

public MainViewModel(Abstractions.IDialogService dialogService, INavigationServiceExtension navigationService)
{
    //_dataService = ServiceLocator.Current.GetInstance<SwitchFriendService>();
    _dataService = ViewModelLocator.Instance.NinjectContainer.Get<SwitchFriendService>();
    _dialogService = dialogService;
    _navigationService = navigationService;
    Friends = new ObservableCollection<Friend>();

    #if DEBUG
        if (DesignMode.IsDesignModeEnabled)
        {
    #pragma warning disable
            Refresh();
    #pragma warning restore
        }
    #endif
    }

and the SwitchFriendService:

public class SwitchFriendService
{
    readonly IFriendServiceFactory _switchFriendServiceFactory;
    public SwitchFriendService(IFriendServiceFactory switchFriendServiceFactory)
    {
        _switchFriendServiceFactory = switchFriendServiceFactory;
    }

    public async Task<IEnumerable<Friend>> Refresh()
    {
        IFriendService friendService;
        if(DesignMode.IsDesignModeEnabled)
        {
            friendService = _switchFriendServiceFactory.GetDesignFriendService();
        }
        else
        {
            friendService = _switchFriendServiceFactory.GetFriendService();
        }
        return await friendService.Refresh();
    }

    public async Task<string> Save(Friend updatedFriend)
    {
        IFriendService friendService;
        if (DesignMode.IsDesignModeEnabled)
        {
            friendService = _switchFriendServiceFactory.GetDesignFriendService();
        }
        else
        {
            friendService = _switchFriendServiceFactory.GetFriendService();
        }

        return await friendService.Save(updatedFriend);
    }
}

Now I'll show you the exception with screenshots because I don't understand why it isn't loading the proper implementation, the concept and appliance is practically the same as the book.

Here is where it should trigger the Custom Instance Provider, but after I step to next step it doesnt trigger anything and the services are null.

Here I show you it ends null, after this step it throws an exception.

This is the message I receive from Ninject:

Unhandled Exception: System.NotImplementedException: This is a DynamicProxy2 error: There are no interceptors specified for method 'WhyMvvm.MvvmLightIoc.Abstractions.IFriendService GetFriendService()' which has no target. When calling method without target there is no implementation to 'proceed' to and it is the responsibility of the interceptor to mimic the implementation (set return value, out arguments etc)

Thread finished: #13

but I'm not using interceptor. I was supposed to use a custom provider.

Can somebody help make this work, plz. For the sake of applying this to xamarin.forms.

In the Console version it works fine.

Hopefully somebody can tell me what am I missing?


Solution

  • I was able to solve it, it turns out that the Exception can be corrected by Loading Ninject.Extensions.Factory manually and it can be achieve by firstable adding a using statement of

    using Ninject.Extensions.Factory;

    and in my case the place where I load the Kernel, is in my ViewModelLocator so instead of declaring my Standard Kernel like I show at the question I had to add a FuncModule to the kernel like this:

    var settings = new NinjectSettings() { LoadExtensions = false };
    
    NinjectContainer = new StandardKernel(settings, new WhyMvvmModule(), new FuncModule());
    

    the second line of code make the InstanceProvider works fine!!!