开发者

C#: Filter the members of an object

开发者 https://www.devze.com 2022-12-16 11:56 出处:网络
I have a model public class User : EntityObject { public int Id { get; set; } public string Username { get; set; }

I have a model

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

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

and I would like to return to the client a JSON result containing only the Username and Password members of the User.

I am currently doing this like so

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

But I would like to be able to have an interface

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

and use it to know what to return to the client

Ho开发者_Go百科w can I use interface ClientAvailableUser to create a new object from User that has only the members from the User also present in the interface ?

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


Another option (and my personal pereference) would be to simply put System.Web.Script.Serialization.ScriptIgnoreAttribute on the members of the model class that you don't want serialized (or create an implicitly-convertible DTO class to do the same thing).

Ex:

using System.Web.Script.Serialization;

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

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

That way you don't need to define a special interface, you can put this metadata right in your model.


Update: Apparently that's not an option because the class is a derived class and it's members from the (unmodifiable) base class that should be hidden.

It is possible to dynamically generate a class the way you want, either using Emit or a dynamic proxy library like Castle, but it's going to be very cumbersome. If you can, I would really recommend to use a simple proxy class instead:

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

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

Or, if you really can't deal with maintaining this, you can build a "generic" proxy instantiator:

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);
        }
    }
}

Then you can write a simple proxy class (not interface):

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

And invoke it in a controller method like this:

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

A word of caution about this:

This does not take into account indexed properties and will fail if there are any of those. It does not take into account "deep copying" - if your source class contains reference types, it will only copy the references - maybe that's what you want, maybe it isn't.

Personally, I'd take the former approach and just build individual proxy classes without the generic proxy, because if I make a mistake, I'd prefer a compile-time error over a runtime error. But you asked, so there you go!


Something like this will give you a list of properties in object with the same name as a propety in the interface (haven't compiled this yet, but it should be close). This should get you started.

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 ) );
}


I'm really not sure I get the "why" you're trying to do what you're doing, but you can do a number of things:

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};
    }        
}

Then you can just do this:

IClientAvailableUser ica = myUser.AsClientAvailableUser();

But I don't understand why your user class can't just implement your interface directly, and then you could do:

IClientAvailableUser ica = myUser;

Yes, this isn't a NEW object, but what do you need a new one for anyway?

0

精彩评论

暂无评论...
验证码 换一张
取 消