From fea48643a796134c8395b8536cc3e554d2c1a7fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mladen=20Macanovi=C4=87?= Date: Fri, 1 Jul 2022 11:33:47 +0200 Subject: [PATCH] Generate blogs from markdown files (#3922) * WIP: convert markdown to razor * Rename ExampleMarkup * Code blocks * Add more inlines * Add more page info * Use code block info for naming of example files * Delete test markdown * Persist code block handling and fix inlines * Convert BeginnersGuideToCreateBlazoriseApp blog * Improvements * Make Paths as static class * Remov unused method * Use all compilers * Generate Example files along with Code files * Contribution guide * Skip generating Code from markdown examples * Run all builders * Optimize image note --- CONTRIBUTING.md | 35 +- .../Blazorise.Docs.Compiler.csproj | 3 +- .../Blazorise.Docs.Compiler/BlogBuilder.cs | 272 +++++++++++++ .../Blazorise.Docs.Compiler/BlogMarkdown.cs | 162 ++++++++ ...xamplesMarkup.cs => CodeExamplesMarkup.cs} | 73 +--- .../Blazorise.Docs.Compiler/CodeSnippets.cs | 11 +- .../Blazorise.Docs.Compiler/MarkupBuilder.cs | 107 +++++ .../Blazorise.Docs.Compiler/Paths.cs | 12 +- .../Blazorise.Docs.Compiler/Program.cs | 9 +- .../Blazorise.Docs/Blazorise.Docs.csproj | 3 - ...oCreateBlazoriseApp_CounterExample.snippet | 2 +- ...reateBlazoriseApp_ServicesExample.snippet} | 2 +- ...ateBlazoriseApp_StaticFilesExample.snippet | 4 +- ...ToCreateBlazoriseApp_UsingsExample.snippet | 2 +- .../Index.md | 154 +++++++ .../Index.razor | 32 +- ...ionWithDataAnnotations_FormExample.snippet | 2 +- ...Annotations_MessageProviderExample.snippet | 2 +- ...WithDataAnnotations_ModelsExample.snippet} | 2 +- .../Index.md | 385 ++++++++++++++++++ .../Index.razor | 14 +- 21 files changed, 1174 insertions(+), 114 deletions(-) create mode 100644 Documentation/Blazorise.Docs.Compiler/BlogBuilder.cs create mode 100644 Documentation/Blazorise.Docs.Compiler/BlogMarkdown.cs rename Documentation/Blazorise.Docs.Compiler/{ExamplesMarkup.cs => CodeExamplesMarkup.cs} (61%) create mode 100644 Documentation/Blazorise.Docs.Compiler/MarkupBuilder.cs rename Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/{BeginnersGuideToCreateBlazoriseApp_ServicesExample.csharp => BeginnersGuideToCreateBlazoriseApp_ServicesExample.snippet} (97%) create mode 100644 Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Index.md rename Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/{ValidationWithDataAnnotations_ModelsExample.csharp => ValidationWithDataAnnotations_ModelsExample.snippet} (99%) create mode 100644 Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Index.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 79ad2b89c5..d5f63f68f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,34 @@ To get the development and test environment set up on your local machine, you ne You are now ready to build and test Blazorise: -## Running documentation +## How to Write a Community Blog? + +To write a blog post, you must create a new subfolder under the `Pages/Blog` in the `Blazorise.Docs` project. The subfolder must contain a blog date and name in the format `YYYY-MM-DD_BlogName`, e.g., `2022-06-08_BeginnersGuideToCreateBlazoriseApp`. Note that the blog name must be unique. + +Next create the `Index.md` file under the same folder. The following metadata should always be present at the beginning of the file. Once pasted, adjust them for your blog. + +``` +--- +title: How to create a Blazorise WASM application: A Beginner's Guide +description: Learn How to create a Blazorise WASM application: A Beginner's Guide. +permalink: /blog/how-to-create-a-blazorise-application-beginners-guide +canonical: /blog/how-to-create-a-blazorise-application-beginners-guide +image-url: img/blog/2022-06-08/How_to_create_a_Blazorise_application_A_Beginners_Guide.png +image-title: Blazorise WASM application: A Beginner's Guide +author-name: Mladen Macanović +author-image: mladen +posted-on: June 8th, 2022 +read-time: 5 min +--- +``` + +Most of the settings should be self-explanatory. The only one that might need some extra work is the `author-image`. The image should be placed in the `wwwroot/img/avatars` under the `Blazorise.Docs.Server` project and in the `*.png` file format. An image should be at least **512x512 px** in size and should be [optimized for minimum size](https://imagecompressor.com/). + +After you have written or added a blog content, you can try running the documentation. + +> :info: Before you start writing, please look at the **Branch Organization** to learn how we organize our work and publish the new versions. + +## Running the Documentation 1. Start a command prompt and navigate to the `\Documentation\Blazorise.Docs.Server` folder under Blazorise root. 2. Run command: `dotnet watch run`. @@ -21,14 +48,14 @@ You are now ready to build and test Blazorise: 1. Start a command prompt and run: selenium-standalone start. Note: this service needs to be running before you start Visual Studio, or test runs may fail. 2. Open the Blazorise solution at the root folder (Blazorise.sln). -3. Select Build > Build Solution on main menu. All of the projects should build sucessfully. +3. Select Build > Build Solution on main menu. All of the projects should build successfully. 4. Run all of the unit tests in the solution use Test > Run All Tests on main menu. They should all pass at this point. ## Branch Organization We use `master` branch for all development and for all new features and bug fixes that are going into the ongoing milestone. -Once we finish with the work for the current milestone we will create a new **support** branch named `rel-X.Y` where all the bug fixes will go. +Once we finish the work for the current milestone, we will create a new **support** branch named `rel-X.Y`, where all the bug fixes will go. Also, we regularly publish our web from the latest `rel-X.Y` branch, so if you plan to write a blog, this is the recommended way and place to create it. Branch naming must follow this guidelines: @@ -43,4 +70,4 @@ When submitting a pull request: 2. Create a branch from `master` or `rel-X.Y` and give it a meaningful name (e.g. `rel-{version-num}-my-awesome-new-feature`) and describe the feature or fix. 4. Open a pull request on GitHub. -> :warning: **Don't make any changes on master branch**: You must always create a feature branch by following our guidelines or we will close the PR until the changes are properly organized. +> :warning: **Don't make any changes on master branch**: You must always create a feature branch by following our guidelines or we will close the PR until the changes are properly organized. \ No newline at end of file diff --git a/Documentation/Blazorise.Docs.Compiler/Blazorise.Docs.Compiler.csproj b/Documentation/Blazorise.Docs.Compiler/Blazorise.Docs.Compiler.csproj index 89207098cb..8ce1829cac 100644 --- a/Documentation/Blazorise.Docs.Compiler/Blazorise.Docs.Compiler.csproj +++ b/Documentation/Blazorise.Docs.Compiler/Blazorise.Docs.Compiler.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,6 +7,7 @@ + diff --git a/Documentation/Blazorise.Docs.Compiler/BlogBuilder.cs b/Documentation/Blazorise.Docs.Compiler/BlogBuilder.cs new file mode 100644 index 0000000000..215d5e8e9f --- /dev/null +++ b/Documentation/Blazorise.Docs.Compiler/BlogBuilder.cs @@ -0,0 +1,272 @@ +using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using System.Text; +using ColorCode; +using Markdig.Helpers; +using Markdig.Syntax; +using Markdig.Syntax.Inlines; + +namespace Blazorise.Docs.Compiler +{ + public class BlogBuilder + { + private readonly string blogName; + private readonly string blogDirectory; + private readonly StringBuilder sb; + private const int IndentSize = 4; + private string NewLine = "\r\n"; + private int codeIndex = 0; + + public BlogBuilder( string blogName, string blogDirectory ) + { + this.blogName = blogName; + this.blogDirectory = blogDirectory; + + sb = new StringBuilder(); + } + + public void AddPageAndSeo( string url, string title, string description, string imageUrl, string imageTitle ) + { + sb.Append( $"@page \"{url}\"" ).Append( NewLine ).Append( NewLine ); + + sb.Append( $"" ).Append( NewLine ).Append( NewLine ); + + sb.Append( $"" ).Append( NewLine ).Append( NewLine ); + } + + public void AddPagePostInto( string authorName, string authorImage, string postedOn, string readTime ) + { + sb.Append( $"" ).Append( NewLine ); + } + + private void AddInlines( ContainerInline containerInline ) + { + foreach ( var inline in containerInline ) + { + if ( inline is EmphasisInline emphasisInline ) + { + if ( emphasisInline.DelimiterCount == 2 ) + sb.Append( "" ).Append( string.Join( "", emphasisInline ) ).Append( "" ); + else if ( emphasisInline.DelimiterCount == 1 ) + sb.Append( "" ).Append( string.Join( "", emphasisInline ) ).Append( "" ); + } + else if ( inline is LinkInline linkInline ) + { + var title = string.IsNullOrEmpty( linkInline.Title ) ? linkInline.FirstChild?.ToString() : linkInline.Title; + + if ( linkInline.IsImage ) + sb.Append( $"" ); + //sb.Append( $"" ).Append( linkInline.FirstChild?.ToString() ).Append( "" ); + else + sb.Append( $"" ).Append( linkInline.FirstChild?.ToString() ).Append( "" ); + } + else if ( inline is CodeInline codeInline ) + { + var content = codeInline.Content; + + if ( content.StartsWith( '<' ) && content.EndsWith( '>' ) ) + { + content = content.Trim( '<', '>' ); + + sb.Append( $"" ).Append( content ).Append( "" ); + } + else + sb.Append( $"" ).Append( content ).Append( "" ); + } + else + sb.Append( inline.ToString() ); + } + + sb.Append( NewLine ); + } + + public void AddPageTitle( HeadingBlock headingBlock ) + { + sb.Append( "" ).Append( NewLine ); + + sb.Append( "".PadLeft( IndentSize, ' ' ) ); + + if ( headingBlock.Inline != null ) + AddInlines( headingBlock.Inline ); + + sb.Append( "" ).Append( NewLine ).Append( NewLine ); + } + + public void AddPageSubtitle( HeadingBlock headingBlock ) + { + sb.Append( "" ).Append( NewLine ); + + sb.Append( "".PadLeft( IndentSize, ' ' ) ); + + if ( headingBlock.Inline != null ) + AddInlines( headingBlock.Inline ); + + sb.Append( "" ).Append( NewLine ).Append( NewLine ); + } + + public void AddPageParagraph( ParagraphBlock paragraphBlock ) + { + if ( paragraphBlock.Inline == null ) + return; + + if ( paragraphBlock.Inline.FirstChild is LinkInline linkInline && linkInline.IsImage ) + { + var title = string.IsNullOrEmpty( linkInline.Title ) ? linkInline.FirstChild?.ToString() : linkInline.Title; + + sb.Append( $"" ).Append( NewLine ).Append( NewLine ); + } + else + { + sb.Append( "" ).Append( NewLine ); + + sb.Append( "".PadLeft( IndentSize, ' ' ) ); + + if ( paragraphBlock.Inline != null ) + AddInlines( paragraphBlock.Inline ); + + sb.Append( "" ).Append( NewLine ).Append( NewLine ); + } + } + + public void AddPageQuote( QuoteBlock quoteBlock ) + { + foreach ( var block in quoteBlock ) + { + if ( block is ParagraphBlock paragraphBlock ) + { + sb.Append( "" ).Append( NewLine ); + + sb.Append( "".PadLeft( IndentSize, ' ' ) ); + + sb.Append( "
" ).Append( NewLine ); + + sb.Append( "".PadLeft( IndentSize * 2, ' ' ) ); + + if ( paragraphBlock.Inline != null ) + AddInlines( paragraphBlock.Inline ); + + sb.Append( "".PadLeft( IndentSize, ' ' ) ); + + sb.Append( "
" ).Append( NewLine ); + + sb.Append( "
" ).Append( NewLine ).Append( NewLine ); + } + } + } + + public void AddPageList( ListBlock listBlock ) + { + sb.Append( $"" ).Append( NewLine ); + + foreach ( ListItemBlock listItem in listBlock ) + { + sb.Append( "".PadLeft( IndentSize, ' ' ) ); + sb.Append( "" ).Append( NewLine ); + + sb.Append( "".PadLeft( IndentSize * 2, ' ' ) ); + + foreach ( var block in listItem ) + { + if ( block is ParagraphBlock paragraphBlock ) + { + AddInlines( paragraphBlock.Inline ); + } + else if ( block is FencedCodeBlock fencedCodeBlock ) + { + PersistCodeBlock( fencedCodeBlock, 2 ); + } + } + + sb.Append( "".PadLeft( IndentSize, ' ' ) ); + sb.Append( "" ).Append( NewLine ); + } + + sb.Append( "" ).Append( NewLine ).Append( NewLine ); + } + + public (string builtCodeBlock, string parsedCodeBlock) AddCodeBlock( FencedCodeBlock fencedCodeBlock, string codeBlockName, int indentLevel ) + { + var formatter = new HtmlClassFormatter(); + + sb.Append( "".PadLeft( IndentSize * indentLevel, ' ' ) ); + + sb.Append( $"" ).Append( NewLine ); + + if ( indentLevel == 0 ) + sb.Append( NewLine ); + + return (builtCodeBlock, parsedCodeBlock); + } + + public void PersistCodeBlock( FencedCodeBlock fencedCodeBlock, int indentLevel ) + { + var codeBlockName = fencedCodeBlock.Info != null && fencedCodeBlock.Info.IndexOf( '|' ) > 0 + ? $"{blogName}_{fencedCodeBlock.Info.Substring( fencedCodeBlock.Info.IndexOf( '|' ) + 1 )}" + : $"{blogName}{( ++codeIndex )}"; + + var codeBlockFileName = Path.Combine( blogDirectory, "Code", $"{codeBlockName}Code.html" ); + var codeBlockExampleFileName = Path.Combine( blogDirectory, "Examples", $"{codeBlockName}.snippet" ); + var codeBlockDirectory = Path.GetDirectoryName( codeBlockFileName ); + var codeBlockExamplesDirectory = Path.GetDirectoryName( codeBlockExampleFileName ); + var currentCodeBlock = string.Empty; + var currentCodeBlockExample = string.Empty; + + if ( !Directory.Exists( codeBlockDirectory ) ) + { + Directory.CreateDirectory( codeBlockDirectory ); + } + + if ( !Directory.Exists( codeBlockExamplesDirectory ) ) + { + Directory.CreateDirectory( codeBlockExamplesDirectory ); + } + + var (builtCodeBlock, parsedCodeBlock) = AddCodeBlock( fencedCodeBlock, codeBlockName, indentLevel ); + + if ( File.Exists( codeBlockFileName ) ) + { + currentCodeBlock = File.ReadAllText( codeBlockFileName ); + } + + if ( File.Exists( codeBlockExampleFileName ) ) + { + currentCodeBlockExample = File.ReadAllText( codeBlockExampleFileName ); + } + + if ( currentCodeBlock != builtCodeBlock ) + { + File.WriteAllText( codeBlockFileName, builtCodeBlock ); + } + + if ( currentCodeBlockExample != parsedCodeBlock ) + { + File.WriteAllText( codeBlockExampleFileName, parsedCodeBlock ); + } + } + + static string ParseCodeBlock( FencedCodeBlock fencedCodeBlock ) + { + return string.Join( "\r\n", fencedCodeBlock.Lines ); + } + + public override string ToString() + { + return sb.ToString(); + } + } +} diff --git a/Documentation/Blazorise.Docs.Compiler/BlogMarkdown.cs b/Documentation/Blazorise.Docs.Compiler/BlogMarkdown.cs new file mode 100644 index 0000000000..c457449fc2 --- /dev/null +++ b/Documentation/Blazorise.Docs.Compiler/BlogMarkdown.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using System.Text; +using System.Threading.Tasks; +using Markdig; +using Markdig.Syntax; + +namespace Blazorise.Docs.Compiler +{ + public class BlogMarkdown + { + public bool Execute() + { + var success = true; + + try + { + var dirPath = Paths.DirPath(); + var markdownFiles = Directory.EnumerateFiles( dirPath, "*.md", SearchOption.AllDirectories ); + + foreach ( var entry in markdownFiles.OrderBy( e => e.Replace( "\\", "/" ), StringComparer.Ordinal ) ) + { + var blogDirectory = Path.GetDirectoryName( entry ); + var markdownFilename = Path.GetFileName( entry ); + var razorFilename = Path.Combine( blogDirectory, markdownFilename.Replace( ".md", ".razor" ) ); + + var blogName = Path.GetFileName( blogDirectory ).Substring( "YYYY-MM-DD_".Length ); + var blogBuilder = new BlogBuilder( blogName, blogDirectory ); + + var pageInfo = ParsePageInfo( blogBuilder, File.ReadAllText( entry, Encoding.UTF8 ) ); + var markdownDocument = Markdown.Parse( pageInfo.MarkdownText ); + + var currentPageCode = string.Empty; + var builtPageCode = string.Empty; + + blogBuilder.AddPageAndSeo( pageInfo.Permalink, pageInfo.Title, pageInfo.Description, pageInfo.ImageUrl, pageInfo.ImageTitle ); + + foreach ( var block in markdownDocument ) + { + switch ( block ) + { + case HeadingBlock headingBlock: + if ( headingBlock.Level == 1 ) + blogBuilder.AddPageTitle( headingBlock ); + else if ( headingBlock.Level == 2 ) + blogBuilder.AddPageSubtitle( headingBlock ); + break; + case ParagraphBlock paragraphBlock: + blogBuilder.AddPageParagraph( paragraphBlock ); + break; + case QuoteBlock quoteBlock: + blogBuilder.AddPageQuote( quoteBlock ); + break; + case ListBlock listBlock: + blogBuilder.AddPageList( listBlock ); + break; + case FencedCodeBlock fencedCodeBlock: + blogBuilder.PersistCodeBlock( fencedCodeBlock, 0 ); + break; + } + } + + blogBuilder.AddPagePostInto( pageInfo.AuthorName, pageInfo.AuthorImage, pageInfo.PostedOn, pageInfo.ReadTime ); + + if ( File.Exists( razorFilename ) ) + { + currentPageCode = File.ReadAllText( razorFilename ); + } + + builtPageCode = blogBuilder.ToString(); + + if ( currentPageCode != builtPageCode ) + { + File.WriteAllText( razorFilename, blogBuilder.ToString() ); + } + } + } + catch ( Exception e ) + { + Console.WriteLine( $"Error generating blogs {Paths.SnippetsFilePath} : {e.Message}" ); + success = false; + } + + return success; + } + + /// + /// Reads extra info about the page that should be at the beginning of the *.md file. + /// + /// + /// + /// + private PageInfo ParsePageInfo( BlogBuilder blogBuilder, string markdownText ) + { + PageInfo pageInfo = new PageInfo() + { + MarkdownText = markdownText + }; + + if ( markdownText.StartsWith( "---" ) ) + { + var subString = ( string line ) => line.Substring( line.Length + 1 ).Trim(); + + var seoEnding = markdownText.IndexOf( "---", 3 ); + + var pageInfoString = markdownText.Substring( 3, seoEnding - 3 ).Trim().Split( '\n' ); + + foreach ( var line in pageInfoString ) + { + if ( line.StartsWith( "title:" ) ) + pageInfo.Title = line.Substring( "title:".Length + 1 ).Trim(); + else if ( line.StartsWith( "description:" ) ) + pageInfo.Description = line.Substring( "description:".Length + 1 ).Trim(); + else if ( line.StartsWith( "permalink:" ) ) + pageInfo.Permalink = line.Substring( "permalink:".Length + 1 ).Trim(); + else if ( line.StartsWith( "image-url:" ) ) + pageInfo.ImageUrl = line.Substring( "image-url:".Length + 1 ).Trim(); + else if ( line.StartsWith( "image-title:" ) ) + pageInfo.ImageTitle = line.Substring( "image-title:".Length + 1 ).Trim(); + else if ( line.StartsWith( "author-name:" ) ) + pageInfo.AuthorName = line.Substring( "author-name:".Length + 1 ).Trim(); + else if ( line.StartsWith( "author-image:" ) ) + pageInfo.AuthorImage = line.Substring( "author-image:".Length + 1 ).Trim(); + else if ( line.StartsWith( "posted-on:" ) ) + pageInfo.PostedOn = line.Substring( "posted-on:".Length + 1 ).Trim(); + else if ( line.StartsWith( "read-time:" ) ) + pageInfo.ReadTime = line.Substring( "read-time:".Length + 1 ).Trim(); + } + + pageInfo.MarkdownText = markdownText.Substring( seoEnding + 3 ).TrimStart(); + } + + return pageInfo; + } + + class PageInfo + { + public string Title { get; set; } + + public string Description { get; set; } + + public string Permalink { get; set; } + + public string ImageUrl { get; set; } + + public string ImageTitle { get; set; } + + public string AuthorName { get; set; } + + public string AuthorImage { get; set; } + + public string PostedOn { get; set; } + + public string ReadTime { get; set; } + + public string MarkdownText { get; set; } + } + } +} diff --git a/Documentation/Blazorise.Docs.Compiler/ExamplesMarkup.cs b/Documentation/Blazorise.Docs.Compiler/CodeExamplesMarkup.cs similarity index 61% rename from Documentation/Blazorise.Docs.Compiler/ExamplesMarkup.cs rename to Documentation/Blazorise.Docs.Compiler/CodeExamplesMarkup.cs index 399de9f332..a11551cb25 100644 --- a/Documentation/Blazorise.Docs.Compiler/ExamplesMarkup.cs +++ b/Documentation/Blazorise.Docs.Compiler/CodeExamplesMarkup.cs @@ -7,11 +7,10 @@ namespace Blazorise.Docs.Compiler { - public class ExamplesMarkup + public class CodeExamplesMarkup { public bool Execute() { - var paths = new Paths(); var newFiles = new StringBuilder(); var success = true; var noOfFilesUpdated = 0; @@ -21,13 +20,13 @@ public bool Execute() { var formatter = new HtmlClassFormatter(); var lastCheckedTime = new DateTime(); - if ( File.Exists( paths.NewFilesToBuildPath() ) ) + if ( File.Exists( Paths.NewFilesToBuildPath() ) ) { - var lastNewFilesToBuild = new FileInfo( paths.NewFilesToBuildPath() ); + var lastNewFilesToBuild = new FileInfo( Paths.NewFilesToBuildPath() ); lastCheckedTime = lastNewFilesToBuild.LastWriteTime; } - var dirPath = paths.DirPath(); + var dirPath = Paths.DirPath(); var directoryInfo = new DirectoryInfo( dirPath ); var razorFiles = directoryInfo.GetFiles( "*.razor", SearchOption.AllDirectories ); @@ -36,7 +35,9 @@ public bool Execute() foreach ( var entry in razorFiles.Concat( snippetFiles ).Concat( csharpFiles ) ) { - if ( entry.Name.EndsWith( "Code.razor" ) ) + // We need to skip blog examples becaouse they are generated from markdown code block and we don't want to process them again + if ( entry.Name.EndsWith( "Code.razor" ) + || entry.FullName.Contains( $"{Path.DirectorySeparatorChar}Blog{Path.DirectorySeparatorChar}", StringComparison.InvariantCultureIgnoreCase ) ) { continue; } @@ -61,56 +62,23 @@ public bool Execute() Directory.CreateDirectory( markupDir ); } - var cb = new CodeBuilder(); + //var cb = new CodeBuilder(); var currentCode = string.Empty; + var builtCode = string.Empty; var isCSharp = entry.FullName.EndsWith( ".csharp" ); - var src = StripComponentSource( entry.FullName ); + var source = File.ReadAllText( entry.FullName, Encoding.UTF8 ); - if ( isCSharp ) + if ( File.Exists( markupPath ) ) { - cb.AddLine( "
" ); - - cb.AddLine( - formatter.GetHtmlString( src, Languages.CSharp ) - .Replace( "@", "@" ) - .ToLfLineEndings() ); - - cb.AddLine( "
" ); + currentCode = File.ReadAllText( markupPath ); } - else - { - var blocks = src.Split( "@code" ); - var blocks0 = Regex.Replace( blocks[0], @"", string.Empty ) - .Replace( "@", "PlaceholdeR" ) - .Trim(); + builtCode = new MarkupBuilder( formatter ).Build( source, isCSharp ); - // Note: the @ creates problems and thus we replace it with an unlikely placeholder and in the markup replace back. - var html = formatter.GetHtmlString( blocks0, Languages.Html ).Replace( "PlaceholdeR", "@" ); - html = AttributePostprocessing( html ).Replace( "@", "@" ); - - if ( File.Exists( markupPath ) ) - { - currentCode = File.ReadAllText( markupPath ); - } - - cb.AddLine( "
" ); - cb.AddLine( html.ToLfLineEndings() ); - - if ( blocks.Length == 2 ) - { - cb.AddLine( - formatter.GetHtmlString( "@code" + blocks[1], Languages.CSharp ) - .Replace( "@", "@" ) - .ToLfLineEndings() ); - } - - cb.AddLine( "
" ); - } - - if ( currentCode != cb.ToString() ) + if ( currentCode != builtCode ) { - File.WriteAllText( markupPath, cb.ToString() ); + File.WriteAllText( markupPath, builtCode ); + if ( currentCode == string.Empty ) { newFiles.AppendLine( markupPath ); @@ -123,7 +91,7 @@ public bool Execute() } } - File.WriteAllText( paths.NewFilesToBuildPath(), newFiles.ToString() ); + File.WriteAllText( Paths.NewFilesToBuildPath(), newFiles.ToString() ); } catch ( Exception e ) { @@ -136,13 +104,6 @@ public bool Execute() return success; } - private static string StripComponentSource( string path ) - { - var source = File.ReadAllText( path, Encoding.UTF8 ); - source = Regex.Replace( source, "@(namespace|layout|page) .+?\n", string.Empty ); - return source.Trim(); - } - public static string AttributePostprocessing( string html ) { return Regex.Replace( diff --git a/Documentation/Blazorise.Docs.Compiler/CodeSnippets.cs b/Documentation/Blazorise.Docs.Compiler/CodeSnippets.cs index 93ca7e9b75..eca2abe932 100644 --- a/Documentation/Blazorise.Docs.Compiler/CodeSnippets.cs +++ b/Documentation/Blazorise.Docs.Compiler/CodeSnippets.cs @@ -12,15 +12,14 @@ public class CodeSnippets { public bool Execute() { - var paths = new Paths(); var success = true; try { var currentCode = string.Empty; - if ( File.Exists( paths.SnippetsFilePath() ) ) + if ( File.Exists( Paths.SnippetsFilePath() ) ) { - currentCode = File.ReadAllText( paths.SnippetsFilePath() ); + currentCode = File.ReadAllText( Paths.SnippetsFilePath() ); } var cb = new CodeBuilder(); @@ -32,7 +31,7 @@ public bool Execute() cb.AddLine( "{" ); cb.IndentLevel++; - var dirPath = paths.DirPath(); + var dirPath = Paths.DirPath(); var razorFiles = Directory.EnumerateFiles( dirPath, "*.razor", SearchOption.AllDirectories ); var snippetFiles = Directory.EnumerateFiles( dirPath, "*.snippet", SearchOption.AllDirectories ); var csharpFiles = Directory.EnumerateFiles( dirPath, "*.csharp", SearchOption.AllDirectories ); @@ -53,12 +52,12 @@ public bool Execute() if ( currentCode != cb.ToString() ) { - File.WriteAllText( paths.SnippetsFilePath(), cb.ToString() ); + File.WriteAllText( Paths.SnippetsFilePath(), cb.ToString() ); } } catch ( Exception e ) { - Console.WriteLine( $"Error generating {paths.SnippetsFilePath} : {e.Message}" ); + Console.WriteLine( $"Error generating {Paths.SnippetsFilePath} : {e.Message}" ); success = false; } diff --git a/Documentation/Blazorise.Docs.Compiler/MarkupBuilder.cs b/Documentation/Blazorise.Docs.Compiler/MarkupBuilder.cs new file mode 100644 index 0000000000..a7bb4921b0 --- /dev/null +++ b/Documentation/Blazorise.Docs.Compiler/MarkupBuilder.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using ColorCode; + +namespace Blazorise.Docs.Compiler +{ + public class MarkupBuilder + { + private readonly HtmlClassFormatter formatter; + + public MarkupBuilder( HtmlClassFormatter formatter ) + { + this.formatter = formatter; + } + + public string Build( string source, bool isCSharp ) + { + var cb = new CodeBuilder(); + + var strippedSource = StripComponentSource( source ); + + if ( isCSharp ) + { + cb.AddLine( "
" ); + + cb.AddLine( + formatter.GetHtmlString( strippedSource, Languages.CSharp ) + .Replace( "@", "@" ) + .ToLfLineEndings() ); + + cb.AddLine( "
" ); + } + else + { + var blocks = strippedSource.Split( "@code" ); + + var blocks0 = Regex.Replace( blocks[0], @"", string.Empty ) + .Replace( "@", "PlaceholdeR" ) + .Trim(); + + // Note: the @ creates problems and thus we replace it with an unlikely placeholder and in the markup replace back. + var html = formatter.GetHtmlString( blocks0, Languages.Html ).Replace( "PlaceholdeR", "@" ); + html = AttributePostprocessing( html ).Replace( "@", "@" ); + + cb.AddLine( "
" ); + cb.AddLine( html.ToLfLineEndings() ); + + if ( blocks.Length == 2 ) + { + cb.AddLine( + formatter.GetHtmlString( "@code" + blocks[1], Languages.CSharp ) + .Replace( "@", "@" ) + .ToLfLineEndings() ); + } + + cb.AddLine( "
" ); + } + + return cb.ToString(); + } + + private static string StripComponentSource( string source ) + { + source = Regex.Replace( source, "@(namespace|layout|page) .+?\n", string.Empty ); + return source.Trim(); + } + + public static string AttributePostprocessing( string html ) + { + return Regex.Replace( + html, + @""(?'value'.*?)"", + new MatchEvaluator( m => + { + var value = m.Groups["value"].Value; + return + $@""{AttributeValuePostprocessing( value )}""; + } ) ); + } + + private static string AttributeValuePostprocessing( string value ) + { + if ( string.IsNullOrWhiteSpace( value ) ) + return value; + if ( value == "true" || value == "false" ) + return $"{value}"; + if ( Regex.IsMatch( value, "^[A-Z][A-Za-z0-9]+[.][A-Za-z][A-Za-z0-9]+$" ) ) + { + var tokens = value.Split( '.' ); + return $"{tokens[0]}.{tokens[1]}"; + } + + if ( Regex.IsMatch( value, "^@[A-Za-z0-9]+$" ) ) + { + return $"{value}"; + } + + return $"{value}"; + } + } +} diff --git a/Documentation/Blazorise.Docs.Compiler/Paths.cs b/Documentation/Blazorise.Docs.Compiler/Paths.cs index 7c43a20247..2c37b97f4b 100644 --- a/Documentation/Blazorise.Docs.Compiler/Paths.cs +++ b/Documentation/Blazorise.Docs.Compiler/Paths.cs @@ -3,7 +3,7 @@ namespace Blazorise.Docs.Compiler { - public class Paths + public static class Paths { private const string NewFilesToBuild = "NewFilesToBuild.txt"; @@ -25,14 +25,14 @@ public static string RootDirPath } } - public string DirPath() => Directory.EnumerateDirectories( RootDirPath, $"Blazorise.Docs" ).FirstOrDefault(); + public static string DirPath() => Directory.EnumerateDirectories( RootDirPath, $"Blazorise.Docs" ).FirstOrDefault(); - public string DocsStringSnippetsDirPath() => Path.Join( DirPath(), "Models" ); + public static string DocsStringSnippetsDirPath() => Path.Join( DirPath(), "Models" ); - public string DocStringsFilePath() => Path.Join( DocsStringSnippetsDirPath(), "Strings.generated.cs" ); + public static string DocStringsFilePath() => Path.Join( DocsStringSnippetsDirPath(), "Strings.generated.cs" ); - public string SnippetsFilePath() => Path.Join( DocsStringSnippetsDirPath(), "Snippets.generated.cs" ); + public static string SnippetsFilePath() => Path.Join( DocsStringSnippetsDirPath(), "Snippets.generated.cs" ); - public string NewFilesToBuildPath() => Path.Join( DirPath(), NewFilesToBuild ); + public static string NewFilesToBuildPath() => Path.Join( DirPath(), NewFilesToBuild ); } } \ No newline at end of file diff --git a/Documentation/Blazorise.Docs.Compiler/Program.cs b/Documentation/Blazorise.Docs.Compiler/Program.cs index fab3dcad1a..198032b261 100644 --- a/Documentation/Blazorise.Docs.Compiler/Program.cs +++ b/Documentation/Blazorise.Docs.Compiler/Program.cs @@ -8,13 +8,14 @@ class Program static int Main() { var stopWatch = Stopwatch.StartNew(); - var success = - new CodeSnippets().Execute() - && new ExamplesMarkup().Execute(); + + var blogMarkdownResult = new BlogMarkdown().Execute(); + var codeSnippetResult = new CodeSnippets().Execute(); + var codeExamplesResult = new CodeExamplesMarkup().Execute(); Console.WriteLine( $"Blazorise.Docs.Compiler completed in {stopWatch.ElapsedMilliseconds} milliseconds." ); - return success ? 0 : 1; + return blogMarkdownResult && codeSnippetResult && codeExamplesResult ? 0 : 1; } } } diff --git a/Documentation/Blazorise.Docs/Blazorise.Docs.csproj b/Documentation/Blazorise.Docs/Blazorise.Docs.csproj index e9b7a24c5c..7ef8db2bc3 100644 --- a/Documentation/Blazorise.Docs/Blazorise.Docs.csproj +++ b/Documentation/Blazorise.Docs/Blazorise.Docs.csproj @@ -68,9 +68,6 @@ - - - diff --git a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_CounterExample.snippet b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_CounterExample.snippet index e0e1590e86..9368472ebb 100644 --- a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_CounterExample.snippet +++ b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_CounterExample.snippet @@ -1,4 +1,4 @@ -@page "/counter" +@page "/counter" Counter with Blazorise diff --git a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_ServicesExample.csharp b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_ServicesExample.snippet similarity index 97% rename from Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_ServicesExample.csharp rename to Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_ServicesExample.snippet index 717919c110..bedf19f6db 100644 --- a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_ServicesExample.csharp +++ b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_ServicesExample.snippet @@ -1,4 +1,4 @@ -using Blazorise; +using Blazorise; using Blazorise.Bootstrap5; using Blazorise.Icons.FontAwesome; using BlazoriseSampleApplication; diff --git a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_StaticFilesExample.snippet b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_StaticFilesExample.snippet index 55d5beb213..4008a2a194 100644 --- a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_StaticFilesExample.snippet +++ b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_StaticFilesExample.snippet @@ -1,4 +1,4 @@ - + @@ -26,4 +26,4 @@ - + \ No newline at end of file diff --git a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_UsingsExample.snippet b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_UsingsExample.snippet index ba3b223030..f105ebb1f9 100644 --- a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_UsingsExample.snippet +++ b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Examples/BeginnersGuideToCreateBlazoriseApp_UsingsExample.snippet @@ -1 +1 @@ -@using Blazorise \ No newline at end of file +@using Blazorise \ No newline at end of file diff --git a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Index.md b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Index.md new file mode 100644 index 0000000000..ad1e952dfa --- /dev/null +++ b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Index.md @@ -0,0 +1,154 @@ +--- +title: How to create a Blazorise WASM application: A Beginner's Guide +description: Learn How to create a Blazorise WASM application: A Beginner's Guide. +permalink: /blog/how-to-create-a-blazorise-application-beginners-guide +canonical: /blog/how-to-create-a-blazorise-application-beginners-guide +image-url: img/blog/2022-06-08/How_to_create_a_Blazorise_application_A_Beginners_Guide.png +image-title: Blazorise WASM application: A Beginner's Guide +author-name: Mladen Macanović +author-image: mladen +posted-on: June 8th, 2022 +read-time: 5 min +--- + +# How to create a Blazorise WASM application: A Beginner's Guide + +In this **_Imports.razor** article we will learn how to create a Blazorise WebAssembly (WASM) application. As an example, we will also use basic Blazorise components to setup a simple form. + +## Prerequisites + +To work on a Blazor app, you can start by taking of the following approaches: + +- [.NET CLI](https://docs.microsoft.com/en-us/dotnet/core/tools/) and [Visual Studio Code](https://code.visualstudio.com/): Preferred for Linux. +- [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) and [Visual Studio Code](https://code.visualstudio.com/): Preferred for Windows and macOS. + +In this tutorial, we are going to use [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/). Please install the latest version of Visual Studio 2022. While installing, make sure you have selected the ASP.NET and web development workload. + +## Creating the Blazorise WebAssembly application + +First, we'll create a Blazor WebAssembly app. Please follow these steps to do so: + +1. Open Visual Studio 2022 and click on the **Create a new Project** option. +2. In the Create a new Project dialog that opens, search for **Blazor** and select **Blazor WebAssembly App** from the search results. Then, click **Next**. Refer to the following image. ![Create a new project dialog](img/blog/2022-06-08/Create-a-new-project-dialog.png) +3. Now you will be at the **Configure your new project** dialog. Provide the name for your application. Here, we are naming the application **BlazoriseSampleApplication**. Then, click **Next**. Refer to the following image. ![Configure your new project dialog](img/blog/2022-06-08/Configure-your-new-project-dialog.png) +4. On the **Additional information** page, select the target framework **.NET 6.0** and set the authentication type to **None**. Also, check the options **Configure for HTTPS** and uncheck **ASP.NET Core hosted**, and then click on **Create**. Refer to the following image. ![Additional information dialog](img/blog/2022-06-08/Additional-information-dialog.png) + +## Installing the Blazorise packages + +We have now completed our Blazor WebAssembly project. Continue by installing the **Blazorise NuGet** packages and configuring the project to use Blazorise. + +1. Right click on the project in solution explorer and click on **Manage NuGet Packages** from the dropdown menu. ![Manage NuGet Packages](img/blog/2022-06-08/Manage-NuGet-Packages.png) +2. Navigate to the **Browse** tab and search for **Blazorise**. To install it, use the **Blazorise.Bootstrap5** package. Repeat for **Blazorise.Icons.FontAwesome** package. ![Install Blazorise NuGet](img/blog/2022-06-08/Install-Blazorise-NuGet.png) +3. The next step is to change your **index.html** and include the Blazorise CSS source files: + ```html|StaticFilesExample + + + + + + + BlazoriseSampleApplication + + + + + + + + + + + +
Loading...
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + + ``` +4. Next, define the Blazorise using in your main **_Imports.razor** file. This will instruct Visual Studio IntelliSense to suggest Blazorise components to us. + ```html|UsingsExample + @using Blazorise + ``` +5. Go to the **Client** folder and define the following in **Program.cs**. + ```cs|ServicesExample + using Blazorise; + using Blazorise.Bootstrap5; + using Blazorise.Icons.FontAwesome; + using BlazoriseSampleApplication; + using Microsoft.AspNetCore.Components.Web; + using Microsoft.AspNetCore.Components.WebAssembly.Hosting; + + namespace Company.WebApplication1 + { + public class Program + { + public static async Task Main( string[] args ) + { + var builder = WebAssemblyHostBuilder.CreateDefault( args ); + builder.RootComponents.Add( "#app" ); + builder.RootComponents.Add( "head::after" ); + + builder.Services.AddScoped( sp => new HttpClient { BaseAddress = new Uri( builder.HostEnvironment.BaseAddress ) } ); + + builder.Services + .AddBlazorise( options => + { + options.Immediate = true; + } ) + .AddBootstrap5Providers() + .AddFontAwesomeIcons(); + + await builder.Build().RunAsync(); + } + } + } + ``` + +## Setting the Simple Example + +The last step is to adjust a default Blazor example to use Blazorise components. + +Go the **Counter.razor** under the **Pages** folder and copy/paste the following snippet. + +```html|CounterExample +@page "/counter" + +Counter with Blazorise + +Current count: @currentCount + + + +@code { + int currentCount = 0; + + void IncrementCount() + { + currentCount++; + } +} +``` + +## Executing the demo + +You should now be able to run the Blazorise sample project without incident. Press **F5** on your keyboard, or select **Start Debugging** from the Debug menu. + +Wait for VisualStudio to complete the build process, and you should see the new application running in your browser. To see an example of a counter, click on the Counter button in the sidebar. + +![Counter Example](img/blog/2022-06-08/Counter-Example.png) + +## Resource + +Also, you can get the source code of the sample from the [BlazoriseSampleApplication](https://github.com/Megabit/Blazorise-Samples) in Blazor demo on GitHub. + +## Summary + +Thanks for reading! In this blog, we learned how to create and setup Blazorise in a Blazor WebAssembly app. We have also modified default Counter example to make use of Blazorise components. Try out this demo and let us know what you think! + +Blazorise provides more than 80 high-performance, lightweight, and responsive web UI components in a single package. Create charming web applications with them! \ No newline at end of file diff --git a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Index.razor b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Index.razor index 304d54cc75..01c0ecaa9e 100644 --- a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Index.razor +++ b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-08_BeginnersGuideToCreateBlazoriseApp/Index.razor @@ -1,15 +1,15 @@ -@page "/blog/how-to-create-a-blazorise-application-beginners-guide" +@page "/blog/how-to-create-a-blazorise-application-beginners-guide" - + - + How to create a Blazorise WASM application: A Beginner's Guide - In this article we will learn how to create a Blazorise WebAssembly (WASM) application. As an example, we will also use basic Blazorise components to setup a simple form. + In this _Imports.razor article we will learn how to create a Blazorise WebAssembly (WASM) application. As an example, we will also use basic Blazorise components to setup a simple form. @@ -25,12 +25,12 @@ .NET CLI and Visual Studio Code: Preferred for Linux. - Visual Studio 2022 and Visual Studio Code: Preferred for Windows and macOS. + Visual Studio 2022 and Visual Studio Code: Preferred for Windows and macOS. - In this tutorial, we are going to use Visual Studio 2022. Please install the latest version of Visual Studio 2022. While installing, make sure you have selected the ASP.NET and web development workload. + In this tutorial, we are going to use Visual Studio 2022. Please install the latest version of Visual Studio 2022. While installing, make sure you have selected the ASP.NET and web development workload. @@ -46,17 +46,13 @@ Open Visual Studio 2022 and click on the Create a new Project option. - In the Create a new Project dialog that opens, search for Blazor and select Blazor WebAssembly App from the search results. Then, click Next. - Refer to the following image. - + In the Create a new Project dialog that opens, search for Blazor and select Blazor WebAssembly App from the search results. Then, click Next. Refer to the following image. - Now you will be at the Configure your new project dialog. Provide the name for your application. Here, we are naming the application BlazoriseSampleApplication. Then, click Next. Refer to the following image. - + Now you will be at the Configure your new project dialog. Provide the name for your application. Here, we are naming the application BlazoriseSampleApplication. Then, click Next. Refer to the following image. - On the Additional information page, select the target framework .NET 6.0 and set the authentication type to None. Also, check the options Configure for HTTPS and uncheck ASP.NET Core hosted, and then click on Create. Refer to the following image. - + On the Additional information page, select the target framework .NET 6.0 and set the authentication type to None. Also, check the options Configure for HTTPS and uncheck ASP.NET Core hosted, and then click on Create. Refer to the following image. @@ -70,12 +66,10 @@ - Right click on the project in solution explorer and click on Manage NuGet Packages from the dropdown menu. - + Right click on the project in solution explorer and click on Manage NuGet Packages from the dropdown menu. - Navigate to the Browse tab and search for Blazorise. To install it, use the Blazorise.Bootstrap5 package. Repeat for Blazorise.Icons.FontAwesome package. - + Navigate to the Browse tab and search for Blazorise. To install it, use the Blazorise.Bootstrap5 package. Repeat for Blazorise.Icons.FontAwesome package. The next step is to change your index.html and include the Blazorise CSS source files: @@ -124,7 +118,7 @@ - Also, you can get the source code of the sample from the BlazoriseSampleApplication in Blazor demo on GitHub. + Also, you can get the source code of the sample from the BlazoriseSampleApplication in Blazor demo on GitHub. @@ -139,4 +133,4 @@ Blazorise provides more than 80 high-performance, lightweight, and responsive web UI components in a single package. Create charming web applications with them! - \ No newline at end of file + diff --git a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/ValidationWithDataAnnotations_FormExample.snippet b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/ValidationWithDataAnnotations_FormExample.snippet index ec8878aa41..3242126330 100644 --- a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/ValidationWithDataAnnotations_FormExample.snippet +++ b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/ValidationWithDataAnnotations_FormExample.snippet @@ -1,4 +1,4 @@ -@page "/" +@page "/" @using ValidationWithDataAnnotations.Models diff --git a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/ValidationWithDataAnnotations_MessageProviderExample.snippet b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/ValidationWithDataAnnotations_MessageProviderExample.snippet index a5d600314b..014c3bf07a 100644 --- a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/ValidationWithDataAnnotations_MessageProviderExample.snippet +++ b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/ValidationWithDataAnnotations_MessageProviderExample.snippet @@ -1,4 +1,4 @@ - + diff --git a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/ValidationWithDataAnnotations_ModelsExample.csharp b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/ValidationWithDataAnnotations_ModelsExample.snippet similarity index 99% rename from Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/ValidationWithDataAnnotations_ModelsExample.csharp rename to Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/ValidationWithDataAnnotations_ModelsExample.snippet index 774df4f551..908df2e13f 100644 --- a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/ValidationWithDataAnnotations_ModelsExample.csharp +++ b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Examples/ValidationWithDataAnnotations_ModelsExample.snippet @@ -1,4 +1,4 @@ -public class Employee +public class Employee { [Required] public string FirstName { get; set; } diff --git a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Index.md b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Index.md new file mode 100644 index 0000000000..4e8a07534b --- /dev/null +++ b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Index.md @@ -0,0 +1,385 @@ +--- +title: Blazorise Form Validation With Data Annotations: A Beginner's Guide +description: Learn how to create Blazorise form validation without model. +permalink: /blog/blazor-form-validation-with-data-annotations +canonical: /blog/blazor-form-validation-with-data-annotations +image-url: img/blog/2022-06-09/Blazorise-Form-Validation-With-Data-Annotations.png +image-title: Blazorise Form Validation With Data Annotations: A Beginner's Guide +author-name: Mladen Macanović +author-image: mladen +posted-on: June 9th, 2022 +read-time: 5 min +--- + +# Blazorise Form Validation With Data Annotations: A Beginner's Guide + +Validation is critical for any application to obtain reliable data from the user on any data entry form. The Blazorise UI components includes form validation support that makes use of data annotations. It also allows you to use custom validation handlers and regex patterns to solve complex validation problems. Since data annotations are the most popular way of validating forms we will cover it in this blog, and in one of next blogs we will explain some of the other ways of how to do the validation. + +Over 80 responsive and lightweight UI controls are available in the Blazorise Blazor component library for building modern web apps. + +In this blog post, we will look at how to use Blazorise Blazor UI components to create an edit form for employee details and apply complex validation using data annotations. + +## Prerequisites + +It is recommended that you already have a working Blazorise project before we begin. If you don't already have it, you can read our blog post on [How to create a Blazorise WASM application](blog/how-to-create-a-blazorise-application-beginners-guide). + +When you have finished with setting the project make sure that you also have the **MessageAlert** defined in the **App.razor**. We will use it to give feedback to the user once we have an error or a successfull form submition. + +```html|MessageProviderExample + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
+ + +``` + +## Creating the Model + +Let's start by creating a new model class file inside the **Model** folder with the name **Employee**. + +In this class file, add the class definitions for the Countries and Cities classes with the required properties and methods to generate the appropriate data for the dropdown list. + +Refer to the following code example. + +```cs|ModelsExample +public class Employee +{ + [Required] + public string FirstName { get; set; } + + [Required] + public string LastName { get; set; } + + [Required] + [EmailAddress] + public string Email { get; set; } + + [Required] + public string Gender { get; set; } + + [Required] + public DateTime? DateOfBirth { get; set; } + + [Required] + public decimal? YearsOfExperience { get; set; } + + public Address Address { get; set; } = new Address(); +} + +public class Address +{ + [Required] + public string Street { get; set; } + + [Required] + public string City { get; set; } + + [Required] + public string Zip { get; set; } + + [Required] + public string Country { get; set; } +} + +public class Country +{ + public string Name { get; set; } + + public string Code { get; set; } + + public static IEnumerable GetCountries() + { + return new List + { + new() { Name = "Croatia", Code = "HR" }, + new() { Name = "United Kingdom", Code = "UK" }, + new() { Name = "United States", Code = "US" }, + }; + } +} + +public class City +{ + public string Name { get; set; } + + public string Code { get; set; } + + public string CountryCode { get; set; } + + public static IEnumerable GetCities() + { + return new List + { + new() { Name = "San Francisco", CountryCode = "US", Code="US-101" }, + new() { Name = "Los Angeles", CountryCode = "US", Code="US-102" }, + new() { Name = "Boston", CountryCode = "US", Code="US-103" }, + new() { Name = "Portland", CountryCode = "US", Code="US-104" }, + new() { Name = "Split", CountryCode = "HR", Code="HR-101" }, + new() { Name = "Zagreb", CountryCode = "HR", Code="HR-102" }, + new() { Name = "Dubrovnik", CountryCode = "HR", Code="HR-103" }, + new() { Name = "London", CountryCode = "UK", Code="UK-101" }, + new() { Name = "Glasgow", CountryCode = "UK", Code="UK-102" }, + new() { Name = "Liverpool", CountryCode = "UK", Code="UK-103" } + }; + } +} + +public class Gender +{ + public string Name { get; set; } + + public string Code { get; set; } + + public static IEnumerable GetGenders() + { + return new List + { + new() { Name = "Male", Code = "A" }, + new() { Name = "Female", Code = "B" }, + new() { Name = "Non-binary", Code = "C" }, + new() { Name = "Transgender", Code = "D" }, + new() { Name = "Intersex", Code = "E" }, + new() { Name = "I prefer not to say", Code = "F" }, + }; + } +} +``` + +We've now created the **Employee** class and annotated all of its properties with the **[Required]** attribute. + +## Creating the Page form + +We will create new form in the **Index.razor** file under the **Pages** folder. + +On this page we will structure a fairly simple form structure that represents a form where we will ask for an employee data. + +Refer to the following code example. + +```html|FormExample +@page "/" +@using ValidationWithDataAnnotations.Models + + + + + + + + First Name + + + + + + + + + + + + Last Name + + + + + + + + + + + + + + Email Address + + + + + + + + + + + + Date of Birth + + + + + + + + + + + + + + Years of Experience + + + + + + + + + + + + Gender + + + + + + + + + Address + + + + + + + + + + + + + City + + + + + + + + Zip + + + + + + + + + + + + + Country + + + + + + + + + +@code { + [Inject] IMessageService MessageService { get; set; } + + Validations ValidationsRef { get; set; } + + Employee EmployeeModel { get; set; } = new Employee(); + + async Task OnSaveClicked() + { + if ( await ValidationsRef.ValidateAll() ) + { + await MessageService.Info( "Thank you for filling the form." ); + + await ValidationsRef.ClearAll(); + } + } +} +``` + +## Breakdown + +![Explaining Validation Parts](img/blog/2022-06-09/Explaining-Validation-Parts.png) + +1. We use `` component to group all validations under a single submit request. +2. **Model** parameter is used to give **Validations** enough information about the object and attributes that we are validating. +3. **ValidateOnLoad** is set to false so that form is NOT validated when the page is first opened. +4. We place **Feedback** inside of input component. The reason for this structure is that Blazorise will handle the right HTML structure for you when the components are rendered. + +## Executing the demo + +Launch the application if you have modified it in accordance with all of the code samples. + +In the left-hand navigation menu, click the **Home** button. The output will then look like the image below. + +![Form Validation Opened](img/blog/2022-06-09/Form-Validation-Opened.png) + +Try to partially fill the form and click on the Validate and Submit button. You should see some of the errors. + +If you fill the entire form form and submit you should see the following message. + +![Form Validation Opened](img/blog/2022-06-09/Form-Validation-Success.png) + +Thus, we have created the form and included the Blazorise form validation in our Blazor WebAssembly application. + +## GitHub Reference + +The full source code of the sample from the [ValidationWithDataAnnotations](https://github.com/Megabit/Blazorise-Samples) in Blazor demo on GitHub. + +## Conclusion + +Thank you for your time! In this blog, we saw how to use Blazorise UI components to create an edit form and easily apply complex validation using data annotations. Try out the steps in this blog post and leave your comments on our Discord community channel. + +Download our [Blazorise NuGet](https://www.nuget.org/profiles/Megabit) to try our Blazor components or purchase our [commercial Blazorise web](https://commercial.blazorise.com/) license to gain access to our support forum. To learn more about other available features, please see our online examples and [documentation](docs). + +## Related blogs + +- [How to create a Blazorise WASM application: A Beginner's Guide](blog/how-to-create-a-blazorise-application-beginners-guide) \ No newline at end of file diff --git a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Index.razor b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Index.razor index fe74f9346c..341fbf92fa 100644 --- a/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Index.razor +++ b/Documentation/Blazorise.Docs/Pages/Blog/2022-06-09_ValidationWithDataAnnotations/Index.razor @@ -1,6 +1,6 @@ -@page "/blog/blazor-form-validation-with-data-annotations" +@page "/blog/blazor-form-validation-with-data-annotations" - + @@ -29,7 +29,7 @@ - When you have finished with setting the project make sure that you also have the MessageAlert defined in the App.razor. We will use it to give feedback to the user once we have an error or a successfull form submition. + When you have finished with setting the project make sure that you also have the MessageAlert defined in the App.razor. We will use it to give feedback to the user once we have an error or a successfull form submition. @@ -110,7 +110,7 @@ - Try to partially fill the form and click on the Validate and Submit button. You should see some of the errors. + Try to partially fill the form and click on the Markdig.Syntax.Inlines.HtmlInlineValidate and SubmitMarkdig.Syntax.Inlines.HtmlInline button. You should see some of the errors. @@ -128,7 +128,7 @@
- The full source code of the sample from the ValidationWithDataAnnotations in Blazor demo on GitHub. + The full source code of the sample from the ValidationWithDataAnnotations in Blazor demo on GitHub. @@ -140,7 +140,7 @@ - Download our NuGet package to try our Blazor components or purchase our commercial license to gain access to our support forum. To learn more about other available features, please see our online examples and documentation. + Download our Blazorise NuGet to try our Blazor components or purchase our commercial Blazorise web license to gain access to our support forum. To learn more about other available features, please see our online examples and documentation. @@ -153,4 +153,4 @@ - \ No newline at end of file +