2012-09-10 11 views
8

Estoy tratando de genéricos un control complejo que se utiliza en mi sitio web con bastante frecuencia pero con diferentes campos. La funcionalidad en el control es siempre la misma, solo cambian los campos subyacentes.Iteración de las propiedades de una expresión lambda

Para lograr el método de mostrar diferentes campos, intento crear una extensión HTMLHelper que acepte un Expression<Func<TModel,TProperty>> como parámetro, que contendría las propiedades de una clase requerida para mostrar en el control. Por ejemplo:

La vista:

@model Project.Core.Page 
@Html.MyHelper(p => new { p.Author.Name, p.Author.Location, p.Author.Age }); 

Es la extensión que estoy teniendo problemas con - ¿cómo puedo iterar sobre los parametros proporcionados en el lambda para proporcionar cada uno con un TextBoxFor(), o incluso crear manualmente una input elemento y llenarlo con value y name del parámetro lambda?

La extensión de pseudo:

public static MvcHtmlString MyHelper<TModel,TProperty>(
    this HtmlHelper<TModel> helper, 
    Expression<Func<TModel,TProperty>> expression) { 
    foreach (var parameter in expression.???) { 
     // helper.TextBoxFor(???) 
     // TagBuilder("input").Attributes("name", expression.???) 
    } 
} 

me siento como si hubiera estado mirando en esto por mucho tiempo, y yo también siento que hay una manera más sencilla que estoy con vistas a la consecución de este.

Cualquier ayuda es muy apreciada. Si necesita más detalles, o me he perdido algo importante, hágamelo saber.

+0

Después de volver a leer la pregunta y mi respuesta, me di cuenta de que mi primer ejemplo no podía manejar el acceso a valores de propiedades complejas en el modelo. He actualizado mi ejemplo de código para dar cuenta de esta situación. – mclark1129

Respuesta

2

Si se asume lo siguiente:

  1. El resultado de la expresión de entrada es una proyección (devuelve un nuevo objeto, anónima o no)
  2. Los elementos de la proyección son todos MemberExpressions, y no contienen una llamada a un método en el modelo o en sus hijos

A continuación, puede lograr lo que se desea realizar con el siguiente enfoque:

Editar:

Después de darse cuenta de que mi primer ejemplo no podía manejar objetos con propiedades complejas, he actualizado el código para utilizar un ayudante método para acceder a los valores de propiedad. Este método atraviesa la cadena de propiedad utilizando la recursión para devolver los valores apropiados.

public static MvcHtmlString MyHelper<TModel,object>(
    this HtmlHelper<TModel> helper, 
    Expression<Func<TModel,object>> expression) { 

     var newExpression = expression.Body as NewExpression; 
     TModel model = helper.ViewData.Model; 

     foreach (MemberExpression a in newExpression.Arguments) { 

      var propertyName = a.Member.Name; 
      var propertyValue = GetPropertyValue<TModel>(model, a); 

      // Do whatever you need to with the property name and value; 

     } 

    } 

    private static object GetPropertyValue<T>(T instance, MemberExpression me) { 

     object target; 

     if (me.Expression.NodeType == ExpressionType.Parameter) { 
      // If the current MemberExpression is at the root object, set that as the target.    
      target = instance; 
     } 
     else {     
      target = GetPropertyValue<T>(instance, me.Expression as MemberExpression); 
     } 

     // Return the value from current MemberExpression against the current target 
     return target.GetType().GetProperty(me.Member.Name).GetValue(target, null); 

    } 

Nota: que no aplicó de manera directa como método de extensión MVC en mi IDE, por lo que puede ser necesaria una ligera variación de la sintaxis.

2

La expresión que habrá creado será relativamente complicada: deberá buscar todas las propiedades y luego llamar al constructor de tipo anónimo. "Desmontar" puede ser doloroso ... aunque si aún quieres probar, te sugiero que dejes una implementación de método vacía y busques en el depurador para ver cómo se ve la expresión.

Si desea ser feliz que conformarse con una forma ligeramente más feo del código de llamada, sería mucho más sencillo de implementar esto:

@Html.MyHelper(p => p.Author.Name, p => p.Author.Location, p => p.Author.Age); 

Usted podría hacer que echar un params Expression<TModel, object> o usted podría declarar sobrecargas múltiples con diferentes números de parámetros, por ejemplo

// Overload for one property 
MyHelper<TModel, TProperty1>(this ..., Expression<Func<TModel, TProperty1>> func1) 

// Overload for two properties 
MyHelper<TModel, TProperty1, TProperty2>(this ..., 
    Expression<Func<TModel, TProperty1>> func1, 
    Expression<Func<TModel, TProperty2>> func2) 

etc.

+0

Había considerado este enfoque, pero como dices que es un poco antiestético, lo mantendré como un 'plan B'. :) Gracias por responder, Jon. –

1

Tal vez una API de estilo constructor podría simplificar las cosas:

@(Html.MyHelper(p) 
    .Add(p => p.Author.Age) 
    .Add(p => p.Author.LastName, "Last Name") 
    .Build()) 

Tenga en cuenta que esto le permite añadir parametros opcionales en caso de que los necesite.

El código sería algo como esto

public static class Test 
{ 
    public static Helper<TModel> MyHelper<TModel>(this HtmlHelper helper, TModel model) 
    { 
     return new Helper<TModel>(helper, model); 
    } 
} 

public class Helper<TModel> 
{ 
    private readonly HtmlHelper helper; 
    private readonly TModel model; 

    public Helper(HtmlHelper helper, TModel model) 
    { 
     this.helper = helper; 
     this.model = model; 
    } 

    public Helper<TModel> Add<TProperty>(Expression<Func<TModel, TProperty>> expression) 
    { 
     // TODO 
     return this; 
    } 

    public MvcHtmlString Build() 
    { 
     return new MvcHtmlString("TODO"); 
    } 
} 
1

Considere esto:

crear la carpeta App_Code

Ponga el archivo de afeitar ayudante Templates.cshtml.

Parece que a continuación:

@helper Print(string myvalue,string special="") 
{ 
    <pre> <input id="id" type="text" value ="@myvalue" data-val="@special"/> </pre> 
} 

De esta manera usted no tiene que escribir archivos HTML en C#. Es muy útil

Authors.cshtml se parece a continuación:

@model IEnumerable<MvcApplication1.Models.Author> 
@{ 
    ViewBag.Title = "Authors"; 
} 

<h2>Authors</h2> 

@foreach(var auth in Model) 
{ 
    @Templates.Print(auth.Name);  
} 

books.cshtml se parece a continuación:

@model IEnumerable<MvcApplication1.Models.Book> 
@{ 
    ViewBag.Title = "Books"; 
} 

<h2>Books</h2> 

@foreach(var book in Model) 
{ 
    @Templates.Print(book.Title,book.ISBN);  
} 

Plugin todas las propiedades especiales como sea necesario por clase de modelo. Si se vuelve demasiado complicado, mira dentro del objeto dinámico y expando. Depende de cuán complejos sean sus modelos/modelos de vista.

Cuestiones relacionadas