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
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Modificare i metadati nell'head dell'HTML di una Blazor Web App
Utilizzare le Cache API di JavaScript per salvare elementi nella cache del browser
Definire lo stile CSS in base alle dimensioni del container
Effettuare chiamate con versioning da Blazor ad ASP.NET Core
Sostituire la GitHub Action di login su private registry
Eseguire attività con Azure Container Jobs
Le novità di Angular: i miglioramenti alla CLI
Gestione degli environment per il deploy con un workflow di GitHub
Effettuare il deploy di immagini solo da container registry approvati in Kubernetes
Eseguire query manipolando liste di tipi semplici con Entity Framework Core
Effettuare lo stream della risposta in ASP.NET Core tramite IAsyncEnumerable
Registrare servizi multipli tramite chiavi in ASP.NET Core 8