Aspnetcore: Composants basés sur des modèles

Créé le 27 mars 2018  ·  60Commentaires  ·  Source: dotnet/aspnetcore

Par exemple, vous devriez pouvoir créer un composant Grid ou ListView qui se lie à chaque élément d'une collection à l'aide d'un modèle de définition.

area-blazor

Commentaire le plus utile

Mon point de vue personnel est que Razor devrait s'en tenir le plus possible aux idiomes HTML, plutôt que de dériver vers les idiomes que nous voyons dans XAML. Cela signifierait ressembler davantage à la hiérarchie des éléments que vous voyez dans <table> plutôt qu'à des éléments XAML typiques. Nous pourrions être assez génériques pour prendre en charge l'un ou l'autre, mais je suis toujours très fermement dans le camp de "Razor is HTML" en ce moment.

Tous les 60 commentaires

Plus précisément, cela signifie que nous avons besoin d'un moyen de signaler qu'une partie donnée du contenu enfant d'un composant doit être compilée en tant que Action<RenderTreeBuilder, T> , pour certains types T que nous devons pouvoir résoudre dynamiquement à partir de l'utilisation à la conception temps. Ensuite, notre codegen au moment de la conception sera en mesure de produire le contexte intellisense correct lors de l'écriture d'expressions Razor à l'intérieur de cet élément enfant.

Exemple:

<MyListWithHeader RowItems=@People>
    <Title>
        Here I'm still in the outer context so can reference properties on the component itself 
        such as <strong i="8">@TitleText</strong>
    </Title>
    <RowTemplate> <!-- Somehow need intellisense to know that RowTemplate is meaningful here, and that its "item" value is taken from the generic type arg on the RowItems property -->
        Here I'm in the context of an Action<RenderTreeBuilder, Person> because we supplied
        an IEnumerable<Person> as the value for RowItems, so I can write @item.FirstName
    </RowTemplate>
</MyListWithHeader>

<strong i="9">@functions</strong>
{
    string TitleText { get; set; }
    IEnumerable<Person> People { get; set; }

    class Person
    {
        public string FirstName { get; set; }
    }
}

Je ne sais pas à quel point nous voulons généraliser cette fonctionnalité. Peut-être moins général qu'indiqué dans le croquis ci-dessus. @rynowak serait l'expert de ce qu'il est raisonnable de mettre en œuvre ici. Toute la complexité est au moment de la conception/compilation. Il ne nécessite aucun support d'exécution auquel je puisse penser.

Salut, j'ai essayé d'accomplir cela en passant moi-même un IEnumerableen tant que paramètre, j'ai pu produire une grille mais je n'ai pas pu la mettre à jour lorsque le paramètre change. C'est le code :

<table class="@ClassName">
  <tr>
    <strong i="7">@foreach</strong> (var col in Columns)
    {
      <th>@col</th>
    }
  </tr>

  <strong i="8">@foreach</strong> (System.Data.DataRow row in table.Rows)
  {
    <tr>
      <strong i="9">@foreach</strong> (var col in Columns)
      {
        <td>@row[col]</td>
      }
      <strong i="10">@if</strong> (Select != null)
      {
        <td><a @onclick(() => Select(row))>Seleccionar</a></td>
      }
    </tr>
  }
</table>

@functions{
    public string ClassName { get; set; }
    public List<string> Columns { get; set; }
    public object DataSource { get; set; }
    private System.Data.DataTable table;


    private string Log;

    //call backs it seens I will have many of them

    public Action Change { get; set; }
    public Action<object> Select { get; set; }

    protected void UpdateState()
    {
      int count = ((IEnumerable<object>)DataSource).Count();
      Log = $"Table count rows:{table.Rows.Count} count object ${count}";
      StateHasChanged();
    }

    protected override void OnInit()
    {
      base.OnInit();

      Change += StateHasChanged;

      var enumerable = (IEnumerable<object>)DataSource;
      table = new System.Data.DataTable();

      if (Columns == null)
      {
        Columns = new List<string>();
        Columns.AddRange(enumerable.FirstOrDefault().GetType().GetProperties().Select(x => x.Name).ToList());
      }


      Columns.ForEach(x =>
      {
        table.Columns.Add(x);
      });

      enumerable.ToList()
                .ForEach(x =>
                {
                  var row = table.NewRow();

                  foreach (var property in x.GetType().GetProperties())
                  {
                    row[property.Name] = property.GetValue(x);
                  }

                  table.Rows.Add(row);
                });

      StateHasChanged();
    }
}

@SteveSandersonMS , est-ce que Title et RowTemplate dans votre exemple sont censés être des propriétés de MyListWithHeader ou des composants spéciaux ? S'il s'agit de propriétés, cela pourrait valoir la peine de réfléchir à d'autres moyens d'exprimer cela...

Je travaille beaucoup avec XAML et lorsque j'ai commencé à faire du prototypage avec Blazor, j'ai rapidement ressenti le besoin de cette fonctionnalité. Pensez-vous que la syntaxe des éléments de propriété de XAML pourrait être raisonnable dans ce scénario ? Bien qu'assez verbeux, il exprime clairement que nous attribuons des propriétés ici au lieu d'ajouter des composants enfants à MyListWithHeader . Exemple:

<MyListWithHeader>
    <MyListWithHeader.Title>
       ...
    </MyListWithHeader.Title>
    <MyListWithHeader.RowTemplate>
       ...
    </MyListWithHeader.RowTemplate>
</MyListWithHeader>

Mon point de vue personnel est que Razor devrait s'en tenir le plus possible aux idiomes HTML, plutôt que de dériver vers les idiomes que nous voyons dans XAML. Cela signifierait ressembler davantage à la hiérarchie des éléments que vous voyez dans <table> plutôt qu'à des éléments XAML typiques. Nous pourrions être assez génériques pour prendre en charge l'un ou l'autre, mais je suis toujours très fermement dans le camp de "Razor is HTML" en ce moment.

totalement à l'appui de cela. si on pouvait faire la liste d'item "ObservableCollection" plutôt qu'un "IEnumerable" ce serait un bonus. passer à xaml comme pattrens nous donne un système de modèles approprié, ce qui manque au html, tous les frameworks populaires dans l'interface utilisateur Web ont un système de modèles, ce qui les rend très extensibles, évidemment blazor devrait en avoir un aussi, et au lieu d'en faire un nouveau ce serait génial d'en avoir un qui est déjà aimé par MS et de nombreux développeurs .Net, techniquement, il sera toujours basé sur html/css donc tous les développeurs html seront toujours attirés par la technologie car ce ne sera pas xaml (je ne sais pas pourquoi html guyx déteste xaml ) mais il ne suivra que les modèles xaml.

@DamianEdwards Je suis entièrement d'accord avec vous concernant "Razor is HTML". Cependant, les valeurs d'attribut en HTML sont toujours spécifiées en tant qu'attributs dans la balise d'ouverture. Il n'y a aucun moyen (et aucun besoin d') exprimer des valeurs d'attributs complexes comme des modèles en tant qu'éléments enfants. Par exemple, vous ne pouvez pas écrire

<a>
    <href>http://dot.net</href>
</a>

Je pense donc que ce que nous visons techniquement ne peut tout simplement pas être exprimé en HTML pur.
Voici un résumé des options que je vois actuellement :

  1. Tenez-vous en à la syntaxe HTML : les valeurs de propriété ne peuvent être spécifiées que dans la balise d'ouverture d'un composant.

    • Je pense que c'est ce que React a choisi, par exemple :
      <MyListWithHeader RowTemplate={(data) => <p>...</p>} />
    • Pro : Très proche de la syntaxe HTML normale
    • Inconvénient : Devient désordonné pour les valeurs multilignes, c'est-à-dire des modèles plus complexes
  2. Tenez-vous en à la syntaxe HTML : selon le contexte, les nœuds enfants d'un composant peuvent représenter des éléments imbriqués ou des affectations de propriétés.

    • Pro : Très proche de la syntaxe HTML normale
    • Inconvénient : peut être déroutant car les affectations de propriétés et les éléments enfants sont syntaxiquement indiscernables. Dans l'exemple, <Title>...</Title> pourrait être un composant autonome tandis que <RowTemplate>...</RowTemplate> pourrait être une propriété (un attribut) du <MyListWithHeader> environnant - sans IntelliSense, vous ne savez pas vraiment
    • Inconvénient : Peut provoquer des conflits de nom s'il y a un composant et une propriété avec le même nom
  3. Inventez une nouvelle syntaxe : la syntaxe des éléments de propriété de XAML n'est qu'une option, cela pourrait tout aussi bien être une construction Razor similaire à <strong i="39">@functions</strong> { } ou quelque chose de totalement différent.

    • Pro : Permet des modèles arbitrairement complexes tout en restant lisibles
    • Con: Pourraient web devs aliéner

Il est tout à fait possible que j'aie mal compris quelque chose ici. Si oui, merci de me le faire savoir.

@SvenEV Je ne pense pas que la syntaxe xaml aliène qui que ce soit, si vous remarquez, la syntaxe du modèle pour angular, vue, react et des millions d'autres frameworks, toutes ont une manière et une syntaxe différentes de faire des choses, mais elles sont toutes aimées, NON? de même, blazor peut avoir sa propre syntaxe, le seul avantage dans son cas sera que nous n'inventerons pas une nouvelle syntaxe en apportant simplement une syntaxe déjà populaire à ce framework (d'où moins de courbe d'apprentissage pour les développeurs xaml) mais en ce qui concerne les développeurs Web, ils apprendra de toute façon les modèles de blazor, que blazor suive xaml ou non, la courbe d'apprentissage pour les développeurs Web reste la même, la seule façon pour les développeurs Web de rester à l'écart de cet inconvénient est si blazor utilise directement le html, qui, je ne pense pas, sera aussi extensible qu'un modèle de modèle. :)

ainsi, une toute nouvelle syntaxe de modèle donne une courbe d'apprentissage à chaque développeur. mais une syntaxe de type xaml résout au moins le problème de la moitié des développeurs. mais xaml ressemble aussi beaucoup au html, les deux sont après tout du balisage. une fois que vous l'aurez appris, ce sera un jeu d'enfant, une bonne chose est que la méthode xaml est que vous n'avez pas à vous soucier de css du tout, tout le style est fait avec des propriétés directes sur les commandes. et même le style comple également fait avec la même syntaxe, mais d'un autre côté, html et css sont des langages totalement différents avec une syntaxe et une structure différentes pour faire les choses.

Mais pourquoi avons-nous même besoin de modèles ? Je veux dire quand vous pouvez faire foreach juste là dans Razor. Cela me semble beaucoup plus facile que de créer des modèles.
Ou est-ce à cause de la réutilisation ? Je peux imaginer un composant séparé qui fait foreach dans ce cas.

Les modèles

Eh bien, je ne sais pas à ce sujet. IMO foreach est un modèle, juste un modèle plus puissant et vous pouvez placer un modèle "foreach" dans un composant différent et l'appeler de n'importe où avec ces deux lignes de code.

foreach est juste une boucle et oui pour un contrôle plus simple, nous pouvons le faire, comme pour un ListView de base ( table ), nous pouvons simplement créer un contrôle qui prend une entrée, fait le foreach dessus et crache une table, puis nous avons juste besoin mettre une étiquettequelque chose comme ça et ensuite nous pourrons produire une table, aussi si nous voulons une liste personnalisée, avec un design très moderne, hautement interactif, etc. de code blazor dans un fichier, peut-être plusieurs fichiers, et enveloppez tout cela dans un composant, puis utilisez à nouveau tout cela avec une seule balise. vous voyez où je veux en venir ? peu importe la quantité de code d'un contrôle, tout cela peut être encapsulé dans 1 balise :)

Il ne s'agit pas seulement de rendre plusieurs éléments avec le même modèle. Il est également nécessaire de personnaliser plusieurs parties d'un composant. Actuellement, un composant ne peut avoir qu'un seul "trou" dans sa disposition qui peut être rempli avec ChildContent , une propriété prédéfinie, comme ceci :

<div style="some fancy styling">
    <strong i="7">@ChildContent</strong>
</div>

Imaginez maintenant que vous vouliez écrire un composant avec plusieurs de ces "trous" comme une boîte de dialogue qui vous permet de personnaliser le contenu ainsi que l'en-tête, ou un bouton où vous pouvez personnaliser le contenu interne mais également spécifier des modèles pour le menu contextuel et l'info-bulle peut être. Donc ce serait bien si vous pouviez écrire quelque chose comme ce qui suit (quelle que soit la syntaxe alors) :

<MyButton>
    <h2>I am the button caption (the usual ChildContent)</h2>

    <MyButton.ContextMenu>
        <button>Context menu button 1</button>
        <button>Context menu button 2</button>
    </MyButton.ContextMenu>

    <MyButton.ToolTip>
        This explains <strong>what the button does</strong>. It is displayed when hovering over the button.
    </MyButton.ToolTip>
</MyButton>

@SvenEV N'est-ce pas simplement avoir plusieurs trous et ne pas nécessiter strictement un modèle?

oui, c'est un bon exemple d'un autre cas d'utilisation, chaque partie doit être personnalisable.

@MihaMarkic C'est vrai, on parle ici de deux choses différentes :

  1. Prise en charge de plusieurs trous ( RenderFragment s / Action<RenderTreeBuilder> ) comme dans mon exemple
  2. Prise en charge des modèles qui ont une entrée de données ( Action<RenderTreeBuilder, T> ) comme dans ListView etc.

Je pense que la première est une exigence pour la seconde et j'espère que les deux fonctionnalités seront implémentées à un moment donné. Cela nous permettrait de construire de puissantes bibliothèques de composants.

Quel problème cela essaie-t-il de résoudre ? Je me demande juste puisque je n'ai pas vu ce genre de chose dans d'autres frameworks SPA.

Cela entre-t-il en conflit avec une structure basée sur des composants ? En outre, cela va-t-il à l'encontre des pratiques normales de développement Web simplement pour forcer une structure plus « semblable à une application » ?

@AWulkan, cela ne va pas à l'encontre de la structure normale des composants, mais oui, cela va légèrement vers une structure de type app-dev, mais cela ne signifie pas que cela s'éloigne du développement Web, car tous les frameworks populaires font également la même chose, tout l'objectif du concept de SPA était-ce. SPA a été créé pour que le développement Web ressemble davantage au développement d'applications. et maintenant chaque cadre suit le spa. la prochaine étape du spa est maintenant pwa. tu sais ce qu'est un pwa ? il introduit certaines fonctionnalités majeures d'applications natives telles que les notifications et la synchronisation hors ligne, de sorte que le développeur Web dans son ensemble essaie de suivre le développement d'applications natives.

@AWulkan Puisque vous semblez être familier avec Vue, je viens de regarder dans la documentation de Vue pour voir s'il y a quelque chose de similaire. Il y a et ça s'appelle " Slots ", en particulier " Named Slots " et " Scoped Slots " - c'est exactement de quoi traite ce problème. C'est le même concept. Peut-être que cela vous aide à comprendre le problème?

@SvenEV Okey, je vois un peu ce que vous voulez dire alors. C'est une fonctionnalité assez niche en ce qui me concerne. Merci de clarifier.

Utilisera-t-il ce brouillon comme modèle?
https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md

Il semble que les emplacements Vue.js soient similaires aux modèles de données XAML.
Les modèles offrent un moyen puissant de personnaliser les composants existants, un cas que j'ai pu voir est un ListView où, par défaut, sans modèles, il parcourt simplement une liste de chaînes et l'affiche.

<ListView list=<strong i="7">@ListOfUrlsAsStrings</strong> />

Avec les modèles, vous pouvez remplacer cela pour dire, afficher à la place une liste de balises d'image en utilisant le Type T passé.

<ListView list=@ListOfUrlsAsStrings>
    <ListView.ListTemplate>
         <img src=<strong i="12">@item</strong> alt="overriden by a template" />
        <CustomComponentHere />
    </ListView.ListTemplate>
</ListView>

Cela augmenterait considérablement la réutilisation des composants car ils peuvent être dynamiques. Les fournisseurs de composants tiers peuvent être plus créatifs sur la création de composants de cette façon.

oui exactement avec la liaison de modèle de données devient plus simple et observable

Nous espérons vivement que les composants Blazor bénéficieront d'un bon support pour l'imbrication et la création de modèles.
Auparavant, nous avons eu un certain nombre de discussions avec @Mehul , @NTaylorMullen , @DamianEdwards sur le même sujet pour Razor Tag Helpers dans lesquelles nous avons essayé d'expliquer pourquoi nous avons besoin d'une approche plus proche du XAML. Problèmes GitHub pertinents : aspnet/Razor#474, ​​aspnet/Razor#576, aspnet/Razor#570.

Nous développons des composants d'interface utilisateur tels que des grilles de données et le balisage typique pour configurer un widget peut être aussi délicat que celui-ci :

<TabPanel>
    <Grid>
        <DataSource>
            <More about data source />
        </DataSource>

        <Column />
        <Column>
            <Lookup>
                <DataSource>
            . . .
            <CellTemplate>
                arbitrary html
            </CellTemplate>
        </Column>

        <DetailTemplate>
            <div>arbitrary content</div>
            <ThirdPartyComponent />

            <Grid>
                detail grid configuration with own templates (and there may be more levels)
            </Grid>
        </DetailTemplate>
    </Grid>
</TabPanel>

Pour une telle hiérarchie, un développeur souhaiterait avoir une connaissance du contexte et un sens précis.

@AlekseyMartynov pouvons-nous aussi avoir du style xaml + des animations à long terme ?

Je suis tellement content que cette idée fasse son chemin. Depuis que MS a lancé l'idée de commandes modifiables avec Silverlight, WPF et UWP, il est logique d'en faire une partie de Blazor.

Une question serait les contextes de données/code. Lorsque le modèle cshtml référence du code dans l'objet C# lié, il ne sera probablement pas dans le même contexte que le composant plus large. Une sorte de liaison de données dynamique ne serait-elle donc pas nécessaire ? Ou un modèle aurait-il une exigence TargetType stricte afin que l'analyseur sache comment résoudre les références aux membres d'objets liés aux données ?

Sur cette note, qu'en est-il des propriétés de liaison et de dépendance de données dynamiques ? Autant que je sache, rien n'empêcherait cela pour le moment. Ou la réflexion va-t-elle être problématique si le C# ou l'IL finit par être compilé en wasm plutôt qu'interprété par mono ?

La mise en œuvre de ce que suggère @AlekseyMartynov (ou une variante de quelque sorte) est à mon avis critique pour le succès de Blazor. En pratique, de nombreux développeurs écrivant des applications d'entreprise dépendent d'un riche ensemble de composants tiers faciles à utiliser et j'espère que ce projet arrivera très bientôt au moment où différents fournisseurs pourront commencer à développer des composants Blazor.

Dans mon projet Xamzor, j'ai implémenté une prise en charge limitée des modèles comme celle-ci :

<ListView ItemTemplate="typeof(MyCustomTemplate)" ... />

MyCustomTemplate est un autre composant Blazor avec une propriété "DataContext" qui est attribuée par ListView. Bien que cela fonctionne assez bien, il est toujours souhaitable de définir des modèles en ligne plutôt que dans un composant séparé, surtout si les modèles sont petits et simples.

Peut-être que les composants contextuels ember.js peuvent fournir une certaine inspiration : https://www.mutuallyhuman.com/blog/2016/09/23/creating-an-accordion-with-contextual-components-in-ember

@SvenEV Je suis d'accord que dans le cas de modèles simples, les modèles en ligne seraient utiles, mais nous devrions toujours avoir une option pour des modèles de composants séparés comme vous l'avez implémenté dans votre projet, car cela nous aide à séparer les préoccupations et à réutiliser le modèle :)

Trois choses dans ma tête :

  1. Rendre les composants nativement hiérarchiques (ex. propriété ParentComponentType ; afin que vous puissiez appliquer MyTableComponent -> MyRowComponent -> MyColumnComponent). Si ParentComponentType est null, le composant peut être un enfant de n'importe quel type de composant (ex. MyTableComponent.ParentComponentType peut être null).

La hiérarchie est appliquée via des déclarations dans les définitions des composants telles que :

```C#
// Dans MyRowComponent
Tapez ParentComponentType { get; } = typeof(MyTableComponent);
...

// In MyColumnComponent
Type ParentComponentType {get; } = typeof(MyRowComponent); 
...
Thus, MyColumnComponent can only be created within a MyRowComponent, and MyRowComponent can only be created within a MyTableComponent.

2. Allow components to be extended via ITemplateable interface. When the ITemplateable.Template [Tag] property is valued, it overrides the component's default rendering. Internally, it's something like:

```C#
    if (component is ITemplateable)
    {
        if (component.Template != null)
        {
            … return template rendering ...
        }
    }
    … return default rendering ...
  1. Créez une interface ITemplate qui peut être implémentée en tant que classe de base facultative BlazorTemplate, qui peut être utilisée pour objectiver la programmation avec des modèles.

Exemple d'utilisation où MyColumnComponent est ITemplateable :

    <MyTableComponent>  
        <MyRowComponent>  
            <MyColumnComponent>  
                <Template>  
                    …  template codes here; can contain a call to another component ...
                </Template>  
            </MyColumnComponent>  
        </MyRowComponent>  
    </MyTableComponent>  

Avec MyColumnComponent défini comme ITemplateable, il prend en charge le fait de contenir une balise <Template> … </Template> . Les entrées de cette balise remplacent le rendu par défaut de MyColumnComponent.

En interne, MyColumnComponent peut avoir/utiliser une implémentation BlazorTemplate pour lui permettre de gérer par programme des comportements, des événements et des fonctionnalités de modèle spéciaux.

Je vote pour la syntaxe des éléments de propriété

<Button>
  <Button.ContextMenu>
    <ContextMenu>
      <MenuItem Header="1">First item</MenuItem>
      <MenuItem Header="2">Second item</MenuItem>
    </ContextMenu>
  </Button.ContextMenu>

  Right-click me!
</Button>

En alternative à

<Button ContextMenu=@GetContextMenuRenderFragment()>
  Right-click me!
</Button>

J'aime ça, mais les puristes du Web ne diraient-ils pas que cela ressemble trop à XAML ?

Non, ils iront bien, property element syntax est une alternative à html attribute syntax (pas de remplacement). Mais je quitte le nombre de possibilités qu'il offre aux développeurs de composants (l'interface utilisateur contrôle les développeurs de bibliothèques)

Je l'aime aussi car il indique plus clairement comment l'enfant se rapporte au parent, c'est particulièrement bon sur Templating car vous pourrez distinguer qu'il s'agit d'un modèle de données d'un parent plutôt que d'un composant personnalisé qui n'a aucun rapport avec le composant parent.

Comme ça:

<List>
    <List.DataTemplate>
      <!-- Template Here -->
    </List.DataTemplate>
    <ComponentWithNoRelationToTheList />
</List>

Cela vous donne une idée que DataTemplate est lié à List plutôt qu'à un modèle personnalisé dans son ensemble. Bien que je sois d'accord comme XAML, cela devrait être juste une alternative.

Et si "Modèle" était un mot-clé réservé/spécial pour tout composant Blazor modifiable ? Lorsqu'il est utilisé comme une balise, il peut contenir les codes du modèle. Lorsqu'il est utilisé comme attribut, il peut spécifier un modèle nommé, qui peut contenir les codes de modèle.

alors, imaginons un composant comme :

ComponentTemplateTest.cshtml

<strong i="7">@functions</strong>
{
  [Parameter]
  public RenderFragment Template { get; set; }

  [Parameter]
  public RenderFragment<int> TemplateWithInt { get; set; }

  [Parameter]
  public RenderFragment<TestObject> TemplateWithObject { get; set; }

  public class TestObject
  {
      public string PropString { get; set; }
      public int PropInt { get; set; }
  }

  private TestObject testobject = new TestObject();
}

<div>
    <div><button onclick="@((x) => this.testobject.PropInt++)">test</button></div>
    <div>@Template</div>
    <div>@((RenderFragment)(x => TemplateWithInt(x, testobject.PropInt)))</div>
    <div>@((RenderFragment)(x => TemplateWithObject(x, testobject)))</div>
</div>

nous pouvons l'utiliser comme

<ComponentTemplateTest>
    <Template>
        test simple template
    </Template>

    <TemplateWithInt params="param1">
        <strong i="11">@param1</strong>
    </TemplateWithInt>

    <TemplateWithObject params="paramObject">
        @paramObject.PropInt
    </TemplateWithObject>
</ComponentTemplateTest>

nous avons besoin d'un attribut comme params parce que Blazor écrit le code comme

            builder.OpenComponent<StandaloneApp.ComponentTemplateTest>(16);
            builder.AddAttribute(17, "template", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
                builder2.AddContent(18, "\n        test simple template\n    ");
            }
            ));
            builder.AddAttribute(19, "templatewithint", (Microsoft.AspNetCore.Blazor.RenderFragment<System.Int32>)((builder2, param1) => {
                builder2.AddContent(20, param1);
            }
            ));
            builder.AddAttribute(21, "templatewithobject", (Microsoft.AspNetCore.Blazor.RenderFragment<StandaloneApp.ComponentTemplateTest.TestObject>)((builder2, paramObject) => {
                builder2.AddContent(22, "\n        ");
                builder2.AddContent(23, paramObject.PropInt);
                builder2.AddContent(24, "\n    ");
            }
            ));
            builder.CloseComponent();

ça suffirait ? quelque chose manque?

@uazo Notez simplement que sans la syntaxe des éléments de propriété, il est impossible de faire la distinction entre property et component .

@danieldegtyarev Je pense que nous parlons ici de deux choses différentes... mon objectif est uniquement un modèle pour les composants pour l'instant...

jetez un oeil à https://github.com/uazo/Blazor/tree/dev-experimental-template-component
vous devez construire et installer cette extension ...

« Template » utilisé comme balise est déclaratif et naturellement en ligne :

<MyCom MyComProp="MyComPropValue">
    <Template>
        Components, @MyCom.MyComProp, HTML, content and <strong i="6">@values</strong>
    </Template>
</MyCom>

"Modèle" utilisé comme balise pour attribuer un "modèle nommé". En interne, le modèle nommé aura accès à son composant parent, de sorte que quelque chose comme MyCom.MyComProp est également possible :

<MyCom MyComProp="MyComPropValue">
    <Template Name="MyTemplate {Content: @content; Values: @values}" />
</MyCom>

"Modèle" utilisé comme attribut pour attribuer un "modèle nommé". En interne, le modèle nommé aura accès à son composant parent, de sorte que quelque chose comme MyCom.MyComProp est également possible :

<MyCom 
    MyComProp="MyComPropValue"
    Template="MyTemplate {Content: @content; Values: @values}" />

Le "Modèle" utilisé soit comme balise, soit comme attribut devrait aboutir à la même compilation, de sorte que le composant rendrait les informations de modèle fournies, qui remplacent simplement le rendu par défaut du composant.

Un "modèle nommé" est un modèle réutilisable. Créer un "modèle nommé" revient presque à créer un composant. Cependant, la grande différence est qu'un modèle nommé ne peut être utilisé que via la prise en charge d'un composant pour "Modèle".

Un modèle peut contenir des composants, mais des contrôles doivent être effectués pour éviter les boucles infinies potentielles lorsque, par exemple, un composant qui utilise un modèle qui utilise le même composant est codé... erreur de conception/compilation peut-être ?

Questions : Un modèle nommé peut-il hériter d'un autre modèle ? Un modèle nommé peut-il être limité à un ou plusieurs types de composants spécifiques ?

Je pense que vous devriez privilégier la syntaxe HTML autant que possible, ne pas introduire de nouveautés. Pas de syntaxe Xamarin xaml ou de composants de formulaires Web asp.net laids (répéteur asp et ainsi de suite).
Je viens du monde asp.net mvc, je me souviens de la joie et de la liberté lorsque je suis passé de "asp.net webforms" à "asp.net mvc", plus de composants étranges comme asp:repeater et ainsi de suite.
S'il vous plaît, ne laissez pas BLAZOR devenir un nouveau "formulaires Web asp.net".

Je suis d'accord avec @GoranHalvarsson . La création de modèles dans Blazor ne devrait être que l'implémentation de RenderFragment générique, avec le même cas d'utilisation (premier exemple dans @etmendz post) .

Pour cela, j'ai mis à jour ma branche, maintenant avec un support complet pour intellisense dans visual studio (paramètres et enfant autorisé).

quelqu'un peut-il me donner un avis?

@GoranHalvarsson Je suis d'accord. Le MVP peut permettre aux composants Blazor de prendre en charge les modèles de base. En ce sens, un "Modèle" peut contenir du HTML, des composants, du contenu et des codes Razor. À la base, les composants + modèles Blazor doivent toujours être des codes HTML + Razor reconnaissables. La syntaxe déclarative correspond à ce projet de loi, qui peut être @template mot-clé <Template> ou @template , par exemple.

Un élément de la phase suivante peut être les bibliothèques d'API/d'extensibilité de Blazor qui permettent aux créateurs de composants de promouvoir leur propre marque - ex. : syntaxe de type helper HTML (Progress Telerik/Kendo UI) ou syntaxe de type Xamarin/XAML (ooui.wasm, Platform .Uno) peuvent à la place être des livraisons au niveau des tiers/fournisseurs. Dans Blazor, la programmabilité des modèles et la flexibilité de la compilation peuvent être travaillées en étroite collaboration avec les partenaires créateurs de composants pour répondre à des problèmes/besoins/exigences spécifiques.

Il y a une suggestion dans ce fil sur les modèles réutilisables. Les modèles réutilisables ou « modèles nommés » peuvent favoriser la réutilisation du code. Bien que, honnêtement, je n'y vois pas beaucoup de valeur et peut être une faible priorité. Plus probablement, s'il est destiné à être réutilisé, alors peut-être devrait-il être un composant ? ;-)

@uazo À mon humble avis , sur la base de vos exemples, passer des paramètres à <Template> semble vaincre la nature déclarative de la simple utilisation du paramètre/de la valeur directement à l'intérieur de la balise <Template> .

J'ai également des doutes quant à la possibilité pour les composants de prendre en charge plusieurs propriétés de modèle. En ce sens, le rendant trop freestyle. Je pense que Blazor devrait avoir un moyen plus simple et plus standard de prendre en charge les modèles de composants, peut-être via l'interface ITemplate et quelques ajustements du compilateur ici et là.

@SteveSandersonMS pour https://github.com/aspnet/Blazor/issues/404#issuecomment-377229910
avec le code aspnet/AspNetCore#15829 sera

MaListeAvecEn-tête.cshtml

<strong i="10">@functions</strong>
{
    [Parameter]
    public RenderFragment Title { get; set; }

// NOT MANDATORY
    [Parameter]
    public RenderFragment ChildContent { get; set; }
// *******************

    [Parameter]
    public IEnumerable<example.Person> RowItems { get; set; }

    [Parameter]
    public RenderFragment<example.Person> RowTemplate { get; set; }
}

<div>
    <strong i="11">@Title</strong>
</div>

<strong i="12">@foreach</strong> (var item in RowItems)
{
    <div>
    @RowTemplate.Render(item)
    </div>
}

<strong i="13">@ChildContent</strong>

exemple.cshtml

<strong i="17">@page</strong> "/component-test"

<MyListWithHeader RowItems=@People>
    <MyListWithHeader.Title>
        Here I'm still in the outer context so can reference properties on the component itself
        such as <strong i="18">@TitleText</strong>
    </MyListWithHeader.Title>

    <MyListWithHeader.RowTemplate WithParams="item">
        <!-- Somehow need intellisense to know that RowTemplate is meaningful here, and that its "item" value is taken from the generic type arg on the RowItems property -->
        Here I'm in the context of an Action RenderTreeBuilder, Person
        because we supplied
        an IEnumerable Person as the value for RowItems, so I can write <b>@item.FirstName</b>
    </MyListWithHeader.RowTemplate>
</MyListWithHeader>

<strong i="19">@functions</strong>
{
  string TitleText { get; set; }
  IEnumerable<Person> People { get; set; }

  protected override void OnInit()
  {
      var people = new List<Person>();
      people.Add(new Person { FirstName = "uno" });
      people.Add(new Person { FirstName = "due" });
      people.Add(new Person { FirstName = "tre" });

      this.People = people;
  }

  public class Person
  {
      public string FirstName { get; set; }
  }
}

avec un support complet pour intellisense !

@uazo Très très bien ! Il n'y a qu'une seule chose : supprimons WithParams="item" et utilisons simplement @item pour accéder au 'contexte' du modèle

euh .. mais si item existait déjà dans le composant example ?
Étant donné que le nom de la variable utilisé pour accéder au « contexte » du modèle ne peut pas être découvert, donc avec WithParams , les développeurs peuvent décider du nom de la variable.

pensez-vous à un nom câblé par défaut (comme ChildContent ), avec ou sans WithParams option ? Ou une syntaxe comme <MyListWithHeader.RowTemplate WithParams=@item> ?

Avec le recul, un nom par défaut ne conviendrait pas pour les modèles imbriqués...

Réfléchissons. Le modèle est la génération de code avec un paramètre. Le modèle n'a accès qu'à cet élément. Donc, si nous 'réservons' une variable item - elle est sûre et ne peut pas entrer en conflit. Les modèles imbriqués ont leur propre portée et l'utilisation de @item est toujours sûre.

BTW, ressemble à ce modèle - scope doivent également avoir leur bloc @functions

ayons

<MyListWithHeader RowItems=@People>
    <MyListWithHeader.Title>
        ...<strong i="6">@TitleText</strong>
    </MyListWithHeader.Title>

    <MyListWithHeader.RowTemplate WithParams="item">
        ... <b>@item.FirstName</b>

        <MyListWithHeader>
            <MyListWithHeader.RowTemplate WithParams="item">
                @item.FirstName
            </MyListWithHeader.RowTemplate>
        </MyListWithHeader>

    </MyListWithHeader.RowTemplate>
</MyListWithHeader>

sera rendu comme

protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
{
    base.BuildRenderTree(builder);
    builder.OpenComponent<StandaloneApp.Pages.testtemplated.MyListWithHeader>(0);
    builder.AddAttribute(1, "RowItems", Microsoft.AspNetCore.Blazor.Components.RuntimeHelpers.TypeCheck<System.Collections.Generic.IEnumerable<StandaloneApp.Pages.testtemplated.example.Person>>(People));
    builder.AddAttribute(2, "Title", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
        builder2.AddContent(3, "\n        Here I\'m still in the outer context so can reference properties on the component itself\n        such as ");
        builder2.AddContent(4, TitleText);
        builder2.AddContent(5, "\n    ");
    }
    ));
    builder.AddAttribute(6, "RowTemplate", (Microsoft.AspNetCore.Blazor.RenderFragment<StandaloneApp.Pages.testtemplated.example.Person>)((builder2, item) => {
        builder2.AddContent(7, "\n        ");
        builder2.AddContent(8, "\n        Here I\'m in the context of an Action RenderTreeBuilder, Person\n        because we supplied\n        an IEnumerable Person as the value for RowItems, so I can write ");
        builder2.OpenElement(9, "b");
        builder2.AddContent(10, item.FirstName);
        builder2.CloseElement();
        builder2.AddContent(11, "\n\n        ");
        builder2.OpenComponent<StandaloneApp.Pages.testtemplated.MyListWithHeader>(12);
// line 43
        builder2.AddAttribute(13, "RowTemplate", (Microsoft.AspNetCore.Blazor.RenderFragment<StandaloneApp.Pages.testtemplated.example.Person>)((builder3, item) => {
            builder3.AddContent(14, "\n                ");
            builder3.AddContent(15, item.FirstName);
            builder3.AddContent(16, "\n            ");
        }
        ));
        builder2.CloseComponent();
        builder2.AddContent(17, "\n\n    ");
    }
    ));
    builder.CloseComponent();
}

avec

Error   CS0136  A local or parameter named 'item' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter

StandaloneApp\obj\Debug\netstandard2.0\Pages\testtemplated\example.g.cs line 43

Oui, bien sûr, vous devez le résoudre pendant le codegen.
Mais... Je pense que le modèle doit être compilé dans un component (éventuellement un composant spécial) plutôt que RenderFragment car nous devons gérer avec son état, par exemple pour invoquer StateHasChanged() quand @item.FirstName mute.

Oui, bien sûr, vous devez le résoudre pendant le codegen.

Je ne pense pas que ce soit possible.
Pendant le codegen (c'est rasoir modifié) tous les nœuds sont croisés, certains sont en html, d'autres sont en nœud c# : ceux avec c# auraient tort car les variables ne seraient pas définies et donc le type de variable ne serait pas connu (et il faut récupérer une variable de type T de RenderFragment<T> , car on connait T ) ... donc je ne sais pas quel est le nom de la variable à utiliser.
Vous pourriez probablement rechercher cette variable non définie, mais cela semble un gâchis

Mais... Je pense que le modèle doit être compilé dans un composant (éventuellement un composant spécial) plutôt que RenderFragment car nous devons gérer avec son état, par exemple pour invoquer StateHasChanged() [email protected] mute.

Je ne pense pas qu'il soit nécessaire d'introduire autre chose.
Si vous avez besoin d'un composant, utilisez BlazorComponent , si vous avez besoin d'une "propriété de modèle", utilisez RenderFragment<T>
Après tout, StateHasChanged() pour un RenderFragment est invoqué par le StateHasChanged() du composant sous-jacent, je n'ai pas besoin de forcer l'appel des modifications d'une autre manière

Désolé d'intervenir ici parce que cela ressemble à une bonne conversation - mais -

Cela ne serait-il pas résolu avec un concept de contexte de données comme WPF/UWP ? Ensuite, dans votre modèle, vous feriez toujours référence à this.DataContext.[Member] .

@ peter0302 J'aime personnellement cette idée...

Le défi reste d'être fortement typé. Bien sûr, cela est résolu en XAML par la liaison effectuée au moment de l'exécution. Le modèle de données ici devrait probablement prendre en charge la saisie à l'aide de génériques. Mais certainement faisable.

Là encore, je soutiendrais également un mécanisme de liaison d'exécution entièrement dynamique.

Vous pouvez opter pour une déclaration du type de données via une déclaration. Un peu similaire à x:DataType sur XAML, bien que cela devrait être plus idéal et moins sujet aux erreurs.

@uazo Mon retour est que ça a l'air très bien ! Voici mes réflexions...

1) Qu'une propriété de type RenderFragment soit assignée avec un nom qualifié d'une propriété dans une balise comme <MyListWithHeader.Title /> me convient, mais la gestion brise mon ancienne intuition que les propriétés avec ParameterAttribute sont définies via des attributs html. Que diriez-vous d'avoir un autre attribut c# pour ces propriétés afin de distinguer ce comportement ? Comme [ContentParameter] ?

2) Qu'une propriété de type RenderFragment<Person> soit assignée avec une balise de propriété avec des attributs
<MyListWithHeader.RowTemplate WithParams="item" /> me semblait un peu étrange au début. Ce n'est pas un attribut qui définit une propriété de paramètre, mais c'est le nom de variable à utiliser dans l'expression lambda lors de la génération de code local pour ce modèle. Là encore, il serait bon d'avoir une autre syntaxe pour faire la différence. Mais je ne sais pas à quel point on peut être créatif pour être conforme à l'outillage et au compilateur. <MyListWithHeader.RowTemplate params(item) /> peut-être ?

3) Si je veux afficher <MyListWithHeader RowItems=<strong i="16">@PeopleOnline</strong> /> et <MyListWithHeader RowItems=<strong i="18">@PeopleOffline</strong> /> , dois-je à nouveau spécifier les modèles ? Existe-t-il un moyen de les réutiliser ? Je pensais peut-être faire une initialisation basée sur le modèle via ce mécanisme, similaire à EditorFor avec différents sélecteurs de propriétés utilisant les mêmes modèles.

Il peut être un peu risqué de discuter de la mise en œuvre lorsque je suis nouveau dans le cadre, mais…

si vous optez pour un attribut de paramètre distinct pour l'attribution de modèles, comme indiqué au point 1 ci-dessus, vous pouvez alors lui ajouter un indicateur booléen (valeur par défaut vraie) pour indiquer si une valeur par défaut (si elle n'est pas explicitement indiquée) doit être appliquée. Ensuite, vous pouvez résoudre le point 3 ci-dessus en introduisant une interface comme IDefaultTemplateProvider que l'on peut utiliser pour fournir séparément des définitions pour les balises template-property. Ensuite, ceux-ci peuvent être découverts par réflexion de la même manière que vous découvrez IComponent(s) et les utilisez dans les cas où l'indicateur use-default est défini et que l'utilisateur n'en fournit pas. Vous pouvez autoriser plusieurs composants de modèle implémentant l'interface mais lever une exception sans ambiguïté si une balise de propriété est trouvée plusieurs fois. De cette façon, vous pouvez fournir des modèles standardisés à un composant générique spécifique à une application qui sont utilisés plusieurs fois et obtenir la résolution au moment de la conception, peut-être ?

Je pense que décrire mon expérience d'apprentissage autour de cela vous donne une bonne base…, et j'ai quelques réflexions à discuter…

Je souhaite effectuer une configuration de composants basée sur un modèle via des fichiers de métadonnées et de ressources pour récupérer le texte traduit statique, la validation, etc. Je pensais que ce mécanisme de création de modèles pourrait être la chose à faire pour que cela se produise. Pour l'évaluer, j'ai créé une interface public interface IRenderFragment<T> { RenderFragment UsingDataItem(T item); } et un composant ordinaire qui implémente également cette interface à utiliser comme modèle. Puis fait un composant générique en l'utilisant comme ceci

  <InputField For=@DataSource(Model, x => x.NewTodo) Template="@InputBasic.Template" />

Ainsi, de cette manière, le composant InputField a des informations sur la façon de récupérer les métadonnées, etc. et peut les intégrer dans un modèle qui est rendu dans un modèle que vous fournissez. Il fonctionne très bien.

En XAML, c'est le seul mécanisme permettant de rendre les choses lorsqu'un comportement environnant est implémenté dans un composant, comme dans une vue de liste, vous devez alors fournir un modèle d'élément. Dans Blazor, ce n'est pas nécessaire car vous pouvez utiliser du code avec une boucle pour avoir ce comportement environnant à la place. J'ai alors réalisé que j'aurais pu faire la même chose ici. J'aurais pu récupérer un modèle à partir de métadonnées dans le code et l'alimenter dans un composant normal. @{ var inputModel = InputModelHelper.GetFor(ViewModel, x => x.NewTodo); } <InputBasic DataItem="@inputModel"/>

Peut-être évident pour certains… donc les modèles ne sont en fait pas absolument nécessaires pour mon cas d'utilisation. Mais ça m'a fait me demander ce que je veux vraiment. Peut-être s'agit-il plutôt d'injecter des attributs et du contenu dans des éléments existants ? Et peut-être le faire en étant capable de définir un attribut personnalisé comme :

  <label For=”@DataSource(Model, x => x.NewTodo)”></label>
  <input For=”@DataSource(Model, x => x.NewTodo)” />

Pour tester s'il est même possible d'ajouter des nœuds d'attribut dans un élément existant, j'ai créé une fonction (renvoie une chaîne vide pour ce nœud de contenu) après la balise ouverte <label>@For(x => x.NewTodo)</label> qui étend le processus de rendu en ajoutant des nœuds d'attribut (et un contenu node dans le cas de l'élément label) au générateur. (Le générateur a été capturé dans la méthode BuildRenderTree.) Le générateur a la position du nœud d'étiquette afin que l'on puisse facilement voir dans son tampon si un attribut a été défini et qu'il ne doit donc pas être remplacé par ce mécanisme. Cela fonctionne réellement. C'est un peu plus difficile pour l'élément d'entrée car c'est un élément vide (il doit être rouvert et la fonction ne peut pas être placée parmi les attributs), mais en tant que preuve de concept, cela semble fonctionner. Comment peut-on faire en sorte que cela soit pris en charge et cela a-t-il du sens?

Je suppose que cette fonction de continuation/extension de rendu peut être accrochée à plusieurs endroits. Pour vous faire réfléchir… peut-être avoir la possibilité d'injecter un "RenderElementExtensionHandler" et si un composant a une telle propriété, un appel à celui-ci est compilé dans la position lorsque tous les attributs ont été ajoutés et après fermeture dans le code généré sur les éléments qui ont une personnalisation attributs, comme For dans ce cas.
Est-ce quelque chose qui peut être utilisé pour d'autres choses ou est-ce un cas limite ? Existe-t-il d'autres idées pour étendre le comportement à des éléments existants ?

Désolé si ça a été un peu long...

Pourquoi ne pas rendre les accessoires ?

<List RenderHeader={(text) => (<h4>{text}</h4>)}>
    {({ item, index }) => (
        <li value={index} class={item.Type}>
            {item.Content}
        </li>)}
</List>

Terminé:
b2c8b1aec98e65054c590d356fdf15e6a5e6a822
409026c2f41926d85d18ea2abf345453fa083e34
ce11f517d370b501433a62a56fbd4be1704000ad

Cette page vous a été utile?
0 / 5 - 0 notes