我有以下一对模型/视图模型。这是一种非常普遍的情况-从ViewModel到Model属性的纯映射-包含许多重复且容易出错的代码。
我想知道是否有更好的方法来做到这一点,特别是减少出错的机会(使用错误的属性名称来忘记属性)。
欢迎使用更多新的语言功能,例如CallingMemberName
,但目前不确定我是否理解它们。
public class ParametrosGeometricos
{
public double DistanciaProjetorParede { get; set; } = 2280;
public double AlturaProjetor { get; set; } = 1000;
public double AlturaInferiorProjecao { get; set; } = 1010;
public double AlturaSuperiorProjecao { get; set; } = 1940;
public double DistanciaCameraParede { get; set; } = 2320;
public double AlturaCamera { get; set; } = 1770;
public double AlturaInferiorImagem { get; set; } = 860;
public double AlturaSuperiorImagem { get; set; } = 1740;
}
public class ParametrosGeometricosViewModel : ConfiguracoesViewModel<ParametrosGeometricos>
{
// (...)
public double DistanciaProjetorParede
{
get => Model.DistanciaProjetorParede;
set
{
Model.DistanciaProjetorParede = value;
RaisePropertyChanged(() => DistanciaProjetorParede);
}
}
public double AlturaProjetor
{
get => Model.AlturaProjetor;
set
{
Model.AlturaProjetor = value;
RaisePropertyChanged(() => AlturaProjetor);
}
}
public double AlturaInferiorProjecao
{
get => Model.AlturaInferiorProjecao;
set
{
Model.AlturaInferiorProjecao = value;
RaisePropertyChanged(() => AlturaInferiorProjecao);
}
}
public double AlturaSuperiorProjecao
{
get => Model.AlturaSuperiorProjecao;
set
{
Model.AlturaSuperiorProjecao = value;
RaisePropertyChanged(() => AlturaSuperiorProjecao);
}
}
public double DistanciaCameraParede
{
get => Model.DistanciaCameraParede;
set
{
Model.DistanciaCameraParede = value;
RaisePropertyChanged(() => DistanciaCameraParede);
}
}
public double AlturaCamera
{
get => Model.AlturaCamera;
set
{
Model.AlturaCamera = value;
RaisePropertyChanged(() => AlturaCamera);
}
}
public double AlturaInferiorImagem
{
get => Model.AlturaInferiorImagem;
set
{
Model.AlturaInferiorImagem = value;
RaisePropertyChanged(() => AlturaInferiorImagem);
}
}
public double AlturaSuperiorImagem
{
get => Model.AlturaSuperiorImagem;
set
{
Model.AlturaSuperiorImagem = value;
RaisePropertyChanged(() => AlturaSuperiorImagem);
}
}
}
答案 0 :(得分:2)
听起来您正在寻找类似AutoMapper
的东西答案 1 :(得分:1)
无需将ViewModel编写为模型顶部的外观。
让模型直接或使用诸如Fody.PropertyChanged之类的库实现INotifyPropertyChanged。然后将整个模型作为ViewModel的单个属性发布,并绑定到您的View中。
答案 2 :(得分:0)
如果可以使用生成的代码,则可以使用T4 Text Templates来生成ViewModel中的所有属性。创建一个属性以保存模型类型:
[AttributeUsage(AttributeTargets.Class)]
public class ViewsAttribute : Attribute {
public ViewsAttribute(Type type) {
}
}
将此添加到VM,并使其部分化:
[Views(typeof(ParametrosGeometricos))]
partial class ParametrosGeometricosViewModel {
(...)
}
以下T4是我使用的简短版本,但我确定还有更好的方法,因为我不是专家:
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="$(SolutionDir)\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll" #>
<#@ assembly name="$(SolutionDir)\packages\System.Collections.Immutable.1.3.1\lib\netstandard1.0\System.Collections.Immutable.dll" #>
<#@ assembly name="$(SolutionDir)\packages\Microsoft.CodeAnalysis.Common.2.8.2\lib\netstandard1.3\Microsoft.CodeAnalysis.dll" #>
<#@ assembly name="$(SolutionDir)\packages\Microsoft.CodeAnalysis.CSharp.2.8.2\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll" #>
<#@ assembly name="System.Runtime" #>
<#@ assembly name="System.Text.Encoding" #>
<#@ assembly name="System.Threading.Tasks" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="Microsoft.CodeAnalysis" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="Microsoft.CodeAnalysis.CSharp" #>
<#@ import namespace="Microsoft.CodeAnalysis.CSharp.Syntax" #>
<#
var solutionPath = Host.ResolveAssemblyReference("$(ProjectDir)");
var files = Directory.GetFiles(solutionPath,"*.cs",SearchOption.AllDirectories);
IEnumerable<ClassDeclarationSyntax> syntaxTrees = files.Select(x => CSharpSyntaxTree.ParseText(File.ReadAllText(x))).Cast<CSharpSyntaxTree>().SelectMany(c => c.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>());
foreach(ClassDeclarationSyntax declaration in syntaxTrees.Where(x => (x.AttributeLists != null && x.AttributeLists.Count > 0 && x.AttributeLists.SelectMany(y => y.Attributes.Where(z=> z.Name.ToString()=="Views")).Any()))) {
SyntaxNode namespaceNode = declaration.Parent;
Write("\n\n");
while(namespaceNode != null && !(namespaceNode is NamespaceDeclarationSyntax)) {
namespaceNode = namespaceNode.Parent;
}
if(namespaceNode != null) {
WriteLine("namespace " + ((NamespaceDeclarationSyntax)namespaceNode).Name.ToString() + " {");
}
string modelName= declaration.AttributeLists.SelectMany(y => y.Attributes.Where(z=> z.Name.ToString()=="Views")).First().ArgumentList.Arguments.ToString();
modelName = modelName.Substring(7, modelName.Length-8);
ClassDeclarationSyntax modelClass = syntaxTrees.Where(x => x.Identifier.ToString() == modelName).First();
WriteLine(" public partial class " + declaration.Identifier.Text + " {");
foreach(PropertyDeclarationSyntax prp in modelClass.DescendantNodes().OfType<PropertyDeclarationSyntax>()){
WriteLine(" public " + prp.Type + " " + prp.Identifier + " {");
WriteLine(" get => Model." + prp.Identifier + ";");
WriteLine(" set");
WriteLine(" {");
WriteLine(" Model." + prp.Identifier + " = value;");
WriteLine(" RaisePropertyChanged(() => " + prp.Identifier + ");");
WriteLine(" }");
WriteLine(" }\n");
}
WriteLine(" }");
if(namespaceNode != null) {
Write("}");
}
}
#>
这将获取项目中具有Views
属性的所有类声明,并为每个属性生成代码。生成的类是
namespace TTTTTest {
public partial class ParametrosGeometricosViewModel {
public double DistanciaProjetorParede {
get => Model.DistanciaProjetorParede;
set
{
Model.DistanciaProjetorParede = value;
RaisePropertyChanged(() => DistanciaProjetorParede);
}
}
public double AlturaProjetor {
get => Model.AlturaProjetor;
set
{
Model.AlturaProjetor = value;
RaisePropertyChanged(() => AlturaProjetor);
}
}
public double AlturaInferiorProjecao {
get => Model.AlturaInferiorProjecao;
set
{
Model.AlturaInferiorProjecao = value;
RaisePropertyChanged(() => AlturaInferiorProjecao);
}
}
public double AlturaSuperiorProjecao {
get => Model.AlturaSuperiorProjecao;
set
{
Model.AlturaSuperiorProjecao = value;
RaisePropertyChanged(() => AlturaSuperiorProjecao);
}
}
public double DistanciaCameraParede {
get => Model.DistanciaCameraParede;
set
{
Model.DistanciaCameraParede = value;
RaisePropertyChanged(() => DistanciaCameraParede);
}
}
public double AlturaCamera {
get => Model.AlturaCamera;
set
{
Model.AlturaCamera = value;
RaisePropertyChanged(() => AlturaCamera);
}
}
public double AlturaInferiorImagem {
get => Model.AlturaInferiorImagem;
set
{
Model.AlturaInferiorImagem = value;
RaisePropertyChanged(() => AlturaInferiorImagem);
}
}
public double AlturaSuperiorImagem {
get => Model.AlturaSuperiorImagem;
set
{
Model.AlturaSuperiorImagem = value;
RaisePropertyChanged(() => AlturaSuperiorImagem);
}
}
}
}
您可以添加很多更改,例如,可以使用从ConfiguracoesViewModel
继承的所有类生成代码,而不是使用自定义属性。您还可以检查是否已经将每个属性添加到VM并没有生成它们,这使您可以通过简单地将其添加到类中来为所需属性创建自定义getter和setter。
答案 3 :(得分:0)
我偶然发现了这个问题,所以我想我应该补充一下如何解决这个问题。我有一个MyViewModelBase
类型,用于包装我的MyModel
。这两个类都实现INotifyPropertyChanged
,而ViewModel只是转发PropertyChanged事件,如下所示:
public class MyViewModelBase : INotifyPropertyChanged
{
public int MyProperty
{
get => _model.MyProperty;
set => _model.MyProperty = value;
}
public MyViewModelBase(MyModel model)
{
// we name wrapper properties the same as the model,
// and here we just forward the property changed notifications
model.PropertyChanged += (sender, e) => PropertyChanged?.Invoke(this, e);
}
...
}
public class MyModel : INotifyPropertyChanged
{
// We use fody to raise property changed,
// but can be raised normally here otherwise
public int MyProperty { get; set; }
}
我们有许多不同的模型和视图模型,它们继承自这两个基类。要获取模型中某个属性的更改通知,只需在视图模型中为其添加一个具有相同名称的包装器,当模型中的属性发生更改时,更改也会在视图模型中传播。
请注意,我们在适合它的应用程序的有限部分中使用了它。我看不到它可以扩展到大型应用程序的每个部分。在合适的地方使用它。
现在,为基础模型自动生成包装器属性时,最好的选择(AFAIK)是Fody。我很快看了一下,发现:https://github.com/tom-englert/AutoProperties.Fody。不知道是否可以使用它,但这是我能找到的最接近的东西。
当发布C#9 / .NET 5时,也可以选择源生成器。