在构建时未知数据类型时,将数据对象映射到合适的ViewModel

时间:2014-12-18 08:31:36

标签: c# wpf mvvm mapping

背景和问题
我有一个WPF应用程序,它基本上由一组不同的技能对象组成,其中每个技能都是从一个抽象基类(SkillBase)继承而来的。每个技能都应该有自己的View和ViewModel,以支持编辑特定技能的值。当用户选择列表中的技能并按下"编辑"时,应该在注入技能的情况下实例化相应的Edit-ViewModel。

当我只有Skill-data对象时,如何选择应该实例化的Edit-ViewModel?技能可以通过插件系统添加,所以我事先不知道技能的类型。

我今天的解决方案(可行)是使用属性(EditViewModelForAttribute)来装饰每个Edit-ViewModel,告诉它应该映射到哪种数据类型。这是要走的路还是有更好的方法吗?我甚至走在正确的轨道上吗?

我能想到的其他解决方案是跳过属性并使用Edit-ViewModels的某种命名约定,另一种解决方案是在启动时通过注入服务注册具有Edit-ViewModel类型的Skill类型( EditService.RegisterEditViewModel(typeof(WalkSkill), typeof(EditWalkSkillViewModel));

这是我的代码:

"数据层"

public abstract class SkillBase
{
    // Common properties for all skills
}

public class WalkSkill : SkillBase
{
    // Some properties
}

public class RunSkill : SkillBase
{
    // Some properties
}

" ViewModel Layer"

public abstract class EditSkillViewModel<T> : ViewModelBase where T : Skill
{
    public abstract T Skill { get; protected set; }
}

[EditViewModelFor(typeof(WalkSkill))] // Attribute telling that this viewmodel should be instantiated when we want to edit a WalkSkill object
public class EditWalkSkillViewModel : EditSkillViewModel<WalkSkill>
{
    public override WalkSkill Skill { get; protected set; }

    public EditWalkSkillViewModel(WalkSkill skill)
    {
        Skill = skill;
    }
}

[EditViewModelFor(typeof(RunSkill))] // Attribute telling that this viewmodel should be instantiated when we want to edit a RunSkill object
public class EditRunSkillViewModel : EditSkillViewModel<RunSkill>
{
    public override RunSkill Skill { get; protected set; }

    public EditRunSkillViewModel(RunSkill skill)
    {
        Skill = skill;
    }
}

要为特定技能找到正确的ViewModel,我有一个扩展方法来搜索app域中具有EditViewModelForAttribute且具有特定技能类型的类型:

public static ViewModelBase GetEditSkillViewModel(this Skill skill)
{
    var viewModelType = (from assembly in AppDomain.CurrentDomain.GetAssemblies()
                         from type in assembly.GetTypes()
                         where
                             type.IsDefined(typeof(EditViewModelForAttribute)) &&
                             type.GetCustomAttribute<EditViewModelForAttribute>().SkillType == skill.GetType()
                         select type).SingleOrDefault();

    return viewModelType == null ? null : (ViewModelBase)Activator.CreateInstance(viewModelType, skill);
}

这就是这样称呼的:

var editViewModel = selectedSkill.GetEditSkillViewModel();

3 个答案:

答案 0 :(得分:1)

您使用的当前解决方案似乎很好,只要您每个技能只有一个ViewModel。 但是有一个建议是,使用FirstOrDefault代替SingleOrDefault并将结果缓存到某处。搜索所有类型可能非常耗时。

答案 1 :(得分:1)

就软件开发而言,您似乎走在了正确的轨道上。您当前的解决方案没有任何问题。如果你使用像MEF这样的框架,这是经典的方法。

那说,如果你要继续;考虑切换到MEF或任何其他框架,而不是编写自己的属性/导入函数/其他术语。

// Define other methods and classes here
public abstract class EditSkillViewModel<T> where T : SkillBase
{
    public T Skill { get; protected set; }

    protected EditSkillViewModel(T skill){
        Skill = skill;
    }
}

public static class EditSkillViewModelManager
{
    private static IDictionary<Type, Type> _types;

    public static EditSkillViewModel<T> CreateEditSkillViewModel<T>(T skill) 
        where T : SkillBase
    {
        return (EditSkillViewModel<T>)Activator.CreateInstance(_types[typeof(T)], skill);
    }

    static EditSkillViewModelManager()
    {
        var editSkillViewModelTypes = from x in Assembly.GetAssembly(typeof(SkillBase)).GetTypes()
                    let y = x.BaseType
                    where   !x.IsAbstract && 
                            !x.IsInterface &&
                            y != null && 
                            y.IsGenericType &&
                            y.GetGenericTypeDefinition() == typeof(EditSkillViewModel<>)
                    select x;

        _types = editSkillViewModelTypes
            .Select(x => 
                new { 
                        To = x, // EditWalkSkillViewModel
                        From = x.BaseType.GetGenericArguments()[0] // WalkSkill
                    })
            .ToList()
            .ToDictionary(x => x.From, x => x.To);

        _types.Dump();
    }
}

public class EditWalkSkillViewModel : EditSkillViewModel<WalkSkill>
{
    public EditWalkSkillViewModel(WalkSkill skill) : base(skill)
    {
    }
}

public class EditRunSkillViewModel : EditSkillViewModel<RunSkill>
{
    public EditRunSkillViewModel(RunSkill skill): base(skill)
    {
    }
}

答案 2 :(得分:1)

我正在解决非常类似的问题,这就是我所做的 - 转换为你的课程:

  • 你现在拥有的技能+添加属性/通用,说明&#34;我需要配置类型X&#34;。
  • 每项技能的配置类(可以共享/继承,如果&#34;步行&#34;&#34;运行&#34;有速度等)
  • 使用属性/通用说法&#34查看每个配置;我可以编辑X&#34;
  • 类型的配置
  • 一个具有Configuration类属性的EditViewModel。此VM还可以具有所有技能共有的属性 - 名称,ID,已启用,....

它基于与您的解决方案相同的想法,但抽象是在其他地方。我这样做了,因为我不得不将UI架构与逻辑分开。如果我想从WPF迁移到其他东西,我唯一要改变的是为所有配置创建视图。只有一个EditViewModel将WPF UI与我的应用程序逻辑/模型连接起来。