Issue #2
Building Blazor Image Gallery
Description: This tutorial will guide you step by step through creating a complete Blazor application that includes many features that would be needed for a real world site, including:
1. Creating a Blazor project from scratch
2. Creating a SQL Server Database with two tables: Artist, Image
3. Creating some images with my open source project Random Art
4. Building the data tier with DataTier.Net and execute the generated stored procedures
5. Uploading images with DataJuggler.Blazor.FileUpload
5. Uploading images with DataJuggler.Blazor.FileUpload
6. Create a SignUp Component to allow new artists to join, including uploading a profile picture
7. Create a Login Component to login existing users with option to store email address and password
8. Create an Artist List Viewer component
9. Create an Image List Viewer component
10. Create an Image Button component to display and select images
11. Load and display images for an artist when selected
12. Display a larger image when selected by changing the Scale property
13. Use BlazorStyled to dynamically change CSS values
14. Use paging to display artists 1 - 5, 6 - 10, etc.
I built this project because I wanted to make sure I could build a real world application, before I started charging a client I wanted to find out what I can and cannot do.
I left out a few things that I may cover in a future tutorial, such as:
1. Forgot Password / Recovery Email
2. I didn't add paging to the Gallery; I capped it at 15 images per artist for now
3. Possibly a shopping cart and / or download image functionality
4. I didn't create any way to delete an image yet.
5. Edit Profile / Change Profile Picture
All these things will make a good Part II, but I wanted to keep version 1 simple.
What you will need:
Visual Studio Version 16.4 or higher. I am using 16.4.5
SQL Server / Express Newer the better. I am using SQL Server Express 2017
Hopefully you are familiar with C# and SQL, but if not you are in the right place to learn.
Here is a sample of the tutorial we are building:
7. Create a Login Component to login existing users with option to store email address and password
8. Create an Artist List Viewer component
9. Create an Image List Viewer component
10. Create an Image Button component to display and select images
11. Load and display images for an artist when selected
12. Display a larger image when selected by changing the Scale property
13. Use BlazorStyled to dynamically change CSS values
14. Use paging to display artists 1 - 5, 6 - 10, etc.
I built this project because I wanted to make sure I could build a real world application, before I started charging a client I wanted to find out what I can and cannot do.
I left out a few things that I may cover in a future tutorial, such as:
1. Forgot Password / Recovery Email
2. I didn't add paging to the Gallery; I capped it at 15 images per artist for now
3. Possibly a shopping cart and / or download image functionality
4. I didn't create any way to delete an image yet.
5. Edit Profile / Change Profile Picture
All these things will make a good Part II, but I wanted to keep version 1 simple.
What you will need:
Visual Studio Version 16.4 or higher. I am using 16.4.5
SQL Server / Express Newer the better. I am using SQL Server Express 2017
Hopefully you are familiar with C# and SQL, but if not you are in the right place to learn.
Here is a sample of the tutorial we are building:
If you want to clone the working version, it is available here:
You will still need to setup up the SQL Server Database, scroll down for Part II.
Part I: Create a new Blazor Project
Step 1: Open Visual Studio and click 'Create New Project
Step 2: Select Blazor App and click Next.
Step 3: Configure Your Project details and click the Create button.
Step 4: Leave everything as is. ASP.NET Core 3.0 and Blazor Server App will already be selected.
Click Create.
As the note on the screen shot mentions, we have to leave it as ASP.NET Core 3.0 or Visual Studio creates a class library for some reason.
Step 5: After your project is created, expand the data folder and delete the two files:
Step 6: Build your project and you will encounter four errors:
Expand the Pages folder, and select the files FetchData.razor & Counter.razor
Right Click > Select Delete.
Next, double click on Startup.cs to open it.
Remove line 12:
using BlazorImageGallery.Data;
Remove line 30:
services.AddSingleton<WeatherForecastService>();
Build your project again and it should compile.
Step 7: Delete NavMenu.razor
Step 8: Right click the BlazorImageGallery project and select Properties.
In project Properties, change the Target Framework to Dot Net Core 3.1
Right click on the tab Blazor Image Gallery > Select Close to close Project Properties.
This concludes part 1.
Part II: Create SQL Database & DataTier
In SQL Server Management Studio create a new database named BlazorImageGallery.
Execute the following SQL Script to create the tables and stored procedures:
Click the View Raw button at the bottom of the SQL, then click Control + A to select all followed by Control + C to copy to your clipboard.
In SQL Server Management Studio click the New Query button, and paste in the copied text.
Click the execute button to create the tables and stored procedures.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
USE [BlazorImageGallery] | |
GO | |
/****** Object: Table [dbo].[Artist] Script Date: 2/13/2020 10:59:00 PM ******/ | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
CREATE TABLE [dbo].[Artist]( | |
[Id] [int] IDENTITY(1,1) NOT NULL, | |
[Name] [nvarchar](40) NOT NULL, | |
[EmailAddress] [nvarchar](80) NOT NULL, | |
[PasswordHash] [nvarchar](255) NOT NULL, | |
[ProfilePicture] [nvarchar](255) NULL, | |
[FolderPath] [nvarchar](255) NOT NULL, | |
[ImagesCount] [int] NOT NULL, | |
[CreatedDate] [datetime] NOT NULL, | |
[LastUpdated] [datetime] NOT NULL, | |
[IsAdmin] [bit] NOT NULL, | |
[Active] [bit] NOT NULL, | |
CONSTRAINT [PK_Artist] PRIMARY KEY CLUSTERED | |
( | |
[Id] ASC | |
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] | |
) ON [PRIMARY] | |
GO | |
/****** Object: Table [dbo].[Image] Script Date: 2/13/2020 10:59:00 PM ******/ | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
CREATE TABLE [dbo].[Image]( | |
[Id] [int] IDENTITY(1,1) NOT NULL, | |
[Name] [nvarchar](40) NOT NULL, | |
[FullPath] [nvarchar](512) NOT NULL, | |
[Extension] [nvarchar](10) NOT NULL, | |
[ImageUrl] [nvarchar](255) NOT NULL, | |
[SitePath] [nvarchar](255) NOT NULL, | |
[OwnerId] [int] NOT NULL, | |
[FileSize] [int] NOT NULL, | |
[Height] [int] NOT NULL, | |
[Width] [int] NOT NULL, | |
[CreatedDate] [datetime] NOT NULL, | |
[Visible] [bit] NOT NULL, | |
[ImageNumber] [int] NULL, | |
CONSTRAINT [PK_Image] PRIMARY KEY CLUSTERED | |
( | |
[Id] ASC | |
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] | |
) ON [PRIMARY] | |
GO | |
ALTER TABLE [dbo].[Artist] ADD CONSTRAINT [DF_Artist_Active] DEFAULT ((1)) FOR [Active] | |
GO | |
ALTER TABLE [dbo].[Image] ADD CONSTRAINT [DF_Image_Visible] DEFAULT ((1)) FOR [Visible] | |
GO | |
/****** Object: StoredProcedure [dbo].[Artist_Delete] Script Date: 2/13/2020 10:59:00 PM ******/ | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
Create PROCEDURE [dbo].[Artist_Delete] | |
-- Primary Key Paramater | |
@Id int | |
AS | |
BEGIN | |
-- SET NOCOUNT ON added to prevent extra result sets from | |
-- interfering with SELECT statements. | |
SET NOCOUNT ON | |
-- Begin Delete Statement | |
Delete From [Artist] | |
-- Delete Matching Record | |
Where [Id] = @Id | |
END | |
set ANSI_NULLS ON | |
set QUOTED_IDENTIFIER ON | |
GO | |
/****** Object: StoredProcedure [dbo].[Artist_FetchAll] Script Date: 2/13/2020 10:59:00 PM ******/ | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
Create PROCEDURE [dbo].[Artist_FetchAll] | |
AS | |
BEGIN | |
-- SET NOCOUNT ON added to prevent extra result sets from | |
-- interfering with SELECT statements. | |
SET NOCOUNT ON | |
-- Begin Select Statement | |
Select [Active],[CreatedDate],[EmailAddress],[FolderPath],[Id],[ImagesCount],[IsAdmin],[LastUpdated],[Name],[PasswordHash],[ProfilePicture] | |
-- From tableName | |
From [Artist] | |
END | |
set ANSI_NULLS ON | |
set QUOTED_IDENTIFIER ON | |
GO | |
/****** Object: StoredProcedure [dbo].[Artist_Find] Script Date: 2/13/2020 10:59:00 PM ******/ | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
Create PROCEDURE [dbo].[Artist_Find] | |
-- Primary Key Paramater | |
@Id int | |
AS | |
BEGIN | |
-- SET NOCOUNT ON added to prevent extra result sets from | |
-- interfering with SELECT statements. | |
SET NOCOUNT ON | |
-- Begin Select Statement | |
Select [Active],[CreatedDate],[EmailAddress],[FolderPath],[Id],[ImagesCount],[IsAdmin],[LastUpdated],[Name],[PasswordHash],[ProfilePicture] | |
-- From tableName | |
From [Artist] | |
-- Find Matching Record | |
Where [Id] = @Id | |
END | |
set ANSI_NULLS ON | |
set QUOTED_IDENTIFIER ON | |
GO | |
/****** Object: StoredProcedure [dbo].[Artist_FindByEmailAddress] Script Date: 2/13/2020 10:59:00 PM ******/ | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
Create PROCEDURE [dbo].[Artist_FindByEmailAddress] | |
-- Create @EmailAddress Paramater | |
@EmailAddress nvarchar(80) | |
AS | |
BEGIN | |
-- SET NOCOUNT ON added to prevent extra result sets from | |
-- interfering with SELECT statements. | |
SET NOCOUNT ON | |
-- Begin Select Statement | |
Select [Active],[CreatedDate],[EmailAddress],[FolderPath],[Id],[ImagesCount],[IsAdmin],[LastUpdated],[Name],[PasswordHash],[ProfilePicture] | |
-- From tableName | |
From [Artist] | |
-- Find Matching Record | |
Where [EmailAddress] = @EmailAddress | |
END | |
set ANSI_NULLS ON | |
set QUOTED_IDENTIFIER ON | |
GO | |
/****** Object: StoredProcedure [dbo].[Artist_Insert] Script Date: 2/13/2020 10:59:00 PM ******/ | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
Create PROCEDURE [dbo].[Artist_Insert] | |
-- Add the parameters for the stored procedure here | |
@Active bit, | |
@CreatedDate datetime, | |
@EmailAddress nvarchar(80), | |
@FolderPath nvarchar(255), | |
@ImagesCount int, | |
@IsAdmin bit, | |
@LastUpdated datetime, | |
@Name nvarchar(40), | |
@PasswordHash nvarchar(255), | |
@ProfilePicture nvarchar(255) | |
AS | |
BEGIN | |
-- SET NOCOUNT ON added to prevent extra result sets from | |
-- interfering with SELECT statements. | |
SET NOCOUNT ON | |
-- Begin Insert Statement | |
Insert Into [Artist] | |
([Active],[CreatedDate],[EmailAddress],[FolderPath],[ImagesCount],[IsAdmin],[LastUpdated],[Name],[PasswordHash],[ProfilePicture]) | |
-- Begin Values List | |
Values(@Active, @CreatedDate, @EmailAddress, @FolderPath, @ImagesCount, @IsAdmin, @LastUpdated, @Name, @PasswordHash, @ProfilePicture) | |
-- Return ID of new record | |
SELECT SCOPE_IDENTITY() | |
END | |
set ANSI_NULLS ON | |
set QUOTED_IDENTIFIER ON | |
GO | |
/****** Object: StoredProcedure [dbo].[Artist_Update] Script Date: 2/13/2020 10:59:00 PM ******/ | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
Create PROCEDURE [dbo].[Artist_Update] | |
-- Add the parameters for the stored procedure here | |
@Active bit, | |
@CreatedDate datetime, | |
@EmailAddress nvarchar(80), | |
@FolderPath nvarchar(255), | |
@Id int, | |
@ImagesCount int, | |
@IsAdmin bit, | |
@LastUpdated datetime, | |
@Name nvarchar(40), | |
@PasswordHash nvarchar(255), | |
@ProfilePicture nvarchar(255) | |
AS | |
BEGIN | |
-- SET NOCOUNT ON added to prevent extra result sets from | |
-- interfering with SELECT statements. | |
SET NOCOUNT ON | |
-- Begin Update Statement | |
Update [Artist] | |
-- Update Each field | |
Set [Active] = @Active, | |
[CreatedDate] = @CreatedDate, | |
[EmailAddress] = @EmailAddress, | |
[FolderPath] = @FolderPath, | |
[ImagesCount] = @ImagesCount, | |
[IsAdmin] = @IsAdmin, | |
[LastUpdated] = @LastUpdated, | |
[Name] = @Name, | |
[PasswordHash] = @PasswordHash, | |
[ProfilePicture] = @ProfilePicture | |
-- Update Matching Record | |
Where [Id] = @Id | |
END | |
set ANSI_NULLS ON | |
set QUOTED_IDENTIFIER ON | |
GO | |
/****** Object: StoredProcedure [dbo].[Image_Delete] Script Date: 2/13/2020 10:59:00 PM ******/ | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
Create PROCEDURE [dbo].[Image_Delete] | |
-- Primary Key Paramater | |
@Id int | |
AS | |
BEGIN | |
-- SET NOCOUNT ON added to prevent extra result sets from | |
-- interfering with SELECT statements. | |
SET NOCOUNT ON | |
-- Begin Delete Statement | |
Delete From [Image] | |
-- Delete Matching Record | |
Where [Id] = @Id | |
END | |
set ANSI_NULLS ON | |
set QUOTED_IDENTIFIER ON | |
GO | |
/****** Object: StoredProcedure [dbo].[Image_FetchAll] Script Date: 2/13/2020 10:59:00 PM ******/ | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
Create PROCEDURE [dbo].[Image_FetchAll] | |
AS | |
BEGIN | |
-- SET NOCOUNT ON added to prevent extra result sets from | |
-- interfering with SELECT statements. | |
SET NOCOUNT ON | |
-- Begin Select Statement | |
Select [CreatedDate],[Extension],[FileSize],[FullPath],[Height],[Id],[ImageNumber],[ImageUrl],[Name],[OwnerId],[SitePath],[Visible],[Width] | |
-- From tableName | |
From [Image] | |
END | |
-- Begin Custom Methods | |
set ANSI_NULLS ON | |
set QUOTED_IDENTIFIER ON | |
GO | |
/****** Object: StoredProcedure [dbo].[Image_FetchAllForOwnerId] Script Date: 2/13/2020 10:59:00 PM ******/ | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
CREATE PROCEDURE [dbo].[Image_FetchAllForOwnerId] | |
-- Create @OwnerId Paramater | |
@OwnerId int | |
AS | |
BEGIN | |
-- SET NOCOUNT ON added to prevent extra result sets from | |
-- interfering with SELECT statements. | |
SET NOCOUNT ON | |
-- Begin Select Statement | |
Select [CreatedDate],[Extension],[FileSize],[FullPath],[Height],[Id],[ImageNumber],[ImageUrl],[Name],[OwnerId],[SitePath],[Visible],[Width] | |
-- From tableName | |
From [Image] | |
-- Load Matching Records | |
Where [OwnerId] = @OwnerId | |
-- Order By ImageNumber | |
Order By ImageNumber | |
END | |
-- End Custom Methods | |
-- Thank you for using DataTier.Net. | |
GO | |
/****** Object: StoredProcedure [dbo].[Image_Find] Script Date: 2/13/2020 10:59:00 PM ******/ | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
Create PROCEDURE [dbo].[Image_Find] | |
-- Primary Key Paramater | |
@Id int | |
AS | |
BEGIN | |
-- SET NOCOUNT ON added to prevent extra result sets from | |
-- interfering with SELECT statements. | |
SET NOCOUNT ON | |
-- Begin Select Statement | |
Select [CreatedDate],[Extension],[FileSize],[FullPath],[Height],[Id],[ImageNumber],[ImageUrl],[Name],[OwnerId],[SitePath],[Visible],[Width] | |
-- From tableName | |
From [Image] | |
-- Find Matching Record | |
Where [Id] = @Id | |
END | |
set ANSI_NULLS ON | |
set QUOTED_IDENTIFIER ON | |
GO | |
/****** Object: StoredProcedure [dbo].[Image_Insert] Script Date: 2/13/2020 10:59:00 PM ******/ | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
Create PROCEDURE [dbo].[Image_Insert] | |
-- Add the parameters for the stored procedure here | |
@CreatedDate datetime, | |
@Extension nvarchar(10), | |
@FileSize int, | |
@FullPath nvarchar(512), | |
@Height int, | |
@ImageNumber int, | |
@ImageUrl nvarchar(255), | |
@Name nvarchar(40), | |
@OwnerId int, | |
@SitePath nvarchar(255), | |
@Visible bit, | |
@Width int | |
AS | |
BEGIN | |
-- SET NOCOUNT ON added to prevent extra result sets from | |
-- interfering with SELECT statements. | |
SET NOCOUNT ON | |
-- Begin Insert Statement | |
Insert Into [Image] | |
([CreatedDate],[Extension],[FileSize],[FullPath],[Height],[ImageNumber],[ImageUrl],[Name],[OwnerId],[SitePath],[Visible],[Width]) | |
-- Begin Values List | |
Values(@CreatedDate, @Extension, @FileSize, @FullPath, @Height, @ImageNumber, @ImageUrl, @Name, @OwnerId, @SitePath, @Visible, @Width) | |
-- Return ID of new record | |
SELECT SCOPE_IDENTITY() | |
END | |
set ANSI_NULLS ON | |
set QUOTED_IDENTIFIER ON | |
GO | |
/****** Object: StoredProcedure [dbo].[Image_Update] Script Date: 2/13/2020 10:59:00 PM ******/ | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
Create PROCEDURE [dbo].[Image_Update] | |
-- Add the parameters for the stored procedure here | |
@CreatedDate datetime, | |
@Extension nvarchar(10), | |
@FileSize int, | |
@FullPath nvarchar(512), | |
@Height int, | |
@Id int, | |
@ImageNumber int, | |
@ImageUrl nvarchar(255), | |
@Name nvarchar(40), | |
@OwnerId int, | |
@SitePath nvarchar(255), | |
@Visible bit, | |
@Width int | |
AS | |
BEGIN | |
-- SET NOCOUNT ON added to prevent extra result sets from | |
-- interfering with SELECT statements. | |
SET NOCOUNT ON | |
-- Begin Update Statement | |
Update [Image] | |
-- Update Each field | |
Set [CreatedDate] = @CreatedDate, | |
[Extension] = @Extension, | |
[FileSize] = @FileSize, | |
[FullPath] = @FullPath, | |
[Height] = @Height, | |
[ImageNumber] = @ImageNumber, | |
[ImageUrl] = @ImageUrl, | |
[Name] = @Name, | |
[OwnerId] = @OwnerId, | |
[SitePath] = @SitePath, | |
[Visible] = @Visible, | |
[Width] = @Width | |
-- Update Matching Record | |
Where [Id] = @Id | |
END | |
set ANSI_NULLS ON | |
set QUOTED_IDENTIFIER ON | |
GO |
Next download the data tier and save it to a temp folder.
Download DataTier
Right click the downloaded file and click Properties
Check the box at the bottom for Unblock and hit Apply.
Next extract the contents of the zip file, and copy the contents to the Data folder in the BlazorImageGallery project.
Next right click the Data folder and select 'Exclude From Project'.
Now right click the root of your solution and select 'New Solution folder'.
Set the name of the folder to Data
Right click the new Data folder, and select 'Add Existing Project'.
Browse to each of the four projects in your Data folder:
Select the Dependencies folder of the BlazorImageGallery project and select 'Add Reference'.
Add references to the ObjectLibrary and DataGateway projects.
Build the solution to make sure everything compiles.
Create ConnectionString
We must build a connectionstring and then create an environment variable to hold it.
My open source project DataTier.Net comes with a ConnectionStringBuilder project, located in the Tools folder: https://github.com/DataJuggler/DataTier.Net.git
![]() |
Connection String Builder |
Or
Manually Create ConnectionString
Windows Authentication
Data Source=[ServerName];Initial Catalog=BlazorImageGallery;Integrated Security=True
SQL Authentication
Data Source=[ServerName];Initial Catalog=BlazorImageGallery;Integrated Security=False;User ID=[UserName];Password=[Password]
Create System Environment Variable
In the Windows 10 search box, start type Edit System Environment Variables
Click the Edit System Environment Variables button.
In the System Environment Variables section at the bottom, click 'New'.
Environment Variable Properties
Name: BlazorImageGallery
Value: (Paste In Your Connection String)
This concludes part 2.
Part III: Complete Web Project
In the final step we are to complete the following tasks:
1. Modify MainLayout.razor
2. Add Nuget packages
3. Download wwwroot content and copy to web folder
4. Create Components Or Download Components.zip
5. Modify Index Page
6. Modify Startup.cs
7. Modify Imports.razor
8. Create GalleryManager class
9. Create Models
10. Download Images for Gallery
11. Run Program / Create Artists / Galleries
12. Test everything works
Modify MainLayout.razor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@inherits LayoutComponentBase | |
<div class="main"> | |
<div class="content px-4"> | |
@Body | |
</div> | |
</div> |
Add Nuget Packages
![]() |
Screenshot is from the completed project |
Add the following Nuget packages
1. DataJuggler.Core.Cryptography
2. DataJuggler.Blazor.Components *
3. DataJuggler.Blazor.FileUpload **
4. Microsoft.AspNetCore.ProtectedBrowserStorage
* Will Install BlazorStyled
** Will install DataJuggler.UltimateHelper.Core
Download wwwroot Content
Download the wwwrootcontent.zip file, save and extract it to a temp folder.
Copy and replace site.css with the existing file wwwroot/css/site.css.
Copy and replace favicon.ico with the existing favicon.css
Copy the Images folder to below the wwwroot folder.
The end result should look like this:
Create Components
Originally I planned creating ten files and pasting in the code for each, but to save a lot of time I created a zip file of the components. Create a new folder named Components and then download extract the contents to a temp folder. Paste the extracted files into the new Components directory. Visual Studio automatically includes the files in the project.
Modify Index Page
Expand the Pages folder and double click on the Index.razor.
Index.razor
Hit Control + A to select all followed by the backspace to erase the existing code.
Select the Pages folder, and create a new class named Index.razor.cs.
Hit Control + A to select all followed by the backspace to erase the existing code.
Paste in the following code to the corresponding file for Index.razor & Index.razor.cs:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@page "/" | |
@using BlazorImageGallery.Components | |
@using BlazorImageGallery.Util | |
<div class="message"> | |
@message | |
</div> | |
<div class="logincontrol"> | |
<Login OnLogin="LoginComplete" Parent=this></Login> | |
</div> | |
<CascadingValue Value="GalleryManager"> | |
<ArtistListViewer></ArtistListViewer> | |
<ImageListViewer></ImageListViewer> | |
</CascadingValue> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#region using statements | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Threading.Tasks; | |
using System.Threading; | |
using ObjectLibrary.BusinessObjects; | |
using ObjectLibrary.Models; | |
using DataJuggler.UltimateHelper.Core; | |
using BlazorImageGallery.Util; | |
using DataGateway.Services; | |
using DataJuggler.Blazor.Components; | |
using BlazorImageGallery.Components; | |
using DataJuggler.Blazor.Components.Interfaces; | |
using Microsoft.AspNetCore.Components; | |
#endregion | |
namespace BlazorImageGallery.Pages | |
{ | |
#region class Index | |
/// <summary> | |
/// This class is the code behind for the Index page | |
/// </summary> | |
public partial class Index : IBlazorComponentParent | |
{ | |
#region Private Variables | |
private Artist artist; | |
private GalleryManager galleryManager; | |
private int artistId; | |
private List<IBlazorComponent> children; | |
private Login login; | |
private string passwordHash; | |
private string message; | |
private bool loginOrSignUpInProgress; | |
#endregion | |
#region Constructor | |
/// <summary> | |
/// Create a new instance of an Index page object | |
/// </summary> | |
public Index() | |
{ | |
// Perform initializations for this object | |
Init(); | |
} | |
#endregion | |
#region Methods | |
#region FindChildByName(string name) | |
/// <summary> | |
/// This method returns the Child Component By Name | |
/// </summary> | |
public IBlazorComponent FindChildByName(string name) | |
{ | |
// initial value | |
IBlazorComponent child = null; | |
// if the value for HasChildren is true | |
if ((HasChildren) && (!String.IsNullOrEmpty(name))) | |
{ | |
// Iterate the collection of IBlazorComponent objects | |
foreach (IBlazorComponent childComponent in Children) | |
{ | |
// if this is the item being sought | |
if (childComponent.Name == name) | |
{ | |
// set the return value | |
child = childComponent; | |
// break out of the loop | |
break; | |
} | |
} | |
} | |
// return value | |
return child; | |
} | |
#endregion | |
#region Init() | |
/// <summary> | |
/// This method performs initializations for this object. | |
/// </summary> | |
public void Init() | |
{ | |
// Create a new collection of 'IBlazorComponent' objects. | |
this.Children = new List<IBlazorComponent>(); | |
} | |
#endregion | |
#region LoadImagesForArtist(int artistId) | |
/// <summary> | |
/// This method returns a list of Images For Artist | |
/// </summary> | |
public async Task<List<Image>> LoadImagesForArtist(int artistId) | |
{ | |
// initial value | |
List<Image> images = null; | |
// initial value | |
images = await ImageService.GetImageList(artistId); | |
// return the list | |
return images; | |
} | |
#endregion | |
#region LoginComplete(LoginResponse loginResponse) | |
/// <summary> | |
/// This method is called by the Login control after a login attempt. | |
/// </summary> | |
/// <param name="loginResponse"></param> | |
private async void LoginComplete(LoginResponse loginResponse) | |
{ | |
// If the loginResponse object exists | |
if (NullHelper.Exists(loginResponse)) | |
{ | |
// if the login was successful | |
if (loginResponse.Success) | |
{ | |
// Erase any messages | |
this.Message = ""; | |
// Set the artist | |
this.Artist = loginResponse.Artist; | |
// if we do not have a Gallerymanager | |
if (!HasGalleryManager) | |
{ | |
// Create a new instance of a 'GalleryManager' object. | |
this.GalleryManager = new GalleryManager(this); | |
} | |
// Set the Artist | |
this.GalleryManager.Artist = this.Artist; | |
// Load the Artists in case this is a new artist | |
this.GalleryManager.Artists = await ArtistService.GetArtistList(); | |
// Call Refresh | |
Refresh(); | |
} | |
} | |
} | |
#endregion | |
#region OnInitializedAsync() | |
/// <summary> | |
/// This method is used to restored values stored in local storage | |
/// </summary> | |
protected override async Task OnInitializedAsync() | |
{ | |
// Create the GalleryManager | |
this.GalleryManager = new GalleryManager(this); | |
// Load the Artists | |
this.GalleryManager.Artists = await ArtistService.GetArtistList(); | |
} | |
#endregion | |
#region ReceiveData(Message message) | |
/// <summary> | |
/// This method Receives Data from a child or parent component | |
/// </summary> | |
public void ReceiveData(Message message) | |
{ | |
// If the message object exists | |
if (NullHelper.Exists(message)) | |
{ | |
// if a NewArtist signed up | |
if (message.Text == "Artist Logged In") | |
{ | |
// if the parameters collection exists | |
if (message.HasParameters) | |
{ | |
// iterate the parameters | |
foreach (NamedParameter parameter in message.Parameters) | |
{ | |
// if this is the name | |
if (parameter.Name == "Artist") | |
{ | |
// Get the login response | |
LoginResponse loginResponse = parameter.Value as LoginResponse; | |
// If the loginResponse object exists | |
if (NullHelper.Exists(loginResponse)) | |
{ | |
// Update the UI that we have a login | |
LoginComplete(loginResponse); | |
} | |
} | |
} | |
} | |
} | |
else | |
{ | |
// Set the message text | |
this.Message = message.Text; | |
// Update the UI | |
Refresh(); | |
} | |
} | |
} | |
#endregion | |
#region Refresh() | |
/// <summary> | |
/// This method is called by a Sprite when as it refreshes. | |
/// </summary> | |
public void Refresh() | |
{ | |
// Update the UI | |
InvokeAsync(() => | |
{ | |
StateHasChanged(); | |
}); | |
} | |
#endregion | |
#region Register(IBlazorComponent component) | |
/// <summary> | |
/// This method Registers the component with this component. | |
/// </summary> | |
public void Register(IBlazorComponent component) | |
{ | |
// If the component object exists | |
if (NullHelper.Exists(component, Children)) | |
{ | |
// If this is the Login component | |
if (component.Name == "Login") | |
{ | |
// Set the Signup control | |
this.Login = component as Login; | |
} | |
// add this child | |
Children.Add(component); | |
} | |
} | |
#endregion | |
#endregion | |
#region Properties | |
#region Artist | |
/// <summary> | |
/// This property gets or sets the value for 'Artist'. | |
/// </summary> | |
public Artist Artist | |
{ | |
get { return artist; } | |
set { artist = value; } | |
} | |
#endregion | |
#region ArtistId | |
/// <summary> | |
/// This property gets or sets the value for 'ArtistId'. | |
/// </summary> | |
[Parameter] | |
public int ArtistId | |
{ | |
get { return artistId; } | |
set { artistId = value; } | |
} | |
#endregion | |
#region Children | |
/// <summary> | |
/// This property gets or sets the value for 'Children'. | |
/// </summary> | |
public List<IBlazorComponent> Children | |
{ | |
get { return children; } | |
set { children = value; } | |
} | |
#endregion | |
#region GalleryManager | |
/// <summary> | |
/// This property gets or sets the value for 'GalleryManager'. | |
/// </summary> | |
public GalleryManager GalleryManager | |
{ | |
get { return galleryManager; } | |
set { galleryManager = value; } | |
} | |
#endregion | |
#region HasArtist | |
/// <summary> | |
/// This property returns true if this object has an 'Artist'. | |
/// </summary> | |
public bool HasArtist | |
{ | |
get | |
{ | |
// initial value | |
bool hasArtist = (this.Artist != null); | |
// return value | |
return hasArtist; | |
} | |
} | |
#endregion | |
#region HasChildren | |
/// <summary> | |
/// This property returns true if this object has a 'Children'. | |
/// </summary> | |
public bool HasChildren | |
{ | |
get | |
{ | |
// initial value | |
bool hasChildren = (this.Children != null); | |
// return value | |
return hasChildren; | |
} | |
} | |
#endregion | |
#region HasGalleryManager | |
/// <summary> | |
/// This property returns true if this object has a 'GalleryManager'. | |
/// </summary> | |
public bool HasGalleryManager | |
{ | |
get | |
{ | |
// initial value | |
bool hasGalleryManager = (this.GalleryManager != null); | |
// return value | |
return hasGalleryManager; | |
} | |
} | |
#endregion | |
#region Login | |
/// <summary> | |
/// This property gets or sets the value for 'Login'. | |
/// </summary> | |
public Login Login | |
{ | |
get { return login; } | |
set { login = value; } | |
} | |
#endregion | |
#region LoginOrSignUpInProgress | |
/// <summary> | |
/// This property gets or sets the value for 'LoginOrSignUpInProgress'. | |
/// </summary> | |
public bool LoginOrSignUpInProgress | |
{ | |
get { return loginOrSignUpInProgress; } | |
set { loginOrSignUpInProgress = value; } | |
} | |
#endregion | |
#region Message | |
/// <summary> | |
/// This property gets or sets the value for 'Message'. | |
/// </summary> | |
public string Message | |
{ | |
get { return message; } | |
set { message = value; } | |
} | |
#endregion | |
#region PasswordHash | |
/// <summary> | |
/// This property gets or sets the value for 'PasswordHash'. | |
/// </summary> | |
[Parameter] | |
public string PasswordHash | |
{ | |
get { return passwordHash; } | |
set { passwordHash = value; } | |
} | |
#endregion | |
#endregion | |
} | |
#endregion | |
} |
Close both files and save them.
Modify Startup.cs
Add the following changes to the file Startup.cs
Replace the using statements with these:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using BlazorStyled; | |
using Microsoft.AspNetCore.Builder; | |
using Microsoft.AspNetCore.Hosting; | |
using Microsoft.Extensions.Configuration; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.Hosting; |
Add these two lines to the ConfigureServices method, below AddServerSideBlazor().
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
services.AddBlazorStyled(); | |
services.AddProtectedBrowserStorage(); |
Modify Imports.razor
Add these four lines to the bottom of the existing import statements:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@using BlazorStyled | |
@using DataJuggler.Blazor.Components | |
@using DataJuggler.Blazor.Components.Interfaces | |
@using DataJuggler.Blazor.FileUpload |
Create GalleryManager
Create a new folder named Util at the same level as wwwroot, Pages, etc.
Create a new class named GalleryManager.cs and replace out the existing code with:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#region using statements | |
using BlazorImageGallery.Pages; | |
using DataJuggler.UltimateHelper.Core; | |
using ObjectLibrary.BusinessObjects; | |
using System.Collections.Generic; | |
#endregion | |
namespace BlazorImageGallery.Util | |
{ | |
#region class GalleryManager | |
/// <summary> | |
/// This class is used to provide a CascadingParameter so values | |
/// such as the current Artist can be available to other components. | |
/// </summary> | |
public class GalleryManager | |
{ | |
#region Private Variables | |
private Artist artist; | |
private Artist selectedArtist; | |
private List<Artist> artists; | |
private Index indexPage; | |
private int artistPageIndex; | |
#endregion | |
#region Constructor(Index indexPage) | |
/// <summary> | |
/// Create a new instance of GalleryManager object | |
/// </summary> | |
public GalleryManager(Index indexPage) | |
{ | |
// Store the Index page | |
this.IndexPage = indexPage; | |
} | |
#endregion | |
#region Methods | |
#region FindArtistIndex(int id) | |
/// <summary> | |
/// This method returns the Artist Index | |
/// </summary> | |
public int FindArtistIndex(int id) | |
{ | |
// initial value | |
int artistIndex = -1; | |
// local | |
int tempIndex = -1; | |
// If the Artists collection exists and has one or more items | |
if (ListHelper.HasOneOrMoreItems(Artists)) | |
{ | |
foreach (Artist artist in Artists) | |
{ | |
// Increment the value for tempIndex | |
tempIndex++; | |
// if this is the Id being sought | |
if (artist.Id == id) | |
{ | |
// set the return value | |
artistIndex = tempIndex; | |
// break out of loop | |
break; | |
} | |
} | |
} | |
// return value | |
return artistIndex; | |
} | |
#endregion | |
#region FindPosition(int artistId) | |
/// <summary> | |
/// This method returns the Position | |
/// </summary> | |
public int FindPosition(int artistId) | |
{ | |
// initial value | |
int position = 0; | |
// Get the index | |
int index = FindArtistIndex(artistId); | |
// if the index was found | |
if (index >= 0) | |
{ | |
// set the return value | |
position = index % 5 + 1; | |
} | |
// return value | |
return position; | |
} | |
#endregion | |
#region SetSelectedArtist(int positionNumber, int pageIndex) | |
/// <summary> | |
/// This method Set Selected Artist | |
/// </summary> | |
public async void SetSelectedArtist(int positionNumber, int pageIndex) | |
{ | |
try | |
{ | |
// if there is a collection of selected artists | |
if (ListHelper.HasOneOrMoreItems(Artists)) | |
{ | |
// set the index | |
int index = (pageIndex * 5) + positionNumber - 1; | |
// verify this value is in range | |
if ((index >= 0) && (index < Artists.Count)) | |
{ | |
// Storing this so we can call this again | |
this.ArtistPageIndex = pageIndex; | |
// Set the SelectedArtist | |
this.SelectedArtist = Artists[index]; | |
// if the value for HasIndexPage is true | |
if ((HasIndexPage) && (HasSelectedArtist)) | |
{ | |
// load the images for this artist | |
SelectedArtist.Images = await IndexPage.LoadImagesForArtist(SelectedArtist.Id); | |
} | |
} | |
} | |
} | |
catch (System.Exception error) | |
{ | |
// for debugging only | |
DebugHelper.WriteDebugError("SetSelectedArtist", "GalleryManager", error); | |
} | |
} | |
#endregion | |
#endregion | |
#region Properties | |
#region Artist | |
/// <summary> | |
/// This property gets or sets the value for 'Artist'. | |
/// </summary> | |
public Artist Artist | |
{ | |
get { return artist; } | |
set { artist = value; } | |
} | |
#endregion | |
#region ArtistPageIndex | |
/// <summary> | |
/// This property gets or sets the value for 'ArtistPageIndex'. | |
/// </summary> | |
public int ArtistPageIndex | |
{ | |
get { return artistPageIndex; } | |
set { artistPageIndex = value; } | |
} | |
#endregion | |
#region Artists | |
/// <summary> | |
/// This property gets or sets the value for 'Artists'. | |
/// </summary> | |
public List<Artist> Artists | |
{ | |
get { return artists; } | |
set { artists = value; } | |
} | |
#endregion | |
#region HasArtist | |
/// <summary> | |
/// This property returns true if this object has an 'Artist'. | |
/// </summary> | |
public bool HasArtist | |
{ | |
get | |
{ | |
// initial value | |
bool hasArtist = (this.Artist != null); | |
// return value | |
return hasArtist; | |
} | |
} | |
#endregion | |
#region HasIndexPage | |
/// <summary> | |
/// This property returns true if this object has an 'IndexPage'. | |
/// </summary> | |
public bool HasIndexPage | |
{ | |
get | |
{ | |
// initial value | |
bool hasIndexPage = (this.IndexPage != null); | |
// return value | |
return hasIndexPage; | |
} | |
} | |
#endregion | |
#region HasSelectedArtist | |
/// <summary> | |
/// This property returns true if this object has a 'SelectedArtist'. | |
/// </summary> | |
public bool HasSelectedArtist | |
{ | |
get | |
{ | |
// initial value | |
bool hasSelectedArtist = (this.SelectedArtist != null); | |
// return value | |
return hasSelectedArtist; | |
} | |
} | |
#endregion | |
#region IndexPage | |
/// <summary> | |
/// This property gets or sets the value for 'IndexPage'. | |
/// </summary> | |
public Index IndexPage | |
{ | |
get { return indexPage; } | |
set { indexPage = value; } | |
} | |
#endregion | |
#region SelectedArtist | |
/// <summary> | |
/// This property gets or sets the value for 'SelectedArtist'. | |
/// </summary> | |
public Artist SelectedArtist | |
{ | |
get { return selectedArtist; } | |
set | |
{ | |
// set the value | |
selectedArtist = value; | |
// if the value for HasIndexPage is true | |
if (HasIndexPage) | |
{ | |
// Update the UI (to hide the | |
IndexPage.Refresh(); | |
} | |
} | |
} | |
#endregion | |
#endregion | |
} | |
#endregion | |
} |
Create Models
Create a new folder named Models at the same level as wwwroot, Pages, etc.
In the Models folder, create the following two files, and replace out the text for each.
LoginModel.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#region using statements | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Threading.Tasks; | |
using BlazorImageGallery; | |
using ObjectLibrary.Models; | |
#endregion | |
namespace BlazorImageGallery.Models | |
{ | |
#region delegate LoginFinishedCallback(LoginResponse loginResponse) | |
/// <summary> | |
/// This is the delegate to call after the login is complete | |
/// </summary> | |
/// <returns></returns> | |
public delegate void LoginFinishedCallback(LoginResponse loginResponse); | |
#endregion | |
#region class LoginModel | |
/// <summary> | |
/// This class is here so one object can be passed to another thread containing the login information | |
/// </summary> | |
public class LoginModel | |
{ | |
#region Private Variables | |
private string password; | |
private string emailAddress; | |
private string storedPasswordHash; | |
private LoginFinishedCallback onLoginComplete; | |
#endregion | |
#region Constructor | |
/// <summary> | |
/// Create a new instance of a LoginModel object and set its properties | |
/// </summary> | |
/// <param name="emailAddress"></param> | |
/// <param name="password"></param> | |
/// <param name="storedPasswordHash"></param> | |
/// <param name="onLoginFinished"></param> | |
public LoginModel(string emailAddress, string password, string storedPasswordHash, LoginFinishedCallback onLoginComplete) | |
{ | |
// store the args | |
EmailAddress = emailAddress; | |
Password = password; | |
StoredPasswordHash = storedPasswordHash; | |
OnLoginComplete = onLoginComplete; | |
} | |
#endregion | |
#region Properties | |
#region EmailAddress | |
/// <summary> | |
/// This property gets or sets the value for 'EmailAddress'. | |
/// </summary> | |
public string EmailAddress | |
{ | |
get { return emailAddress; } | |
set { emailAddress = value; } | |
} | |
#endregion | |
#region OnLoginComplete | |
/// <summary> | |
/// This property gets or sets the value for 'OnLoginComplete'. | |
/// </summary> | |
public LoginFinishedCallback OnLoginComplete | |
{ | |
get { return onLoginComplete; } | |
set { onLoginComplete = value; } | |
} | |
#endregion | |
#region Password | |
/// <summary> | |
/// This property gets or sets the value for 'Password'. | |
/// </summary> | |
public string Password | |
{ | |
get { return password; } | |
set { password = value; } | |
} | |
#endregion | |
#region StoredPasswordHash | |
/// <summary> | |
/// This property gets or sets the value for 'StoredPasswordHash'. | |
/// </summary> | |
public string StoredPasswordHash | |
{ | |
get { return storedPasswordHash; } | |
set { storedPasswordHash = value; } | |
} | |
#endregion | |
#endregion | |
} | |
#endregion | |
} |
SignUpModel.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#region using statements | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Threading.Tasks; | |
using ObjectLibrary.Models; | |
#endregion | |
namespace BlazorImageGallery.Models | |
{ | |
#region delegate SignUpFinishedCallback(LoginResponse loginResponse) | |
/// <summary> | |
/// This is the delegate to call after the login is complete | |
/// </summary> | |
/// <returns></returns> | |
public delegate void SignUpFinishedCallback(LoginResponse loginResponse); | |
#endregion | |
#region class SignUpModel | |
/// <summary> | |
/// This class is here to be able to sign up in another thread. | |
/// </summary> | |
public class SignUpModel | |
{ | |
#region Private Variables | |
private string password; | |
private string emailAddress; | |
private string displayName; | |
private string profilePictureUrl; | |
private SignUpFinishedCallback signUpFinishedCallback; | |
#endregion | |
#region Properties | |
#region DisplayName | |
/// <summary> | |
/// This property gets or sets the value for 'DisplayName'. | |
/// </summary> | |
public string DisplayName | |
{ | |
get { return displayName; } | |
set { displayName = value; } | |
} | |
#endregion | |
#region EmailAddress | |
/// <summary> | |
/// This property gets or sets the value for 'EmailAddress'. | |
/// </summary> | |
public string EmailAddress | |
{ | |
get { return emailAddress; } | |
set { emailAddress = value; } | |
} | |
#endregion | |
#region HasSignUpFinishedCallback | |
/// <summary> | |
/// This property returns true if this object has a 'SignUpFinishedCallback'. | |
/// </summary> | |
public bool HasSignUpFinishedCallback | |
{ | |
get | |
{ | |
// initial value | |
bool hasSignUpFinishedCallback = (this.SignUpFinishedCallback != null); | |
// return value | |
return hasSignUpFinishedCallback; | |
} | |
} | |
#endregion | |
#region Password | |
/// <summary> | |
/// This property gets or sets the value for 'Password'. | |
/// </summary> | |
public string Password | |
{ | |
get { return password; } | |
set { password = value; } | |
} | |
#endregion | |
#region ProfilePictureUrl | |
/// <summary> | |
/// This property gets or sets the value for 'ProfilePictureUrl'. | |
/// </summary> | |
public string ProfilePictureUrl | |
{ | |
get { return profilePictureUrl; } | |
set { profilePictureUrl = value; } | |
} | |
#endregion | |
#region SignUpFinishedCallback | |
/// <summary> | |
/// This property gets or sets the value for 'SignUpFinishedCallback'. | |
/// </summary> | |
public SignUpFinishedCallback SignUpFinishedCallback | |
{ | |
get { return signUpFinishedCallback; } | |
set { signUpFinishedCallback = value; } | |
} | |
#endregion | |
#endregion | |
} | |
#endregion | |
} |
Create Folders
We need to create two folders under wwwroot/Images
1. Artists
2. Gallery
Modify _Host.cshtml File
Under the pages folder, make the following changes to _Host.cshtml:
1. Change the title on line 10 to Blazor Image Gallery Sample
2. Paste the following 3 lines to directly above the closing </head> tag.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<link href="~/_content/DataJuggler.Blazor.Components/css/site.css" rel="stylesheet" /> | |
<link href="~/_content/DataJuggler.Blazor.FileUpload/styles.css" rel="stylesheet" /> | |
@(await Html.RenderComponentAsync<BlazorStyled.ServerSideStyled>(RenderMode.ServerPrerendered)) |
Paste these two lines directly above the link to Blazor.Server.js (line 22 and 23 after the paste)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="_content/Microsoft.AspNetCore.ProtectedBrowserStorage/protectedBrowserStorage.js"></script> | |
<script src="~/_content/BlazorInputFile/inputfile.js"></script> |
End Of Development
Now the remaining steps involve signing up artists, uploading images and testing everything works.
Download Sample Images
To make things simple I created twenty images using Random Art that you can use for testing the gallery.
Create Your Own Images
Create your own images using my open source project Random Art:
Run Program / Create Artists / Images
I am going to create enough artists to test paging for the Artist List Viewer and create some images for a few artists gallery.
This concludes this tutorial. Please let me know your opinions, thoughts, or any thing else you would like to share.
And I want to say thank you to our model Figmente Imagination for her guest appearances.