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
Estrarre dati randomici da una lista di oggetti in C#
Routing statico e PreRendering in una Blazor Web App
Ottimizzazione dei block template in Angular 17
Gestire liste di tipi semplici con Entity Framework Core
Utilizzare i primary constructor di C# per inizializzare le proprietà
Eseguire operazioni sui blob con Azure Storage Actions
Gestione degli stili CSS con le regole @layer
Usare un KeyedService di default in ASP.NET Core 8
Creare gruppi di client per Event Grid MQTT
Usare i servizi di Azure OpenAI e ChatGPT in ASP.NET Core con Semantic Kernel
Miglioramenti agli screen reader e al contrasto in Angular
Popolare una classe a partire dal testo, con Semantic Kernel e ASP.NET Core Web API