Skip to content

Instantly share code, notes, and snippets.

@bradygaster
Last active November 15, 2024 19:56
Show Gist options
  • Save bradygaster/3d1fcf43d1d1e73ea5d6c1b5aab40130 to your computer and use it in GitHub Desktop.
Save bradygaster/3d1fcf43d1d1e73ea5d6c1b5aab40130 to your computer and use it in GitHub Desktop.
Contoso Crafts
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
});
}
}
}
}
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-navbar border-bottom box-shadow mb-3">
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">&times;</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"
],
@SkepticDude
Copy link

I could not find "card-columns" class in latest update. So everything appears in one column now.

@nisaKTH
Copy link

nisaKTH commented Sep 2, 2022

I could not find "card-columns" class in latest update. So everything appears in one column now.

you could add this one <div class="row row-cols-1 row-cols-md-3 g-4"> before @foreach and div class="col"> after the symbol { near @foreach

`

@foreach (var product in Model.Products)

{
    <div class="col">
<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>
} `

@GuidoKiu
Copy link

thanks @nisaKTH

@MinenhleNkosi
Copy link

Great work guys

@phoenix-beep
Copy link

Thanks!

@Jir0uuu
Copy link

Jir0uuu commented Apr 4, 2023

@nisaKTH THANKS :)

@EACUAMBA
Copy link

EACUAMBA commented Aug 9, 2023

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>

@samhan0511
Copy link

I don't get startup class file in vs 2022, what should i do

@EACUAMBA
Copy link

You got Program.cs which is equivalent to Startup.cs file. Any doubt don't exited.

@jonathanclerence
Copy link

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

@SajjadAli54
Copy link

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

@rsm4217
Copy link

rsm4217 commented Apr 8, 2024

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

@xianne-lee
Copy link

where is the code in ### startup.cs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment