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
Load test di ASP.NET Core con k6
Effettuare chiamate con versioning da Blazor ad ASP.NET Core
Hosting di componenti WebAssembly in un'applicazione Blazor static
Eseguire una GroupBy per entity in Entity Framework
Personalizzare l'errore del rate limiting middleware in ASP.NET Core
Eseguire query manipolando le liste contenute in un oggetto mappato verso una colonna JSON
Code scanning e advanced security con Azure DevOps
Creare un webhook in Azure DevOps
Utilizzare i primary constructor di C# per inizializzare le proprietà
Come migrare da una form non tipizzata a una form tipizzata in Angular
Sviluppare un'interfaccia utente in React con Tailwind CSS e Preline UI
Limitare le richieste lato server con l'interactive routing di Blazor 8
I più letti di oggi
- Utilizzare Docker Compose con Azure App Service
- Utilizzare QuickGrid di Blazor con Entity Framework
- Modernizzare le applicazioni WPF e Windows Forms con Blazor
- ASP 3 per esempi
- annunciato #netstandard 2.1. .NET Core lo supporterà a partire da #netcore3, così come le prossime versione di #xamarin, #mono e #unity.il supporto per #netfx 4.8, invece, non ci sarà. https://aspit.co/bq2