Skip to content

Commit

Permalink
Merge pull request #48 from canro91/clean-public-api-program.cs
Browse files Browse the repository at this point in the history
Use extension methods to clean up PublicApi Program.cs file
  • Loading branch information
ardalis authored Jul 8, 2024
2 parents bd8e052 + 1073793 commit 0cb2e52
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 119 deletions.
2 changes: 1 addition & 1 deletion src/Infrastructure/Dependencies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Microsoft.eShopWeb.Infrastructure;

public static class Dependencies
{
public static void ConfigureServices(IConfiguration configuration, IServiceCollection services)
public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration)
{
bool useOnlyInMemoryDatabase = false;
if (configuration["UseOnlyInMemoryDatabase"] != null)
Expand Down
107 changes: 107 additions & 0 deletions src/PublicApi/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System.Collections.Generic;
using System.Text;
using BlazorShared;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.eShopWeb.ApplicationCore.Constants;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.ApplicationCore.Services;
using Microsoft.eShopWeb.Infrastructure.Data;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.eShopWeb.Infrastructure.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;

namespace Microsoft.eShopWeb.PublicApi.Extensions;

public static class ServiceCollectionExtensions
{
public static void AddCustomServices(this IServiceCollection services, ConfigurationManager configuration)
{
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
services.AddScoped(typeof(IReadRepository<>), typeof(EfRepository<>));
services.Configure<CatalogSettings>(configuration);

var catalogSettings = configuration.Get<CatalogSettings>() ?? new CatalogSettings();
services.AddSingleton<IUriComposer>(new UriComposer(catalogSettings));
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
services.AddScoped<ITokenClaimsService, IdentityTokenClaimService>();
}

public static void AddJwtAuthentication(this IServiceCollection services)
{
var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY);

services.AddAuthentication(config =>
{
config.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(config =>
{
config.RequireHttpsMetadata = false;
config.SaveToken = true;
config.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
}

public static void AddCorsPolicy(this IServiceCollection services, string policyName, BaseUrlConfiguration baseUrlConfig)
{
services.AddCors(options =>
{
options.AddPolicy(name: policyName,
corsPolicyBuilder =>
{
corsPolicyBuilder.WithOrigins(baseUrlConfig!.WebBase.Replace("host.docker.internal", "localhost").TrimEnd('/'));
corsPolicyBuilder.AllowAnyMethod();
corsPolicyBuilder.AllowAnyHeader();
});
});
}

public static void AddSwagger(this IServiceCollection services)
{
// TODO: Update to use FastEndpoints approach to Swagger
services.AddEndpointsApiExplorer();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
c.EnableAnnotations();
c.SchemaFilter<CustomSchemaFilters>();
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = @"JWT Authorization header using the Bearer scheme. \r\n\r\n
Enter 'Bearer' [space] and then your token in the text input below.
\r\n\r\nExample: 'Bearer 12345abcdef'",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});

c.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
});
});
}
}
35 changes: 35 additions & 0 deletions src/PublicApi/Extensions/WebApplicationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.eShopWeb.Infrastructure.Data;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace Microsoft.eShopWeb.PublicApi.Extensions;

public static class WebApplicationExtensions
{
public static async Task SeedDatabaseAsync(this WebApplication app)
{
app.Logger.LogInformation("Seeding Database...");

using var scope = app.Services.CreateScope();
var scopedProvider = scope.ServiceProvider;
try
{
var catalogContext = scopedProvider.GetRequiredService<CatalogContext>();
await CatalogContextSeed.SeedAsync(catalogContext, app.Logger);

var userManager = scopedProvider.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = scopedProvider.GetRequiredService<RoleManager<IdentityRole>>();
var identityContext = scopedProvider.GetRequiredService<AppIdentityDbContext>();
await AppIdentityDbContextSeed.SeedAsync(identityContext, userManager, roleManager);
}
catch (Exception ex)
{
app.Logger.LogError(ex, "An error occurred seeding the DB.");
}
}
}
130 changes: 13 additions & 117 deletions src/PublicApi/Program.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
using BlazorShared;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using BlazorShared;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.eShopWeb;
using Microsoft.eShopWeb.ApplicationCore.Constants;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.ApplicationCore.Services;
using Microsoft.eShopWeb.Infrastructure.Data;
using Microsoft.eShopWeb.Infrastructure;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.eShopWeb.Infrastructure.Logging;
using Microsoft.eShopWeb.PublicApi;
using Microsoft.eShopWeb.PublicApi.Extensions;
using Microsoft.eShopWeb.PublicApi.Middleware;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using MinimalApi.Endpoint.Configurations.Extensions;
using MinimalApi.Endpoint.Extensions;

Expand All @@ -29,132 +19,38 @@

// Use to force loading of appsettings.json of test project
builder.Configuration.AddConfigurationFile("appsettings.test.json");
builder.Logging.AddConsole();

Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services);
builder.Services.ConfigureServices(builder.Configuration);

builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();

builder.Services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
builder.Services.AddScoped(typeof(IReadRepository<>), typeof(EfRepository<>));
builder.Services.Configure<CatalogSettings>(builder.Configuration);
var catalogSettings = builder.Configuration.Get<CatalogSettings>() ?? new CatalogSettings();
builder.Services.AddSingleton<IUriComposer>(new UriComposer(catalogSettings));
builder.Services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
builder.Services.AddScoped<ITokenClaimsService, IdentityTokenClaimService>();

var configSection = builder.Configuration.GetRequiredSection(BaseUrlConfiguration.CONFIG_NAME);
builder.Services.Configure<BaseUrlConfiguration>(configSection);
var baseUrlConfig = configSection.Get<BaseUrlConfiguration>();
builder.Services.AddCustomServices(builder.Configuration);

builder.Services.AddMemoryCache();

// TODO: Move to extension method to wire up auth
var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY);
builder.Services.AddAuthentication(config =>
{
config.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(config =>
{
config.RequireHttpsMetadata = false;
config.SaveToken = true;
config.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
builder.Services.AddJwtAuthentication();

// TODO: Move to extension method to wire up CORS
const string CORS_POLICY = "CorsPolicy";
builder.Services.AddCors(options =>
{
options.AddPolicy(name: CORS_POLICY,
corsPolicyBuilder =>
{
corsPolicyBuilder.WithOrigins(baseUrlConfig!.WebBase.Replace("host.docker.internal", "localhost").TrimEnd('/'));
corsPolicyBuilder.AllowAnyMethod();
corsPolicyBuilder.AllowAnyHeader();
});
});

var configSection = builder.Configuration.GetRequiredSection(BaseUrlConfiguration.CONFIG_NAME);
builder.Services.Configure<BaseUrlConfiguration>(configSection);
var baseUrlConfig = configSection.Get<BaseUrlConfiguration>();
builder.Services.AddCorsPolicy(CORS_POLICY, baseUrlConfig!);

builder.Services.AddControllers();

// TODO: Consider removing AutoMapper dependency (FastEndpoints already has its own Mapper support)
builder.Services.AddAutoMapper(typeof(MappingProfile).Assembly);

// TODO: Do we need this? This should work by default
builder.Configuration.AddEnvironmentVariables();

// TODO: Update to use FastEndpoints approach to Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
c.EnableAnnotations();
c.SchemaFilter<CustomSchemaFilters>();
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = @"JWT Authorization header using the Bearer scheme. \r\n\r\n
Enter 'Bearer' [space] and then your token in the text input below.
\r\n\r\nExample: 'Bearer 12345abcdef'",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});

c.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,

},
new List<string>()
}
});
});
builder.Services.AddSwagger();

var app = builder.Build();

app.Logger.LogInformation("PublicApi App created...");


// TODO: Move to extension method to seed database
app.Logger.LogInformation("Seeding Database...");

using (var scope = app.Services.CreateScope())
{
var scopedProvider = scope.ServiceProvider;
try
{
var catalogContext = scopedProvider.GetRequiredService<CatalogContext>();
await CatalogContextSeed.SeedAsync(catalogContext, app.Logger);

var userManager = scopedProvider.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = scopedProvider.GetRequiredService<RoleManager<IdentityRole>>();
var identityContext = scopedProvider.GetRequiredService<AppIdentityDbContext>();
await AppIdentityDbContextSeed.SeedAsync(identityContext, userManager, roleManager);
}
catch (Exception ex)
{
app.Logger.LogError(ex, "An error occurred seeding the DB.");
}
}
await app.SeedDatabaseAsync();

if (app.Environment.IsDevelopment())
{
Expand Down
3 changes: 2 additions & 1 deletion src/Web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopWeb;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.Infrastructure;
using Microsoft.eShopWeb.Infrastructure.Data;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.eShopWeb.Web;
Expand All @@ -23,7 +24,7 @@

if (builder.Environment.IsDevelopment() || builder.Environment.EnvironmentName == "Docker"){
// Configure SQL Server (local)
Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services);
builder.Services.ConfigureServices(builder.Configuration);
}
else{
// Configure SQL Server (prod)
Expand Down

0 comments on commit 0cb2e52

Please sign in to comment.