1

I separated my data service in another project and trying to use using dependency IOC. But when I do so I am getting an error that I can't set the Binding context if I have parameterized constructor. Unfortunately I couldn't find any other similar suitable answer on stack overflow to get rid of my confusion.

Error Thrown : Severity Code Description Project File Line Suppression State Error XFC0004 Missing default constructor for "CoffeeMocha.ViewModels.CoffeeDbViewModel". CoffeeMocha C:\xxxx\CoffeeMocha\CoffeeMocha\CoffeeMocha\Views\CoffeeDbPage.xaml 12

My question is that how can I use the implementation of CoffeeService(which is a different project) instance using DI in my ViewModel. And how to set Binding Context in such cases. Kindly note I am not using any MVVM provider like Prism, lite or cross. This is just a Vanilla implementations sample which I implemented using a tutorial.

My App.xaml.cs

namespace CoffeeMocha
{
    public partial class App : Application
    {

        public App()
        {
            InitializeComponent();

            DependencyService.Register<MockDataStore>();
            DependencyService.Register<ICoffeeRequestProvider, CoffeeRequestProvider>();
            DependencyService.Register<ICoffeeService, CoffeeService>();
            MainPage = new AppShell();
        }

        protected override void OnStart()
        {
            
        }

        protected override void OnSleep()
        {
        }

        protected override void OnResume()
        {
        }
    }
}

My View.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="CoffeeMocha.Views.CoffeeDbPage"
             xmlns:vm="clr-namespace:CoffeeMocha.ViewModels"
             x:Name="CoffeeDataPage">

    <ContentPage.ToolbarItems>
        <ToolbarItem  Text="Add" Command="{Binding AddCommand}"/>
    </ContentPage.ToolbarItems>
    <ContentPage.BindingContext>
        <vm:CoffeeDbViewModel>
            
        </vm:CoffeeDbViewModel>
    </ContentPage.BindingContext>
    <ContentPage.Content>
        
        <ListView 
                Style="{StaticResource CoffeListStyle}"
                ItemsSource="{Binding CoffeeList}"
                IsPullToRefreshEnabled="True"
                SelectedItem="{Binding SelectedCoffee, Mode=TwoWay}"
                CachingStrategy="RecycleElement">
            <ListView.ItemTemplate>
                <DataTemplate >
                    <ViewCell>
                        <ViewCell.ContextActions>
                            <MenuItem Text="Delete"
                                      CommandParameter="{Binding .}"
                                      Command="{Binding Source={x:Reference CoffeeDataPage}, Path=BindingContext.DeleteCommand }"/>
                        </ViewCell.ContextActions>

                        <Frame Style="{StaticResource CoffeeFrameStyleBlue}">
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="1*"/>
                                    <RowDefinition Height ="2*"/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="1*"/>
                                    <ColumnDefinition Width="2.5*"/>
                                </Grid.ColumnDefinitions>
                                <Image Source="{Binding Image}" 
                                       Grid.RowSpan="2"
                                       Grid.Column="0"/>
                                <Label Grid.Column="1"
                                    Grid.Row="0"
                                       FontSize="Large"
                                       FontFamily="Bold"
                                       TextColor="White"
                                       Text="{Binding Name}"
                                       Margin="0,5,0,5"/>
                                <Label
                                        Grid.Column="1"
                                        Grid.Row="1"
                                        TextColor="White"
                                    Margin="0,5,0,5"
                                        Text="{Binding Description}"/>
                            </Grid>
                        </Frame>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </ContentPage.Content>
</ContentPage>

ViewModel

using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
using MvvmHelpers;
using MvvmHelpers.Commands;
using System.Threading.Tasks;
using CoffeeMocha.Views;
using System.Collections.ObjectModel;
using CoffeeMocha.Models;
using CoffeeMocha.Services;
using CoffeMocha.DataService.CoffeeService;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace CoffeeMocha.ViewModels
{
    public class CoffeeDbViewModel 
    {
        ObservableRangeCollection<Coffe> _coffeeList;
        public ObservableRangeCollection<Coffe> CoffeeList
        {
            get { return _coffeeList; }
            set
            {
                if (_coffeeList != value)
                    _coffeeList = value;
                OnPropertyChanged("randomList");
            }
        }

        private Coffe _selectedCoffee;
        public Coffe SelectedCoffee
        {
            get { return _selectedCoffee; }
            set
            {
                if (_selectedCoffee != value)
                {
                    _selectedCoffee = value;
                    Application.Current.MainPage.DisplayAlert("Selected", value.Name, "OK");
                }

                OnPropertyChanged("SelectedCoffee");
            }
        }

        public AsyncCommand AddCommand { get; set; }
        public AsyncCommand<Coffe> DeleteCommand { get; set; }

        ICoffeeDbService coffeeDbService;
        ICoffeeService coffeeService;

        public CoffeeDbViewModel(ICoffeeService coffeeService)
        {
            
            AddCommand = new AsyncCommand(AddCoffeeItem);
            DeleteCommand = new AsyncCommand<Coffe>(DeleteCoffeeItem);
// coffeeDbService  works as expected but is in the same PCL project
                CoffeeList = new ObservableRangeCollection<Coffe>();
                coffeeDbService = DependencyService.Get<ICoffeeDbService>();
                try
                {
     // Below throws error and expects Coffee service to have parmeterless constructor
    //  this.coffeeService = DependencyService.Get<ICoffeeService>(); 

                this.coffeeService = coffeeService;
            }catch(Exception ex)
            {
                var exc = ex.Message;
            }
        }

        private async Task AddCoffeeItem()
        {
            await Application.Current.MainPage.Navigation.
            PushAsync(new AddCoffeeItemPage(), false);
        }

        private async Task DeleteCoffeeItem(Coffe coffe)
        {
            await coffeeDbService.RemoveCoffeeAsync(coffe.Id);
        }

        public async Task RefreshItemsFromDB()
        {
            bool IsFromDB = false;
            if (CoffeeList != null)
                CoffeeList.Clear();

            if (IsFromDB)
            {

                var coffeeDBList = await coffeeDbService.GetCoffee();
                if (coffeeDBList != null)
                    CoffeeList.AddRange(coffeeDBList);
            }
            else
            {
                // From Web Service
                var coffeeList = coffeeService.GetCoffee();
                var coffeListResult = coffeeList?.Result;
                if(coffeListResult != null)
                {

                    //CoffeeList.AddRange(coffeListResult);
                }
            }


        }

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var changed = PropertyChanged;
            if (changed == null)
                return;

            changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

    }
}

CoffeeService in different project Added this reference in PCL

using CoffeeMocha.Commons;
using CoffeeMocha.Data.Models.Coffee;
using CoffeMocha.DataService.RequestProviders;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace CoffeMocha.DataService.CoffeeService
{
    public class CoffeeService : ICoffeeService
    {
        ICoffeeRequestProvider requestProvider;
        public CoffeeService(ICoffeeRequestProvider coffeeRequestProvider)
        {
            requestProvider = coffeeRequestProvider;
        }

        public async Task<IEnumerable<Coffee>> GetCoffee()
        {
            // Expecting  a list of Coffee as a Response
            try
            {
                var endPoint = BackendConstants.COFEE;
                var response = await this.requestProvider.GetAsync<IEnumerable<Coffee>>(endPoint);
                return response;
            }
            catch(Exception)
            {
                throw;
            }
        }


    }
}

CoffeeRequestProvider

using CoffeeMocha.Commons;
using CoffeMocha.DataService.RequestProviders;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;

namespace CoffeMocha.DataService.RequestProviders
{
    public class CoffeeRequestProvider : ICoffeeRequestProvider
    {
        private readonly JsonSerializerSettings jsonSerializerSettings;

        public CoffeeRequestProvider()
        {
            jsonSerializerSettings = new JsonSerializerSettings
            {
                ContractResolver = new DefaultContractResolver(),
                NullValueHandling = NullValueHandling.Ignore,
            };
        }


        public async Task<TResult> GetAsync<TResult>(string uri, string token = "")
        {
            var baseUrl = BackendConstants.Base_Coffee_Url;

            HttpResponseMessage responseMessage;
            HttpClient httpClient =  CreateHttpClient(baseUrl);

            responseMessage = await httpClient.GetAsync(uri);
            string serialized = await responseMessage.Content.ReadAsStringAsync();

            TResult result = default;

            result = JsonConvert.DeserializeObject<TResult>(serialized, jsonSerializerSettings);
            return result;
        }

        private HttpClient CreateHttpClient(string baseUrl)
        {
            var httpClient = new HttpClient();
            httpClient.BaseAddress = new Uri(baseUrl);
            // for real world projects Headers, versions, tokens can be added
            return httpClient;
        }

    }
}

CoffeeDbPage Some sources suggests me to have empty constructor in code behind which I already have.

namespace CoffeeMocha.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class CoffeeDbPage : ContentPage
    {
        CoffeeDbViewModel vm;
        public CoffeeDbPage()
        {
            InitializeComponent();
            vm = this.BindingContext as CoffeeDbViewModel;
        }
        protected override async void OnAppearing()
        {
            base.OnAppearing();
            await vm.RefreshItemsFromDB();
        }
    }
}
Poornesh V
  • 171
  • 1
  • 10
  • 2
    the built in DependencyService only works with default constructors, but you can use other .NET IOC containers – Jason Jan 29 '22 at 12:42
  • @Cfun I have a parameter less constructor in CoffeeDbPage class. – Poornesh V Jan 29 '22 at 13:04
  • The issue showed that the CoffeeDbViewModel has no default constructor. I read your code and couldn't found it. So you can try to add a public CoffeeDbViewModel(){} into the CoffeeDbViewModel. – Liyun Zhang - MSFT Jan 31 '22 at 07:52
  • @LiyunZhang-MSFT I understand adding empty constructor to CoffeeDbViewModel can resolve the binding issue, but in such cases how can I inject the implementation 'CoffeeService' (which is in a different project) into my CoffeeDbViewModel – Poornesh V Jan 31 '22 at 09:25
  • 1
    You can use the other IOC containers such as [TinyIoCContainer](https://learn.microsoft.com/en-us/xamarin/xamarin-forms/enterprise-application-patterns/dependency-injection). – Liyun Zhang - MSFT Feb 01 '22 at 07:35

0 Answers0