Un helper method per replicare un template per ogni proprietà con ASP.NET MVC

di Cristian Civera, in ASP.NET MVC,

Nello script #1201 abbiamo visto come un helper method possa ricevere una funzione che rappresenti una porzione di HTML. Oltre al passaggio semplice di questa funzione, Razor permette anche la creazione di un delegate che supporti parametri aggiuntivi. L'intento è quindi di dichiarare il template permettendogli di utilizzare parametri che l'helper method gli passa.

Supponiamo quindi di volere fare un helper method che cicli su tutte le proprietà dell'oggetto passato e per ognuna di esse utilizzi il template passato. Il markup desiderato quindi è il seguente.

@Html.ForEachMember(m => m.Details, v =>
@<text>
  <div>
  @v.Html.LabelFor(s => s): @v.Html.EditorFor(s => s)
  </div>
</text>)

Nello snippet va evidenziato il parametro v, espresso nella lambda, che viene passato dall'helper method ad ogni invocazione che fa per ogni proprietà. Il parametro è di fondamentale importanza perché all'interno del template il contesto della view è comunque quello della pagina. Le proprietà con le quali si accede agli helper method, quali Html, Ajax e Url, sono di conseguenza relative al modello della pagina. Invece noi vogliamo riferirli ad ogni valore di ogni proprietà per la quale il template viene invocato. Il parametro v è quindi un nostro oggetto che espone le medesime proprietà, ma riferite al membro sul quale sta ciclando. L'unica differenza sta nel fatto che gli helper method non sono tipizzati, ma si riferiscono genericamente a object, dato che ogni proprietà del modello può avere tipi differenti.

A questo punto vediamo come è definito l'helper method il quale, a differenza dello script precedente, riceve una funzione che dato il parametro riceve la funzione di generazione dell'HTML.

public static IHtmlString ForEachMember<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, Func<MemberView<object>, Func<ViewContext, IHtmlString>> template)
{
  // Ottengo i metadati dall'espressione
  var metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
  // ViewContext specifico per l'espressione
  var parentViewContext = GetViewContext<TValue>(metadata, helper.ViewContext);

  // Ciclo le proprietà
  foreach (var p in metadata.Properties)
  {
    var mv = new MemberView<object>(p, parentViewContext);
    // Ottengo l'HTML per la proprietà
    var html = template(mv)(mv.ViewContext);
    // Scrivo direttamente in uscita
    helper.ViewContext.Writer.Write(html);
  }

  return MvcHtmlString.Empty;
}

Lo snippet non fa altro che creare un ViewContext per la proprietà principale che ci dà il modello, leggere i metadati e ciclare sulle proprietà. Per ognuna di queste passa il contesto specifico di ogni proprietà e genera l'HTML. La classe MemberView è da noi definita ed espone le proprietà per gli helper method contestualizzati al membro corrente.

public class MemberView<TModel> : IViewDataContainer
{
  public MemberView(ModelMetadata metadata, ViewContext parentViewContext)
  {
    Model = (TModel)metadata.Model;
    ViewContext = HtmlExtensions.GetViewContext<TModel>(metadata, parentViewContext);
    ViewData = (ViewDataDictionary<TModel>)ViewContext.ViewData;
    Html = new HtmlHelper<TModel>(ViewContext, this);
    Ajax = new AjaxHelper<TModel>(ViewContext, this);
  }

  public HtmlHelper<TModel> Html { get; }

  public TModel Model { get; }

  public ViewContext ViewContext { get; }

  public AjaxHelper<TModel> Ajax { get; }

  public ViewDataDictionary<TModel> ViewData
  {
    get; set;
  }

  ViewDataDictionary IViewDataContainer.ViewData
  {
    get { return ViewData; }
    set { ViewData = (ViewDataDictionary<TModel>)value; }
  }
}

La funzione GetViewContext è fondamentale, perché si occupa di contestualizzare la view sulla proprietà e sul modello che il ciclo sta eseguendo.

internal static ViewContext GetViewContext<TModel>(ModelMetadata metadata, ViewContext parentViewContext)
{
  var parentViewData = new ViewDataDictionary<TModel>((TModel)metadata.Model)
  {
    ModelMetadata = metadata,
    TemplateInfo = new TemplateInfo
    {
      FormattedModelValue = (TModel)metadata.Model,
      // Ottengo il prefisso per l'HTML tramite il padre
      HtmlFieldPrefix = parentViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(metadata.PropertyName),
    }
  };
  return new ViewContext(parentViewContext.Controller.ControllerContext,
    parentViewContext.View,
    parentViewData, new TempDataDictionary(),
    parentViewContext.Writer);
}

In particolare, la proprietà HtmlFieldPrefix fa sì che le istruzioni come @v.Html.EditorFor(s => s) risolvano gli ID HTML considerando tutto il percorso, che nell'esempio è Details.ProprietaN. Inoltre, vengono innescati come ci aspettiamo tutti i meccanismi automatici di scelta del template.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi