web-dev-qa-db-fra.com

Comment rendre asp.net mvc view en angular 2?

J'essaie d'intégrer asp.net mvc avec une application angulaire 2. Je comprends que ce n’est pas idéal, mais on me demande d’intégrer des fonctionnalités MVC existantes (pensez à une application héritée) dans un tout nouveau spa Angular 2.

Ce que j'aimerais pouvoir faire, c'est avoir une vue cshtml qui contient des composants angulaires, ainsi que de la substance pure mvc ...

<side-bar></side-bar>
<action-bar></action-bar>

@{
    Html.RenderPartial("_SuperLegacyPartialView");   
}

J'ai du mal à trouver un moyen de le faire. Cet article de blog semblait prometteur - http://www.centare.com/tutorial-angular2-mvc-6-asp-net-5/ . Il utilisait une valeur templateUrl qui indiquait un chemin rendu par Mvc, ainsi que AsyncRoute, mais cela ne fonctionne plus dans Angular 2. Ce message semblait également prometteur - http: // gbataille. github.io/2016/02/16/Angular2-Webpack-AsyncRoute.html , mais utilise également AsyncRoute, qui est obsolète.

C’était très simple sous Angular 1. Nous utilisions soit manuellement bootstrap angular dans une vue Razor, soit restituer une vue partielle sous forme de templateUrl d’un composant/directive. Quelle est la meilleure façon de procéder dans le dernier Angular 2 qui utilise Webpack?

7
Tim Hardy

Je suis venu avec une solution qui répondait à mes besoins à l'époque. J'utilise angular-cli avec WebPack, et cela a répondu à mes besoins. Je ne comprends pas tous les exemples que j'ai vus qui disent d'utiliser "templateUrl: '/ Template/Index'", où le chemin est un chemin vers une vue MVC. Cela ne fonctionne tout simplement pas, car le chemin ne peut pas être trouvé dans les vues regroupées créées par WebPack. Peut-être que ces personnes n'utilisent pas angular-cli et WebPack.

Cette réponse de stackoverflow - Comment utiliser/créer un modèle dynamique pour compiler un composant dynamique avec Angular 2.0? a été très utile pour créer la directive suivante. Cette directive utilisera la sortie d’une vue partielle mvc et la compilera. Cela permet à la logique serveur/rasoir d'avoir lieu, et à la compilation d'un certain angular. Bien qu’inclure réellement d’autres composants dans ce MVC partiel posait problème. Si cela fonctionne, faites-le-moi savoir, s'il vous plaît. Dans mon cas, il me fallait juste le rendu du serveur et le placer exactement là où je le voulais dans mon Angular 2 spa.

MvcPartialDirective

import {
  Component,
  Directive,
  NgModule,
  Input,
  ViewContainerRef,
  Compiler,
  ComponentFactory,
  ModuleWithComponentFactories,
  ComponentRef,
  ReflectiveInjector, OnInit, OnDestroy
} from '@angular/core';

import { RouterModule }  from '@angular/router';
import { CommonModule } from '@angular/common';
import {Http} from "@angular/http";
import 'rxjs/add/operator/map';

export function createComponentFactory(compiler: Compiler, metadata: Component): Promise<ComponentFactory<any>> {
  const cmpClass = class DynamicComponent {};
  const decoratedCmp = Component(metadata)(cmpClass);

  @NgModule({ imports: [CommonModule, RouterModule], declarations: [decoratedCmp] })
  class DynamicHtmlModule { }

  return compiler.compileModuleAndAllComponentsAsync(DynamicHtmlModule)
    .then((moduleWithComponentFactory: ModuleWithComponentFactories<any>) => {
      return moduleWithComponentFactory.componentFactories.find(x => x.componentType === decoratedCmp);
    });
}

@Directive({ selector: 'mvc-partial' })
export class MvcPartialDirective implements OnInit, OnDestroy {
  html: string = '<p></p>';
  @Input() url: string;
  cmpRef: ComponentRef<any>;

  constructor(private vcRef: ViewContainerRef, private compiler: Compiler, private http: Http) { }

  ngOnInit() {
    this.http.get(this.url)
      .map(res => res.text())
      .subscribe(
        (html) => {
          this.html = html;
          if (!html) return;

          if(this.cmpRef) {
            this.cmpRef.destroy();
          }

          const compMetadata = new Component({
            selector: 'dynamic-html',
            template: this.html,
          });

          createComponentFactory(this.compiler, compMetadata)
            .then(factory => {
              const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
              this.cmpRef = this.vcRef.createComponent(factory, 0, injector, []);
            });
        },
        err => console.log(err),
        () => console.log('MvcPartial complete')
      );

  }

  ngOnDestroy() {
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }
  }
}

dans some-composant.html (en supposant que votre application mvc partage le domaine avec votre spa)

<mvc-partial [url]="'/stuffs/mvcstuff'"></mvc-partial>

MvcStuff.cshtml

@{
    ViewBag.Title = "This is some MVC stuff!!!";
}
<div>
    <h2>MVC Stuff:</h2>
    <h4>@ViewBag.Title</h4>
    <h2>Angular Stuff:</h2>
    <h4>{{1 + 1}}</h4>
</div>

dans StuffsController.cs

public PartialViewResult MvcStuff() => PartialView();
11
Tim Hardy

Je l'ai fait comme ça.

@Component({
    templateUrl: '/Template/Index'
})
export class TemplateComponent {}

"/ Template/Index" est l'URL de votre contrôleur MVC, puis la méthode.

public IActionResult Index()
  {
    return PartialView();
  }

Mon problème est que je ne sais pas comment l'actualisation de la vue pour appeler la méthode du contrôleur à chaque fois est chargée.

1
Federico

J'avais besoin d'utiliser MVC PartialView html dans mon application angular 4, appelée par la méthode HttpClient .get.

J'ai utilisé le message d'AMD

convertir ma vue partielle en chaîne HTML. J'ai renvoyé ceci dans un objet container JSON et je l'ai défini dans une variable définissant le code HTML d'un div sur ma page.

    ...in the template 
       <div  class="files"  [innerHtml]="myTemplate">
       </div>

... in the component .ts file
      export interface htmldata {
          html: string; 
      }


... inside component

   getDivHtml(path: string): Promise<htmldata> {
            return this.http
                .get<htmldata>(`${this.baseUrl}/MVC/Index?path=` + path , { withCredentials: true })
                .toPromise();
   }

   ngOnInit() { 
       this.getDivHtml('').then(
           data => { this.loadData(data); },
       ).catch( error => { console.log(error);  }); 
   }

   loadData(data: htmldata) {
      this.myTemplate = data.html;
   }

... sur le serveur

  public class HtmlReturn
  {
      public string html { get; set; }
  }

  [Produces("application/json")]
  [Route("api/MVC/[action]")]
  public class MVCController : Controller
  {

      private readonly ViewRender view; 

      public MVCController(ViewRender view)
      {           
           this.view = view;
      }

      public IActionResult Index(string path)
      {
           data.html = this.view.Render("viewpath", viewModel);
           return Json(data);
      }
}

Remarque: ceci ne fonctionne bien qu'avec du code HTML statique qui n'a pas besoin de programmes d'écoute d'événements. Je n'ai pas pu ajouter d'événements de clic au code HTML chargé avec renderer2, bien que je ne sois pas un expert et que cela soit possible. 

Vous devrez créer la classe ViewRender et ajouter une instruction d'injection dans le fichier startup.cs, comme indiqué dans AMDs post

0
davaus

Pour ceux qui sont sur Angular 7 , vous devrez modifier un peu la réponse acceptée pour que cela fonctionne.

Dans MvcPartialDirective :

Mettez à jour Http vers HttpClient afin qu’il se lise:

import { HttpClient } from '@angular/common/http';

Dans ngOnInit (), spécifiez le responseType:

this.http .get(this.url, {responseType: "text"})...

Mise à jour du tuyau:

.pipe(map(res => res.toString())) (note toString () au lieu de .text ())

Utilisez éventuellement le préfixe app pour spécifier la directive:

@Directive({ selector: 'appActionResult' })

Résultat final:

import {
  Component,
  Directive,
  NgModule,
  Input,
  ViewContainerRef,
  Compiler,
  ComponentFactory,
  ModuleWithComponentFactories,
  ComponentRef,
  ReflectiveInjector, OnInit, OnDestroy
} from '@angular/core';

import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';

export function createComponentFactory(compiler: Compiler, metadata: Component): Promise<ComponentFactory<any>> {
  const cmpClass = class DynamicComponent { };
  const decoratedCmp = Component(metadata)(cmpClass);

  @NgModule({ 
    imports: [CommonModule, RouterModule], 
    declarations: [decoratedCmp],
    schemas: [NO_ERRORS_SCHEMA] })
  class DynamicHtmlModule { }

  return compiler.compileModuleAndAllComponentsAsync(DynamicHtmlModule)
    .then((moduleWithComponentFactory: ModuleWithComponentFactories<any>) => {
      return moduleWithComponentFactory.componentFactories.find(x => x.componentType === decoratedCmp);
    });
}

@Directive({
  selector: 'appActionResult'
})
export class ActionResultDirective implements OnInit, OnDestroy {
  html = '<p></p>';
  @Input() url: string;
  cmpRef: ComponentRef<any>;

  constructor(private vcRef: ViewContainerRef, private compiler: Compiler, private http: HttpClient) {}

  ngOnInit() {
    this.http
      .get(this.url, {responseType: "text"})
      .pipe(map(res => res.toString()))
      .subscribe(
        (html) => {
          this.html = html;
          if (!html) { return; }

          if (this.cmpRef) {
            this.cmpRef.destroy();
          }

          const compMetadata = new Component({
            selector: 'dynamic-html',
            template: this.html,
          });

          createComponentFactory(this.compiler, compMetadata)
            .then(factory => {
              const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
              this.cmpRef = this.vcRef.createComponent(factory, 0, injector, []);
            });
        },

        err => console.log(err),
        () => console.log('MvcPartial complete')
      );

  }

  ngOnDestroy() {
    if (this.cmpRef) {
      this.cmpRef.destroy();
    }
  }
}
0
taylorswiftfan