MVC 视图中的多个模型
一种允许在 ASP.NET MVC 视图中访问多个模型实体的方法。
引言
CodeProject QA 部分最近有一个关于在给定的 ASP.NET (MVC) 视图中使用多个模型的问题。正如您可能知道的,您只能在视图中指定一个模型,但有时您可能需要在任何给定时间访问多个模型。
我的解决方案是创建一个包含 `object` 类型对象列表的“模型包”,并将该模型包作为视图中唯一的模型,从而遵守 Microsoft 强加的任意限制。请记住,这并不意味着您不能在其他环境中使用此代码,它只是解释了代码最初开发的原因。
本文档描述了我自己的模型包实现,代码位于 .NET Standard 2.0 程序集中,这使其与 .NET Core (3.0+) 和 .NET Framework 都兼容。这纯粹是为了在这两种环境中演示代码。将相关程序集重新定位到只针对您首选的环境是一件简单的事情。
政治/宗教观点 - 就我个人而言,我认为使用 .NET Core 没有任何实际价值 - 完全没有 - 因此,代码对 Core 的支持仅仅是为了安抚那些不可避免的被激怒的抱怨者。请不要将此声明解释为讨论该主题的邀请。我不会理解,并且会特别不友好。您已被警告。
致未来雇主 - 上述政治/宗教声明并不意味着我不会在公司代码中使用 .NET Core。这只是我自认为狭隘的世界观。例如,我不喜欢 Web 开发,但我目前正在做这份工作。生活就是这样。
代码
代码使用了以下 .NET 功能
- 控制台应用程序 - 用于演示代码
- 库程序集
- 集合
- Reflection(反射)
- Generics
- Linq
项目
演示解决方案中包含以下项目
- PWMultiModelBag - 目标为 .NET Standard 2.0 的类库。包含本文档撰写时所描述的代码
- DemoNetCore - 目标为 .NET Core 5.0 的控制台应用程序,用于测试 PWMultiModelBag 程序集。
- DemoNetFrame - 目标为 .NET Framework 4.7.2 的控制台应用程序,用于测试 PWMultiModelBag 程序集。
- SampleObjects - 目标为 .NET Standard 2.0 的类库,包含用于测试 PWMultiModelBag 程序集的示例类。
PWMultiModelBag 程序集
这是您将在自己的代码中引用的程序集。它只包含一个类 - `MultiModelBag`。这是一个相当简单的类,具有向其内部集合添加对象、检索这些对象供您的代码使用以及从内部集合中删除这些对象的方法。这是该类具有所有 JSOP 優點的版本。
namespace PWMultiModelBag
{
/// <summary>
/// Provides a model container for ASP.NET views that hods 1 or more models for a given view
/// (this skirts the single-model restriction).
/// </summary>
public class MultiModelBag
{
/// <summary>
/// Get/set a flag indicating whether the programmer can add multiple objects of the
/// same type
/// </summary>
public bool AllowDuplicates { get; set; }
/// <summary>
/// Get/set the collection containing the model objects
/// </summary>
public List<object> Models { get; set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="allowDupes"A flag indicating whether multiple objects of the same type can be added</param>
public MultiModelBag (bool allowDupes=false)
{
this.AllowDuplicates = allowDupes;
this.Models = new List<object>();
}
/// <summary>
/// Adds the specified object to the collection (restricted by AllowDuplicates property)
/// </summary>
/// <param name="obj"></param>
public int Add(object obj)
{
int added = 0;
if (obj != null)
{
var found = this.Models.FirstOrDefault(x=>x.GetType() == obj.GetType());
if (this.AllowDuplicates || found == null)
{
this.Models.Add(obj);
added++;
}
}
return added;
}
/// <summary>
/// Get the first item found in the list that matches the specified type (T).
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T Get<T>()
{
var found = this.Models.OfType<T>().Select(x=>x);
return (found.Count()>0) ? found.First() : default(T);
}
/// <summary>
/// Get the object in the colllection where the specified propery's value matches.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="propertyName"></param>
/// <param name="value"></param>
/// <returns>The object if found. Otherwise, null.</returns>
/// <remarks>This overload is only useful when dupes are allowed.</remarks>
public T Get<T>(string propertyName, object value)
{
T result = default(T);
PropertyInfo[] properties = typeof(T).GetProperties();
var found = this.Models.OfType<T>();
foreach(object obj in found)
{
PropertyInfo property = properties.FirstOrDefault(x => x.Name == propertyName);
result = (property != null && property.GetValue(obj) == value) ? (T)obj : default(T);
if (result != null)
{
break;
}
}
return result;
}
/// <summary>
/// Removes the 1st occurrence of the specified type from the collection
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>The object that was removed, or null</returns>
public T Remove<T>()
{
var found = this.Models.OfType<T>().Select(x => x);
T result = (found.Count() > 0) ? found.First() : default(T);
if (result != null)
{
this.Models.Remove(result);
}
return result;
}
/// <summary>
/// Removes the object with the specified value from the bag.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="propertyName"></param>
/// <param name="value"></param>
/// <returns>Returns the removed object</returns>
public T Remove<T>(string propertyName, object value)
{
// gets the properties for the object type
PropertyInfo[] properties = typeof(T).GetProperties();
// find all instances of the specified object type
var found = this.Models.OfType<T>().Select(x => x);
// find the one we want to remove
T result = default(T);
foreach(object obj in found)
{
PropertyInfo property = properties.FirstOrDefault(x => x.Name == propertyName);
result = (property != null && property.GetValue(obj) == value) ? (T)obj : default(T);
if (result != null)
{
this.Models.Remove(result);
break;
}
}
return result;
}
}
}
此对象使用泛型允许程序员添加/获取/删除任何类型的对象。它打算与复杂对象一起使用,而不是与内置类型(因为毕竟我们谈论的是“模型”),但我认为内置类型也可以使用。为了完全披露,我没有用内置对象对其进行测试。
使用反射(查找使用 `PropertyInfo` 的方法)来查找具有指定名称和值的对象以便检索或删除。
实例化
实例化此对象时,您可以指定可以将多个相同类型的对象添加到包中。当允许重复项时,无法防止添加属性值用于标识对象的对象,因为这些属性值完全相同。 `allowDupes` 参数的默认值为 `false`。
添加对象
调用 `MultiModelBag.Add` 方法将执行以下功能
if the specified object is not null
{
find the first item of the object's type in the collection
if duplicates are allowed, or an item was not found
{
add the object to the collection
}
}
该方法返回 1(如果对象已添加),或 0(如果对象未添加)。
删除对象
删除对象会从集合中删除指定类型的对象,并将已删除的对象返回给调用方法。这很方便,因为您可以 a) 验证是否删除了正确的对象,以及 b) 在允许其范围超出之前对该对象进行进一步处理。当然,您也可以完全忽略返回值。
MultiModelBag.Remove 方法有两个重载。
public T Remove<t>()
- 此重载删除找到的第一个指定类型的对象。
public T Remove<t>(string propertyName, object value)
- 此重载接受属性名称和值,并在集合中搜索具有指定属性的值的第一个指定类型的对象,如果找到则删除。
删除对象不受 `AllowDuplicates` 标志的影响。
检索对象
检索对象将搜索并返回指定对象给调用方法。有两种重载
public T Get<t>()
- 此重载查找并返回找到的第一个指定类型的对象。如果找不到指定类型的对象,则返回 `null`。
public T Get<t>(string propertyName, object value)
- 此重载接受属性名称和值,并在集合中搜索具有指定属性的值的第一个指定类型的对象,并返回该对象(如果未找到则为 null)。
SampleObjects 程序集
此程序集提供了两个控制台应用程序使用的示例数据,并且仅用于减小演示代码的占地面积。鉴于程序集的性质,不包含任何注释。
namespace SampleObjects
{
// sample classes used in this demo
public class ModelBase
{
public string Name { get; set; }
}
public class Model1:ModelBase
{
public Model1(string name) { this.Name=name; }
}
public class Model2:ModelBase
{
public Model2(string name) { this.Name=name; }
}
public class Model3:ModelBase
{
public Model3(string name) { this.Name=name; }
}
}
用法
两个演示控制台应用程序在代码和功能上都是相同的,并且完全测试了 `MultiModelBag` 类。
using System;
using PWMultiModelBag;
using SampleObjects;
namespace DemoNetFramework
{
class Program
{
static void Main(string[] args)
{
// the MultiModelBag accepts a bool parameter that indicates whether or
// not to allow multiple objects of the same type.
MultiModelBag bag = new MultiModelBag(true);
bag.Add(new Model1("1"));
bag.Add(new Model2("2a"));
bag.Add(new Model2("2b"));
// should be first model1 obj
Model1 bagModel1 = bag.Get<model1>();
// should be first model2 obj
Model2 bagModel2 = bag.Get<model2>();
// if allowdupes, should be 2nd model2 obj, otherwise will be null
Model2 bagModel2b = bag.Get<model2>("Name", "2b");
// should be null
Model3 bagModel3 = bag.Get<model3>();
// should be null because an object with a "Name" property of "2z" does
// not exist jin the model bag
Model2 bagModel2c = bag.Remove<model2>("Name", "2z");
// should be the same as bagModel2b
Model2 bagModel2d = bag.Remove<model2>("Name", "2a");
// set a breakpoint here to inspect the variables created above
Console.ReadKey();
}
}
}
正如您所看到的,使用非常简单。我们创建了几个对象并将它们添加到包中,接下来我们测试检索,最后我们测试删除。这段代码没有什么特别或花哨的地方。
结语
此代码说明了绕过某些框架扩展(在本例中,是 MVC 生态系统中强加的扩展)很容易。可能有其他(更好)的方法可以达到相同的结果,所以请随意查找并探索它们。我向您保证,我的感受不会受到伤害。
历史
- 2021.11.05 - 首次发布