Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add Support for React #872

Merged
merged 2 commits into from
Jun 26, 2023
Merged

✨ Add Support for React #872

merged 2 commits into from
Jun 26, 2023

Conversation

jasontaylordev
Copy link
Owner

Overview

Since its creation, the template has only supported Angular. With these changes, the template now supports React and Angular.

Changes Made

  • Added support for React to the template
  • Defaults to Angular if cloning the repo or using the .NET project template
  • For React only, created custom NSwag liquid templates to support auth redirects (see ./src/WebUI/Templates)
  • Updated README with relevant instructions

Example

You can create a new project using one of the following commands:

  • Angular: dotnet new ca-sln --clientFramework angular
  • React: dotnet new ca-sln --clientFramework react

If you do not specify a client framework, it will default to Angular.

Please review the changes and provide your feedback. Thank you! ❤️

@jasontaylordev jasontaylordev merged commit ed19244 into main Jun 26, 2023
3 checks passed
@jasontaylordev jasontaylordev deleted the AddReact branch June 26, 2023 10:08
@ghost
Copy link

ghost commented Jun 27, 2023

@jasontaylordev Thank you very much for this.

I am using React Native with your .NET template right now. I use codegen-openapi-generator to generate typescript files for my React Native app. It creates a TypeScript service that utilizes Redux Tookit. Very similar to NSwag, and could also be added to the .csproj targets if the template user wanted to generate React Native client code.

In my current implementation of your architecture I have a folder called ClientApps that contains several submodule repositories. An Angular App, and a React Native mobile app. Since I have an Angular app and a React Native app, I use NSwag to generate the Angular HttpClient code, and Redux-Toolkit-CodeGen-OpenApi to generate the React Native client code.

I love setting up Git submodules for each CilentApp so they are still connected to the API in the folder hierarchy and can receive the generated http client code, but are independently maintained on Github and have their own repo to execute their own Github actions.

I like to use Azure Static Apps for the Angular Client, Azure App Services to host the API, and Expo to host the React Native App. Thus, I need 3 GitHub Actions to deploy when PRs are merged, it just makes it easier if they are 3 different repos that have their own PR's, actions, and version history.

@jasontaylordev
Copy link
Owner Author

Hey @nickgallimoresoftware that sounds like an awesome project! Thanks for the overview and keep up the good work. 😀

@tonven
Copy link

tonven commented Aug 10, 2023

@nickgallimoresoftware do you have an example of your react native app + this template? :)

@ghost
Copy link

ghost commented Aug 10, 2023

@nickgallimoresoftware do you have an example of your react native app + this template? :)

What I did was added a package.json to WebUI, then edited the csproj to install and run this code generation package from Redux Tool Kit:

https://redux-toolkit.js.org/rtk-query/usage/code-generation
https://github.com/reduxjs/redux-toolkit/tree/master/packages/rtk-query-codegen-openapi

Package.json

{
  "name": "sample-api",
  "version": "1.0.0",
  "scripts": {
    "build": "npx @rtk-query/codegen-openapi codegen-openapi-config.ts"
  },
  "devDependencies": {
    "@rtk-query/codegen-openapi": "^1.0.0",
    "typescript": "^4.5.2",
    "ts-node": "^10.9.1"
  }
}

Then added npm install and run events to the WebUI.csproj. Note I also run the Angular one as well simultaneously. This way I auto update my Angular App's HttpClient calls, and my React Native's Redux Tookit Queries simultaneously when making changes to the API. I also have my ClientApps setup as Git Submodules rather than being apart of the same repo.

WebUI.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
      <TargetFramework>net7.0</TargetFramework>
      <Nullable>enable</Nullable>
      <IsPackable>true</IsPackable>
      <ImplicitUsings>enable</ImplicitUsings>
      <RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
  </PropertyGroup>
  
  <PropertyGroup>
      <ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
  </PropertyGroup>
  
  <PropertyGroup Condition=" '$(RunConfiguration)' == 'SampleApi.WebUI' " />
    <ItemGroup>
        <ProjectReference Include="..\Application\Application.csproj" />
        <ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
    </ItemGroup>

    <ItemGroup>
        <InternalsVisibleTo Include="SampleApi.Application.IntegrationTests" />
    </ItemGroup>

  <ItemGroup>
      <PackageReference Include="FluentValidation.AspNetCore" Version="10.3.4" />
      <PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="7.0.0-rc.2.22476.2" />
      <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="7.0.0-rc.2.22476.2" />
      <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.0-rc.2.22476.2" />
      <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="7.0.0-rc.2.22476.2" />
      <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.0-rc.2.22472.11" />
      <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.0-rc.2.22472.11" />
      <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.0-rc.2.22472.11" />
      <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.10" />
      <PackageReference Include="NSwag.AspNetCore" Version="13.18.2" />
      <PackageReference Include="NSwag.MSBuild" Version="13.18.2">
          <PrivateAssets>all</PrivateAssets>
          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      </PackageReference>
  </ItemGroup>

  <ItemGroup>
      <Folder Include="wwwroot\api\" />
  </ItemGroup>

  <!-- Run NSwag to Generate Open API Document -->
  <Target Name="NSwag" AfterTargets="PostBuildEvent" Condition=" '$(Configuration)' == 'Debug' ">
      <Exec WorkingDirectory="$(ProjectDir)" EnvironmentVariables="ASPNETCORE_ENVIRONMENT=Development" Command="$(NSwagExe_Net60) run nswag.json /variables:Configuration=$(Configuration)" />
  </Target>
  
  <!--
      1. Install npm packages after generating the Open API document
      "Inputs" and "Outputs" are used for incremental builds. If all output items are up-to-date, MSBuild skips the target.
      The first time the task is executed. Then, it only runs when you change the package.json file.
      Documentation: https://learn.microsoft.com/en-us/visualstudio/msbuild/incremental-builds?WT.mc_id=DT-MVP-5003978
   -->
  <Target Name="NpmInstall" Inputs="package.json" Condition=" '$(Configuration)' == 'Debug' " Outputs="node_modules/.install-stamp" DependsOnTargets="NSwag" AfterTargets="PostBuildEvent">
    
      <!--
          Use npm install or npm ci depending on RestorePackagesWithLockFile value.
          Uncomment the following lines if you want to use this feature:
       -->
      <PropertyGroup>
          <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
      </PropertyGroup>
    
      <Exec Command="npm ci" Condition="'$(RestorePackagesWithLockFile)' == 'true'" />
      <Exec Command="npm install" Condition="'$(RestorePackagesWithLockFile)' != 'true'" />

      <!-- Write the stamp file, so incremental builds work -->
      <Touch Files="node_modules/.install-stamp" AlwaysCreate="true" />
  </Target>

  <!--
      2. Run `npm run build` after building the .NET project.
      MSBuild runs NpmInstall before this task because of the DependsOnTargets attribute.
   -->
  <Target Name="NpmRunBuild" Condition=" '$(Configuration)' == 'Debug' " DependsOnTargets="NpmInstall" BeforeTargets="PostBuildEvent">
      <Exec Command="npm run build" />
  </Target>
  
</Project>

nswag.json

{
  "runtime": "Net60",
  "defaultVariables": null,
  "documentGenerator": {
    "aspNetCoreToOpenApi": {
      "basePath":  "/SampleApi",
      "project": "WebUI.csproj",
      "msBuildProjectExtensionsPath": null,
      "configuration": null,
      "runtime": null,
      "targetFramework": null,
      "noBuild": true,
      "msBuildOutputPath": null,
      "verbose": false,
      "workingDirectory": null,
      "requireParametersWithoutDefault": true,
      "apiGroupNames": null,
      "defaultPropertyNameHandling": "CamelCase",
      "defaultReferenceTypeNullHandling": "Null",
      "defaultDictionaryValueReferenceTypeNullHandling": "NotNull",
      "defaultResponseReferenceTypeNullHandling": "NotNull",
      "generateOriginalParameterNames": true,
      "defaultEnumHandling": "Integer",
      "flattenInheritanceHierarchy": false,
      "generateKnownTypes": true,
      "generateEnumMappingDescription": false,
      "generateXmlObjects": false,
      "generateAbstractProperties": false,
      "generateAbstractSchemas": true,
      "ignoreObsoleteProperties": false,
      "allowReferencesWithProperties": false,
      "useXmlDocumentation": true,
      "resolveExternalXmlDocumentation": true,
      "excludedTypeNames": [],
      "serviceHost": null,
      "serviceBasePath": null,
      "serviceSchemes": [],
      "infoTitle": "SampleApi API",
      "infoDescription": null,
      "infoVersion": "1.0.0",
      "documentTemplate": null,
      "documentProcessorTypes": [],
      "operationProcessorTypes": [],
      "typeNameGeneratorType": null,
      "schemaNameGeneratorType": null,
      "contractResolverType": null,
      "serializerSettingsType": null,
      "useDocumentProvider": true,
      "documentName": "v1",
      "aspNetCoreEnvironment": null,
      "createWebHostBuilderMethod": null,
      "startupType": null,
      "allowNullableBodyParameters": true,
      "useHttpAttributeNameAsOperationId": false,
      "output": "wwwroot/api/specification.json",
      "outputType": "OpenApi3",
      "newLineBehavior": "Auto",
      "assemblyPaths": [],
      "assemblyConfig": null,
      "referencePaths": [],
      "useNuGetCache": false
    }
  },
  "codeGenerators": {
    "openApiToTypeScriptClient": {
      "className": "{controller}Client",
      "moduleName": "",
      "namespace": "",
      "typeScriptVersion": 4.3,
      "template": "Angular",
      "promiseType": "Promise",
      "httpClass": "HttpClient",
      "withCredentials": false,
      "useSingletonProvider": true,
      "injectionTokenType": "InjectionToken",
      "rxJsVersion": 7.0,
      "dateTimeType": "Date",
      "nullValue": "Undefined",
      "generateClientClasses": true,
      "generateClientInterfaces": true,
      "generateOptionalParameters": false,
      "exportTypes": true,
      "wrapDtoExceptions": false,
      "exceptionClass": "SwaggerException",
      "clientBaseClass": null,
      "wrapResponses": false,
      "wrapResponseMethods": [],
      "generateResponseClasses": true,
      "responseClass": "SwaggerResponse",
      "protectedMethods": [],
      "configurationClass": null,
      "useTransformOptionsMethod": false,
      "useTransformResultMethod": false,
      "generateDtoTypes": true,
      "operationGenerationMode": "MultipleClientsFromOperationId",
      "markOptionalProperties": true,
      "generateCloneMethod": false,
      "typeStyle": "Class",
      "enumStyle": "Enum",
      "useLeafType": false,
      "classTypes": [],
      "extendedClasses": [],
      "extensionCode": null,
      "generateDefaultValues": true,
      "excludedTypeNames": [],
      "excludedParameterNames": [],
      "handleReferences": false,
      "generateConstructorInterface": true,
      "convertConstructorInterfaceData": false,
      "importRequiredTypes": true,
      "useGetBaseUrlMethod": false,
      "baseUrlTokenName": "API_BASE_URL",
      "queryNullValue": "",
      "useAbortSignal": false,
      "inlineNamedDictionaries": false,
      "inlineNamedAny": false,
      "includeHttpContext": false,
      "templateDirectory": null,
      "typeNameGeneratorType": null,
      "propertyNameGeneratorType": null,
      "enumNameGeneratorType": null,
      "serviceHost": null,
      "serviceSchemes": null,
      "output": "ClientApps/sample-angular-app/src/app/shared/services/angular-client.ts",
      "newLineBehavior": "Auto"
    }
  }
}

@tonven
Copy link

tonven commented Aug 10, 2023

@nickgallimoresoftware Thank you!
I will check it. I don't need to have Angular project. I want to use React Native both for web and mobile.

@ghost
Copy link

ghost commented Aug 10, 2023

@nickgallimoresoftware Thank you! I will check it. I don't need to have Angular project. I want to use React Native both for web and mobile.

I had same idea, until I realized react native web doesn't support very many libraries and I haven't been able to get it to work. One day. All you have to do is not use nswag. Remove the target group from WebUI.csproj and don't use the nswag.json.

@ashleysommer ashleysommer mentioned this pull request Sep 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants