C# 是否有一种方法可以自动创建ViewModel属性或将其映射到模型属性?
我有以下模型/视图模型对。这是一种非常常见的情况—从ViewModel到模型属性的纯映射—并且包含大量重复且容易出错的代码 我想知道是否有更好的方法来做到这一点,特别是减少出错的机会(忘记属性,使用错误的属性名称) 欢迎使用较新的语言功能,如C# 是否有一种方法可以自动创建ViewModel属性或将其映射到模型属性?,c#,wpf,mvvm,dry,C#,Wpf,Mvvm,Dry,我有以下模型/视图模型对。这是一种非常常见的情况—从ViewModel到模型属性的纯映射—并且包含大量重复且容易出错的代码 我想知道是否有更好的方法来做到这一点,特别是减少出错的机会(忘记属性,使用错误的属性名称) 欢迎使用较新的语言功能,如CallingMemberName,但目前我不确定是否理解它们 公共类参数Geometricsviewmodel:ConfiguracoesViewModel { // (...) 公共双距离广播电台 { get=>Model.distanceaproj
CallingMemberName
,但目前我不确定是否理解它们
公共类参数Geometricsviewmodel:ConfiguracoesViewModel
{
// (...)
公共双距离广播电台
{
get=>Model.distanceaprojectorparede;
设置
{
Model.distanceaprojectorparede=值;
RaisePropertyChanged(()=>距离APROJETORPAREDE);
}
}
公共双助听器
{
get=>Model.AlturaProjetor;
设置
{
Model.AlturaProjetor=值;
RaisePropertyChanged(()=>AlturaProjetor);
}
}
公共双备选方案
{
get=>Model.alturasuberiorprojecao;
设置
{
Model.alturasuberiorprojecao=值;
RaisePropertyChanged(()=>ALTURABERIOR PROJECAO);
}
}
公共双AlturaSuperiorProjecao
{
get=>Model.AlturaSuperiorProjecao;
设置
{
Model.AlturaSuperiorProjecao=值;
RaisePropertyChanged(()=>AlturaSuperiorProjecao);
}
}
公共双重距离
{
get=>Model.distanceacameraparede;
设置
{
Model.distance acameraparede=值;
RaisePropertyChanged(()=>DistanceAcameraparede);
}
}
公共双Altura照相机
{
get=>Model.AlturaCamera;
设置
{
Model.AlturaCamera=值;
RaisePropertyChanged(()=>AlturaCamera);
}
}
公共双重替代
{
get=>Model.alturasuberiorImagem;
设置
{
Model.alturasuberiorImagem=值;
RaisePropertyChanged(()=>Altura-SuberiorImagem);
}
}
公共双AlturaSuperiorImagem
{
get=>Model.AlturaSuperiorImagem;
设置
{
Model.AlturaSuperiorImagem=值;
RaisePropertyChanged(()=>AlturaSuperiorImagem);
}
}
}
听起来您正在寻找类似这样的东西没有必要将ViewModel编写为模型顶部的外观
直接或使用库(如)更改模型实现InotifyProperty。然后将整个模型发布为ViewModel的单个属性,并绑定到视图中的属性
我在我的博客中介绍了这个主题-/。如果使用生成的代码可以,那么可以使用它生成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("}");
}
}
#>
您可以添加很多更改,例如,不使用自定义属性,而是为继承自
ConfiguracoesViewModel
的所有类生成代码。您还可以检查每个属性是否已经添加到VM中,而不是生成它们,这允许您通过简单地将其添加到类中来为所需的属性创建自定义getter和setter。我偶然发现了这个问题,所以我想我应该添加如何解决这个问题的方法。我有一个MyViewModelBase
类型来包装我的MyModel
。这两个类都实现了INotifyPropertyChanged,ViewModel只是转发PropertyChanged事件,如下所示:
公共类MyViewModelBase:INotifyPropertyChanged
{
公共财产
{
get=>\u model.MyProperty;
set=>\u model.MyProperty=value;
}
公共MyViewModelBase(MyModel模型)
{
//我们将包装器属性命名为与模型相同的名称,
//在这里,我们只是转发属性更改通知
model.PropertyChanged+=(发送方,e)=>PropertyChanged?.Invoke(this,e);
}
...
}
公共类MyModel:INotifyPropertyChanged
{
//我们用fody来筹集财产,
//但在这里可以正常升高,否则
公共int MyProperty{get;set;}
}
我们有许多不同的模型和视图模型继承自这两个基类。要获取模型中某个属性的更改通知,只需在视图模型中为其添加一个同名的包装器,当模型中的属性更改时,更改也将通过视图模型传播
请注意,我们在应用程序的有限部分中使用它,因为它非常适合。我不认为它可以扩展到大型应用程序的每个部分。在合适的地方使用它
现在,当谈到为基础模型自动生成包装器属性时,最好的选择(AFAIK)是Fody。我迅速看了看,发现了这个:。我不确定你是否可以用它,但这是我能找到的最接近的东西
当C#9/.NET 5发布时,源代码生成器也可以是一个选项。AutoMapper?你知道吗?为什么不直接在(DTO?)模型中实现
INotifyPropertyChanged
?@dymanoid最终这是一个偏好问题。我喜欢将模型视为一个简单的属性包,VIEW模型具有通知属性更改的特性——在这种情况下,它用来触发自动保存到一个持久化的文件。从我理解的AutoMaPad主页中,您可以使用它来将一个对象实例映射到另一个对象实例,而我的用例是(双向)将对象(模型)中给定属性的值映射到
[Views(typeof(ParametrosGeometricos))]
partial class ParametrosGeometricosViewModel {
(...)
}
<#@ 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("}");
}
}
#>
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);
}
}
}
}