Blazor中如何确保ViewModel按正确状态初始化,适配EditForm的新旧实体场景
最优方案选型建议
针对Blazor EditForm同时适配新建、编辑两种场景的ViewModel需求,优先推荐构造函数链方案,兼顾代码复用、可继承性和易用性:
方案1:构造函数链(首选)
利用C#构造函数重载调用的特性,把所有校验逻辑集中到核心的受保护构造函数中,完全避免代码重复,同时支持子类继承:
public class PersonViewModel { // 核心校验逻辑全部集中在这个protected构造函数中 protected PersonViewModel(Guid? id, string name, int age) { // Id校验:有值的情况下不能是空Guid if (id.HasValue && id.Value == Guid.Empty) throw new InvalidOperationException($"{nameof(id)} 是无效值"); // 公共字段校验 if (string.IsNullOrWhiteSpace(name)) throw new InvalidOperationException($"{nameof(name)} 不能为空或空白字符"); if (age < 0) throw new InvalidOperationException($"{nameof(age)} 不能小于0"); Id = id; Name = name; Age = age; } // 新建场景调用:不需要传Id public PersonViewModel(string name, int age) : this(null, name, age) { } // 编辑场景调用:必须传有效Id public PersonViewModel(Guid id, string name, int age) : this(id, name, age) { } public Guid? Id { get; } // 适配EditForm绑定要求,设置为public set public string Name { get; set; } public int Age { get; set; } }
该方案的优势:
- 无重复校验逻辑,所有规则集中维护,修改时只需要改一处
- 保留公开构造函数,子类可以正常调用基类构造函数实现扩展
- 两种创建场景显式区分,调用方不会出现参数传错的问题
- 可以按需调整属性的访问器,满足Blazor双向绑定的要求
方案2:拆分独立ViewModel(适合场景差异大的情况)
如果新建和编辑场景的字段、校验规则差异较大,或者需要严格避免非法状态(比如编辑场景下Id绝对不能为null),可以拆分两个独立的ViewModel,通过基类复用公共逻辑:
// 基类存放公共字段和公共校验规则 public abstract class PersonViewModelBase { protected PersonViewModelBase(string name, int age) { if (string.IsNullOrWhiteSpace(name)) throw new InvalidOperationException($"{nameof(name)} 不能为空或空白字符"); if (age < 0) throw new InvalidOperationException($"{nameof(age)} 不能小于0"); Name = name; Age = age; } public string Name { get; set; } public int Age { get; set; } } // 新建场景专用ViewModel:无Id字段 public class NewPersonViewModel : PersonViewModelBase { public NewPersonViewModel(string name, int age) : base(name, age) { } } // 编辑场景专用ViewModel:必填Id public class ExistingPersonViewModel : PersonViewModelBase { public ExistingPersonViewModel(Guid id, string name, int age) : base(name, age) { if (id == Guid.Empty) throw new InvalidOperationException($"{nameof(id)} 是无效值"); Id = id; } public Guid Id { get; set; } }
该方案的优势是状态完全隔离,从类型层面避免了非法状态的出现,适合表单提交逻辑完全分离的场景,新建接口和更新接口可以分别接收对应的ViewModel参数,安全性更高。
方案3:优化版静态工厂方法
如果你偏好静态工厂方法的创建方式,可以把原本的私有构造函数改为protected,即可解决无法继承的问题:
public class PersonViewModel { // 改为protected,子类可以调用 protected PersonViewModel() { } public Guid? Id { get; protected set; } public string Name { get; set; } public int Age { get; set; } // 新建场景工厂方法 public static PersonViewModel CreateForNew(string name, int age) { if (string.IsNullOrWhiteSpace(name)) throw new InvalidOperationException($"{nameof(name)} 不能为空或空白字符"); if (age < 0) throw new InvalidOperationException($"{nameof(age)} 不能小于0"); return new PersonViewModel { Name = name, Age = age }; } // 编辑场景工厂方法 public static PersonViewModel CreateForEdit(Guid id, string name, int age) { if (id == Guid.Empty) throw new InvalidOperationException($"{nameof(id)} 是无效值"); var vm = CreateForNew(name, age); vm.Id = id; return vm; } }
该方案适合创建逻辑比较复杂、需要显式命名区分场景的情况,普通场景下优先级低于构造函数链方案。
内容的提问来源于stack exchange,提问作者marcomallesco
相关产品推荐
相关产品推荐

