-1

I have an application I haven't touched for a while and it is giving me some grief.

When I call the index method of the controller I get the following error:

enter image description here

I am not sure what I am doing wrong but it would seem that AutoMapper is having trouble mapping a collection of Shift objects to a ShiftViewModel.

I have included some snippets below.

Thoughts?

My controller:

using AutoMapper;
using My.DataAccess;
using My.Entity.DatabaseEntities;
using My.Entity.ViewModels;
using My.Service;
using System.Net;
using System.Threading.Tasks;
using System.Web.Mvc;

namespace My.Controllers
{
    public class ShiftController : Controller
    {
        //initialize service object
        readonly IShiftService _shiftService;
        private readonly IMapper _mapper;

        public ShiftController(IShiftService shiftService, IMapper mapper)
        {
            _shiftService = shiftService;
            _mapper = mapper;
        }

        readonly ApplicationDataManager db = new ApplicationDataManager();

        // GET: /Shifts/
        public ActionResult Index()
        {
            var shifts = _shiftService.GetAll();

            if (shifts == null)
            {
                return HttpNotFound();
            }


            var model = _mapper.Map<ShiftViewModel>(shifts);

            return View(model);
        }
    }
}

Shift database entity:

using System;

using System.ComponentModel.DataAnnotations;

namespace My.Entity.DatabaseEntities
{
    public class Shift : AuditableEntity<long>
    {
        [Required, StringLength(6)]
        [Editable(true)]
        public string Code { get; set; }

        public string Detail { get; set; }
        public DateTime Start { get; set; }
        public long Duration { get; set; }
    }
}

ShiftViewModel class:

using System;
using System.ComponentModel.DataAnnotations;

namespace My.Entity.ViewModels
{
    public class ShiftViewModel
    {
        public int Id { get; set; }
        public string Code { get; set; }
        public string Detail { get; set; }
        public DateTime Start { get; set; }

        [Display(Name = "Duration of shift")]
        [DisplayFormat(DataFormatString = "{0:HH:mm}", ApplyFormatInEditMode = true)]
        [DataType(DataType.Duration)]
        public string DurationTime
        {
            get
            {
                var ts = new TimeSpan(Duration);
                var h = ts.Hours == 1 ? "hour" : "hours";
                var m = ts.Minutes == 1 ? "min" : "mins";
                return string.Format("{0} {1} {2} {3}", ts.Hours, h, ts.Minutes, m);
            }
        }
        public long Duration { get; set; }
    }
}

Global.asax:

using Autofac;
using Autofac.Integration.Mvc;
using My.DataAccess.Modules;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace My.App
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            //Autofac Configuration
            var builder = new ContainerBuilder();

            builder.RegisterControllers(typeof(MvcApplication).Assembly).PropertiesAutowired();

            builder.RegisterModule(new RepositoryModule());
            builder.RegisterModule(new ServiceModule());
            builder.RegisterModule(new EFModule());

            //Register AutoMapper here using AutoFacModule class (Both methods works)
            //builder.RegisterModule(new AutoMapperModule());
            builder.RegisterModule<AutoFacModule>();

            var container = builder.Build();

            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
        }
    }
}

AutoFacModule:

using Autofac;
using AutoFacAndAutoMapperMVC.Infrastructure;
using AutoMapper;

public class AutoFacModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.Register(context => new MapperConfiguration(cfg =>
        {
            //Register Mapper Profile
            cfg.AddProfile<AutoMapperProfile>();
        }
        )).AsSelf().InstancePerRequest();

        builder.Register(c =>
        {
            //This resolves a new context that can be used later.
            var context = c.Resolve<IComponentContext>();
            var config = context.Resolve<MapperConfiguration>();
            return config.CreateMapper(context.Resolve);
        })
        .As<IMapper>()
        .InstancePerLifetimeScope();
    }
}

AutoMapperProfile:

using Roster.Entity.DatabaseEntities;
using Roster.Entity.ViewModels;
using AutoMapper;

namespace AutoFacAndAutoMapperMVC.Infrastructure
{
    public class AutoMapperProfile : Profile
    {
        public AutoMapperProfile()
        {
            CreateMap<Shift, ShiftViewModel>();

            CreateMap<ShiftViewModel, Shift>();
        }
    }
}

Trekco, It is called by a generic method of IEnumerable

public virtual IEnumerable<T> GetAll()
        {
            return _repository.GetAll();
        }

it returns a Shift object

3

2 Answers 2

2

In you Index method, the code -

var shifts = _shiftService.GetAll();

is definitely not returning a single Shift object. I guess, its returning a list/collection of Shift object. If so, then with the code -

var model = _mapper.Map<ShiftViewModel>(shifts);

you are trying to map a list of Shift object to a single ShiftViewModel object which is causing the issue.

Change the mapping code to -

var model = _mapper.Map<List<ShiftViewModel>>(shifts);
Sign up to request clarification or add additional context in comments.

Comments

0

So this is how I solved the problem. Thanks everyone for the help.

Solution

So it seems, as Akos Nagy, points out in his blog post the problem is that AutoMapper and Autofac don't play very well together. This wasn't helped by the fact that my code was not very good to begin with.

The AutoMapper docs had a page on Dependency Injection found here. There were no real examples for AutoFac however it did point me towards a Nuget package named AutoMapper.Contrib.Autofac.DependencyInjection 5.2.0. There is a GitHub project here.

So I installed that package with the Package Manager Console.

Install-Package AutoMapper.Contrib.Autofac.DependencyInjection -Version 5.2.0

I then simplified my Shift domain object class.

using System;
using System.ComponentModel.DataAnnotations;

namespace Roster.Entity.DatabaseEntities
{
    public class Shift : AuditableEntity<long>
    {

        #region Public Properties
        [Required, StringLength(6)]
        [Editable(true)]
        [Display(Name = "Code")]
        public string Code { get; set; }

        public string Detail { get; set; }

        public DateTime Start { get; set; }

        public long Duration { get; set; }

        #endregion Public Properties
    }
}

Next I reworked my ViewModel, again just to make things a bit cleaner and adding a little business logic functionality.

using System;
using System.ComponentModel.DataAnnotations;

namespace Roster.Entity.ViewModels
{
    public class ShiftViewModel : AuditableEntity<long>
    {
        [Required, StringLength(6)]
        [Editable(true)]
        [Display(Name = "Code")]
        public string Code { get; set; }

        public string Detail { get; set; }

        [DisplayFormat(DataFormatString = "{0:HH:mm }", ApplyFormatInEditMode = true)]
        public DateTime Start { get; set; }

        public long Duration { get; set; }

        [Display(Name = "Text Duration time of shift")]
        [DisplayFormat(DataFormatString = "{0:hh:mm}", ApplyFormatInEditMode = true)]
        [DataType(DataType.Duration)]
        public string DurationTime
        {
            get
            {
                TimeSpan ts = new TimeSpan(Duration);
                var h = ts.Hours == 1 ? "hour" : "hours";
                var m = ts.Minutes == 1 ? "min" : "mins";
                return string.Format("{0} {1} {2} {3}", ts.Hours, h, ts.Minutes, m);
            }
        }

        [Display(Name = "Shift Duration")]
        [DisplayFormat(DataFormatString = "{0:hh\\:mm}", ApplyFormatInEditMode = true)]
        [DataType(DataType.Duration)]
        public string ShiftDuration
        {
            get
            {
                return TimeSpan.FromTicks(Duration).ToString();
            }
            set
            {
                TimeSpan interval = TimeSpan.FromTicks(Duration);
                Duration = interval.Ticks;
            }
        }
    }
}

Now on to mapping the domain object to my ViewModel.

First I needed to create an AutoMapper profile to create the map. I saved this in the APP_Start folder for no reason than I thought it a good place.

using AutoMapper;
using Roster.Entity.DatabaseEntities;
using Roster.Entity.ViewModels;

namespace AutoFacAndAutoMapperMVC.Infrastructure
{
    public class AutoMapperProfile : Profile
    {
        public AutoMapperProfile()
        {
            CreateMap<Shift, ShiftViewModel>()
            .ReverseMap();
        }
    }
}

Next I needed to change Global.asax.cs

I registered AutoMapper by adding

builder.RegisterAutoMapper(typeof(MvcApplication).Assembly);

I then added the following lines to resolve the mapper service and control the lifetime scope. I am not sure what this actually does but it is recommended in the AutoFac docs here

using(var scope = container.BeginLifetimeScope())
{
  var service = scope.Resolve<IMapper>();
} 

Finally I altered the Shift Controller to use AutoMapper.

using AutoMapper;
using Roster.DataAccess;
using Roster.Entity.ViewModels;
using Roster.Service;
using System.Collections;
using System.Collections.Generic;
using System.Web.Mvc;

namespace Roster.Controllers
{
    public class ShiftController : Controller
    {
        //initialize service object
        private readonly IShiftService _shiftService;
        private readonly IMapper _mapper;

        public ShiftController(IShiftService shiftService, IMapper mapper)
        {
            _shiftService = shiftService;
            _mapper = mapper;
        }
        readonly ApplicationDataManager db = new ApplicationDataManager();
        // GET: /Shifts/
        public ActionResult Index()
        {
            IEnumerable shifts = _shiftService.GetAll();
            var model = _mapper.Map<IEnumerable<ShiftViewModel>>(shifts);
            if (model == null)
            {
                return HttpNotFound();
            }
            return View(model);
        }
    }
}

Importantly and a real beginners error on my behalf is that because I had a collection of shifts to map I had to have a collection of viewmodels. Thanks atiyar. I resolved this by mapping like this. So stupid on my behalf.

var model = _mapper.Map<IEnumerable<ShiftViewModel>>(shifts);

So sorry about the long answer but I thought I would wrap up the question with how I resolved my problem. It was a great learning exercise for someone who is not a professional programmer. Thanks everyone.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.