C#: Filter the members of an object
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?
精彩评论