Skip to content

Commit

Permalink
Serve product images from WebApp, so CatalogApi doesn't have to be pu…
Browse files Browse the repository at this point in the history
…blicly reachable (#74)

* Serve product images from WebApp, so CatalogApi doesn't have to be publicly reachable

* Use Yarp to reverse-proxy the images

* Retrieve HybridApp images from mobile BFF (proxied to catalog API) (#96)

---------

Co-authored-by: Eilon Lipton <Eilon@users.noreply.github.com>
  • Loading branch information
SteveSandersonMS and Eilon authored Nov 27, 2023
1 parent 37cd432 commit 1e764ba
Show file tree
Hide file tree
Showing 17 changed files with 80 additions and 47 deletions.
4 changes: 3 additions & 1 deletion src/Catalog.API/Apis/CatalogApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,9 @@ public static async Task<Results<NotFound, PhysicalFileHttpResult>> GetItemPictu

string imageFileExtension = Path.GetExtension(item.PictureFileName);
string mimetype = GetImageMimeTypeFromImageFileExtension(imageFileExtension);
DateTime lastModified = File.GetLastWriteTimeUtc(path);

return TypedResults.PhysicalFile(path, mimetype);
return TypedResults.PhysicalFile(path, mimetype, lastModified: lastModified);
}

public static async Task<Results<BadRequest<string>, RedirectToRouteHttpResult, Ok<PaginatedItems<CatalogItem>>>> GetItemsBySemanticRelevance(
Expand Down Expand Up @@ -319,6 +320,7 @@ private static List<CatalogItem> ChangeUriPlaceholder(CatalogOptions options, Li
".wmf" => "image/wmf",
".jp2" => "image/jp2",
".svg" => "image/svg+xml",
".webp" => "image/webp",
_ => "application/octet-stream",
};

Expand Down
3 changes: 2 additions & 1 deletion src/HybridApp/Components/Pages/Item/ItemPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@inject CatalogService CatalogService
@* @inject BasketState BasketState *@
@inject NavigationManager Nav
@inject IProductImageUrlProvider ProductImages

@if (item is not null)
{
Expand All @@ -11,7 +12,7 @@
<SectionContent SectionName="page-header-subtitle">@item.CatalogBrand?.Brand</SectionContent>

<div class="item-details">
<img src="@item.PictureUri" />
<img src="@ProductImages.GetProductImageUrl(item.Id)" />
<div class="description">
<p>@item.Description</p>
<p>
Expand Down
11 changes: 7 additions & 4 deletions src/HybridApp/MauiProgram.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using eShop.WebAppComponents.Services;
using eShop.HybridApp.Services;
using eShop.WebAppComponents.Services;
using Microsoft.Extensions.Logging;

namespace eShop.HybridApp;

public static class MauiProgram
{
// NOTE: Must have a trailing slash to ensure the full BaseAddress URL is used to resolve relative URLs
private static string MobileBffBaseUrl = "http://localhost:61632/catalog-api/";
// NOTE: Must have a trailing slash on base URLs to ensure the full BaseAddress URL is used to resolve relative URLs
private static string MobileBffHost = "http://localhost:61632";
internal static string MobileBffCatalogBaseUrl = $"{MobileBffHost}/catalog-api/";

public static MauiApp CreateMauiApp()
{
Expand All @@ -25,7 +27,8 @@ public static MauiApp CreateMauiApp()
builder.Logging.AddDebug();
#endif

builder.Services.AddHttpClient<CatalogService>(o => o.BaseAddress = new(MobileBffBaseUrl));
builder.Services.AddHttpClient<CatalogService>(o => o.BaseAddress = new(MobileBffCatalogBaseUrl));
builder.Services.AddSingleton<IProductImageUrlProvider, ProductImageUrlProvider>();

return builder.Build();
}
Expand Down
9 changes: 9 additions & 0 deletions src/HybridApp/Services/ProductImageUrlProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using eShop.WebAppComponents.Services;

namespace eShop.HybridApp.Services;

public class ProductImageUrlProvider : IProductImageUrlProvider
{
public string GetProductImageUrl(int productId)
=> $"{MauiProgram.MobileBffCatalogBaseUrl}api/v1/catalog/items/{productId}/pic";
}
5 changes: 1 addition & 4 deletions src/Mobile.Bff.Shopping/Extensions/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
{
public static void AddApplicationServices(this IHostApplicationBuilder builder)
{
builder.Services
.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetRequiredSection("ReverseProxy"))
.AddServiceDiscoveryDestinationResolver();
builder.Services.AddHttpForwarderWithServiceDiscovery();

builder.Services.AddHealthChecks()
.AddUrlGroup(new Uri("http://catalog-api/health"), name: "catalogapi-check")
Expand Down
27 changes: 26 additions & 1 deletion src/Mobile.Bff.Shopping/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,31 @@
app.UseHttpsRedirection();

app.MapDefaultEndpoints();
app.MapReverseProxy();

const string CatalogApiPrefix = "/api/v1/catalog/";
var forwardedCatalogApis = new[]
{
CatalogApiPrefix + "items",
CatalogApiPrefix + "items/by",
CatalogApiPrefix + "items/{id}",
CatalogApiPrefix + "items/by/{name}",

CatalogApiPrefix + "items/withsemanticrelevance/{text}",

CatalogApiPrefix + "items/type/{typeId}/brand/{brandId?}",
CatalogApiPrefix + "items/type/all/brand/{brandId?}",
CatalogApiPrefix + "catalogTypes",
CatalogApiPrefix + "catalogBrands",

CatalogApiPrefix + "items/{id}/pic",
};

foreach (var forwardedUrl in forwardedCatalogApis)
{
var mapFromPattern = "/catalog-api" + forwardedUrl;
var mapToTargetPath = forwardedUrl;

app.MapForwarder(mapFromPattern, "http://catalog-api", mapToTargetPath);
}

await app.RunAsync();
31 changes: 0 additions & 31 deletions src/Mobile.Bff.Shopping/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,5 @@
"Microsoft.AspNetCore": "Warning",
"System.Net.Http": "Warning"
}
},
"ReverseProxy": {
"Routes": {
"c-short": {
"ClusterId": "catalog",
"Match": {
"Path": "c/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/c" }
]
},
"c-long": {
"ClusterId": "catalog",
"Match": {
"Path": "catalog-api/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/catalog-api" }
]
}
},
"Clusters": {
"catalog": {
"Destinations": {
"destination0": {
"Address": "http://catalog-api"
}
}
}
}
}
}
3 changes: 2 additions & 1 deletion src/WebApp/Components/Pages/Cart/CartPage.razor
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@page "/cart"
@inject NavigationManager Nav
@inject BasketState Basket
@inject IProductImageUrlProvider ProductImages
@attribute [StreamRendering]
@attribute [Authorize]

Expand Down Expand Up @@ -31,7 +32,7 @@
var quantity = CurrentOrPendingQuantity(item.ProductId, item.Quantity);
<div class="cart-item" @key="@item.Id">
<div class="catalog-item-info">
<img alt="@item.ProductName" src="@item.PictureUrl" />
<img alt="@item.ProductName" src="@ProductImages.GetProductImageUrl(item.ProductId)" />
<div class="catalog-item-content">
<p class="name">@item.ProductName</p>
<p class="price">$@item.UnitPrice.ToString("0.00")</p>
Expand Down
3 changes: 2 additions & 1 deletion src/WebApp/Components/Pages/Item/ItemPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@inject CatalogService CatalogService
@inject BasketState BasketState
@inject NavigationManager Nav
@inject IProductImageUrlProvider ProductImages

@if (item is not null)
{
Expand All @@ -11,7 +12,7 @@
<SectionContent SectionName="page-header-subtitle">@item.CatalogBrand?.Brand</SectionContent>

<div class="item-details">
<img alt="@item.Name" src="@item.PictureUri" />
<img alt="@item.Name" src="@ProductImages.GetProductImageUrl(item)" />
<div class="description">
<p>@item.Description</p>
<p>
Expand Down
3 changes: 3 additions & 0 deletions src/WebApp/Extensions/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ public static void AddApplicationServices(this IHostApplicationBuilder builder)
builder.AddRabbitMqEventBus("EventBus")
.AddEventBusSubscriptions();

builder.Services.AddHttpForwarderWithServiceDiscovery();

// Application services
builder.Services.AddScoped<BasketState>();
builder.Services.AddScoped<LogOutService>();
builder.Services.AddSingleton<BasketService>();
builder.Services.AddSingleton<OrderStatusNotificationService>();
builder.Services.AddSingleton<IProductImageUrlProvider, ProductImageUrlProvider>();

// HTTP and GRPC client registrations
builder.Services.AddGrpcClient<Basket.BasketClient>(o => o.Address = new("http://basket-api"))
Expand Down
2 changes: 2 additions & 0 deletions src/WebApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@

app.MapRazorComponents<App>().AddInteractiveServerRenderMode();

app.MapForwarder("/product-images/{id}", "http://catalog-api", "/api/v1/catalog/items/{id}/pic");

app.Run();
1 change: 0 additions & 1 deletion src/WebApp/Services/BasketItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ public class BasketItem
public decimal UnitPrice { get; set; }
public decimal OldUnitPrice { get; set; }
public int Quantity { get; set; }
public required string PictureUrl { get; set; }
}
1 change: 0 additions & 1 deletion src/WebApp/Services/BasketState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ async Task<IReadOnlyCollection<BasketItem>> FetchCoreAsync()
{
Id = Guid.NewGuid().ToString(), // TODO: this value is meaningless, use ProductId instead.
ProductId = catalogItem.Id,
PictureUrl = catalogItem.PictureUri,
ProductName = catalogItem.Name,
UnitPrice = catalogItem.Price,
Quantity = item.Quantity,
Expand Down
9 changes: 9 additions & 0 deletions src/WebApp/Services/ProductImageUrlProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using eShop.WebAppComponents.Services;

namespace eShop.WebApp.Services;

public class ProductImageUrlProvider : IProductImageUrlProvider
{
public string GetProductImageUrl(int productId)
=> $"product-images/{productId}";
}
1 change: 1 addition & 0 deletions src/WebApp/WebApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery.Yarp" />
<PackageReference Include="Microsoft.SemanticKernel" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" />
<PackageReference Include="Grpc.AspNetCore.Server.ClientFactory" />
Expand Down
3 changes: 2 additions & 1 deletion src/WebAppComponents/Catalog/CatalogListItem.razor
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
@using eShop.WebAppComponents.Item
@inject IProductImageUrlProvider ProductImages

<div class="catalog-item">
<a class="catalog-product" href="@ItemHelper.Url(Item)" data-enhance-nav="false">
<span class='catalog-product-image'>
<img alt="@Item.Name" src='@Item.PictureUri' />
<img alt="@Item.Name" src='@ProductImages.GetProductImageUrl(Item)' />
</span>
<span class='catalog-product-content'>
<span class='name'>@Item.Name</span>
Expand Down
11 changes: 11 additions & 0 deletions src/WebAppComponents/Services/IProductImageUrlProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using eShop.WebAppComponents.Catalog;

namespace eShop.WebAppComponents.Services;

public interface IProductImageUrlProvider
{
string GetProductImageUrl(CatalogItem item)
=> GetProductImageUrl(item.Id);

string GetProductImageUrl(int productId);
}

0 comments on commit 1e764ba

Please sign in to comment.