diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 000000000..34c3e19a0
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "dotnet-ef": {
+ "version": "7.0.5",
+ "commands": [
+ "dotnet-ef"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 000000000..9b5784fb0
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,6 @@
+.git
+**/bin
+**/obj
+**/node_modules
+Dockerfile
+build.sh
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..8b2395878
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,154 @@
+# editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+# end_of_line = crlf
+indent_style = space
+indent_size = 2
+trim_trailing_whitespace = true
+insert_final_newline = true
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_property = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_event = false:silent
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+tab_width = 2
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_code_quality_unused_parameters = all:suggestion
+dotnet_style_readonly_field = true:suggestion
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+dotnet_style_allow_multiple_blank_lines_experimental = true:silent
+dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:silent
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+
+[*.{cshtml}]
+indent_size = 2
+
+[*.{razor}]
+indent_size = 2
+
+[*.{css,scss,js,json,yml}]
+indent_size = 2
+
+[*.{xml,csproj,config,*proj,targets,props}]
+indent_size = 2
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.sln]
+indent_style = tab
+
+[*.cs]
+#### 命名样式 ####
+
+# 命名规则
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# 符号规范
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# 命名样式
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+csharp_using_directive_placement = outside_namespace:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_conditional_delegate_call = true:suggestion
+csharp_style_var_for_built_in_types = false:silent
+csharp_style_var_when_type_is_apparent = false:silent
+csharp_style_var_elsewhere = false:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_prefer_braces = true:silent
+csharp_style_namespace_declarations = block_scoped:silent
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_prefer_static_local_function = true:suggestion
+csharp_style_prefer_readonly_struct = true:suggestion
+csharp_space_around_binary_operators = before_and_after
+csharp_indent_labels = one_less_than_current
+csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
+csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent
+csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
+csharp_style_prefer_switch_expression = true:suggestion
+csharp_style_prefer_pattern_matching = true:silent
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_prefer_not_pattern = true:suggestion
+csharp_style_prefer_extended_property_pattern = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_prefer_null_check_over_type_check = true:suggestion
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_prefer_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
+csharp_style_prefer_tuple_swap = true:suggestion
+csharp_style_prefer_utf8_string_literals = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 000000000..b0fac2a5b
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,40 @@
+# Contributing to Blogifier
+Thanks for contributing to the Blogifier. If you haven't already, please setup your environment and [run the Blogifier locally](https://github.com/blogifierdotnet/Blogifier/blob/main/README.md#development).
+
+Here is the list of things that you can contribute:
+
+1. [Translation](#translation)
+2. [Test and report bugs](#test-and-report-bugs)
+
+# Translation
+
+In the "[/src/Blogifier.Shared/Resouces/](https://github.com/blogifierdotnet/Blogifier/tree/main/src/Blogifier.Shared/Resources)" folder we keep all the resources files.
+
+The `Resource.resx` is the main English language that you can copy and create another Resource file for any other languages. For example, `Resouce.es.resx` is created for Spanish with `es` language code. So if your language is not in the Resources folder, copy the `Resource.resx` and rename it and add your language code at the end.
+
+### With Visual Studio
+Visual studio has a built-in GUI for the `.resx` files which shows you the name and value. which you only edit/translate the value.
+
+
+
+### With VS Code or any other IDE
+Open the `.resx` file and scroll down and you'll see lines like this:
+
+```
+
+ General
+
+```
+
+Basically, you just need to translate what's in the `` tag.
+
+### Testing
+1. In Google Chrome or any other browser, go to the browser settings and add the language to the top of the list.
+2. Run the Blogifier with `dotnet watch run` command on the `/src/blogifier/`.
+3. login to the blogifier admin panel. and see the results as you are translating.
+
+### Pull request
+After It's done and ready, please give it a pull request and we review it and merge it with the main branch.
+
+# Test and report bugs
+coming soon.
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000..f93b1547e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,23 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug**
+
+
+**Screenshots**
+
+
+**Additional context**
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 000000000..bbcbbe7d6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml
new file mode 100644
index 000000000..1baadf4bd
--- /dev/null
+++ b/.github/workflows/demo.yml
@@ -0,0 +1,38 @@
+name: Deploy to demo branch
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ deploy-demo:
+ runs-on: ubuntu-latest
+ steps:
+
+ - name: Checkout
+ uses: actions/checkout@v2.3.1
+
+ - uses: actions/cache@v2
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: '7.0.x'
+
+ - name: Publish Blogifier
+ run: dotnet publish -c Release /p:RuntimeIdentifier=linux-x64 ./src/Blogifier/Blogifier.csproj --output dist
+
+ - name: Add .nojekyll file
+ run: touch dist/.nojekyll
+
+ #- name: Deploy
+ # uses: JamesIves/github-pages-deploy-action@4.1.4
+ # with:
+ # branch: demo
+ # folder: release
diff --git a/.gitignore b/.gitignore
index 6c90d7c71..9d8d3b095 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,246 +1,360 @@
-## Ignore Visual Studio temporary files, build results, and
-## files generated by popular Visual Studio add-ons.
-
-# User-specific files
-*.suo
-*.user
-*.userosscache
-*.sln.docstates
-
-# User-specific files (MonoDevelop/Xamarin Studio)
-*.userprefs
-
-# Build results
-[Dd]ebug/
-[Dd]ebugPublic/
-[Rr]elease/
-[Rr]eleases/
-x64/
-x86/
-bld/
-[Bb]in/
-[Oo]bj/
-
-# Visual Studio 2015 cache/options directory
-.vs/
-# Uncomment if you have tasks that create the project's static files in wwwroot
-#wwwroot/
-
-# MSTest test Results
-[Tt]est[Rr]esult*/
-[Bb]uild[Ll]og.*
-
-# NUNIT
-*.VisualState.xml
-TestResult.xml
-
-# Build Results of an ATL Project
-[Dd]ebugPS/
-[Rr]eleasePS/
-dlldata.c
-
-# DNX
-project.lock.json
-artifacts/
-
-*_i.c
-*_p.c
-*_i.h
-*.ilk
-*.meta
-*.obj
-*.pch
-*.pdb
-*.pgc
-*.pgd
-*.rsp
-*.sbr
-*.tlb
-*.tli
-*.tlh
-*.tmp
-*.tmp_proj
-*.log
-*.vspscc
-*.vssscc
-.builds
-*.pidb
-*.svclog
-*.scc
-
-# Chutzpah Test files
-_Chutzpah*
-
-# Visual C++ cache files
-ipch/
-*.aps
-*.ncb
-*.opendb
-*.opensdf
-*.sdf
-*.cachefile
-
-# Visual Studio profiler
-*.psess
-*.vsp
-*.vspx
-*.sap
-
-# TFS 2012 Local Workspace
-$tf/
-
-# Guidance Automation Toolkit
-*.gpState
-
-# ReSharper is a .NET coding add-in
-_ReSharper*/
-*.[Rr]e[Ss]harper
-*.DotSettings.user
-
-# JustCode is a .NET coding add-in
-.JustCode
-
-# TeamCity is a build add-in
-_TeamCity*
-
-# DotCover is a Code Coverage Tool
-*.dotCover
-
-# NCrunch
-_NCrunch_*
-.*crunch*.local.xml
-nCrunchTemp_*
-
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
-.sass-cache/
-
-# Installshield output folder
-[Ee]xpress/
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
-# Click-Once directory
-publish/
-
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-# TODO: Comment the next line if you want to checkin your web deploy settings
-# but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
-*.publishproj
-
-# NuGet Packages
-*.nupkg
-# The packages folder can be ignored because of Package Restore
-**/packages/*
-# except build/, which is used as an MSBuild target.
-!**/packages/build/
-# Uncomment if necessary however generally it will be regenerated when needed
-#!**/packages/repositories.config
-# NuGet v3's project.json files produces more ignoreable files
-*.nuget.props
-*.nuget.targets
-
-# Microsoft Azure Build Output
-csx/
-*.build.csdef
-
-# Microsoft Azure Emulator
-ecf/
-rcf/
-
-# Microsoft Azure ApplicationInsights config file
-ApplicationInsights.config
-
-# Windows Store app package directory
-AppPackages/
-BundleArtifacts/
-
-# Visual Studio cache files
-# files ending in .cache can be ignored
-*.[Cc]ache
-# but keep track of directories ending in .cache
-!*.[Cc]ache/
-
-# Others
-ClientBin/
-~$*
-*~
-*.dbmdl
-*.dbproj.schemaview
-*.pfx
-*.publishsettings
-node_modules/
-orleans.codegen.cs
-
-# RIA/Silverlight projects
-Generated_Code/
-
-# Backup & report files from converting an old project file
-# to a newer Visual Studio version. Backup files are not needed,
-# because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-UpgradeLog*.htm
-
-# SQL Server files
-*.mdf
-*.ldf
-
-# Business Intelligence projects
-*.rdl.data
-*.bim.layout
-*.bim_*.settings
-
-# Microsoft Fakes
-FakesAssemblies/
-
-# GhostDoc plugin setting file
-*.GhostDoc.xml
-
-# Node.js Tools for Visual Studio
-.ntvs_analysis.dat
-
-# Visual Studio 6 build log
-*.plg
-
-# Visual Studio 6 workspace options file
-*.opt
-
-# Visual Studio LightSwitch build output
-**/*.HTMLClient/GeneratedArtifacts
-**/*.DesktopClient/GeneratedArtifacts
-**/*.DesktopClient/ModelManifest.xml
-**/*.Server/GeneratedArtifacts
-**/*.Server/ModelManifest.xml
-_Pvt_Extensions
-
-# Paket dependency manager
-.paket/paket.exe
-
-# FAKE - F# Make
-.fake/
-
-PublishProfiles/
-samples/**/Logs/*
-samples/**/blogifier/data/*
-
-# Build and CI files
-artifacts/
-tools/
-*.cake
-*.ps1
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+.DS_Store
+Blog.db-shm
+Blog.db-wal
+
+App_Data/
+
+appsettings.Development.json
+
+dist
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 402594323..b90bfb380 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,23 +1,35 @@
{
- "version": "0.2.0",
- "configurations": [
- {
- "name": ".NET Core Launch (console)",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "build",
- "program": "${workspaceRoot}\\samples\\WebApp\\bin\\Debug\\netcoreapp2.0\\WebApp.dll",
- "args": [],
- "cwd": "${workspaceRoot}\\samples\\WebApp",
- "console": "internalConsole",
- "stopAtEntry": false,
- "internalConsoleOptions": "openOnSessionStart"
+ // Use IntelliSense to find out which attributes exist for C# debugging
+ // Use hover for the description of the existing attributes
+ // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Launch and Debug",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "watch"
+ ],
+ "cwd": "${workspaceFolder}/src/Blogifier",
+ "env": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "launchBrowser": {
+ "enabled": true,
+ "args": "${auto-detect-url}",
+ "windows": {
+ "command": "cmd.exe",
+ "args": "/C start ${auto-detect-url}",
},
- {
- "name": ".NET Core Attach",
- "type": "coreclr",
- "request": "attach",
- "processId": "${command:pickProcess}"
+ "osx": {
+ "command": "open"
+ },
+ "linux": {
+ "command": "xdg-open"
}
- ]
-}
\ No newline at end of file
+ }
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..0db3279e4
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+
+}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 44e49c348..0bf8b754e 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -1,16 +1,42 @@
{
- "version": "0.1.0",
- "command": "dotnet",
- "isShellCommand": true,
- "args": [],
- "tasks": [
- {
- "taskName": "build",
- "args": [
- "${workspaceRoot}\\tests\\Blogifier.Test\\Blogifier.Test.csproj"
- ],
- "isBuildCommand": true,
- "problemMatcher": "$msCompile"
- }
- ]
-}
\ No newline at end of file
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/src/Blogifier/Blogifier.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/src/Blogifier/Blogifier.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "${workspaceFolder}/src/Blogifier/Blogifier.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
+}
diff --git a/Blogifier.Core.sln b/Blogifier.Core.sln
deleted file mode 100644
index 86a4003d3..000000000
--- a/Blogifier.Core.sln
+++ /dev/null
@@ -1,43 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26730.3
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blogifier.Core", "src\Blogifier.Core\Blogifier.Core.csproj", "{DB627B64-B1CE-46DA-90A8-144F6CAA60D4}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DB7E99A8-2A8A-4B33-B23C-1F1FA6DF7908}"
- ProjectSection(SolutionItems) = preProject
- .gitignore = .gitignore
- README.md = README.md
- EndProjectSection
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blogifier.Test", "tests\Blogifier.Test\Blogifier.Test.csproj", "{D7ABFDEF-036A-4ACA-B898-F0D93D12B989}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApp", "samples\WebApp\WebApp.csproj", "{0A0D2EA6-7BFC-4BDF-89CB-557E34CE6316}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {DB627B64-B1CE-46DA-90A8-144F6CAA60D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DB627B64-B1CE-46DA-90A8-144F6CAA60D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DB627B64-B1CE-46DA-90A8-144F6CAA60D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DB627B64-B1CE-46DA-90A8-144F6CAA60D4}.Release|Any CPU.Build.0 = Release|Any CPU
- {D7ABFDEF-036A-4ACA-B898-F0D93D12B989}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D7ABFDEF-036A-4ACA-B898-F0D93D12B989}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D7ABFDEF-036A-4ACA-B898-F0D93D12B989}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D7ABFDEF-036A-4ACA-B898-F0D93D12B989}.Release|Any CPU.Build.0 = Release|Any CPU
- {0A0D2EA6-7BFC-4BDF-89CB-557E34CE6316}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0A0D2EA6-7BFC-4BDF-89CB-557E34CE6316}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0A0D2EA6-7BFC-4BDF-89CB-557E34CE6316}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0A0D2EA6-7BFC-4BDF-89CB-557E34CE6316}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {A817A159-9B0A-47D5-B9A4-A4C6CDB9899A}
- EndGlobalSection
-EndGlobal
diff --git a/Blogifier.sln b/Blogifier.sln
new file mode 100644
index 000000000..0482bd743
--- /dev/null
+++ b/Blogifier.sln
@@ -0,0 +1,118 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.33516.290
+MinimumVisualStudioVersion = 16.0.0.0
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blogifier", "src\Blogifier\Blogifier.csproj", "{30113938-1040-459A-A0B6-3663E9534C8B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blogifier.Admin", "src\Blogifier.Admin\Blogifier.Admin.csproj", "{ABD5820B-6A81-42C3-89C7-B324329345C8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blogifier.Shared", "src\Blogifier.Shared\Blogifier.Shared.csproj", "{A0173B1A-FDC9-4E48-94E1-21D6C0B19257}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{512E41FF-25A7-49D6-B126-EC1450E17B72}"
+ ProjectSection(SolutionItems) = preProject
+ docs\01-Authentication.md = docs\01-Authentication.md
+ docs\02-Database.md = docs\02-Database.md
+ docs\03-Logging.md = docs\03-Logging.md
+ docs\04-Localization.md = docs\04-Localization.md
+ docs\05-Emails.md = docs\05-Emails.md
+ docs\06-Newsletters.md = docs\06-Newsletters.md
+ README.md = README.md
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8DDBCDEC-BFD8-4737-93BD-5259C3AE9CAE}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blogifier.Tests", "tests\Blogifier.Tests\Blogifier.Tests.csproj", "{27EB38D0-E275-4033-93B6-3ED6E6B7B442}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "items", "items", "{D2E9C8BA-AC43-4A35-88D9-8D63D26884B8}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ .gitignore = .gitignore
+ Dockerfile = Dockerfile
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blogifier.Themes.Standard", "src\Blogifier.Themes.Standard\Blogifier.Themes.Standard.csproj", "{7784DF02-6BCF-40A9-B327-C1F173975714}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "themes", "themes", "{4155E512-4F91-48D3-823A-45B7C71125A0}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {30113938-1040-459A-A0B6-3663E9534C8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {30113938-1040-459A-A0B6-3663E9534C8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {30113938-1040-459A-A0B6-3663E9534C8B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {30113938-1040-459A-A0B6-3663E9534C8B}.Debug|x64.Build.0 = Debug|Any CPU
+ {30113938-1040-459A-A0B6-3663E9534C8B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {30113938-1040-459A-A0B6-3663E9534C8B}.Debug|x86.Build.0 = Debug|Any CPU
+ {30113938-1040-459A-A0B6-3663E9534C8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {30113938-1040-459A-A0B6-3663E9534C8B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {30113938-1040-459A-A0B6-3663E9534C8B}.Release|x64.ActiveCfg = Release|Any CPU
+ {30113938-1040-459A-A0B6-3663E9534C8B}.Release|x64.Build.0 = Release|Any CPU
+ {30113938-1040-459A-A0B6-3663E9534C8B}.Release|x86.ActiveCfg = Release|Any CPU
+ {30113938-1040-459A-A0B6-3663E9534C8B}.Release|x86.Build.0 = Release|Any CPU
+ {ABD5820B-6A81-42C3-89C7-B324329345C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ABD5820B-6A81-42C3-89C7-B324329345C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ABD5820B-6A81-42C3-89C7-B324329345C8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {ABD5820B-6A81-42C3-89C7-B324329345C8}.Debug|x64.Build.0 = Debug|Any CPU
+ {ABD5820B-6A81-42C3-89C7-B324329345C8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {ABD5820B-6A81-42C3-89C7-B324329345C8}.Debug|x86.Build.0 = Debug|Any CPU
+ {ABD5820B-6A81-42C3-89C7-B324329345C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ABD5820B-6A81-42C3-89C7-B324329345C8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ABD5820B-6A81-42C3-89C7-B324329345C8}.Release|x64.ActiveCfg = Release|Any CPU
+ {ABD5820B-6A81-42C3-89C7-B324329345C8}.Release|x64.Build.0 = Release|Any CPU
+ {ABD5820B-6A81-42C3-89C7-B324329345C8}.Release|x86.ActiveCfg = Release|Any CPU
+ {ABD5820B-6A81-42C3-89C7-B324329345C8}.Release|x86.Build.0 = Release|Any CPU
+ {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Debug|x64.Build.0 = Debug|Any CPU
+ {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Debug|x86.Build.0 = Debug|Any CPU
+ {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Release|x64.ActiveCfg = Release|Any CPU
+ {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Release|x64.Build.0 = Release|Any CPU
+ {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Release|x86.ActiveCfg = Release|Any CPU
+ {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Release|x86.Build.0 = Release|Any CPU
+ {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Debug|x64.Build.0 = Debug|Any CPU
+ {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Debug|x86.Build.0 = Debug|Any CPU
+ {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Release|Any CPU.Build.0 = Release|Any CPU
+ {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Release|x64.ActiveCfg = Release|Any CPU
+ {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Release|x64.Build.0 = Release|Any CPU
+ {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Release|x86.ActiveCfg = Release|Any CPU
+ {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Release|x86.Build.0 = Release|Any CPU
+ {7784DF02-6BCF-40A9-B327-C1F173975714}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7784DF02-6BCF-40A9-B327-C1F173975714}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7784DF02-6BCF-40A9-B327-C1F173975714}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7784DF02-6BCF-40A9-B327-C1F173975714}.Debug|x64.Build.0 = Debug|Any CPU
+ {7784DF02-6BCF-40A9-B327-C1F173975714}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7784DF02-6BCF-40A9-B327-C1F173975714}.Debug|x86.Build.0 = Debug|Any CPU
+ {7784DF02-6BCF-40A9-B327-C1F173975714}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7784DF02-6BCF-40A9-B327-C1F173975714}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7784DF02-6BCF-40A9-B327-C1F173975714}.Release|x64.ActiveCfg = Release|Any CPU
+ {7784DF02-6BCF-40A9-B327-C1F173975714}.Release|x64.Build.0 = Release|Any CPU
+ {7784DF02-6BCF-40A9-B327-C1F173975714}.Release|x86.ActiveCfg = Release|Any CPU
+ {7784DF02-6BCF-40A9-B327-C1F173975714}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {27EB38D0-E275-4033-93B6-3ED6E6B7B442} = {8DDBCDEC-BFD8-4737-93BD-5259C3AE9CAE}
+ {7784DF02-6BCF-40A9-B327-C1F173975714} = {4155E512-4F91-48D3-823A-45B7C71125A0}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {8AA706E7-D820-4892-9F60-FCEEDC51FF5E}
+ EndGlobalSection
+EndGlobal
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..9b213ead1
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,14 @@
+FROM mcr.microsoft.com/dotnet/sdk:7.0-alpine as sdk
+# Copy everything else and build
+COPY ./ /opt/blogifier
+WORKDIR /opt/blogifier
+RUN ["dotnet","publish", "-c", "Release","/p:RuntimeIdentifier=linux-musl-x64", "./src/Blogifier/Blogifier.csproj","-o","dist" ]
+
+FROM mcr.microsoft.com/dotnet/aspnet:7.0-alpine as run
+# TOTO zh-CH
+RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
+RUN apk add --no-cache icu-libs
+ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
+COPY --from=sdk /opt/blogifier/dist /opt/blogifier/
+WORKDIR /opt/blogifier
+ENTRYPOINT ["dotnet", "Blogifier.dll"]
diff --git a/LICENSE b/LICENSE
index 65c6fd622..41bd36966 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2017 Blogifier.NET
+Copyright (c) 2020 rxtur
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 73e2f8a44..761adafde 100644
--- a/README.md
+++ b/README.md
@@ -1,93 +1,52 @@
-## Blogifier.Core [](https://www.nuget.org/packages/Blogifier.Core)
+
+
Blogifier
+
+ Blogifier is a self-hosted open source publishing platform written in ASP.NET and Blazor WebAssembly.
+ It can be used to quickly and easily set up a lightweight, but fully functional personal or group blog.
+
+
+
+## Installation
+
+Steps to install compiled application on the server for a self-hosting:
+
+1. .NET Core Runtime (currently 7.0) must be installed on your host server.
+2. [Download](https://github.com/blogifierdotnet/Blogifier/releases) the latest release.
+3. Unzip and copy to your host server.
+4. Restart your website.
+5. Open your website and only the first time you'll be redirected to the register page. `example.com/admin/register/`
+6. Register, and then log in. `example.com/admin/login/`
+7. Done, enjoy.
+
+
+## Development
+If you want to customize the Blogifier, or contribute:
+
+1. [Download](https://dotnet.microsoft.com/download/dotnet) and Install .NET SDK.
+2. Download, fork, or clone the repository.
+3. Open the project with your favorite IDE (VS Code, Visual Studio, Atom, etc).
+4. Run the app with your IDE or these commands:
-## Blogifier.Core.PostgreSql [](https://www.nuget.org/packages/Blogifier.Core.PostgreSql)
-
-The goal of this project is to "blogify" ASP.NET applications; Blogifier.Core built and published as a [Nuget package](https://www.nuget.org/packages/Blogifier.Core) that can be installed by ASP.NET application to provide common blogging functionality.
-
-## Demo site
-
-The [demo site](http://blogifier.azurewebsites.net) is a playground you can use to check out Blogifier features. You can register new user and write post to test admin panel.
-
-
-
-## System Requirements
-
-* Windows or Linux
-* ASP.NET Core 1.1
-* .NET Framework 4.5.2
-* Visual Studio 2017 or VS Code
-* Authentication enabled
-* SQL Server (Windows) or PostgreSql (Linux)
-
-Designed for cross-platform development, every build pushed to Windows and Linux servers.
-
-## Getting Started
-
-1. Open in VS 2017 and run WebApp sample application
-2. Register new user
-3. You should be able navigate to `/blog` and `/admin`
-
-## Using Blogifier.Core Nuget Package
-
-1. In VS 2017, create new ASP.NET Core 1.1 Web Application with user authentication (single user accounts)
-2. Open Nuget Package Manager console and run this command:
```
-Install-Package Blogifier.Core
+$ cd /your-local-path/Blogifier/src/Blogifier/
+$ dotnet run
```
-3. Configure services and application in Startup.cs:
-```csharp
-public void ConfigureServices(IServiceCollection services)
-{
- ...
- Blogifier.Core.Configuration.InitServices(services, Configuration);
-}
-public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
-{
- ...
- Blogifier.Core.Configuration.InitApplication(app, env, loggerFactory);
-}
+Then you can open `localhost:5000` with your browser, Also login to the admin panel `localhost:5000/admin/`.
```
-4. You should be able to run application and navigate to `/blog` and `/admin`
-
-## Security
-
-* Blogifier.Core inherits user authentication from `parent` application and acts accordingly.
-* If user authenticated but there is no profile for user identity, navigating to `/admin` will redirect to profile page. Filling in profile will effectively create a new blog.
-* First application user will be marked as application administrator and will be able manage application settings.
-
-## Application Settings
-
-Default application settings can be overwritten in application `appsettings.json` configuration file (you can add one if not exists). For example, to change connection string for your database provider:
-
-```json
-{
- "Blogifier": {
- "ConnectionString": "your connection string here"
- }
-}
-```
-
-[More on application settings](https://github.com/blogifierdotnet/Blogifier.Core/wiki/Application-Settings)
-
-## Database Providers
-
-Blogifier.Core implements Entity Framework (code first) as ORM. It uses MS SQL Server provider for Blogigier.Core package and PostgreSql provider for Blogifier.Core.PostgreSql
-
-Connection string cascades based on conditions:
-* Use default built-in Blogifier connection string
-* Use default parent application connection string in `appsettings.json`
-* Use Blogifier connection string in `appsettings.json`.
-
-```json
-{
- "Blogifier": {
- "ConnectionString": "Server=.\\SQLEXPRESS;Database=Blogifier;Trusted_Connection=True;"
- }
-}
+username: admin@example.com
+password: admin
```
-## Administration
+
+## Contributing
+Please read [contributing guidelines](https://github.com/blogifierdotnet/Blogifier/blob/main/.github/CONTRIBUTING.md). We have a list of things there that you can help us with.
-
+
+## Team
+[](https://github.com/farzindev)
+[](https://github.com/rxtur)
-
+
+## Copyright and License
+Code released under the MIT License. Docs released under Creative Commons.
+Copyright 2017–2022 Blogifier
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..405270d05
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,14 @@
+# Security Policy
+
+## Supported Versions
+
+Security patches will be applied to the most recent version.
+
+| Version | Supported |
+| ------- | ------------------ |
+| 2.9.x.x | Most Current Version|
+
+## Reporting a Vulnerability
+
+Please report (suspected) security vulnerabilities to
+**[blogifierdotnet@gmail.com](mailto:blogifierdotnet@gmail.com)**.
diff --git a/build.cmd b/build.cmd
new file mode 100644
index 000000000..a71b5741b
--- /dev/null
+++ b/build.cmd
@@ -0,0 +1,2 @@
+dotnet clean
+dotnet build -c Debug /p:RuntimeIdentifier=linux-x64 ./src/Blogifier/Blogifier.csproj --output dist
diff --git a/build.sh b/build.sh
new file mode 100644
index 000000000..b0214ddf2
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,2 @@
+dotnet clean
+dotnet build -c Debug /p:RuntimeIdentifier=win-x64 ./src/Blogifier/Blogifier.csproj --output dist
diff --git a/docs/01-Authentication.md b/docs/01-Authentication.md
new file mode 100644
index 000000000..b0159e275
--- /dev/null
+++ b/docs/01-Authentication.md
@@ -0,0 +1,18 @@
+
+### User Login and Registration
+On the first login, user gets redirected to the `admin/register` page to register a new account.
+Once account created, registration page is disabled and this user becomes a blog owner.
+
+### Authentication
+The blog posts, including blog themes, are all run as public, server-side rendered MVC site.
+
+Anything under `admin` is Blazor Web Assembly application and is guarded by custom authentication provider (`BlogAuthenticationStateProvider`).
+User password is one-way hashed and saved in the `Authors` table on the back-end.
+The salt used to hash password pulled from `appsettings.json` configuration file and should be updated **before** creating user account.
+
+```
+"Blogifier": {
+ ...
+ "Salt": "SECRET-CHANGE-ME!"
+}
+```
\ No newline at end of file
diff --git a/docs/02-Database.md b/docs/02-Database.md
new file mode 100644
index 000000000..e7c5ede61
--- /dev/null
+++ b/docs/02-Database.md
@@ -0,0 +1,74 @@
+## To change Database Provider
+Update provider and connection string in the `appsettings.json`:
+
+#### SQLite
+``` json
+"Blogifier": {
+ "DbProvider": "Sqlite",
+ "ConnString": "Data Source=App_Data/blogifier.db",
+ ...
+}
+```
+It is recommended to put the database file under the App_Data folder. The logs and local pictures in the project will be stored in this path for persistence.
+
+#### SqlServer
+``` json
+"Blogifier": {
+ "DbProvider": "SqlServer",
+ "ConnString": "Data Source=mssql; User Id=sa; Password=Password; Initial Catalog=blogifier;TrustServerCertificate=True",
+ ...
+}
+```
+In the latest version of sql server connection, SqlClient will perform a secure connection by default, and you need to add a server certificate to the system. The example adds TrustServerCertificate=True to ignore this requirement. You can also delete this ignore and enable a secure connection.
+
+#### MySql
+``` json
+"Blogifier": {
+ "DbProvider": "MySql",
+ "ConnString": "server=mysql;user=root;password=password;database=blogifier",
+ ...
+}
+```
+
+#### Postgres
+``` json
+"Blogifier": {
+ "DbProvider": "Postgres",
+ "ConnString": "Host=postgres;Username=postgres;Password=password;Database=blogifier;",
+ ...
+}
+```
+In the above example, ConnString requires you to fill in the correct database host address username and password to connect normally
+
+
+## When a change to an entity field requires a database migration
+
+The database migration is stored in the src/Blogifier/Data/Migrations directory. The current project is still under development. When there is a modification, this directory may be deleted for quick migration. After the project is officially released, it is no longer recommended to delete the updated database migrate.
+
+The following is the way to generate a new migration or delete the previous migration command. Before executing the command, please configure the corresponding DbProvider and ConnString in appsettings.json and then execute the corresponding migration command
+``` shell
+# Revert Migration Tool
+dotnet tool restore
+
+# Jump to project directory
+cd src/Blogifier
+
+# Sqlite
+dotnet ef migrations add Init --context SqliteDbContext --output-dir Data/Migrations/Sqlite
+dotnet ef migrations remove --context SqliteDbContext
+
+# SqlServer
+dotnet ef migrations add Init --context SqlServerDbContext --output-dir Data/Migrations/SqlServer
+dotnet ef migrations remove --context SqlServerDbContext
+
+# MySql
+dotnet ef migrations add Init --context MySqlDbContext --output-dir Data/Migrations/MySql
+dotnet ef migrations remove --context MySqlDbContext
+
+# Postgres
+dotnet ef migrations add Init --context PostgresDbContext --output-dir Data/Migrations/Postgres
+dotnet ef migrations remove --context MySqlDbContext
+```
+
+### Warn
+Do not add or delete database migration at will. After the application generates data, random migration may cause data loss. This project will automatically apply the migration when it starts.
diff --git a/docs/03-Logging.md b/docs/03-Logging.md
new file mode 100644
index 000000000..6ec7510ea
--- /dev/null
+++ b/docs/03-Logging.md
@@ -0,0 +1,13 @@
+### Serilog
+
+Logging done using [Serilog Sink File](https://github.com/serilog/serilog-sinks-file) package.
+All logs saved to the `/Logs` folder as configured in the application startup:
+
+```
+Log.Logger = new LoggerConfiguration()
+ .Enrich.FromLogContext()
+ .WriteTo.File("Logs/log-.txt", rollingInterval: RollingInterval.Day)
+ .CreateLogger();
+
+Log.Warning("Test log");
+```
\ No newline at end of file
diff --git a/docs/04-Localization.md b/docs/04-Localization.md
new file mode 100644
index 000000000..72708cf67
--- /dev/null
+++ b/docs/04-Localization.md
@@ -0,0 +1,8 @@
+### Blazor Internationalization(I18n) Text
+
+Localization and internationalization is done on admin UI level using
+[Blazor Internationalization](https://github.com/jsakamoto/Toolbelt.Blazor.I18nText) package.
+It uses JSON files instead of standard XML resources used by MS frameworks and compiles resources on build making them strongly typed.
+
+Resource language files located under `Blogifier.Admin/i18ntext` folder.
+If browser set to use one of the cultures in this folder, admin UI should display accordingly.
\ No newline at end of file
diff --git a/samples/WebApp/wwwroot/blogifier/admin/custom/js/lib/tinymce/skins/blogifier/skin.min.css b/docs/05-Emails.md
similarity index 100%
rename from samples/WebApp/wwwroot/blogifier/admin/custom/js/lib/tinymce/skins/blogifier/skin.min.css
rename to docs/05-Emails.md
diff --git a/samples/WebApp/wwwroot/blogifier/admin/custom/scss/components/_tooltip.scss b/docs/06-Newsletters.md
similarity index 100%
rename from samples/WebApp/wwwroot/blogifier/admin/custom/scss/components/_tooltip.scss
rename to docs/06-Newsletters.md
diff --git a/publish.cmd b/publish.cmd
new file mode 100644
index 000000000..4eec9c5bd
--- /dev/null
+++ b/publish.cmd
@@ -0,0 +1 @@
+dotnet publish -c Release /p:RuntimeIdentifier=win-x64 ./src/Blogifier/Blogifier.csproj -v minimal --output dist
diff --git a/publish.sh b/publish.sh
new file mode 100644
index 000000000..39fb990e8
--- /dev/null
+++ b/publish.sh
@@ -0,0 +1,7 @@
+# Local machine
+# rm -fr dist
+# dotnet publish -c Release /p:RuntimeIdentifier=linux-x64 ./src/Blogifier/Blogifier.csproj --output dist
+
+# docker
+docker build -t dorthl/blogifier:latest .
+docker push dorthl/blogifier:latest
diff --git a/samples/WebApp/.bowerrc b/samples/WebApp/.bowerrc
deleted file mode 100644
index 6406626ab..000000000
--- a/samples/WebApp/.bowerrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "directory": "wwwroot/lib"
-}
diff --git a/samples/WebApp/Controllers/AccountController.cs b/samples/WebApp/Controllers/AccountController.cs
deleted file mode 100644
index dc4e11703..000000000
--- a/samples/WebApp/Controllers/AccountController.cs
+++ /dev/null
@@ -1,464 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Security.Claims;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Authentication;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Identity;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.Rendering;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using WebApp.Models;
-using WebApp.Models.AccountViewModels;
-using WebApp.Services;
-
-namespace WebApp.Controllers
-{
- [Authorize]
- [Route("[controller]/[action]")]
- public class AccountController : Controller
- {
- private readonly UserManager _userManager;
- private readonly SignInManager _signInManager;
- private readonly IEmailSender _emailSender;
- private readonly ILogger _logger;
-
- public AccountController(
- UserManager userManager,
- SignInManager signInManager,
- IEmailSender emailSender,
- ILogger logger)
- {
- _userManager = userManager;
- _signInManager = signInManager;
- _emailSender = emailSender;
- _logger = logger;
- }
-
- [TempData]
- public string ErrorMessage { get; set; }
-
- [HttpGet]
- [AllowAnonymous]
- public async Task Login(string returnUrl = null)
- {
- // Clear the existing external cookie to ensure a clean login process
- await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
-
- ViewData["ReturnUrl"] = returnUrl;
- return View();
- }
-
- [HttpPost]
- [AllowAnonymous]
- [ValidateAntiForgeryToken]
- public async Task Login(LoginViewModel model, string returnUrl = null)
- {
- ViewData["ReturnUrl"] = returnUrl;
- if (ModelState.IsValid)
- {
- // This doesn't count login failures towards account lockout
- // To enable password failures to trigger account lockout, set lockoutOnFailure: true
- var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
- if (result.Succeeded)
- {
- _logger.LogInformation("User logged in.");
- return RedirectToLocal(returnUrl);
- }
- if (result.RequiresTwoFactor)
- {
- return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe });
- }
- if (result.IsLockedOut)
- {
- _logger.LogWarning("User account locked out.");
- return RedirectToAction(nameof(Lockout));
- }
- else
- {
- ModelState.AddModelError(string.Empty, "Invalid login attempt.");
- return View(model);
- }
- }
-
- // If we got this far, something failed, redisplay form
- return View(model);
- }
-
- [HttpGet]
- [AllowAnonymous]
- public async Task LoginWith2fa(bool rememberMe, string returnUrl = null)
- {
- // Ensure the user has gone through the username & password screen first
- var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
-
- if (user == null)
- {
- throw new ApplicationException($"Unable to load two-factor authentication user.");
- }
-
- var model = new LoginWith2faViewModel { RememberMe = rememberMe };
- ViewData["ReturnUrl"] = returnUrl;
-
- return View(model);
- }
-
- [HttpPost]
- [AllowAnonymous]
- [ValidateAntiForgeryToken]
- public async Task LoginWith2fa(LoginWith2faViewModel model, bool rememberMe, string returnUrl = null)
- {
- if (!ModelState.IsValid)
- {
- return View(model);
- }
-
- var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var authenticatorCode = model.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
-
- var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, model.RememberMachine);
-
- if (result.Succeeded)
- {
- _logger.LogInformation("User with ID {UserId} logged in with 2fa.", user.Id);
- return RedirectToLocal(returnUrl);
- }
- else if (result.IsLockedOut)
- {
- _logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
- return RedirectToAction(nameof(Lockout));
- }
- else
- {
- _logger.LogWarning("Invalid authenticator code entered for user with ID {UserId}.", user.Id);
- ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
- return View();
- }
- }
-
- [HttpGet]
- [AllowAnonymous]
- public async Task LoginWithRecoveryCode(string returnUrl = null)
- {
- // Ensure the user has gone through the username & password screen first
- var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
- if (user == null)
- {
- throw new ApplicationException($"Unable to load two-factor authentication user.");
- }
-
- ViewData["ReturnUrl"] = returnUrl;
-
- return View();
- }
-
- [HttpPost]
- [AllowAnonymous]
- [ValidateAntiForgeryToken]
- public async Task LoginWithRecoveryCode(LoginWithRecoveryCodeViewModel model, string returnUrl = null)
- {
- if (!ModelState.IsValid)
- {
- return View(model);
- }
-
- var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
- if (user == null)
- {
- throw new ApplicationException($"Unable to load two-factor authentication user.");
- }
-
- var recoveryCode = model.RecoveryCode.Replace(" ", string.Empty);
-
- var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
-
- if (result.Succeeded)
- {
- _logger.LogInformation("User with ID {UserId} logged in with a recovery code.", user.Id);
- return RedirectToLocal(returnUrl);
- }
- if (result.IsLockedOut)
- {
- _logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
- return RedirectToAction(nameof(Lockout));
- }
- else
- {
- _logger.LogWarning("Invalid recovery code entered for user with ID {UserId}", user.Id);
- ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
- return View();
- }
- }
-
- [HttpGet]
- [AllowAnonymous]
- public IActionResult Lockout()
- {
- return View();
- }
-
- [HttpGet]
- [AllowAnonymous]
- public IActionResult Register(string returnUrl = null)
- {
- ViewData["ReturnUrl"] = returnUrl;
- return View();
- }
-
- [HttpPost]
- [AllowAnonymous]
- [ValidateAntiForgeryToken]
- public async Task Register(RegisterViewModel model, string returnUrl = null)
- {
- ViewData["ReturnUrl"] = returnUrl;
- if (ModelState.IsValid)
- {
- var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
- var result = await _userManager.CreateAsync(user, model.Password);
- if (result.Succeeded)
- {
- _logger.LogInformation("User created a new account with password.");
-
- var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
- var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
- await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
-
- await _signInManager.SignInAsync(user, isPersistent: false);
- _logger.LogInformation("User created a new account with password.");
- return RedirectToLocal(returnUrl);
- }
- AddErrors(result);
- }
-
- // If we got this far, something failed, redisplay form
- return View(model);
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task Logout()
- {
- await _signInManager.SignOutAsync();
- _logger.LogInformation("User logged out.");
- return RedirectToAction(nameof(HomeController.Index), "Home");
- }
-
- [HttpPost]
- [AllowAnonymous]
- [ValidateAntiForgeryToken]
- public IActionResult ExternalLogin(string provider, string returnUrl = null)
- {
- // Request a redirect to the external login provider.
- var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new { returnUrl });
- var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
- return Challenge(properties, provider);
- }
-
- [HttpGet]
- [AllowAnonymous]
- public async Task ExternalLoginCallback(string returnUrl = null, string remoteError = null)
- {
- if (remoteError != null)
- {
- ErrorMessage = $"Error from external provider: {remoteError}";
- return RedirectToAction(nameof(Login));
- }
- var info = await _signInManager.GetExternalLoginInfoAsync();
- if (info == null)
- {
- return RedirectToAction(nameof(Login));
- }
-
- // Sign in the user with this external login provider if the user already has a login.
- var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
- if (result.Succeeded)
- {
- _logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
- return RedirectToLocal(returnUrl);
- }
- if (result.IsLockedOut)
- {
- return RedirectToAction(nameof(Lockout));
- }
- else
- {
- // If the user does not have an account, then ask the user to create an account.
- ViewData["ReturnUrl"] = returnUrl;
- ViewData["LoginProvider"] = info.LoginProvider;
- var email = info.Principal.FindFirstValue(ClaimTypes.Email);
- return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
- }
- }
-
- [HttpPost]
- [AllowAnonymous]
- [ValidateAntiForgeryToken]
- public async Task ExternalLoginConfirmation(ExternalLoginViewModel model, string returnUrl = null)
- {
- if (ModelState.IsValid)
- {
- // Get the information about the user from the external login provider
- var info = await _signInManager.GetExternalLoginInfoAsync();
- if (info == null)
- {
- throw new ApplicationException("Error loading external login information during confirmation.");
- }
- var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
- var result = await _userManager.CreateAsync(user);
- if (result.Succeeded)
- {
- result = await _userManager.AddLoginAsync(user, info);
- if (result.Succeeded)
- {
- await _signInManager.SignInAsync(user, isPersistent: false);
- _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
- return RedirectToLocal(returnUrl);
- }
- }
- AddErrors(result);
- }
-
- ViewData["ReturnUrl"] = returnUrl;
- return View(nameof(ExternalLogin), model);
- }
-
- [HttpGet]
- [AllowAnonymous]
- public async Task ConfirmEmail(string userId, string code)
- {
- if (userId == null || code == null)
- {
- return RedirectToAction(nameof(HomeController.Index), "Home");
- }
- var user = await _userManager.FindByIdAsync(userId);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{userId}'.");
- }
- var result = await _userManager.ConfirmEmailAsync(user, code);
- return View(result.Succeeded ? "ConfirmEmail" : "Error");
- }
-
- [HttpGet]
- [AllowAnonymous]
- public IActionResult ForgotPassword()
- {
- return View();
- }
-
- [HttpPost]
- [AllowAnonymous]
- [ValidateAntiForgeryToken]
- public async Task ForgotPassword(ForgotPasswordViewModel model)
- {
- if (ModelState.IsValid)
- {
- var user = await _userManager.FindByEmailAsync(model.Email);
- if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
- {
- // Don't reveal that the user does not exist or is not confirmed
- return RedirectToAction(nameof(ForgotPasswordConfirmation));
- }
-
- // For more information on how to enable account confirmation and password reset please
- // visit https://go.microsoft.com/fwlink/?LinkID=532713
- var code = await _userManager.GeneratePasswordResetTokenAsync(user);
- var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme);
- await _emailSender.SendEmailAsync(model.Email, "Reset Password",
- $"Please reset your password by clicking here: link");
- return RedirectToAction(nameof(ForgotPasswordConfirmation));
- }
-
- // If we got this far, something failed, redisplay form
- return View(model);
- }
-
- [HttpGet]
- [AllowAnonymous]
- public IActionResult ForgotPasswordConfirmation()
- {
- return View();
- }
-
- [HttpGet]
- [AllowAnonymous]
- public IActionResult ResetPassword(string code = null)
- {
- if (code == null)
- {
- throw new ApplicationException("A code must be supplied for password reset.");
- }
- var model = new ResetPasswordViewModel { Code = code };
- return View(model);
- }
-
- [HttpPost]
- [AllowAnonymous]
- [ValidateAntiForgeryToken]
- public async Task ResetPassword(ResetPasswordViewModel model)
- {
- if (!ModelState.IsValid)
- {
- return View(model);
- }
- var user = await _userManager.FindByEmailAsync(model.Email);
- if (user == null)
- {
- // Don't reveal that the user does not exist
- return RedirectToAction(nameof(ResetPasswordConfirmation));
- }
- var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password);
- if (result.Succeeded)
- {
- return RedirectToAction(nameof(ResetPasswordConfirmation));
- }
- AddErrors(result);
- return View();
- }
-
- [HttpGet]
- [AllowAnonymous]
- public IActionResult ResetPasswordConfirmation()
- {
- return View();
- }
-
-
- [HttpGet]
- public IActionResult AccessDenied()
- {
- return View();
- }
-
- #region Helpers
-
- private void AddErrors(IdentityResult result)
- {
- foreach (var error in result.Errors)
- {
- ModelState.AddModelError(string.Empty, error.Description);
- }
- }
-
- private IActionResult RedirectToLocal(string returnUrl)
- {
- if (Url.IsLocalUrl(returnUrl))
- {
- return Redirect(returnUrl);
- }
- else
- {
- return RedirectToAction(nameof(HomeController.Index), "Home");
- }
- }
-
- #endregion
- }
-}
diff --git a/samples/WebApp/Controllers/HomeController.cs b/samples/WebApp/Controllers/HomeController.cs
deleted file mode 100644
index 52ba95f9c..000000000
--- a/samples/WebApp/Controllers/HomeController.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Mvc;
-using WebApp.Models;
-
-namespace WebApp.Controllers
-{
- public class HomeController : Controller
- {
- public IActionResult Index()
- {
- return View();
- }
-
- public IActionResult About()
- {
- ViewData["Message"] = "Your application description page.";
-
- return View();
- }
-
- public IActionResult Contact()
- {
- ViewData["Message"] = "Your contact page.";
-
- return View();
- }
-
- public IActionResult Error()
- {
- return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
- }
- }
-}
diff --git a/samples/WebApp/Controllers/ManageController.cs b/samples/WebApp/Controllers/ManageController.cs
deleted file mode 100644
index ec36c7c36..000000000
--- a/samples/WebApp/Controllers/ManageController.cs
+++ /dev/null
@@ -1,505 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Text.Encodings.Web;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Authentication;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Identity;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using WebApp.Models;
-using WebApp.Models.ManageViewModels;
-using WebApp.Services;
-
-namespace WebApp.Controllers
-{
- [Authorize]
- [Route("[controller]/[action]")]
- public class ManageController : Controller
- {
- private readonly UserManager _userManager;
- private readonly SignInManager _signInManager;
- private readonly IEmailSender _emailSender;
- private readonly ILogger _logger;
- private readonly UrlEncoder _urlEncoder;
-
- private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
-
- public ManageController(
- UserManager userManager,
- SignInManager signInManager,
- IEmailSender emailSender,
- ILogger logger,
- UrlEncoder urlEncoder)
- {
- _userManager = userManager;
- _signInManager = signInManager;
- _emailSender = emailSender;
- _logger = logger;
- _urlEncoder = urlEncoder;
- }
-
- [TempData]
- public string StatusMessage { get; set; }
-
- [HttpGet]
- public async Task Index()
- {
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var model = new IndexViewModel
- {
- Username = user.UserName,
- Email = user.Email,
- PhoneNumber = user.PhoneNumber,
- IsEmailConfirmed = user.EmailConfirmed,
- StatusMessage = StatusMessage
- };
-
- return View(model);
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task Index(IndexViewModel model)
- {
- if (!ModelState.IsValid)
- {
- return View(model);
- }
-
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var email = user.Email;
- if (model.Email != email)
- {
- var setEmailResult = await _userManager.SetEmailAsync(user, model.Email);
- if (!setEmailResult.Succeeded)
- {
- throw new ApplicationException($"Unexpected error occurred setting email for user with ID '{user.Id}'.");
- }
- }
-
- var phoneNumber = user.PhoneNumber;
- if (model.PhoneNumber != phoneNumber)
- {
- var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, model.PhoneNumber);
- if (!setPhoneResult.Succeeded)
- {
- throw new ApplicationException($"Unexpected error occurred setting phone number for user with ID '{user.Id}'.");
- }
- }
-
- StatusMessage = "Your profile has been updated";
- return RedirectToAction(nameof(Index));
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task SendVerificationEmail(IndexViewModel model)
- {
- if (!ModelState.IsValid)
- {
- return View(model);
- }
-
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
- var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
- var email = user.Email;
- await _emailSender.SendEmailConfirmationAsync(email, callbackUrl);
-
- StatusMessage = "Verification email sent. Please check your email.";
- return RedirectToAction(nameof(Index));
- }
-
- [HttpGet]
- public async Task ChangePassword()
- {
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var hasPassword = await _userManager.HasPasswordAsync(user);
- if (!hasPassword)
- {
- return RedirectToAction(nameof(SetPassword));
- }
-
- var model = new ChangePasswordViewModel { StatusMessage = StatusMessage };
- return View(model);
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task ChangePassword(ChangePasswordViewModel model)
- {
- if (!ModelState.IsValid)
- {
- return View(model);
- }
-
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var changePasswordResult = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
- if (!changePasswordResult.Succeeded)
- {
- AddErrors(changePasswordResult);
- return View(model);
- }
-
- await _signInManager.SignInAsync(user, isPersistent: false);
- _logger.LogInformation("User changed their password successfully.");
- StatusMessage = "Your password has been changed.";
-
- return RedirectToAction(nameof(ChangePassword));
- }
-
- [HttpGet]
- public async Task SetPassword()
- {
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var hasPassword = await _userManager.HasPasswordAsync(user);
-
- if (hasPassword)
- {
- return RedirectToAction(nameof(ChangePassword));
- }
-
- var model = new SetPasswordViewModel { StatusMessage = StatusMessage };
- return View(model);
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task SetPassword(SetPasswordViewModel model)
- {
- if (!ModelState.IsValid)
- {
- return View(model);
- }
-
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var addPasswordResult = await _userManager.AddPasswordAsync(user, model.NewPassword);
- if (!addPasswordResult.Succeeded)
- {
- AddErrors(addPasswordResult);
- return View(model);
- }
-
- await _signInManager.SignInAsync(user, isPersistent: false);
- StatusMessage = "Your password has been set.";
-
- return RedirectToAction(nameof(SetPassword));
- }
-
- [HttpGet]
- public async Task ExternalLogins()
- {
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var model = new ExternalLoginsViewModel { CurrentLogins = await _userManager.GetLoginsAsync(user) };
- model.OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
- .Where(auth => model.CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
- .ToList();
- model.ShowRemoveButton = await _userManager.HasPasswordAsync(user) || model.CurrentLogins.Count > 1;
- model.StatusMessage = StatusMessage;
-
- return View(model);
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task LinkLogin(string provider)
- {
- // Clear the existing external cookie to ensure a clean login process
- await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
-
- // Request a redirect to the external login provider to link a login for the current user
- var redirectUrl = Url.Action(nameof(LinkLoginCallback));
- var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
- return new ChallengeResult(provider, properties);
- }
-
- [HttpGet]
- public async Task LinkLoginCallback()
- {
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var info = await _signInManager.GetExternalLoginInfoAsync(user.Id);
- if (info == null)
- {
- throw new ApplicationException($"Unexpected error occurred loading external login info for user with ID '{user.Id}'.");
- }
-
- var result = await _userManager.AddLoginAsync(user, info);
- if (!result.Succeeded)
- {
- throw new ApplicationException($"Unexpected error occurred adding external login for user with ID '{user.Id}'.");
- }
-
- // Clear the existing external cookie to ensure a clean login process
- await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
-
- StatusMessage = "The external login was added.";
- return RedirectToAction(nameof(ExternalLogins));
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task RemoveLogin(RemoveLoginViewModel model)
- {
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var result = await _userManager.RemoveLoginAsync(user, model.LoginProvider, model.ProviderKey);
- if (!result.Succeeded)
- {
- throw new ApplicationException($"Unexpected error occurred removing external login for user with ID '{user.Id}'.");
- }
-
- await _signInManager.SignInAsync(user, isPersistent: false);
- StatusMessage = "The external login was removed.";
- return RedirectToAction(nameof(ExternalLogins));
- }
-
- [HttpGet]
- public async Task TwoFactorAuthentication()
- {
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var model = new TwoFactorAuthenticationViewModel
- {
- HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null,
- Is2faEnabled = user.TwoFactorEnabled,
- RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user),
- };
-
- return View(model);
- }
-
- [HttpGet]
- public async Task Disable2faWarning()
- {
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- if (!user.TwoFactorEnabled)
- {
- throw new ApplicationException($"Unexpected error occured disabling 2FA for user with ID '{user.Id}'.");
- }
-
- return View(nameof(Disable2fa));
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task Disable2fa()
- {
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);
- if (!disable2faResult.Succeeded)
- {
- throw new ApplicationException($"Unexpected error occured disabling 2FA for user with ID '{user.Id}'.");
- }
-
- _logger.LogInformation("User with ID {UserId} has disabled 2fa.", user.Id);
- return RedirectToAction(nameof(TwoFactorAuthentication));
- }
-
- [HttpGet]
- public async Task EnableAuthenticator()
- {
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
- if (string.IsNullOrEmpty(unformattedKey))
- {
- await _userManager.ResetAuthenticatorKeyAsync(user);
- unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
- }
-
- var model = new EnableAuthenticatorViewModel
- {
- SharedKey = FormatKey(unformattedKey),
- AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey)
- };
-
- return View(model);
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task EnableAuthenticator(EnableAuthenticatorViewModel model)
- {
- if (!ModelState.IsValid)
- {
- return View(model);
- }
-
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- // Strip spaces and hypens
- var verificationCode = model.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
-
- var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
- user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
-
- if (!is2faTokenValid)
- {
- ModelState.AddModelError("model.Code", "Verification code is invalid.");
- return View(model);
- }
-
- await _userManager.SetTwoFactorEnabledAsync(user, true);
- _logger.LogInformation("User with ID {UserId} has enabled 2FA with an authenticator app.", user.Id);
- return RedirectToAction(nameof(GenerateRecoveryCodes));
- }
-
- [HttpGet]
- public IActionResult ResetAuthenticatorWarning()
- {
- return View(nameof(ResetAuthenticator));
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task ResetAuthenticator()
- {
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- await _userManager.SetTwoFactorEnabledAsync(user, false);
- await _userManager.ResetAuthenticatorKeyAsync(user);
- _logger.LogInformation("User with id '{UserId}' has reset their authentication app key.", user.Id);
-
- return RedirectToAction(nameof(EnableAuthenticator));
- }
-
- [HttpGet]
- public async Task GenerateRecoveryCodes()
- {
- var user = await _userManager.GetUserAsync(User);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- if (!user.TwoFactorEnabled)
- {
- throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled.");
- }
-
- var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
- var model = new GenerateRecoveryCodesViewModel { RecoveryCodes = recoveryCodes.ToArray() };
-
- _logger.LogInformation("User with ID {UserId} has generated new 2FA recovery codes.", user.Id);
-
- return View(model);
- }
-
- #region Helpers
-
- private void AddErrors(IdentityResult result)
- {
- foreach (var error in result.Errors)
- {
- ModelState.AddModelError(string.Empty, error.Description);
- }
- }
-
- private string FormatKey(string unformattedKey)
- {
- var result = new StringBuilder();
- int currentPosition = 0;
- while (currentPosition + 4 < unformattedKey.Length)
- {
- result.Append(unformattedKey.Substring(currentPosition, 4)).Append(" ");
- currentPosition += 4;
- }
- if (currentPosition < unformattedKey.Length)
- {
- result.Append(unformattedKey.Substring(currentPosition));
- }
-
- return result.ToString().ToLowerInvariant();
- }
-
- private string GenerateQrCodeUri(string email, string unformattedKey)
- {
- return string.Format(
- AuthenicatorUriFormat,
- _urlEncoder.Encode("WebApp"),
- _urlEncoder.Encode(email),
- unformattedKey);
- }
-
- #endregion
- }
-}
diff --git a/samples/WebApp/Data/ApplicationDbContext.cs b/samples/WebApp/Data/ApplicationDbContext.cs
deleted file mode 100644
index bee57a7b0..000000000
--- a/samples/WebApp/Data/ApplicationDbContext.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore;
-using WebApp.Models;
-
-namespace WebApp.Data
-{
- public class ApplicationDbContext : IdentityDbContext
- {
- public ApplicationDbContext(DbContextOptions options)
- : base(options)
- {
- }
-
- protected override void OnModelCreating(ModelBuilder builder)
- {
- base.OnModelCreating(builder);
- // Customize the ASP.NET Identity model and override the defaults if needed.
- // For example, you can rename the ASP.NET Identity table names and more.
- // Add your customizations after calling base.OnModelCreating(builder);
- }
- }
-}
diff --git a/samples/WebApp/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs b/samples/WebApp/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs
deleted file mode 100644
index 1b47790a7..000000000
--- a/samples/WebApp/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs
+++ /dev/null
@@ -1,212 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-namespace WebApp.Data.Migrations
-{
- [DbContext(typeof(ApplicationDbContext))]
- [Migration("00000000000000_CreateIdentitySchema")]
- partial class CreateIdentitySchema
- {
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
- modelBuilder
- .HasAnnotation("ProductVersion", "1.0.2");
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
- {
- b.Property("Id");
-
- b.Property("ConcurrencyStamp")
- .IsConcurrencyToken();
-
- b.Property("Name")
- .HasMaxLength(256);
-
- b.Property("NormalizedName")
- .HasMaxLength(256);
-
- b.HasKey("Id");
-
- b.HasIndex("NormalizedName")
- .HasName("RoleNameIndex");
-
- b.ToTable("AspNetRoles");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("ClaimType");
-
- b.Property("ClaimValue");
-
- b.Property("RoleId")
- .IsRequired();
-
- b.HasKey("Id");
-
- b.HasIndex("RoleId");
-
- b.ToTable("AspNetRoleClaims");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("ClaimType");
-
- b.Property("ClaimValue");
-
- b.Property("UserId")
- .IsRequired();
-
- b.HasKey("Id");
-
- b.HasIndex("UserId");
-
- b.ToTable("AspNetUserClaims");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
- {
- b.Property("LoginProvider");
-
- b.Property("ProviderKey");
-
- b.Property("ProviderDisplayName");
-
- b.Property("UserId")
- .IsRequired();
-
- b.HasKey("LoginProvider", "ProviderKey");
-
- b.HasIndex("UserId");
-
- b.ToTable("AspNetUserLogins");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
- {
- b.Property("UserId");
-
- b.Property("RoleId");
-
- b.HasKey("UserId", "RoleId");
-
- b.HasIndex("RoleId");
-
- b.HasIndex("UserId");
-
- b.ToTable("AspNetUserRoles");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
- {
- b.Property("UserId");
-
- b.Property("LoginProvider");
-
- b.Property("Name");
-
- b.Property("Value");
-
- b.HasKey("UserId", "LoginProvider", "Name");
-
- b.ToTable("AspNetUserTokens");
- });
-
- modelBuilder.Entity("WebApp.Models.ApplicationUser", b =>
- {
- b.Property("Id");
-
- b.Property("AccessFailedCount");
-
- b.Property("ConcurrencyStamp")
- .IsConcurrencyToken();
-
- b.Property("Email")
- .HasMaxLength(256);
-
- b.Property("EmailConfirmed");
-
- b.Property("LockoutEnabled");
-
- b.Property("LockoutEnd");
-
- b.Property("NormalizedEmail")
- .HasMaxLength(256);
-
- b.Property("NormalizedUserName")
- .HasMaxLength(256);
-
- b.Property("PasswordHash");
-
- b.Property("PhoneNumber");
-
- b.Property("PhoneNumberConfirmed");
-
- b.Property("SecurityStamp");
-
- b.Property("TwoFactorEnabled");
-
- b.Property("UserName")
- .HasMaxLength(256);
-
- b.HasKey("Id");
-
- b.HasIndex("NormalizedEmail")
- .HasName("EmailIndex");
-
- b.HasIndex("NormalizedUserName")
- .IsUnique()
- .HasName("UserNameIndex");
-
- b.ToTable("AspNetUsers");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
- .WithMany("Claims")
- .HasForeignKey("RoleId")
- .OnDelete(DeleteBehavior.Cascade);
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
- {
- b.HasOne("WebApp.Models.ApplicationUser")
- .WithMany("Claims")
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade);
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
- {
- b.HasOne("WebApp.Models.ApplicationUser")
- .WithMany("Logins")
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade);
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
- .WithMany("Users")
- .HasForeignKey("RoleId")
- .OnDelete(DeleteBehavior.Cascade);
-
- b.HasOne("WebApp.Models.ApplicationUser")
- .WithMany("Roles")
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade);
- });
- }
- }
-}
diff --git a/samples/WebApp/Data/Migrations/00000000000000_CreateIdentitySchema.cs b/samples/WebApp/Data/Migrations/00000000000000_CreateIdentitySchema.cs
deleted file mode 100644
index e01e861b6..000000000
--- a/samples/WebApp/Data/Migrations/00000000000000_CreateIdentitySchema.cs
+++ /dev/null
@@ -1,216 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-namespace WebApp.Data.Migrations
-{
- public partial class CreateIdentitySchema : Migration
- {
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "AspNetRoles",
- columns: table => new
- {
- Id = table.Column(nullable: false),
- ConcurrencyStamp = table.Column(nullable: true),
- Name = table.Column(maxLength: 256, nullable: true),
- NormalizedName = table.Column(maxLength: 256, nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetRoles", x => x.Id);
- });
-
- migrationBuilder.CreateTable(
- name: "AspNetUserTokens",
- columns: table => new
- {
- UserId = table.Column(nullable: false),
- LoginProvider = table.Column(nullable: false),
- Name = table.Column(nullable: false),
- Value = table.Column(nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
- });
-
- migrationBuilder.CreateTable(
- name: "AspNetUsers",
- columns: table => new
- {
- Id = table.Column(nullable: false),
- AccessFailedCount = table.Column(nullable: false),
- ConcurrencyStamp = table.Column(nullable: true),
- Email = table.Column(maxLength: 256, nullable: true),
- EmailConfirmed = table.Column(nullable: false),
- LockoutEnabled = table.Column(nullable: false),
- LockoutEnd = table.Column(nullable: true),
- NormalizedEmail = table.Column(maxLength: 256, nullable: true),
- NormalizedUserName = table.Column(maxLength: 256, nullable: true),
- PasswordHash = table.Column(nullable: true),
- PhoneNumber = table.Column(nullable: true),
- PhoneNumberConfirmed = table.Column(nullable: false),
- SecurityStamp = table.Column(nullable: true),
- TwoFactorEnabled = table.Column(nullable: false),
- UserName = table.Column(maxLength: 256, nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetUsers", x => x.Id);
- });
-
- migrationBuilder.CreateTable(
- name: "AspNetRoleClaims",
- columns: table => new
- {
- Id = table.Column(nullable: false)
- .Annotation("Autoincrement", true),
- ClaimType = table.Column(nullable: true),
- ClaimValue = table.Column(nullable: true),
- RoleId = table.Column(nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
- table.ForeignKey(
- name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
- column: x => x.RoleId,
- principalTable: "AspNetRoles",
- principalColumn: "Id",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "AspNetUserClaims",
- columns: table => new
- {
- Id = table.Column(nullable: false)
- .Annotation("Autoincrement", true),
- ClaimType = table.Column(nullable: true),
- ClaimValue = table.Column(nullable: true),
- UserId = table.Column(nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
- table.ForeignKey(
- name: "FK_AspNetUserClaims_AspNetUsers_UserId",
- column: x => x.UserId,
- principalTable: "AspNetUsers",
- principalColumn: "Id",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "AspNetUserLogins",
- columns: table => new
- {
- LoginProvider = table.Column(nullable: false),
- ProviderKey = table.Column(nullable: false),
- ProviderDisplayName = table.Column(nullable: true),
- UserId = table.Column(nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
- table.ForeignKey(
- name: "FK_AspNetUserLogins_AspNetUsers_UserId",
- column: x => x.UserId,
- principalTable: "AspNetUsers",
- principalColumn: "Id",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "AspNetUserRoles",
- columns: table => new
- {
- UserId = table.Column(nullable: false),
- RoleId = table.Column(nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
- table.ForeignKey(
- name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
- column: x => x.RoleId,
- principalTable: "AspNetRoles",
- principalColumn: "Id",
- onDelete: ReferentialAction.Cascade);
- table.ForeignKey(
- name: "FK_AspNetUserRoles_AspNetUsers_UserId",
- column: x => x.UserId,
- principalTable: "AspNetUsers",
- principalColumn: "Id",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateIndex(
- name: "RoleNameIndex",
- table: "AspNetRoles",
- column: "NormalizedName");
-
- migrationBuilder.CreateIndex(
- name: "IX_AspNetRoleClaims_RoleId",
- table: "AspNetRoleClaims",
- column: "RoleId");
-
- migrationBuilder.CreateIndex(
- name: "IX_AspNetUserClaims_UserId",
- table: "AspNetUserClaims",
- column: "UserId");
-
- migrationBuilder.CreateIndex(
- name: "IX_AspNetUserLogins_UserId",
- table: "AspNetUserLogins",
- column: "UserId");
-
- migrationBuilder.CreateIndex(
- name: "IX_AspNetUserRoles_RoleId",
- table: "AspNetUserRoles",
- column: "RoleId");
-
- migrationBuilder.CreateIndex(
- name: "IX_AspNetUserRoles_UserId",
- table: "AspNetUserRoles",
- column: "UserId");
-
- migrationBuilder.CreateIndex(
- name: "EmailIndex",
- table: "AspNetUsers",
- column: "NormalizedEmail");
-
- migrationBuilder.CreateIndex(
- name: "UserNameIndex",
- table: "AspNetUsers",
- column: "NormalizedUserName",
- unique: true);
- }
-
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(
- name: "AspNetRoleClaims");
-
- migrationBuilder.DropTable(
- name: "AspNetUserClaims");
-
- migrationBuilder.DropTable(
- name: "AspNetUserLogins");
-
- migrationBuilder.DropTable(
- name: "AspNetUserRoles");
-
- migrationBuilder.DropTable(
- name: "AspNetUserTokens");
-
- migrationBuilder.DropTable(
- name: "AspNetRoles");
-
- migrationBuilder.DropTable(
- name: "AspNetUsers");
- }
- }
-}
diff --git a/samples/WebApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/samples/WebApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs
deleted file mode 100644
index 8847f0684..000000000
--- a/samples/WebApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs
+++ /dev/null
@@ -1,211 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-namespace WebApp.Data.Migrations
-{
- [DbContext(typeof(ApplicationDbContext))]
- partial class ApplicationDbContextModelSnapshot : ModelSnapshot
- {
- protected override void BuildModel(ModelBuilder modelBuilder)
- {
- modelBuilder
- .HasAnnotation("ProductVersion", "1.0.2");
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole", b =>
- {
- b.Property("Id");
-
- b.Property("ConcurrencyStamp")
- .IsConcurrencyToken();
-
- b.Property("Name")
- .HasMaxLength(256);
-
- b.Property("NormalizedName")
- .HasMaxLength(256);
-
- b.HasKey("Id");
-
- b.HasIndex("NormalizedName")
- .HasName("RoleNameIndex");
-
- b.ToTable("AspNetRoles");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("ClaimType");
-
- b.Property("ClaimValue");
-
- b.Property("RoleId")
- .IsRequired();
-
- b.HasKey("Id");
-
- b.HasIndex("RoleId");
-
- b.ToTable("AspNetRoleClaims");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("ClaimType");
-
- b.Property("ClaimValue");
-
- b.Property("UserId")
- .IsRequired();
-
- b.HasKey("Id");
-
- b.HasIndex("UserId");
-
- b.ToTable("AspNetUserClaims");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b =>
- {
- b.Property("LoginProvider");
-
- b.Property("ProviderKey");
-
- b.Property("ProviderDisplayName");
-
- b.Property("UserId")
- .IsRequired();
-
- b.HasKey("LoginProvider", "ProviderKey");
-
- b.HasIndex("UserId");
-
- b.ToTable("AspNetUserLogins");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b =>
- {
- b.Property("UserId");
-
- b.Property("RoleId");
-
- b.HasKey("UserId", "RoleId");
-
- b.HasIndex("RoleId");
-
- b.HasIndex("UserId");
-
- b.ToTable("AspNetUserRoles");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserToken", b =>
- {
- b.Property("UserId");
-
- b.Property("LoginProvider");
-
- b.Property("Name");
-
- b.Property("Value");
-
- b.HasKey("UserId", "LoginProvider", "Name");
-
- b.ToTable("AspNetUserTokens");
- });
-
- modelBuilder.Entity("WebApp.Models.ApplicationUser", b =>
- {
- b.Property("Id");
-
- b.Property("AccessFailedCount");
-
- b.Property("ConcurrencyStamp")
- .IsConcurrencyToken();
-
- b.Property("Email")
- .HasMaxLength(256);
-
- b.Property("EmailConfirmed");
-
- b.Property("LockoutEnabled");
-
- b.Property("LockoutEnd");
-
- b.Property("NormalizedEmail")
- .HasMaxLength(256);
-
- b.Property("NormalizedUserName")
- .HasMaxLength(256);
-
- b.Property("PasswordHash");
-
- b.Property("PhoneNumber");
-
- b.Property("PhoneNumberConfirmed");
-
- b.Property("SecurityStamp");
-
- b.Property("TwoFactorEnabled");
-
- b.Property("UserName")
- .HasMaxLength(256);
-
- b.HasKey("Id");
-
- b.HasIndex("NormalizedEmail")
- .HasName("EmailIndex");
-
- b.HasIndex("NormalizedUserName")
- .IsUnique()
- .HasName("UserNameIndex");
-
- b.ToTable("AspNetUsers");
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole")
- .WithMany("Claims")
- .HasForeignKey("RoleId")
- .OnDelete(DeleteBehavior.Cascade);
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b =>
- {
- b.HasOne("WebApp.Models.ApplicationUser")
- .WithMany("Claims")
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade);
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b =>
- {
- b.HasOne("WebApp.Models.ApplicationUser")
- .WithMany("Logins")
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade);
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b =>
- {
- b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole")
- .WithMany("Users")
- .HasForeignKey("RoleId")
- .OnDelete(DeleteBehavior.Cascade);
-
- b.HasOne("WebApp.Models.ApplicationUser")
- .WithMany("Roles")
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade);
- });
- }
- }
-}
diff --git a/samples/WebApp/Extensions/EmailSenderExtensions.cs b/samples/WebApp/Extensions/EmailSenderExtensions.cs
deleted file mode 100644
index 47d8db688..000000000
--- a/samples/WebApp/Extensions/EmailSenderExtensions.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.Encodings.Web;
-using System.Threading.Tasks;
-using WebApp.Services;
-
-namespace WebApp.Services
-{
- public static class EmailSenderExtensions
- {
- public static Task SendEmailConfirmationAsync(this IEmailSender emailSender, string email, string link)
- {
- return emailSender.SendEmailAsync(email, "Confirm your email",
- $"Please confirm your account by clicking this link: link");
- }
- }
-}
diff --git a/samples/WebApp/Extensions/UrlHelperExtensions.cs b/samples/WebApp/Extensions/UrlHelperExtensions.cs
deleted file mode 100644
index 2c724d8ee..000000000
--- a/samples/WebApp/Extensions/UrlHelperExtensions.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using WebApp.Controllers;
-
-namespace Microsoft.AspNetCore.Mvc
-{
- public static class UrlHelperExtensions
- {
- public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
- {
- return urlHelper.Action(
- action: nameof(AccountController.ConfirmEmail),
- controller: "Account",
- values: new { userId, code },
- protocol: scheme);
- }
-
- public static string ResetPasswordCallbackLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
- {
- return urlHelper.Action(
- action: nameof(AccountController.ResetPassword),
- controller: "Account",
- values: new { userId, code },
- protocol: scheme);
- }
- }
-}
diff --git a/samples/WebApp/Models/AccountViewModels/ExternalLoginViewModel.cs b/samples/WebApp/Models/AccountViewModels/ExternalLoginViewModel.cs
deleted file mode 100644
index afae9acc9..000000000
--- a/samples/WebApp/Models/AccountViewModels/ExternalLoginViewModel.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Models.AccountViewModels
-{
- public class ExternalLoginViewModel
- {
- [Required]
- [EmailAddress]
- public string Email { get; set; }
- }
-}
diff --git a/samples/WebApp/Models/AccountViewModels/ForgotPasswordViewModel.cs b/samples/WebApp/Models/AccountViewModels/ForgotPasswordViewModel.cs
deleted file mode 100644
index 19e047858..000000000
--- a/samples/WebApp/Models/AccountViewModels/ForgotPasswordViewModel.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Models.AccountViewModels
-{
- public class ForgotPasswordViewModel
- {
- [Required]
- [EmailAddress]
- public string Email { get; set; }
- }
-}
diff --git a/samples/WebApp/Models/AccountViewModels/LoginViewModel.cs b/samples/WebApp/Models/AccountViewModels/LoginViewModel.cs
deleted file mode 100644
index 7eecfa99e..000000000
--- a/samples/WebApp/Models/AccountViewModels/LoginViewModel.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Models.AccountViewModels
-{
- public class LoginViewModel
- {
- [Required]
- [EmailAddress]
- public string Email { get; set; }
-
- [Required]
- [DataType(DataType.Password)]
- public string Password { get; set; }
-
- [Display(Name = "Remember me?")]
- public bool RememberMe { get; set; }
- }
-}
diff --git a/samples/WebApp/Models/AccountViewModels/LoginWith2faViewModel.cs b/samples/WebApp/Models/AccountViewModels/LoginWith2faViewModel.cs
deleted file mode 100644
index 260e39f44..000000000
--- a/samples/WebApp/Models/AccountViewModels/LoginWith2faViewModel.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Models.AccountViewModels
-{
- public class LoginWith2faViewModel
- {
- [Required]
- [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
- [DataType(DataType.Text)]
- [Display(Name = "Authenticator code")]
- public string TwoFactorCode { get; set; }
-
- [Display(Name = "Remember this machine")]
- public bool RememberMachine { get; set; }
-
- public bool RememberMe { get; set; }
- }
-}
diff --git a/samples/WebApp/Models/AccountViewModels/LoginWithRecoveryCodeViewModel.cs b/samples/WebApp/Models/AccountViewModels/LoginWithRecoveryCodeViewModel.cs
deleted file mode 100644
index 66be47d25..000000000
--- a/samples/WebApp/Models/AccountViewModels/LoginWithRecoveryCodeViewModel.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Models.AccountViewModels
-{
- public class LoginWithRecoveryCodeViewModel
- {
- [Required]
- [DataType(DataType.Text)]
- [Display(Name = "Recovery Code")]
- public string RecoveryCode { get; set; }
- }
-}
diff --git a/samples/WebApp/Models/AccountViewModels/RegisterViewModel.cs b/samples/WebApp/Models/AccountViewModels/RegisterViewModel.cs
deleted file mode 100644
index ddd6c8036..000000000
--- a/samples/WebApp/Models/AccountViewModels/RegisterViewModel.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Models.AccountViewModels
-{
- public class RegisterViewModel
- {
- [Required]
- [EmailAddress]
- [Display(Name = "Email")]
- public string Email { get; set; }
-
- [Required]
- [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
- [DataType(DataType.Password)]
- [Display(Name = "Password")]
- public string Password { get; set; }
-
- [DataType(DataType.Password)]
- [Display(Name = "Confirm password")]
- [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
- public string ConfirmPassword { get; set; }
- }
-}
diff --git a/samples/WebApp/Models/AccountViewModels/ResetPasswordViewModel.cs b/samples/WebApp/Models/AccountViewModels/ResetPasswordViewModel.cs
deleted file mode 100644
index 92a5f8e5a..000000000
--- a/samples/WebApp/Models/AccountViewModels/ResetPasswordViewModel.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Models.AccountViewModels
-{
- public class ResetPasswordViewModel
- {
- [Required]
- [EmailAddress]
- public string Email { get; set; }
-
- [Required]
- [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
- [DataType(DataType.Password)]
- public string Password { get; set; }
-
- [DataType(DataType.Password)]
- [Display(Name = "Confirm password")]
- [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
- public string ConfirmPassword { get; set; }
-
- public string Code { get; set; }
- }
-}
diff --git a/samples/WebApp/Models/ApplicationUser.cs b/samples/WebApp/Models/ApplicationUser.cs
deleted file mode 100644
index f09635dbd..000000000
--- a/samples/WebApp/Models/ApplicationUser.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Identity;
-
-namespace WebApp.Models
-{
- // Add profile data for application users by adding properties to the ApplicationUser class
- public class ApplicationUser : IdentityUser
- {
- }
-}
diff --git a/samples/WebApp/Models/ErrorViewModel.cs b/samples/WebApp/Models/ErrorViewModel.cs
deleted file mode 100644
index 838fd1b19..000000000
--- a/samples/WebApp/Models/ErrorViewModel.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System;
-
-namespace WebApp.Models
-{
- public class ErrorViewModel
- {
- public string RequestId { get; set; }
-
- public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
- }
-}
\ No newline at end of file
diff --git a/samples/WebApp/Models/ManageViewModels/ChangePasswordViewModel.cs b/samples/WebApp/Models/ManageViewModels/ChangePasswordViewModel.cs
deleted file mode 100644
index 18d29be91..000000000
--- a/samples/WebApp/Models/ManageViewModels/ChangePasswordViewModel.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Models.ManageViewModels
-{
- public class ChangePasswordViewModel
- {
- [Required]
- [DataType(DataType.Password)]
- [Display(Name = "Current password")]
- public string OldPassword { get; set; }
-
- [Required]
- [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
- [DataType(DataType.Password)]
- [Display(Name = "New password")]
- public string NewPassword { get; set; }
-
- [DataType(DataType.Password)]
- [Display(Name = "Confirm new password")]
- [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
- public string ConfirmPassword { get; set; }
-
- public string StatusMessage { get; set; }
- }
-}
diff --git a/samples/WebApp/Models/ManageViewModels/EnableAuthenticatorViewModel.cs b/samples/WebApp/Models/ManageViewModels/EnableAuthenticatorViewModel.cs
deleted file mode 100644
index 7e57c48a6..000000000
--- a/samples/WebApp/Models/ManageViewModels/EnableAuthenticatorViewModel.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Models.ManageViewModels
-{
- public class EnableAuthenticatorViewModel
- {
- [Required]
- [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
- [DataType(DataType.Text)]
- [Display(Name = "Verification Code")]
- public string Code { get; set; }
-
- [ReadOnly(true)]
- public string SharedKey { get; set; }
-
- public string AuthenticatorUri { get; set; }
- }
-}
diff --git a/samples/WebApp/Models/ManageViewModels/ExternalLoginsViewModel.cs b/samples/WebApp/Models/ManageViewModels/ExternalLoginsViewModel.cs
deleted file mode 100644
index 8f98531df..000000000
--- a/samples/WebApp/Models/ManageViewModels/ExternalLoginsViewModel.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Authentication;
-using Microsoft.AspNetCore.Identity;
-
-namespace WebApp.Models.ManageViewModels
-{
- public class ExternalLoginsViewModel
- {
- public IList CurrentLogins { get; set; }
-
- public IList OtherLogins { get; set; }
-
- public bool ShowRemoveButton { get; set; }
-
- public string StatusMessage { get; set; }
- }
-}
diff --git a/samples/WebApp/Models/ManageViewModels/GenerateRecoveryCodesViewModel.cs b/samples/WebApp/Models/ManageViewModels/GenerateRecoveryCodesViewModel.cs
deleted file mode 100644
index 00da4d72f..000000000
--- a/samples/WebApp/Models/ManageViewModels/GenerateRecoveryCodesViewModel.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Models.ManageViewModels
-{
- public class GenerateRecoveryCodesViewModel
- {
- public string[] RecoveryCodes { get; set; }
- }
-}
diff --git a/samples/WebApp/Models/ManageViewModels/IndexViewModel.cs b/samples/WebApp/Models/ManageViewModels/IndexViewModel.cs
deleted file mode 100644
index 934d2b3a8..000000000
--- a/samples/WebApp/Models/ManageViewModels/IndexViewModel.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Models.ManageViewModels
-{
- public class IndexViewModel
- {
- public string Username { get; set; }
-
- public bool IsEmailConfirmed { get; set; }
-
- [Required]
- [EmailAddress]
- public string Email { get; set; }
-
- [Phone]
- [Display(Name = "Phone number")]
- public string PhoneNumber { get; set; }
-
- public string StatusMessage { get; set; }
- }
-}
diff --git a/samples/WebApp/Models/ManageViewModels/RemoveLoginViewModel.cs b/samples/WebApp/Models/ManageViewModels/RemoveLoginViewModel.cs
deleted file mode 100644
index 8f2dcfede..000000000
--- a/samples/WebApp/Models/ManageViewModels/RemoveLoginViewModel.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Models.ManageViewModels
-{
- public class RemoveLoginViewModel
- {
- public string LoginProvider { get; set; }
- public string ProviderKey { get; set; }
- }
-}
diff --git a/samples/WebApp/Models/ManageViewModels/SetPasswordViewModel.cs b/samples/WebApp/Models/ManageViewModels/SetPasswordViewModel.cs
deleted file mode 100644
index 71ceba70c..000000000
--- a/samples/WebApp/Models/ManageViewModels/SetPasswordViewModel.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Models.ManageViewModels
-{
- public class SetPasswordViewModel
- {
- [Required]
- [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
- [DataType(DataType.Password)]
- [Display(Name = "New password")]
- public string NewPassword { get; set; }
-
- [DataType(DataType.Password)]
- [Display(Name = "Confirm new password")]
- [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
- public string ConfirmPassword { get; set; }
-
- public string StatusMessage { get; set; }
- }
-}
diff --git a/samples/WebApp/Models/ManageViewModels/TwoFactorAuthenticationViewModel.cs b/samples/WebApp/Models/ManageViewModels/TwoFactorAuthenticationViewModel.cs
deleted file mode 100644
index 2a8b80044..000000000
--- a/samples/WebApp/Models/ManageViewModels/TwoFactorAuthenticationViewModel.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Models.ManageViewModels
-{
- public class TwoFactorAuthenticationViewModel
- {
- public bool HasAuthenticator { get; set; }
-
- public int RecoveryCodesLeft { get; set; }
-
- public bool Is2faEnabled { get; set; }
- }
-}
diff --git a/samples/WebApp/Program.cs b/samples/WebApp/Program.cs
deleted file mode 100644
index 70b017f39..000000000
--- a/samples/WebApp/Program.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using Microsoft.AspNetCore;
-using Microsoft.AspNetCore.Hosting;
-
-namespace WebApp
-{
- public class Program
- {
- public static void Main(string[] args)
- {
- BuildWebHost(args).Run();
- }
-
- public static IWebHost BuildWebHost(string[] args) =>
- WebHost.CreateDefaultBuilder(args)
- .UseStartup()
- .Build();
- }
-}
diff --git a/samples/WebApp/Properties/launchSettings.json b/samples/WebApp/Properties/launchSettings.json
deleted file mode 100644
index c2c678d8a..000000000
--- a/samples/WebApp/Properties/launchSettings.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "iisSettings": {
- "windowsAuthentication": false,
- "anonymousAuthentication": true,
- "iisExpress": {
- "applicationUrl": "http://localhost:8227/",
- "sslPort": 0
- }
- },
- "profiles": {
- "IIS Express": {
- "commandName": "IISExpress",
- "launchBrowser": true,
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- },
- "WebApp": {
- "commandName": "Project",
- "launchBrowser": true,
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- },
- "applicationUrl": "http://localhost:8228/"
- }
- }
-}
\ No newline at end of file
diff --git a/samples/WebApp/Services/EmailSender.cs b/samples/WebApp/Services/EmailSender.cs
deleted file mode 100644
index bee471fa0..000000000
--- a/samples/WebApp/Services/EmailSender.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Services
-{
- // This class is used by the application to send email for account confirmation and password reset.
- // For more details see https://go.microsoft.com/fwlink/?LinkID=532713
- public class EmailSender : IEmailSender
- {
- public Task SendEmailAsync(string email, string subject, string message)
- {
- return Task.CompletedTask;
- }
- }
-}
diff --git a/samples/WebApp/Services/IEmailSender.cs b/samples/WebApp/Services/IEmailSender.cs
deleted file mode 100644
index cc3750793..000000000
--- a/samples/WebApp/Services/IEmailSender.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace WebApp.Services
-{
- public interface IEmailSender
- {
- Task SendEmailAsync(string email, string subject, string message);
- }
-}
diff --git a/samples/WebApp/Startup.cs b/samples/WebApp/Startup.cs
deleted file mode 100644
index 1e93eb3ba..000000000
--- a/samples/WebApp/Startup.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Identity;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using WebApp.Data;
-using WebApp.Models;
-using WebApp.Services;
-
-namespace WebApp
-{
- public class Startup
- {
- public Startup(IConfiguration configuration)
- {
- Configuration = configuration;
- }
-
- public IConfiguration Configuration { get; }
-
- // This method gets called by the runtime. Use this method to add services to the container.
- public void ConfigureServices(IServiceCollection services)
- {
- //services.AddDbContext(options =>
- // options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
-
- services.AddDbContext(options => options.UseSqlServer(
- Configuration.GetConnectionString("DefaultConnection")));
-
- services.AddIdentity()
- .AddEntityFrameworkStores()
- .AddDefaultTokenProviders();
-
- // Add application services.
- services.AddTransient();
-
- services.AddMvc();
-
- Blogifier.Core.Configuration.InitServices(services, Configuration);
- }
-
- // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
- {
- if (env.IsDevelopment())
- {
- app.UseDeveloperExceptionPage();
- app.UseDatabaseErrorPage();
- }
- else
- {
- app.UseExceptionHandler("/Home/Error");
- }
-
- app.UseStaticFiles();
-
- app.UseAuthentication();
-
- app.UseMvc(routes =>
- {
- routes.MapRoute(
- name: "default",
- template: "{controller=Home}/{action=Index}/{id?}");
- });
-
- Blogifier.Core.Configuration.InitApplication(app, env, null);
- }
- }
-}
diff --git a/samples/WebApp/Views/Account/AccessDenied.cshtml b/samples/WebApp/Views/Account/AccessDenied.cshtml
deleted file mode 100644
index 3a5a00852..000000000
--- a/samples/WebApp/Views/Account/AccessDenied.cshtml
+++ /dev/null
@@ -1,8 +0,0 @@
-@{
- ViewData["Title"] = "Access denied";
-}
-
-
-
Associate your @ViewData["LoginProvider"] account.
-
-
-
- You've successfully authenticated with @ViewData["LoginProvider"].
- Please enter an email address for this site below and click the Register button to finish
- logging in.
-
-
- @{
- var loginProviders = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList();
- if (loginProviders.Count == 0)
- {
-
-
- There are no external authentication services configured. See this article
- for details on setting up this ASP.NET application to support logging in via external services.
-
-
-@section Scripts {
- @await Html.PartialAsync("_ValidationScriptsPartial")
-}
\ No newline at end of file
diff --git a/samples/WebApp/Views/Account/LoginWithRecoveryCode.cshtml b/samples/WebApp/Views/Account/LoginWithRecoveryCode.cshtml
deleted file mode 100644
index e2eb4c5a5..000000000
--- a/samples/WebApp/Views/Account/LoginWithRecoveryCode.cshtml
+++ /dev/null
@@ -1,28 +0,0 @@
-@model LoginWithRecoveryCodeViewModel
-@{
- ViewData["Title"] = "Recovery code verification";
-}
-
-
@ViewData["Title"]
-
-
- You have requested to login with a recovery code. This login will not be remembered until you provide
- an authenticator app code at login or disable 2FA and login again.
-
-
-
-
-
-
-
-@section Scripts {
- @await Html.PartialAsync("_ValidationScriptsPartial")
-}
\ No newline at end of file
diff --git a/samples/WebApp/Views/Account/Register.cshtml b/samples/WebApp/Views/Account/Register.cshtml
deleted file mode 100644
index 05c667654..000000000
--- a/samples/WebApp/Views/Account/Register.cshtml
+++ /dev/null
@@ -1,36 +0,0 @@
-@model RegisterViewModel
-@{
- ViewData["Title"] = "Register";
-}
-
-
-
\ No newline at end of file
diff --git a/samples/WebApp/Views/Blogifier/Blog/Custom/Error.cshtml b/samples/WebApp/Views/Blogifier/Blog/Custom/Error.cshtml
deleted file mode 100644
index e17ce6409..000000000
--- a/samples/WebApp/Views/Blogifier/Blog/Custom/Error.cshtml
+++ /dev/null
@@ -1,35 +0,0 @@
-@using Blogifier.Core.Common
-@model int
-@{
- var statusCode = Model;
- var statusmessage = "";
-
- switch (statusCode)
- {
- case 400:
- statusmessage = "Bad request: The request cannot be fulfilled due to bad syntax";
- break;
- case 403:
- statusmessage = "Forbidden";
- break;
- case 404:
- statusmessage = "Page not found";
- break;
- case 408:
- statusmessage = "The server timed out waiting for the request";
- break;
- case 500:
- statusmessage = "Internal Server Error - server was unable to finish processing the request";
- break;
- default:
- statusmessage = "That’s odd... Something we didn't expect happened";
- break;
- }
-}
-
-
- Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key
- used in an authenticator app you should reset your
- authenticator keys.
-
To use an authenticator app go through the following steps:
-
-
-
- Download a two-factor authenticator app like Microsoft Authenticator for
- Windows Phone,
- Android and
- iOS or
- Google Authenticator for
- Android and
- iOS.
-
-
-
-
Scan the QR Code or enter this key @Model.SharedKey into your two factor authenticator app. Spaces and casing do not matter.
-
To enable QR code generation please read our documentation.
-
-
-
-
-
- Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
- with a unique code. Enter the code in the confirmation box below.
-
-
- If you reset your authenticator key your authenticator app will not work until you reconfigure it.
-
-
- This process disables 2FA until you verify your authenticator app and will also reset your 2FA recovery codes.
- If you do not complete your authenticator app configuration you may lose access to your account.
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/WebApp/Views/Manage/SetPassword.cshtml b/samples/WebApp/Views/Manage/SetPassword.cshtml
deleted file mode 100644
index 56c359935..000000000
--- a/samples/WebApp/Views/Manage/SetPassword.cshtml
+++ /dev/null
@@ -1,34 +0,0 @@
-@model SetPasswordViewModel
-@{
- ViewData["Title"] = "Set password";
- ViewData.AddActivePage(ManageNavPages.ChangePassword);
-}
-
-
-}
diff --git a/samples/WebApp/Views/Manage/_ViewImports.cshtml b/samples/WebApp/Views/Manage/_ViewImports.cshtml
deleted file mode 100644
index d80ba1149..000000000
--- a/samples/WebApp/Views/Manage/_ViewImports.cshtml
+++ /dev/null
@@ -1 +0,0 @@
-@using WebApp.Views.Manage
\ No newline at end of file
diff --git a/samples/WebApp/Views/Shared/Error.cshtml b/samples/WebApp/Views/Shared/Error.cshtml
deleted file mode 100644
index ec2ea6bd0..000000000
--- a/samples/WebApp/Views/Shared/Error.cshtml
+++ /dev/null
@@ -1,22 +0,0 @@
-@model ErrorViewModel
-@{
- ViewData["Title"] = "Error";
-}
-
-
Error.
-
An error occurred while processing your request.
-
-@if (Model.ShowRequestId)
-{
-
- Request ID:@Model.RequestId
-
-}
-
-
Development Mode
-
- Swapping to Development environment will display more detailed information about the error that occurred.
-
-
- Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application.
-