-
-
Save bradygaster/3d1fcf43d1d1e73ea5d6c1b5aab40130 to your computer and use it in GitHub Desktop.
endpoints.MapGet("/products", (context) => | |
{ | |
var products = app.ApplicationServices.GetService<JsonFileProductService>().GetProducts(); | |
var json = JsonSerializer.Serialize<IEnumerable<Product>>(products); | |
return context.Response.WriteAsync(json); | |
}); |
public void AddRating(string productId, int rating) | |
{ | |
var products = GetProducts(); | |
var query = products.First(x => x.Id == productId); | |
if(query.Ratings == null) | |
{ | |
query.Ratings = new int[] { rating }; | |
} | |
else | |
{ | |
var ratings = query.Ratings.ToList(); | |
ratings.Add(rating); | |
query.Ratings = ratings.ToArray(); | |
} | |
using(var outputStream = File.OpenWrite(JsonFileName)) | |
{ | |
JsonSerializer.Serialize<IEnumerable<Product>>( | |
new Utf8JsonWriter(outputStream, new JsonWriterOptions | |
{ | |
SkipValidation = true, | |
Indented = true | |
}), | |
products | |
); | |
} | |
} |
app.UseEndpoints(endpoints => | |
{ | |
endpoints.MapRazorPages(); | |
endpoints.MapControllers(); | |
endpoints.MapBlazorHub(); | |
}); |
public void ConfigureServices(IServiceCollection services) | |
{ | |
services.AddRazorPages(); | |
services.AddTransient<JsonFileProductService>(); | |
} |
public void ConfigureServices(IServiceCollection services) | |
{ | |
services.AddRazorPages(); | |
services.AddServerSideBlazor(); | |
services.AddControllers(); | |
services.AddTransient<JsonFileProductService>(); | |
} |
curl https://localhost:5001/products -k | jq .[0] |
curl -X PATCH --header "Accept: application/json" --header "Content-Type: application/json" -d '{"productId" : "jenlooper-cactus", "rating" : 5}' https://localhost:5001/products -k |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> |
<link href="https://fonts.googleapis.com/css?family=Yellowtail&display=swap" rel="stylesheet"> | |
<link href="https://fonts.googleapis.com/css?family=Nunito&display=swap" rel="stylesheet"> |
@page | |
@using ContosoCrafts.WebSite.Components | |
@model IndexModel | |
@{ | |
ViewData["Title"] = "Home page"; | |
} | |
@(await Html.RenderComponentAsync<ProductList>(RenderMode.ServerPrerendered)) | |
<script src="_framework/blazor.server.js"></script> |
@page | |
@model IndexModel | |
@{ | |
ViewData["Title"] = "Home page"; | |
} | |
<div class="card-columns"> | |
@foreach (var product in Model.Products) | |
{ | |
<div class="card"> | |
<div class="card-img" style="background-image: url('@product.Image');"> | |
</div> | |
<div class="card-body"> | |
<h5 class="card-title">@product.Title</h5> | |
</div> | |
</div> | |
} | |
</div> |
public IndexModel(ILogger<IndexModel> logger, | |
JsonFileProductService productService) | |
{ | |
_logger = logger; | |
ProductService = productService; | |
} | |
public JsonFileProductService ProductService { get; } | |
public IEnumerable<Product> Products { get; private set; } | |
public void OnGet() | |
{ | |
Products = ProductService.GetProducts(); | |
} |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Text.Json; | |
using ContosoCrafts.WebSite.Models; | |
using Microsoft.AspNetCore.Hosting; | |
namespace ContosoCrafts.WebSite.Services | |
{ | |
public class JsonFileProductService | |
{ | |
public JsonFileProductService(IWebHostEnvironment webHostEnvironment) | |
{ | |
WebHostEnvironment = webHostEnvironment; | |
} | |
public IWebHostEnvironment WebHostEnvironment { get; } | |
private string JsonFileName | |
{ | |
get { return Path.Combine(WebHostEnvironment.WebRootPath, "data", "products.json"); } | |
} | |
public IEnumerable<Product> GetProducts() | |
{ | |
using(var jsonFileReader = File.OpenText(JsonFileName)) | |
{ | |
return JsonSerializer.Deserialize<Product[]>(jsonFileReader.ReadToEnd(), | |
new JsonSerializerOptions | |
{ | |
PropertyNameCaseInsensitive = true | |
}); | |
} | |
} | |
} | |
} |
using System.Text.Json; | |
using System.Text.Json.Serialization; | |
namespace ContosoCrafts.WebSite.Models | |
{ | |
public class Product | |
{ | |
public string Id { get; set; } | |
public string Maker { get; set; } | |
[JsonPropertyName("img")] | |
public string Image { get; set; } | |
public string Url { get; set; } | |
public string Title { get; set; } | |
public string Description { get; set; } | |
public int[] Ratings { get; set; } | |
public override string ToString() => JsonSerializer.Serialize<Product>(this); | |
} | |
} |
@using Microsoft.AspNetCore.Components.Web | |
@using ContosoCrafts.WebSite.Models | |
@using ContosoCrafts.WebSite.Services | |
@inject JsonFileProductService ProductService | |
<div class="card-columns"> | |
@foreach (var product in ProductService.GetProducts()) | |
{ | |
<div class="card"> | |
<div class="card-img" style="background-image: url('@product.Image');"> | |
</div> | |
<div class="card-body"> | |
<h5 class="card-title">@product.Title</h5> | |
</div> | |
<div class="card-footer"> | |
<small class="text-muted"><button @onclick="(e => SelectProduct(product.Id))" | |
data-toggle="modal" data-target="#productModal" class="btn btn-primary">More Info</button> | |
</small> | |
</div> | |
</div> | |
} | |
</div> | |
@code | |
{ | |
Product selectedProduct; | |
string selectedProductId; | |
void SelectProduct(string productId) | |
{ | |
selectedProductId = productId; | |
selectedProduct = ProductService.GetProducts().First(x => x.Id == productId); | |
} | |
} |
@if(selectedProduct != null) | |
{ | |
<div class="modal fade" id="productModal" tabindex="-1" role="dialog" aria-labelledby="productTitle" aria-hidden="true"> | |
<div class="modal-dialog modal-dialog-centered" role="document"> | |
<div class="modal-content"> | |
<div class="modal-header"> | |
<h5 class="modal-title" id="productTitle">@selectedProduct.Title</h5> | |
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> | |
<span aria-hidden="true">×</span> | |
</button> | |
</div> | |
<div class="modal-body"> | |
<div class="card"> | |
<div class="card-img" style="background-image: url('@selectedProduct.Image');"> | |
</div> | |
<div class="card-body"> | |
<p class="card-text">@selectedProduct.Description</p> | |
</div> | |
</div> | |
</div> | |
<div class="modal-footer"> | |
</div> | |
</div> | |
</div> | |
</div> | |
} |
[ | |
{ | |
"id" : "jenlooper-cactus", | |
"maker" : "@jenlooper", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567048-13938600-aa33-11e9-9cfd-712191013192.jpeg", | |
"url" : "https://www.hackster.io/agent-hawking-1/the-quantified-cactus-an-easy-plant-soil-moisture-sensor-e65393", | |
"title" : "The Quantified Cactus: An Easy Plant Soil Moisture Sensor", | |
"description" : "This project is a good learning project to get comfortable with soldering and programming an Arduino." | |
}, | |
{ | |
"id" : "jenlooper-light", | |
"maker" : "jenlooper", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567049-13938600-aa33-11e9-9c69-a4184bf8e524.jpeg", | |
"url" : "https://www.hackster.io/agent-hawking-1/book-light-dee7e4", | |
"title" : "A beautiful switch-on book light", | |
"description" : "Use craft items you have around the house, plus two LEDs and a LilyPad battery holder, to create a useful book light for reading in the dark." | |
}, | |
{ | |
"id" : "jenlooper-lightshow", | |
"maker" : "@jenlooper", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567053-13938600-aa33-11e9-9780-104fe4019659.png", | |
"url" : "https://www.hackster.io/agent-hawking-1/bling-your-laptop-with-an-internet-connected-light-show-30e4db", | |
"title" : "Bling your Laptop with an Internet-Connected Light Show", | |
"description" : "Create a web-connected light-strip API controllable from your website, using the Particle.io." | |
}, | |
{ | |
"id" : "jenlooper-survival", | |
"maker" : "jenlooper", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567051-13938600-aa33-11e9-8ae7-0b5c19aafab4.jpeg", | |
"url" : "https://www.hackster.io/agent-hawking-1/create-a-compact-survival-kit-38bfdb", | |
"title" : "Create a Compact Survival Kit with LED Track Lighting", | |
"description" : "Use an Altoids tin with Chibitronics sticker LEDs to create a light-up compact that doubles as a survival kit for the young hipster" | |
}, | |
{ | |
"id" : "sailorhg-bubblesortpic", | |
"maker" : "sailorhg", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567054-13938600-aa33-11e9-9163-eec98e239b7a.png", | |
"url" : "https://twitter.com/sailorhg/status/1090107740049952770", | |
"title" : "Bubblesort Visualization", | |
"description" : "Visualization of sailor scouts sorted by bubblesort algorithm by their planet's distance from the sun" | |
}, | |
{ | |
"id" : "sailorhg-corsage", | |
"maker" : "sailorhg", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567055-142c1c80-aa33-11e9-96ff-9fbac6413625.png", | |
"url" : "https://twitter.com/sailorhg/status/1090113666911891456", | |
"title" : "Light-up Corsage", | |
"description" : "Light-up corsage I made with my summer intern." | |
}, | |
{ | |
"id" : "sailorhg-kit", | |
"maker" : "sailorhg", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567056-142c1c80-aa33-11e9-8682-10065d338145.png", | |
"url" : "https://twitter.com/sailorhg/status/1090122822007963648", | |
"title" : "Pastel hardware kit", | |
"description" : "Pastel hardware kits complete with custom manufactured pastel alligator clips." | |
}, | |
{ | |
"id" : "sailorhg-led", | |
"maker" : "sailorhg", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567052-13938600-aa33-11e9-9a88-cd842073ba44.jpg", | |
"url" : "https://twitter.com/sailorhg/status/1090117277540745216", | |
"title" : "Heart-shaped LED", | |
"description" : "custom molded heart shaped LED with sprinkles." | |
}, | |
{ | |
"id" : "selinazawacki-soi-shirt", | |
"maker" : "selinazawacki", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567060-142c1c80-aa33-11e9-8188-5a4803844a9e.png", | |
"url" : "https://www.instagram.com/p/BNvESj-j8PI/", | |
"title" : "Black Sweatshirt", | |
"description" : "Black sweatshirt hoody with the Sick of the Internet logo." | |
}, | |
{ | |
"id" : "selinazawacki-soi-pins", | |
"maker" : "selinazawacki", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567059-142c1c80-aa33-11e9-939b-2ecf4492786d.png", | |
"url" : "https://www.instagram.com/p/BNm6hZzDoEF/", | |
"title" : "Sick of the Internet Pins", | |
"description" : "Still some time to enter the pin/sticker giveaway! " | |
}, | |
{ | |
"id" : "vogueandcode-hipster-dev-bro", | |
"maker" : "vogueandcode", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567061-14c4b300-aa33-11e9-9fee-63ff2c0c9823.png", | |
"url" : "https://www.vogueandcode.com/shop/hipster-dev-bro", | |
"title" : "Hipster Dev", | |
"description" : "Hipster Dev is busy coding away while styled in a camo jacket and orange beanie." | |
}, | |
{ | |
"id" : "vogueandcode-pretty-girls-code-tee", | |
"maker" : "vogueandcode", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567062-14c4b300-aa33-11e9-9dcd-8bfed4ece810.png", | |
"url" : "https://www.vogueandcode.com/shop/pretty-girls-code-tee", | |
"title" : "Pretty Girls Code Tee", | |
"description" : "Everyone’s favorite design is finally here on a tee! The Pretty Girls Code crew-neck tee is available in a soft pink with red writing." | |
}, | |
{ | |
"id" : "vogueandcode-ruby-sis-2", | |
"maker" : "vogueandcode", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567063-14c4b300-aa33-11e9-8515-bcb866da9ea3.png", | |
"url" : "https://www.vogueandcode.com/shop/ruby-sis-2", | |
"title" : "Ruby Sis", | |
"description" : "Styled in a dashiki, Ruby Sis is listening to music while coding in her favorite language, Ruby!" | |
}, | |
{ | |
"id" : "selinazawacki-moon", | |
"maker" : "selinazawacki", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567057-142c1c80-aa33-11e9-9781-9e442418eaab.png", | |
"url" : "https://www.instagram.com/p/BFktVYPinKQ/", | |
"title" : "Holographic Dark Moon Necklace", | |
"description" : "Not sure if I'll be making more, get it while I have it in the store." | |
}, | |
{ | |
"id" : "selinazawacki-shirt", | |
"maker" : "selinazawacki", | |
"img" : "https://user-images.githubusercontent.com/41929050/61567058-142c1c80-aa33-11e9-89fb-b4f30d84d69d.png", | |
"url" : "https://www.instagram.com/p/BEXlpiZCnJ3/", | |
"title" : "Floppy Crop", | |
"description" : "Used up the Diskette fabric today to make 2 of these crops." | |
} | |
] |
[HttpPatch] | |
public ActionResult Patch([FromBody] RatingRequest request) | |
{ | |
ProductService.AddRating(request.ProductId, request.Rating); | |
return Ok(); | |
} | |
public class RatingRequest | |
{ | |
public string ProductId { get; set; } | |
public int Rating { get; set; } | |
} |
using System.Collections.Generic; | |
using ContosoCrafts.WebSite.Models; | |
using ContosoCrafts.WebSite.Services; | |
using Microsoft.AspNetCore.Mvc; | |
namespace ContosoCrafts.WebSite.Controllers | |
{ | |
[ApiController] | |
[Route("[controller]")] | |
public class ProductsController : ControllerBase | |
{ | |
public ProductsController(JsonFileProductService productService) | |
{ | |
ProductService = productService; | |
} | |
public JsonFileProductService ProductService { get; } | |
[HttpGet] | |
public IEnumerable<Product> Get() | |
{ | |
return ProductService.GetProducts(); | |
} | |
} | |
} |
int currentRating = 0; | |
int voteCount = 0; | |
string voteLabel; | |
void GetCurrentRating() | |
{ | |
if(selectedProduct.Ratings == null) | |
{ | |
currentRating = 0; | |
voteCount = 0; | |
} | |
else | |
{ | |
voteCount = selectedProduct.Ratings.Count(); | |
voteLabel = voteCount > 1 ? "Votes" : "Vote"; | |
currentRating = selectedProduct.Ratings.Sum() / voteCount; | |
} | |
System.Console.WriteLine($"Current rating for {selectedProduct.Id}: {currentRating}"); | |
} | |
void SubmitRating(int rating) | |
{ | |
System.Console.WriteLine($"Rating received for {selectedProduct.Id}: {rating}"); | |
ProductService.AddRating(selectedProductId, rating); | |
SelectProduct(selectedProductId); | |
} |
<div class="modal-footer"> | |
@if(voteCount == 0) | |
{ | |
<span>Be the first to vote!</span> | |
} | |
else | |
{ | |
<span>@voteCount @voteLabel</span> | |
} | |
@for(int i=1; i<6; i++) | |
{ | |
var currentStar = i; | |
if(i<=currentRating) | |
{ | |
<span class="fa fa-star checked" @onclick="(e => SubmitRating(currentStar))"></span> | |
} | |
else | |
{ | |
<span class="fa fa-star" @onclick="(e => SubmitRating(currentStar))"></span> | |
} | |
} | |
</div> |
void SelectProduct(string productId) | |
{ | |
selectedProductId = productId; | |
selectedProduct = ProductService.GetProducts().First(x => x.Id == productId); | |
GetCurrentRating(); | |
} |
.checked { | |
color: orange; | |
} | |
.fa-star { | |
cursor: pointer; | |
} | |
/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification | |
for details on configuring this project to bundle and minify static web assets. */ | |
.card-columns .card:hover .card-img { | |
opacity: 1; | |
} | |
.card-columns .card-body { | |
height: 100px; | |
font-family: 'Nunito', sans-serif; | |
background: #fbfafd; | |
} | |
.card-columns .card-img { | |
height: 330px; | |
vertical-align: bottom; | |
background-position: center; /* Center the image */ | |
background-repeat: no-repeat; /* Do not repeat the image */ | |
background-size: cover; /* Resize the background image to cover the entire container */ | |
opacity: .8; | |
} | |
.modal .card-img { | |
height: 500px; | |
vertical-align: bottom; | |
background-position: center; /* Center the image */ | |
background-repeat: no-repeat; /* Do not repeat the image */ | |
background-size: cover; /* Resize the background image to cover the entire container */ | |
} | |
.card-columns .card:hover { | |
transform: scale(1.05); | |
box-shadow: 0 10px 20px rgba(37,33,82,.12), 0 4px 8px rgba(37,33,82,.06); | |
} | |
a.navbar-brand { | |
white-space: normal; | |
font-family: 'Yellowtail', cursive; | |
font-size: xx-large; | |
text-align: center; | |
word-break: break-all; | |
} | |
/* Provide sufficient contrast against white background */ | |
a { | |
color: #0366d6; | |
} | |
.btn-primary { | |
color: #fff; | |
background-color: #1b6ec2; | |
border-color: #1861ac; | |
} | |
.nav-pills .nav-link.active, .nav-pills .show > .nav-link { | |
color: #fff; | |
background-color: #1b6ec2; | |
border-color: #1861ac; | |
} | |
/* Sticky footer styles | |
-------------------------------------------------- */ | |
html { | |
font-size: 14px; | |
} | |
@media (min-width: 768px) { | |
html { | |
font-size: 16px; | |
} | |
} | |
.border-top { | |
border-top: 1px solid #e5e5e5; | |
} | |
.border-bottom { | |
border-bottom: 1px solid #e5e5e5; | |
} | |
.box-shadow { | |
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); | |
} | |
button.accept-policy { | |
font-size: 1rem; | |
line-height: inherit; | |
} | |
/* Sticky footer styles | |
-------------------------------------------------- */ | |
html { | |
position: relative; | |
min-height: 100%; | |
} | |
body { | |
/* Margin bottom by footer height */ | |
margin-bottom: 60px; | |
} | |
p { | |
font-family: 'Nunito', sans-serif; | |
} | |
h1 { | |
font-family: 'Nunito', sans-serif; | |
} | |
ul { | |
font-family: 'Nunito', sans-serif; | |
} | |
.footer { | |
position: absolute; | |
bottom: 0; | |
width: 100%; | |
white-space: nowrap; | |
line-height: 60px; /* Vertically center the text there */ | |
font-family: 'Nunito', sans-serif; | |
} | |
/*Backgrounds*/ | |
.bg-navbar { | |
background: -webkit-linear-gradient(110deg, #e9a8a6 60%, #252152 60%); | |
background: -o-linear-gradient(110deg, #e9a8a6 60%, #252152 60%); | |
background: -moz-linear-gradient(110deg, #e9a8a6 60%, #252152 60%); | |
background: linear-gradient(110deg, #e9a8a6 60%, #252152 60%); | |
} | |
.bg-footer { | |
background: -webkit-linear-gradient(110deg, #252152 60%, #e9a8a6 60%); | |
background: -o-linear-gradient(110deg, #252152 60%, #e9a8a6 60%); | |
background: -moz-linear-gradient(110deg, #252152 60%, #e9a8a6 60%); | |
background: linear-gradient(110deg, #252152 60%, #e9a8a6 60%); | |
} |
"args": [ | |
"watch", | |
"--project", | |
"${workspaceFolder}/ContosoCrafts.WebSite/ContosoCrafts.WebSite.csproj", | |
"run", | |
"/property:GenerateFullPaths=true", | |
"/consoleloggerparameters:NoSummary" | |
], |
Great work guys
Thanks!
@nisaKTH THANKS :)
I could not find "card-columns" class in latest update. So everything appears in one column now.
use card-groups class
<div class="card-group">
<!-- your foreach goes here -->
</div>
or
enclose inside
<div class="container">
<div class="row row-cols-4 g-2">
<!-- your foreach goes here -->
</div>
</div>
I don't get startup class file in vs 2022, what should i do
You got Program.cs which is equivalent to Startup.cs file. Any doubt don't exited.
I don't get startup class file in vs 2022, what should i do
apparently after .net6 startup is not made by default to add the service shown in the videos you could add "builder.Services.AddTransient();" in the program.cs file instead of "services.AddTransient();" in the startup file
More Info button not working on firefox
I am using Dotnet 8.0 and firefox browser. I have followed along the codes using vscode! However, the more info button is not working at my side. I have searched different sites for solution, but nothing is working. The button is not event firing any events
I was able to solve the modal not popping up. The series was made using Bootstrap 4 or before, so if you're using a newer version, you need to change "data-toggle" and "data-target" to "data-bs-toggle" and "data-bs-target" in ProductList.razor
Source: https://stackoverflow.com/questions/74185184/bootstrap-modal-doesnt-popup-in-razor-pages
where is the code in ### startup.cs
where is the code in ### startup.cs
thanks @nisaKTH