mirror of
https://github.com/PacktPublishing/Web-Development-with-Blazor-Second-Edition.git
synced 2026-04-21 06:03:59 +00:00
Initial commit
This commit is contained in:
parent
2190113c56
commit
3088165398
1765 changed files with 192085 additions and 0 deletions
111
Chapter06/MyBlog/Components/Pages/Admin/BlogPostEdit.razor
Normal file
111
Chapter06/MyBlog/Components/Pages/Admin/BlogPostEdit.razor
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
@page "/admin/blogposts/new"
|
||||
@page "/admin/blogposts/{Id}"
|
||||
@inject IBlogApi _api
|
||||
@inject NavigationManager _manager
|
||||
@using Components.RazorComponents
|
||||
@using Markdig;
|
||||
|
||||
<EditForm Model="Post" OnValidSubmit="SavePost">
|
||||
<DataAnnotationsValidator />
|
||||
<CustomCssClassProvider ProviderType="BootstrapFieldCssClassProvider" />
|
||||
<BlogNavigationLock @ref="NavigationLock" />
|
||||
<InputText @bind-Value="Post.Title" />
|
||||
<ValidationMessage For="()=>Post.Title" />
|
||||
<InputDate @bind-Value="Post.PublishDate" />
|
||||
<ValidationMessage For="()=>Post.PublishDate" />
|
||||
<InputSelect @bind-Value="selectedCategory">
|
||||
<option value="0" disabled>None selected</option>
|
||||
@foreach (var category in Categories)
|
||||
{
|
||||
<option value="@category.Id">@category.Name </option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ul>
|
||||
@foreach (var tag in Tags)
|
||||
{
|
||||
<li>
|
||||
@tag.Name
|
||||
@if (Post.Tags.Any(t => t.Id == tag.Id))
|
||||
{
|
||||
<button type="button" @onclick="@(() => {Post.Tags.Remove(Post.Tags.Single(t=>t.Id==tag.Id)); })">Remove</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" @onclick="@(()=> { Post.Tags.Add(tag); })">Add</button>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<InputTextAreaOnInput @bind-Value="Post.Text" @onkeyup="UpdateHTML" />
|
||||
<ValidationMessage For="()=>Post.Text" />
|
||||
<button type="submit" class="btn btn-success">Save</button>
|
||||
</EditForm>
|
||||
|
||||
@((MarkupString)markDownAsHTML)
|
||||
|
||||
@code{
|
||||
|
||||
[Parameter]
|
||||
public string? Id { get; set; }
|
||||
BlogPost Post { get; set; } = new();
|
||||
List<Category> Categories { get; set; } = new();
|
||||
List<Tag> Tags { get; set; } = new();
|
||||
string? selectedCategory = null;
|
||||
string? markDownAsHTML { get; set; }
|
||||
BlogNavigationLock? NavigationLock { get; set; }
|
||||
|
||||
MarkdownPipeline pipeline = default!;
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
pipeline = new MarkdownPipelineBuilder()
|
||||
.UseEmojiAndSmiley()
|
||||
.Build();
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
protected void UpdateHTML()
|
||||
{
|
||||
markDownAsHTML = Markdig.Markdown.ToHtml(Post.Text, pipeline);
|
||||
}
|
||||
bool hasTag(Tag tag)
|
||||
{
|
||||
return Post.Tags.Contains(tag);
|
||||
}
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (Id != null)
|
||||
{
|
||||
var p = await _api.GetBlogPostAsync(Id);
|
||||
if (p != null)
|
||||
{
|
||||
Post = p;
|
||||
if (Post.Category != null)
|
||||
{
|
||||
selectedCategory = Post.Category.Id;
|
||||
}
|
||||
UpdateHTML();
|
||||
}
|
||||
}
|
||||
Categories = (await _api.GetCategoriesAsync())??new();
|
||||
Tags = (await _api.GetTagsAsync())?? new();
|
||||
base.OnParametersSet();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task SavePost()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(selectedCategory) && Categories != null)
|
||||
{
|
||||
var category = Categories.FirstOrDefault(c => c.Id == selectedCategory);
|
||||
if (category != null)
|
||||
{
|
||||
Post.Category = category;
|
||||
}
|
||||
}
|
||||
await _api.SaveBlogPostAsync(Post);
|
||||
NavigationLock?.CurrentEditContext.MarkAsUnmodified();
|
||||
_manager.NavigateTo("/admin/blogposts");
|
||||
}
|
||||
|
||||
}
|
||||
25
Chapter06/MyBlog/Components/Pages/Admin/BlogPostList.razor
Normal file
25
Chapter06/MyBlog/Components/Pages/Admin/BlogPostList.razor
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
@page "/admin/blogposts"
|
||||
@inject IBlogApi _api
|
||||
<a href="/admin/blogposts/new">New blog post</a>
|
||||
<ul>
|
||||
<Virtualize ItemsProvider="LoadPosts" Context="p">
|
||||
<li>
|
||||
@p.PublishDate
|
||||
<a href="/admin/blogposts/@p.Id">@p.Title</a>
|
||||
</li>
|
||||
</Virtualize>
|
||||
</ul>
|
||||
@code {
|
||||
public int TotalBlogposts { get; set; }
|
||||
private async ValueTask<ItemsProviderResult<BlogPost>> LoadPosts(ItemsProviderRequest request)
|
||||
{
|
||||
if (TotalBlogposts == 0)
|
||||
{
|
||||
TotalBlogposts = await
|
||||
_api.GetBlogPostCountAsync();
|
||||
}
|
||||
var numblogposts = Math.Min(request.Count, TotalBlogposts - request.StartIndex);
|
||||
List<BlogPost> posts = (await _api.GetBlogPostsAsync(numblogposts,request.StartIndex))??new();
|
||||
return new ItemsProviderResult<BlogPost>(posts, TotalBlogposts);
|
||||
}
|
||||
}
|
||||
66
Chapter06/MyBlog/Components/Pages/Admin/CategoryList.razor
Normal file
66
Chapter06/MyBlog/Components/Pages/Admin/CategoryList.razor
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
@page "/admin/categories"
|
||||
@attribute [Authorize]
|
||||
@using Components.RazorComponents
|
||||
@inject IBlogApi _api
|
||||
<h3>Categories</h3>
|
||||
<EditForm OnValidSubmit="Save" Model="Item">
|
||||
<DataAnnotationsValidator />
|
||||
<CustomCssClassProvider ProviderType="BootstrapFieldCssClassProvider" />
|
||||
<InputText @bind-Value="@Item.Name" />
|
||||
<ValidationMessage For="@(()=>Item.Name)" />
|
||||
<button class="btn btn-success" type="submit">Save</button>
|
||||
</EditForm>
|
||||
<ItemList Items="Items" DeleteEvent="@Delete" SelectEvent="@Select" ItemType="Category">
|
||||
<ItemTemplate>
|
||||
@{
|
||||
var item = context as Category;
|
||||
if (item != null)
|
||||
{
|
||||
@item.Name
|
||||
}
|
||||
}
|
||||
</ItemTemplate>
|
||||
</ItemList>
|
||||
@code {
|
||||
private List<Category> Items { get; set; } = new();
|
||||
public Category Item { get; set; } = new();
|
||||
protected async override Task OnInitializedAsync()
|
||||
{
|
||||
Items = (await _api.GetCategoriesAsync()) ?? new();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private async Task Delete(Category category)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _api.DeleteCategoryAsync(category.Id!);
|
||||
Items.Remove(category);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _api.SaveCategoryAsync(Item);
|
||||
if (!Items.Contains(Item))
|
||||
{
|
||||
Items.Add(Item);
|
||||
}
|
||||
Item = new Category();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private Task Select(Category category)
|
||||
{
|
||||
try
|
||||
{
|
||||
Item = category;
|
||||
}
|
||||
catch { }
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
67
Chapter06/MyBlog/Components/Pages/Admin/TagList.razor
Normal file
67
Chapter06/MyBlog/Components/Pages/Admin/TagList.razor
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
@page "/admin/tags"
|
||||
@attribute [Authorize]
|
||||
@using Components.RazorComponents
|
||||
@inject IBlogApi _api
|
||||
<h3>Tags</h3>
|
||||
<EditForm OnValidSubmit="Save" Model="Item">
|
||||
<DataAnnotationsValidator />
|
||||
<CustomCssClassProvider ProviderType="BootstrapFieldCssClassProvider" />
|
||||
<InputText @bind-Value="@Item.Name" />
|
||||
<ValidationMessage For="@(()=>Item.Name)" />
|
||||
<button class="btn btn-success" type="submit">Save</button>
|
||||
</EditForm>
|
||||
<ItemList Items="Items" DeleteEvent="@Delete" SelectEvent="@Select" ItemType="Tag">
|
||||
<ItemTemplate>
|
||||
@{
|
||||
var item = context as Tag;
|
||||
if (item != null)
|
||||
{
|
||||
@item.Name
|
||||
}
|
||||
}
|
||||
</ItemTemplate>
|
||||
</ItemList>
|
||||
|
||||
@code {
|
||||
private List<Tag> Items { get; set; } = new List<Tag>();
|
||||
public Tag Item { get; set; } = new Tag();
|
||||
protected async override Task OnInitializedAsync()
|
||||
{
|
||||
Items = (await _api.GetTagsAsync())??new();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private async Task Delete(Tag tag)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _api.DeleteTagAsync(tag.Id!);
|
||||
Items.Remove(tag);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _api.SaveTagAsync(Item);
|
||||
if (!Items.Contains(Item))
|
||||
{
|
||||
Items.Add(Item);
|
||||
}
|
||||
Item = new Tag();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private Task Select(Tag tag)
|
||||
{
|
||||
try
|
||||
{
|
||||
Item = tag;
|
||||
}
|
||||
catch { }
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
11
Chapter06/MyBlog/Components/Pages/AlertTest.razor
Normal file
11
Chapter06/MyBlog/Components/Pages/AlertTest.razor
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
@page "/alerttest"
|
||||
@using Components.RazorComponents
|
||||
<Alert Style="Alert.AlertStyle.Danger">
|
||||
This is a test
|
||||
</Alert>
|
||||
<Alert Style="Alert.AlertStyle.Success">
|
||||
<ChildContent>
|
||||
This is another test
|
||||
</ChildContent>
|
||||
</Alert>
|
||||
<Alert Style="Alert.AlertStyle.Success"/>
|
||||
27
Chapter06/MyBlog/Components/Pages/Bind-after.razor
Normal file
27
Chapter06/MyBlog/Components/Pages/Bind-after.razor
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
@page "/bind-after"
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
|
||||
<h1>Bind After Examples</h1>
|
||||
|
||||
<h2>Elements</h2>
|
||||
|
||||
<input type="text" @bind="text" @bind:after="() => { }" />
|
||||
|
||||
<input type="text" @bind="text" @bind:after="After" />
|
||||
|
||||
<input type="text" @bind="text" @bind:after="AfterAsync" />
|
||||
|
||||
<h2>Components</h2>
|
||||
|
||||
<InputText @bind-Value="text" @bind-Value:after="() => { }" />
|
||||
|
||||
<InputText @bind-Value="text" @bind-Value:after="After" />
|
||||
|
||||
<InputText @bind-Value="text" @bind-Value:after="AfterAsync" />
|
||||
|
||||
@code {
|
||||
private string text = "";
|
||||
|
||||
private void After() { }
|
||||
private Task AfterAsync() { return Task.CompletedTask; }
|
||||
}
|
||||
32
Chapter06/MyBlog/Components/Pages/Bind-get-set.razor
Normal file
32
Chapter06/MyBlog/Components/Pages/Bind-get-set.razor
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
@page "/bind-get-set"
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
|
||||
<h1>Bind Get Set Examples</h1>
|
||||
|
||||
<h2>Elements</h2>
|
||||
|
||||
<input type="text" @bind:get="text" @bind:set="(value) => { text = value; }" />
|
||||
|
||||
<input type="text" @bind:get="text" @bind:set="Set" />
|
||||
|
||||
<input type="text" @bind:get="text" @bind:set="SetAsync" />
|
||||
|
||||
<h2>Components</h2>
|
||||
|
||||
|
||||
<InputText @bind-Value="text" />
|
||||
|
||||
<InputText @bind-Value:get="text" @bind-Value:set="(value) => {text=value; }" />
|
||||
<InputText @bind-Value:get="text" @bind-Value:set="Set" />
|
||||
<InputText @bind-Value:get="text" @bind-Value:set="SetAsync" />
|
||||
|
||||
@code {
|
||||
private string text = "";
|
||||
|
||||
private void Set(string value) { text = value; }
|
||||
private Task SetAsync(string value)
|
||||
{
|
||||
text = value;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
18
Chapter06/MyBlog/Components/Pages/Counter.razor
Normal file
18
Chapter06/MyBlog/Components/Pages/Counter.razor
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
@page "/counter"
|
||||
|
||||
<PageTitle>Counter</PageTitle>
|
||||
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p role="status">Current count: @currentCount</p>
|
||||
|
||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
||||
|
||||
@code {
|
||||
private int currentCount = 0;
|
||||
|
||||
private void IncrementCount()
|
||||
{
|
||||
currentCount++;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
@page "/counterwithparameter"
|
||||
<h1>Counter</h1>
|
||||
<p>Current count: @CurrentCount</p>
|
||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
||||
@code {
|
||||
[Parameter]
|
||||
public int IncrementAmount { get; set; } = 1;
|
||||
[Parameter]
|
||||
public int CurrentCount { get; set; } = 0;
|
||||
//private void IncrementCount()
|
||||
//{
|
||||
// CurrentCount+=IncrementAmount;
|
||||
//}
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<int> CurrentCountChanged { get; set; }
|
||||
private void IncrementCount()
|
||||
{
|
||||
CurrentCount += IncrementAmount;
|
||||
CurrentCountChanged.InvokeAsync(CurrentCount);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<h3>ComponentWithError</h3>
|
||||
|
||||
|
||||
<button @onclick="@(()=>{ throw new Exception("This is an error");})">Throw error</button>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
@page "/customerrorboundary"
|
||||
<ErrorBoundary>
|
||||
<ChildContent>
|
||||
<ComponentWithError />
|
||||
</ChildContent>
|
||||
<ErrorContent>
|
||||
<h1 style="color: red;">Oops... something broke</h1>
|
||||
</ErrorContent>
|
||||
</ErrorBoundary>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
@page "/errorboundary"
|
||||
<ErrorBoundary>
|
||||
<ComponentWithError />
|
||||
</ErrorBoundary>
|
||||
13
Chapter06/MyBlog/Components/Pages/ErrorBoundaryTest.razor
Normal file
13
Chapter06/MyBlog/Components/Pages/ErrorBoundaryTest.razor
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
@page "/errorboundarytest"
|
||||
<ErrorBoundary Context=ex>
|
||||
<ChildContent>
|
||||
<p>@(1/zero)</p>
|
||||
</ChildContent>
|
||||
<ErrorContent>
|
||||
An error occured
|
||||
@ex.Message
|
||||
</ErrorContent>
|
||||
</ErrorBoundary>
|
||||
@code {
|
||||
int zero = 0;
|
||||
}
|
||||
30
Chapter06/MyBlog/Components/Pages/Index.razor
Normal file
30
Chapter06/MyBlog/Components/Pages/Index.razor
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
@page "/"
|
||||
@using Data.Models.Interfaces
|
||||
@using Data.Models
|
||||
@inject IBlogApi _api
|
||||
|
||||
<ul>
|
||||
<Virtualize ItemsProvider="LoadPosts" Context="p">
|
||||
<li><a href="/Post/@p.Id">@p.Title</a></li>
|
||||
</Virtualize>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
public int totalBlogposts { get; set; }
|
||||
private async ValueTask<ItemsProviderResult<BlogPost>> LoadPosts(ItemsProviderRequest request)
|
||||
{
|
||||
if (totalBlogposts == 0)
|
||||
{
|
||||
totalBlogposts = await _api.GetBlogPostCountAsync();
|
||||
}
|
||||
var numblogposts = Math.Min(request.Count, totalBlogposts - request.StartIndex);
|
||||
var blogposts = await _api.GetBlogPostsAsync(numblogposts, request.StartIndex);
|
||||
return new ItemsProviderResult<BlogPost>(blogposts, totalBlogposts);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
10
Chapter06/MyBlog/Components/Pages/ParentCounter.razor
Normal file
10
Chapter06/MyBlog/Components/Pages/ParentCounter.razor
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
@page "/parentcounter"
|
||||
<CounterWithParameterAndEvent IncrementAmount="@incrementamount" @bind-CurrentCount="currentcount"/>
|
||||
The current count is: @currentcount
|
||||
@code {
|
||||
int incrementamount = 10;
|
||||
int currentcount = 0;
|
||||
|
||||
|
||||
|
||||
}
|
||||
39
Chapter06/MyBlog/Components/Pages/Post.razor
Normal file
39
Chapter06/MyBlog/Components/Pages/Post.razor
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
@page "/post/{BlogPostId}"
|
||||
@inject IBlogApi _api
|
||||
@inject NavigationManager _navman
|
||||
@using Markdig;
|
||||
@if (BlogPost != null)
|
||||
{
|
||||
<PageTitle>@BlogPost.Title</PageTitle>
|
||||
<HeadContent>
|
||||
<meta property="og:title" content="@BlogPost.Title" />
|
||||
<meta property="og:description" content="@(new string(BlogPost.Text.Take(100).ToArray()))" />
|
||||
<meta property="og:image" content="@($"{_navman.BaseUri}/pathtoanimage.png")" />
|
||||
<meta property="og:url" content="@_navman.Uri" />
|
||||
<meta name="twitter:card" content="@(new string(BlogPost.Text.Take(100).ToArray()))" />
|
||||
</HeadContent>
|
||||
|
||||
<h2>@BlogPost.Title</h2>
|
||||
@((MarkupString)Markdig.Markdown.ToHtml(BlogPost.Text, pipeline))
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string BlogPostId { get; set; } = default!;
|
||||
|
||||
public BlogPost? BlogPost { get; set; }
|
||||
protected async override Task OnParametersSetAsync()
|
||||
{
|
||||
BlogPost = await _api.GetBlogPostAsync(BlogPostId);
|
||||
await base.OnParametersSetAsync();
|
||||
}
|
||||
MarkdownPipeline pipeline;
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
pipeline = new MarkdownPipelineBuilder()
|
||||
.UseEmojiAndSmiley()
|
||||
.Build();
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
}
|
||||
8
Chapter06/MyBlog/Components/Pages/SetFocus.razor
Normal file
8
Chapter06/MyBlog/Components/Pages/SetFocus.razor
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
@page "/setfocus"
|
||||
|
||||
<input @ref="textInput" />
|
||||
<button @onclick="() => textInput.FocusAsync()">Set focus</button>
|
||||
|
||||
@code {
|
||||
ElementReference textInput;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue