Background
Using MVC in Asp.Net Core, I'm using the controller -> service -> view model -> view approach.
I want 2 services to share some base data and functionality.
My requirement is for one entity to be shared by 2 different user types. I.e. An admin user and a standard user.
The admin user will have access to additional properties (defined on the view model) and functionality (defined on the service) whereas the standard user won't.
In the example code below, Foo1 is the equivalent to an admin and Foo2 is a standard user.
Controller actions
private readonly IFoo1Service _foo1Service;
private readonly IFoo2Service _foo2Service;
public HomeController
(IFoo1Service foo1Service,
IFoo2Service foo2Service)
{
_foo1Service = foo1Service;
_foo2Service = foo2Service;
}
public IActionResult Foo1()
{
Foo1ViewModel vm = _foo1Service.NewViewModel();
return View(vm);
}
public IActionResult Foo2()
{
Foo2ViewModel vm = _foo2Service.NewViewModel();
return View(vm);
}
Services
public class Foo1Service : BaseFooService, IFoo1Service
{
public Foo1ViewModel NewViewModel()
{
//*** LINE CAUSING THE ERROR ***
//NewBaseFooViewModel returns a BaseFooViewModel
//AS Foo1ViewModel derives from the BaseFooViewModel
//I thought I could cast it
Foo1ViewModel vm = (Foo1ViewModel) NewBaseFooViewModel();
//set some defaults
vm.Foo1Field1 = "Foo1Field1";
vm.Foo1Field2 = "Foo1Field2";
return vm;
}
public Foo1ViewModel GetViewModelFromEntity(Entity entity)
{
Foo1ViewModel vm = (Foo1ViewModel) GetBaseFooViewModelFromEntity(entity);
vm.Foo1Field1 = entity.Foo1Field1;
vm.Foo1Field2 = entity.Foo1Field2;
return vm;
}
}
public class Foo2Service : BaseFooService, IFoo2Service
{
public Foo2ViewModel NewViewModel()
{
Foo2ViewModel vm = (Foo2ViewModel) NewBaseFooViewModel();
return vm;
}
public Foo2ViewModel GetViewModelFromEntity(Entity entity)
{
Foo2ViewModel vm = (Foo2ViewModel) GetBaseFooViewModelFromEntity(entity);
return vm;
}
}
public class BaseFooService : IBaseFooService
{
public BaseFooViewModel NewBaseFooViewModel()
{
return new BaseFooViewModel()
{
BaseFooField1 = "BaseFooField1",
BaseFooField2 = "BaseFooField2",
BaseFooField3 = "BaseFooField3"
};
}
public BaseFooViewModel GetBaseFooViewModelFromEntity(Entity entity)
{
return new BaseFooViewModel()
{
BaseFooField1 = entity.BaseFooField1,
BaseFooField2 = entity.BaseFooField2,
BaseFooField3 = entity.BaseFooField3
};
}
}
Interfaces
public interface IFoo1Service : IBaseFooService
{
Foo1ViewModel NewViewModel();
}
public interface IFoo2Service : IBaseFooService
{
Foo2ViewModel NewViewModel();
}
public interface IBaseFooService
{
BaseFooViewModel NewBaseFooViewModel();
}
View Models
public class Foo1ViewModel : BaseFooViewModel
{
public string Foo1Field1 { get; set; }
public string Foo1Field2 { get; set; }
}
public class Foo2ViewModel : BaseFooViewModel
{
}
public class BaseFooViewModel
{
public string BaseFooField1 { get; set; }
public string BaseFooField2 { get; set; }
public string BaseFooField3 { get; set; }
}
Views
Foo1
@model BaseServiceSample.ViewModels.Foo.Foo1ViewModel
<h1>Base foo fields</h1>
<p>@Model.BaseFooField1</p>
<p>@Model.BaseFooField2</p>
<p>@Model.BaseFooField3</p>
<h2>Foo1 fields</h2>
<p>@Model.Foo1Field1</p>
<p>@Model.Foo1Field2</p>
Foo2
@model BaseServiceSample.ViewModels.Foo.Foo2ViewModel
<h1>Base foo fields</h1>
<p>@Model.BaseFooField1</p>
<p>@Model.BaseFooField2</p>
<p>@Model.BaseFooField3</p>
Dependency injection in startup
services.AddScoped<IFoo1Service, Foo1Service>();
services.AddScoped<IFoo2Service, Foo2Service>();
Issue
The application compiles okay but I'm getting the error at run time:
InvalidCastException: Unable to cast object of type 'BaseServiceSample.ViewModels.Foo.BaseFooViewModel' to type 'BaseServiceSample.ViewModels.Foo.Foo1ViewModel'
See my comments within Foo1Service which are above the line in the code causing the error at run time.
I thought that if a class derived from a base class that it could be casted as the derived class but I'm probably confusing this with how model binding in MVC can work.
Question
How can I change my code so that it supports the basic requirement of a base view model and base service which manage the shared properties and functionality for 2 different user groups, but allowing a user group to extend those properties / functionality?
From my research it looks like I might need to use abstract classes or type arguments but I've not been able to get this working.
I've included the code sample without my attempts at providing type arguments to keep the code more simple and hopefully easier for someone to guide me on where I need to go with this.
By type arguments, I mean:
BaseFooService<T> : IBaseFooService<T> where T : class