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
Utilizzare politiche di resiliency con Azure Container App
Utilizzare domini personalizzati gestiti automaticamente con Azure Container Apps
Evitare il flickering dei componenti nel prerender di Blazor 8
Registrare servizi multipli tramite chiavi in ASP.NET Core 8
Disabilitare automaticamente un workflow di GitHub
Determinare lo stato di un pod in Kubernetes
Short-circuiting della Pipeline in ASP.NET Core
Eseguire una query su SQL Azure tramite un workflow di GitHub
Filtrare e rimuovere gli elementi dalla cache del browser tramite le API JavaScript
Cache policy su route groups di Minimal API in ASP.NET Core 7
Utilizzare i nuovi piani dedicati di Azure Container Apps
Utilizzare ChatGPT con Azure OpenAI