using System.Collections.Generic; using System.Linq; using CTA.WebForms.Extensions; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace CTA.WebForms.Helpers { public static class StartupSyntaxHelper { public const string StartupClassName = "Startup"; public const string StartupConfigureMethodName = "Configure"; public const string StartupConfigureServicesMethodName = "ConfigureServices"; public static IEnumerable RequiredNamespaces => new[] { "Microsoft.AspNetCore.Hosting", "Microsoft.AspNetCore.Builder", "Microsoft.Extensions.Configuration", "Microsoft.Extensions.DependencyInjection", "Microsoft.Extensions.Hosting" }; /// /// Condition that will return true when in development /// public static string DevEnvConditionText => $"{RuntimeInjectable.EnvInjectable.PropertyName}.IsDevelopment()"; /// /// Required statement in Configure method, adds use of wwwroot to Blazor app /// public static string AppUseStaticFilesText => $"{RuntimeInjectable.AppBuilderInjectable.ParamName}.UseStaticFiles();"; /// /// Required statement in Configure method, adds routing capabilities to Blazor app /// public static string AppUseRoutingText => $"{RuntimeInjectable.AppBuilderInjectable.ParamName}.UseRouting();"; /// /// Statement that will allow us to debug easier when running resulting project, uses a detailed /// dev error page on exception /// public static string AppUseDevExceptionPageCall => $"{RuntimeInjectable.AppBuilderInjectable.ParamName}.UseDeveloperExceptionPage();"; /// /// Statement that will configure router and other stuff required for normal operation of resulting project /// public static string AppUseEndpointsCall => $@"{RuntimeInjectable.AppBuilderInjectable.ParamName}.UseEndpoints(endpoints => {{endpoints.MapBlazorHub();endpoints.MapFallbackToPage(""/_Host"");}});"; /// /// Required statement in ConfigureServices method, adds use of Razor templated pages /// public static string ServiceAddRazorPagesText => $"{RuntimeInjectable.ServiceCollectionInjectable.ParamName}.AddRazorPages();"; /// /// Required statement in ConfigureServices method, configures Blazor app to be Blazor Server /// public static string ServiceAddServerSideBlazorText => $"{RuntimeInjectable.ServiceCollectionInjectable.ParamName}.AddServerSideBlazor();"; /// /// A shortcut method to make building the entire class; /// more hassle free than assembling each of the components /// individually (which is possible if specialized functionality /// is needed, but not recommended) /// /// Statements to put after required statements in constructor /// Statements to put after required statements in configure method /// Statements to put after required statements in configure services method /// Fields that were used in Global.asax.cs /// Properties that were used in Global.asax.cs /// Methods in addition to the "normal" methods of Global.asax.cs /// ClassDeclarationSyntax node for new Startup class public static ClassDeclarationSyntax ConstructStartupClass( IEnumerable constructorAdditionalStatements = null, IEnumerable configureAdditionalStatements = null, IEnumerable configureServicesAdditionalStatements = null, IEnumerable additionalFieldDeclarations = null, IEnumerable additionalPropertyDeclarations = null, IEnumerable additionalMethodDeclarations = null) { var result = SyntaxFactory.ClassDeclaration(StartupClassName).AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); if (additionalFieldDeclarations != null) { result = result.AddMembers(additionalFieldDeclarations.ToArray()); } result = result.AddMembers(AddStartupProperties(additionalPropertyDeclarations).ToArray()); result = result.AddMembers( ConstructStartupConstructor(constructorAdditionalStatements), ConstructStartupConfigureMethod(configureAdditionalStatements), ConstructStartupConfigureServicesMethod(configureServicesAdditionalStatements)); if (additionalMethodDeclarations != null) { result = result.AddMembers(additionalMethodDeclarations.ToArray()); } return result; } public static IEnumerable AddStartupProperties(IEnumerable additionalDeclarations = null) { var newProperties = new List() { // We guarantee that config and env will be present as properties RuntimeInjectable.ConfigInjectable.GetPropertyDeclaration(), RuntimeInjectable.EnvInjectable.GetPropertyDeclaration() }; if (additionalDeclarations == null) { return newProperties; } return newProperties.UnionSyntaxNodeCollections(additionalDeclarations); } public static ConstructorDeclarationSyntax ConstructStartupConstructor(IEnumerable additonalStatements = null) { var statements = new List() { // We guarantee that config and env will be assigned values from // constructor params RuntimeInjectable.ConfigInjectable.PropertyAssignment, RuntimeInjectable.EnvInjectable.PropertyAssignment }; if (additonalStatements != null) { statements.AddRange(additonalStatements); } return SyntaxFactory.ConstructorDeclaration(StartupClassName) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) // Accept runtime injected config and env services .AddParameterListParameters(RuntimeInjectable.ConfigInjectable.AsParameter, RuntimeInjectable.EnvInjectable.AsParameter) .WithBody(CodeSyntaxHelper.GetStatementsAsBlock(statements)); } public static MethodDeclarationSyntax ConstructStartupConfigureMethod(IEnumerable additonalStatements = null) { var devOnlyStatements = new List() { SyntaxFactory.ParseStatement(AppUseDevExceptionPageCall) }; var devEnvCondition = SyntaxFactory.ParseExpression(DevEnvConditionText); var statements = new List() { // Standard blazor configuration statements SyntaxFactory.ParseStatement(AppUseStaticFilesText), SyntaxFactory.ParseStatement(AppUseRoutingText), SyntaxFactory.ParseStatement(AppUseEndpointsCall), // Dev env dependent statements SyntaxFactory.IfStatement(devEnvCondition, CodeSyntaxHelper.GetStatementsAsBlock(devOnlyStatements)) }; if (additonalStatements != null) { statements.AddRange(additonalStatements); } return SyntaxFactory.MethodDeclaration(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), StartupConfigureMethodName) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) // Accept runtime injected application builder .AddParameterListParameters(RuntimeInjectable.AppBuilderInjectable.AsParameter) .WithBody(CodeSyntaxHelper.GetStatementsAsBlock(statements)); } public static MethodDeclarationSyntax ConstructStartupConfigureServicesMethod(IEnumerable additonalStatements = null) { var statements = new List() { // Standard blazor configuration statements SyntaxFactory.ParseStatement(ServiceAddRazorPagesText), SyntaxFactory.ParseStatement(ServiceAddServerSideBlazorText) }; if (additonalStatements != null) { statements.AddRange(additonalStatements); } return SyntaxFactory.MethodDeclaration(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), StartupConfigureServicesMethodName) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) // Accept runtime injected service collection .AddParameterListParameters(RuntimeInjectable.ServiceCollectionInjectable.AsParameter) .WithBody(CodeSyntaxHelper.GetStatementsAsBlock(statements)); } } }