【问题标题】:C#: Filter the members of an objectC#:过滤对象的成员
【发布时间】:2011-01-03 13:02:54
【问题描述】:

我有模特

public class User : EntityObject {
    public int Id { get; set; }

    public string Username { get; set; }
    public string Password { get; set; }
}

我想向客户端返回一个仅包含用户的用户名和密码成员的 JSON 结果。

我目前正在这样做

return Json(new { MyUser.Username, MyUser.Password });

但是我希望能够有一个界面

public interface ClientAvailableUser {
    string Username { get; set; }
    string Password { get; set; }
}

并使用它来知道返回给客户端的内容

如何使用接口 ClientAvailableUser 从 User 创建一个新对象,该对象只有来自 User 的成员也出现在接口中?

User MyUser = new User();

// MyUser has an Id, Username and Password

Object FilteredMyUser = // Filter MyUser using the ClientAvailableUser interface so that

// FilteredMyUser has only Username and Password according to ClientAvailableUser interface

【问题讨论】:

标签: c# .net json casting filtering


【解决方案1】:

另一种选择(也是我个人的偏好)是简单地将System.Web.Script.Serialization.ScriptIgnoreAttribute 放在您不想序列化的模型类的成员上(或创建一个可隐式转换的 DTO 类来做同样的事情)。

例如:

using System.Web.Script.Serialization;

public class User
{
    [ScriptIgnore]
    public int ID { get; set; }

    public string Username { get; set; }
    public string Password { get; set; }
}

这样你就不需要定义一个特殊的接口,你可以把这个元数据直接放在你的模型中。


更新:显然这不是一个选项,因为该类是派生类,并且它是应该隐藏的(不可修改的)基类的成员。

可以按照你想要的方式动态生成一个类,或者使用Emit 或者像Castle 这样的动态代理库,但是会非常麻烦。如果可以的话,我真的建议使用简单的代理类:

public class UserResult
{
    public UserResult(User user)
    {
        Username = user.Username;
        Password = user.Password;
    }

    public string Username { get; set; }
    public string Password { get; set; }
}

或者,如果您真的无法处理维护这个问题,您可以构建一个“通用”代理实例化器:

static class ProxyInstantiator
{
    public static TProxy CreateProxy<TProxy>(object source)
        where TProxy : new()
    {
        TProxy proxy = new TProxy();
        CopyProperties(source, proxy);
        return proxy;
    }

    protected static void CopyProperties(object source, object dest)
    {
        if (dest == null)
        {
            throw new ArgumentNullException("dest");
        }
        if (source == null)
        {
            return;
        }
        Type sourceType = source.GetType();
        PropertyInfo[] sourceProperties =
            sourceType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
        Type destType = dest.GetType();
        PropertyInfo[] destProperties =
            destType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
        var propsToCopy =
            from sp in sourceProperties
            join dp in destProperties on sp.Name equals dp.Name
            select new { SourceProperty = sp, DestProperty = dp };
        foreach (var p in propsToCopy)
        {
            object sourceValue = p.SourceProperty.GetValue(o, null);
            p.DestProperty.SetValue(dest, sourceValue, null);
        }
    }
}

然后你可以写一个简单的代理类(不是接口):

public class UserResult
{
    public string Username { get; set; }
    public string Password { get; set; }
}

并在控制器方法中调用它,如下所示:

User someUser = GetSomeUser();
UserResult result = ProxyInstantiator.CreateProxy<UserResult>(someUser);

请注意:

这不考虑到索引属性,如果有其中任何一个,则会失败。它没有考虑“深度复制”——如果你的源类包含引用类型,它只会复制引用——也许这就是你想要的,也许不是。

就个人而言,我会采用前一种方法,只构建单独的代理类而不使用通用代理,因为如果我犯了错误,我更喜欢编译时错误而不是运行时错误。但是你问了,所以你去!

【讨论】:

  • 这是一个很好的答案,我试过了,但我忘了提到我的 User 类扩展了一个 EntityObject 并且 EntityObject 有他自己的成员,我无法使用 ScriptIgnoreAttribute 进行过滤。谢谢!
  • @Christian - 您是否考虑过包装和委托给您班级中的实体而不是扩展它?
  • @Aaronaught - 您的示例效果很好,尽管我认为我可以采取更直接的方法。非常感谢 !这是很大的帮助!
  • 为了完整起见,我觉得我还应该提到 AutoMapper:codeplex.com/AutoMapper(这可能是您正在寻找的“更直接”的方法)
【解决方案2】:

这样的东西会给你一个对象中的属性列表,它与接口中的属性同名(还没有编译这个,但它应该很接近)。这应该可以帮助您入门。

public IEnumerable<PropertyInfo> GetPropertiesToTransfer( User user );
{
    Type userType = user.GetType();
    Type clientAvailableType = typeof(ClientAvailableUser);

    PropertyInfo[] userProps = userType.GetProperties();
    IEnumerable<string> caUserProps = clientAvailableType.GetProperties().Select( p => p.Name );

    return userProps.Where( p => caUserProps.Contains( p.Name ) );
}

【讨论】:

  • 这让我得到了 User 和 ClientAvailableUser 接口共有的属性,但我想从 User 对象创建一个只包含这些属性的新对象(这两种类型共有)。谢谢!
【解决方案3】:

我真的不确定我是否明白你正在尝试做你正在做的事情的“原因”,但你可以做很多事情:

public interface IClientAvailableUser 
{
    string Username { get; set; }
    string Password { get; set; }
}

internal class ConcreteClientAvailableUser : IClientAvailableUser 
{
   public string UserName{get;set;}
   public string Password{get;set;}
}

public class UserExtensions
{
    public IClientAvailableUser AsClientAvailableUser(this User user)
    {
        return new ConcreteClientAvailableUser { UserName = user.UserName, Password = user.Password};
    }        
}

那么你可以这样做:

IClientAvailableUser ica = myUser.AsClientAvailableUser();

但我不明白为什么你的用户类不能直接实现你的接口,然后你可以这样做:

IClientAvailableUser ica = myUser;

是的,这不是一个新对象,但是你需要一个新对象来做什么?

【讨论】:

  • 我想这样做是因为我的用户对象包含一些我不想随 Json 结果一起发送的成员(如 Id、EntityKey、EntityState)。您的解决方案工作正常,除了我将在许多地方拥有我的成员并且我只想使用接口 ClientAvailableUser 过滤用户。谢谢!
  • 那么您的解决方案有什么问题? return Json(new {myUser.UserName //});这正是这样做的方法。除了在新 {..} 中指定的属性之外,您不会发送任何其他内容。在幕后,当您执行 new {..} 时,正在为您创建一个新对象。
  • 事实上该解决方案有问题。如果我有几十个方法返回带有 new { ... } 的用户,并且我想向 User 添加一个应该通过 JSON 发送的新成员怎么办?比我必须去每个方法并更改新的 { ... } 语句以适应新成员。它没有生产力。所以这不是一个解决方案......
猜你喜欢
  • 2015-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-21
  • 1970-01-01
  • 2015-06-26
  • 1970-01-01
相关资源
最近更新 更多