背景
在Asp.Net Core中使用MVC,我正在使用控制器->服务->视图模型->视图方法。
我希望2个服务共享一些基本数据和功能。
我的要求是,一个实体要由2种不同的用户类型共享。即管理员用户和标准用户。
管理员用户将有权访问其他属性(在视图模型上定义)和功能(在服务上定义),而标准用户则不能。
在下面的示例代码中,Foo1等同于管理员,而Foo2是标准用户。
控制器动作
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);
}
服务
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
};
}
}
接口
public interface IFoo1Service : IBaseFooService
{
Foo1ViewModel NewViewModel();
}
public interface IFoo2Service : IBaseFooService
{
Foo2ViewModel NewViewModel();
}
public interface IBaseFooService
{
BaseFooViewModel NewBaseFooViewModel();
}
查看模型
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; }
}
观看次数
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>
启动时的依赖注入
services.AddScoped<IFoo1Service, Foo1Service>();
services.AddScoped<IFoo2Service, Foo2Service>();
问题
应用程序可以编译,但是在运行时出现错误:
InvalidCastException:无法转换类型的对象 键入“ BaseServiceSample.ViewModels.Foo.BaseFooViewModel” 'BaseServiceSample.ViewModels.Foo.Foo1ViewModel'
请参阅我在Foo1Service中的注释,这些注释在代码中在运行时导致错误的行上方。
我认为,如果从基类派生的类可以将其强制转换为派生类,但我可能会将其与MVC中的模型绑定如何工作混淆。
问题
如何更改代码,使其支持基本视图模型和基本服务的基本要求,这些基本视图模型和基本服务管理2个不同用户组的共享属性和功能,但允许用户组扩展这些属性/功能? / p>
从我的研究看来,我可能需要使用抽象类或类型自变量,但我无法使它正常工作。
我包含了代码示例,但没有尝试提供类型参数来使代码更简单,希望有人可以引导我了解我该如何去做。
通过类型自变量,我的意思是:
BaseFooService<T> : IBaseFooService<T> where T : class
答案 0 :(得分:1)
这是问题所在
public Foo2ViewModel GetViewModelFromEntity(Entity entity)
{
Foo2ViewModel vm = (Foo2ViewModel) GetBaseFooViewModelFromEntity(entity);
return vm;
}
public BaseFooViewModel GetBaseFooViewModelFromEntity(Entity entity)
{
return new BaseFooViewModel()
{
BaseFooField1 = entity.BaseFooField1,
BaseFooField2 = entity.BaseFooField2,
BaseFooField3 = entity.BaseFooField3
};
}
GetBaseFooViewModelFromEntity
返回一个新的BaseFooViewModel
。它不返回Foo2ViewModel
。您可以将Foo2ViewModel
作为其基类,但反之亦然。
我认为,如果从基类派生的类可以将其强制转换为派生类
您不能将基类的任何对象转换为任何派生类型。仅当对象实际上是该派生类型(或从派生类型派生的对象)时,才能进行强制转换。
换句话说,您无法执行此操作,因为BaseFooViewModel
不是Foo2ViewModel
:
var model = new BaseFooViewModel();
var fooModel = (Foo2ViewModel)model;
但是您可以这样做,因为Foo2ViewModel
始终是BaseFooViewModel
:
var fooModel = new Foo2ViewModel();
var model = (BaseFooViewModel)fooModel;
您可以执行此操作(但是为什么呢?)
Foo2ViewModel fooModel = new Foo2ViewModel();
BaseFooViewModel model = (BaseFooViewModel)fooModel;
Foo2ViewModel thisWorks = (Foo2ViewModel)model;
它只能工作,因为对象始终是Foo2ViewModel
,因此可以将其强制转换为该类型或其基类型。
这里建议让编译器指导您,使您得到编译器错误而不是运行时错误。这将以更多的编译器错误的形式打开蠕虫的一罐,但是好消息是您可以在不运行代码的情况下修复所有蠕虫。编译器错误胜于运行时错误。
第一步是使BaseFooModel
抽象,以便您不能创建它的实例。大概您只想处理Foo1ViewModel
,Foo2ViewModel
之类的类。将基类抽象化将确保您只能创建派生类。
public abstract class BaseFooViewModel // Just added "abstract"
{
public string BaseFooField1 { get; set; }
public string BaseFooField2 { get; set; }
public string BaseFooField3 { get; set; }
}
将BaseFooModel
设为抽象时,它将不会编译:
new BaseFooViewModel()
...因为您无法创建抽象类的实例。您只能创建不是抽象的派生类的实例。
假设您的派生类没有带参数的构造函数,则可以执行以下操作:
public TFooModel GetBaseFooViewModelFromEntity<TFooModel>(Entity entity)
where TFooModel : BaseFooViewModel, new()
{
return new TFooModel()
{
BaseFooField1 = entity.BaseFooField1,
BaseFooField2 = entity.BaseFooField2,
BaseFooField3 = entity.BaseFooField3
};
}
现在,您要告诉该方法创建哪个特定的继承类。通用约束-BaseFooViewModel, new()
-指定它必须继承自BaseFooViewModel
,并且必须具有默认构造函数,以便类可以创建它的新实例。
它将创建您指定的类的实例,但是由于它“是” BaseFooViewModel
,因此可以设置属于该基类的属性。
然后您可以像这样调用方法:
Foo2ViewModel vm = GetBaseFooViewModelFromEntity<Foo2ViewModel>(entity);
一种不同的看待方式。运行时类型如下:
Foo2ViewModel vm = (Foo2ViewModel) GetBaseFooViewModelFromEntity(entity);
通常应被避免。我之所以说是因为有很多例外。但是当编写我们自己的泛型类时,我会说我们应该始终避免使用它。相反,最好编写代码,以便我们拥有的声明类型是我们关心的唯一类型。
换句话说,如果方法返回Foo
或参数的类型为Foo
,那么我们关心的唯一类型是Foo
。我们不在乎其基类,也不在乎我们获取的对象是否实际上是从Foo
继承的对象。
这是另一篇文章,介绍了我的个人经历。我称之为Generic Rabbit Hole of Madness。