Como integrar Vue com ASP.NET Core usando extensão SPA
LUCIANO DE SOUSA PEREIRA
Posted on November 2, 2019
AVISO
Estou usando .NET Core 3.0 e Visual Studio 2019 v16.3.2, porém, acredito que quase tudo esteja disponível no .NET Core 2.x.
Introdução
Single Page Application (SPA) é uma tecnologia muito importante para desenvolvedor front-end.
No ASP.Net Core, existe a funcionalidade de integração com SPA.
Você pode vê-la ao criar um novo projeto do tipo ASP.NET Core Web Application.
Os três templates no fundo são "Angular", "React.js" e "React.js e Redux", utilizados para desenvolver web APIs (usando ASP.NET Core) e SPAs (usando o framework selecionado) em um projeto, como demonstrado abaixo:
No Solution Explorer haverá uma chamada ClientApp pertencente à aplicação SPA. É possível desenvolver usando Visual Studio ou qualquer editor de sua preferência, como o Visual Studio Code.
Se quiser debuggar a aplicação, pressione "F5". O Visual Studio irá executar o servidor de desenvolvimento para a SPA e para o ASP.NET Core, configurando a comunicação entre ambos.
Também serão executados comandos como "npm install" automaticamente.
Parece perfeito, mas você deve estar pensando: "Onde está o Vue que eu gosto tanto?".
É o que veremos a seguir.
Criando um projeto ASP.NET Core Web Application
Vamos primeiro criar um projeto ASP.NET Core Web Application usando o template de API:
Dentro da pasta do projeto, abra um terminal e execute o comando abaixo para criar um projeto Vue usando o Vue-CLI:
vue create client-app
Caso não tenha instalado o Vue-CLI, acesse o link abaixo:
https://cli.vuejs.org/guide/installation.html
Editando o arquivo do projeto para realizar a integração
Edite o arquivo .csproj manualmente com o seguinte código:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<RootNamespace>YOUR-PROJECT-NAME-HERE</RootNamespace>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>client-app\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.0.0-preview6.19307.2" />
</ItemGroup>
<ItemGroup>
<!-- Don't publish the SPA source files, but do show them in the project files list -->
<Content Remove="$(SpaRoot)**" />
<None Remove="$(SpaRoot)**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)dist\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>
Depois de editar o arquivo, o projeto em Vue será construído com o projeto em ASP.NET Core.
Adicionado configuração de conexão
Último passo. Crie uma classe chamada VueHelper para configurar a conexão entre o servidor de desenvolvimento e a aplicação em Vue:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.SpaServices;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace NetCore3_Vue
{
public static class VueHelper
{
// default port number of 'npm run serve'
private static int Port { get; } = 8080;
private static Uri DevelopmentServerEndpoint { get; } = new Uri($"http://localhost:{Port}");
private static TimeSpan Timeout { get; } = TimeSpan.FromSeconds(30);
// done message of 'npm run serve' command.
private static string DoneMessage { get; } = "DONE Compiled successfully in";
public static void UseVueDevelopmentServer(this ISpaBuilder spa)
{
spa.UseProxyToSpaDevelopmentServer(async () =>
{
var loggerFactory = spa.ApplicationBuilder.ApplicationServices.GetService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger("Vue");
// if 'npm run serve' command was executed yourself, then just return the endpoint.
if (IsRunning())
{
return DevelopmentServerEndpoint;
}
// launch vue.js development server
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var processInfo = new ProcessStartInfo
{
FileName = isWindows ? "cmd" : "npm",
Arguments = $"{(isWindows ? "/c npm " : "")}run serve",
WorkingDirectory = "client-app",
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
};
var process = Process.Start(processInfo);
var tcs = new TaskCompletionSource<int>();
_ = Task.Run(() =>
{
try
{
string line;
while ((line = process.StandardOutput.ReadLine()) != null)
{
logger.LogInformation(line);
if (!tcs.Task.IsCompleted && line.Contains(DoneMessage))
{
tcs.SetResult(1);
}
}
}
catch (EndOfStreamException ex)
{
logger.LogError(ex.ToString());
tcs.SetException(new InvalidOperationException("'npm run serve' failed.", ex));
}
});
_ = Task.Run(() =>
{
try
{
string line;
while ((line = process.StandardError.ReadLine()) != null)
{
logger.LogError(line);
}
}
catch (EndOfStreamException ex)
{
logger.LogError(ex.ToString());
tcs.SetException(new InvalidOperationException("'npm run serve' failed.", ex));
}
});
var timeout = Task.Delay(Timeout);
if (await Task.WhenAny(timeout, tcs.Task) == timeout)
{
throw new TimeoutException();
}
return DevelopmentServerEndpoint;
});
}
private static bool IsRunning() => IPGlobalProperties.GetIPGlobalProperties()
.GetActiveTcpListeners()
.Select(x => x.Port)
.Contains(Port);
}
}
Adicione a função AddSpaStaticFiles no método ConfigureServices do arquivo Startup.cs para ter suporte à SPA:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSpaStaticFiles(options => options.RootPath = "client-app/dist");
}
E adicione as funções UseSpaStaticFiles e UseSpa no método Configure:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Other code
(...)
// add following statements
app.UseSpaStaticFiles();
app.UseSpa(spa =>
{
spa.Options.SourcePath = "client-app";
if (env.IsDevelopment())
{
// Launch development server for Vue.js
spa.UseVueDevelopmentServer();
}
});
}
Executando a aplicação
Abra a seção Depurar na página de propriedades do projeto e remova o conteúdo do campo "Iniciar navegador":
Pressione "F5" novamente para ver a página inicial do Vue.
É hora de se conectar com a API. Crie o arquivo ValuesController.cs na pasta Controllers contendo um simples método GET:
using Microsoft.AspNetCore.Mvc;
namespace NetCore3_Vue.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok(new string[] { "value1", "value2" });
}
}
}
Edite o arquivo HelloWorld.vue dentro do projeto em Vue para exibir o resultado da requisição:
<template>
<div>
<div :key="r" v-for="r in this.results">{{ r }}</div>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
results: []
};
},
async created() {
const r = await fetch('/api/values');
this.results = await r.json();
}
};
</script>
Execute a aplicação novamente e este será o resultado:
Conclusão
Integramos um projeto web em ASP.NET Core com um projeto em Vue usando extensões SPA do ASP.NET Core.
Para que houvesse conexão entre ambos os projetos, foi necessário realizar alterações no arquivo de configuração do projeto em .Net Core, assim como, uma classe auxiliar foi criada para gerenciar a aplicação em Vue.
Referências
Artigo original: How to integrate Vue.js and ASP.NET Core using SPA Extension
Projeto completo no GitHub: https://github.com/lucianopereira86/NetCore3-Vue
Posted on November 2, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.