Repository: klemmchr/MvvmBlazor Branch: master Commit: 7115907bf7a8 Files: 133 Total size: 202.1 KB Directory structure: gitextract_455vp84t/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug.yml │ │ ├── config.yml │ │ ├── feature.yml │ │ ├── other.yml │ │ └── question.yml │ └── workflows/ │ ├── build.yaml │ └── release.yaml ├── .gitignore ├── LICENSE ├── README.md ├── samples/ │ ├── BlazorClientsideSample.Client/ │ │ ├── App.razor │ │ ├── BlazorClientsideSample.Client.csproj │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ ├── Services/ │ │ │ └── WeatherForecastGetter.cs │ │ ├── _Imports.razor │ │ └── wwwroot/ │ │ └── index.html │ ├── BlazorClientsideSample.Server/ │ │ ├── BlazorClientsideSample.Server.csproj │ │ ├── Controllers/ │ │ │ └── WeatherForecastController.cs │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ └── Startup.cs │ ├── BlazorSample.Components/ │ │ ├── BlazorSample.Components.csproj │ │ ├── Extensions/ │ │ │ └── ServiceCollectionExtensions.cs │ │ ├── Pages/ │ │ │ ├── Clock.razor │ │ │ ├── Counter.razor │ │ │ ├── Index.razor │ │ │ ├── Parameters.razor │ │ │ ├── TypedParameters.razor │ │ │ └── WeatherForecasts.razor │ │ ├── Shared/ │ │ │ ├── CascadingComponent.razor │ │ │ ├── CustomBaseComponent.cs │ │ │ ├── MainLayout.razor │ │ │ └── Navbar.razor │ │ ├── _Imports.razor │ │ └── wwwroot/ │ │ └── site.css │ ├── BlazorSample.Domain/ │ │ ├── BlazorSample.Domain.csproj │ │ ├── Converters/ │ │ │ └── IdTypeConverter.cs │ │ ├── Entities/ │ │ │ ├── IdType.cs │ │ │ └── WeatherForecastEntity.cs │ │ ├── Extensions/ │ │ │ └── ServiceCollectionExtensions.cs │ │ └── Services/ │ │ ├── IWeatherForecastGetter.cs │ │ └── Navbar/ │ │ ├── NavbarItem.cs │ │ └── NavbarService.cs │ ├── BlazorSample.ViewModels/ │ │ ├── BlazorSample.ViewModels.csproj │ │ ├── CascadingViewModel.cs │ │ ├── ClockViewModel.cs │ │ ├── CounterViewModel.cs │ │ ├── Extensions/ │ │ │ └── ServiceCollectionExtensions.cs │ │ ├── GlobalUsings.cs │ │ ├── Navbar/ │ │ │ ├── NavbarItemViewModel.cs │ │ │ └── NavbarViewModel.cs │ │ ├── ParametersViewModel.cs │ │ ├── TypedParametersViewModel.cs │ │ ├── WeatherForecastViewModel.cs │ │ └── WeatherForecastsViewModel.cs │ └── BlazorServersideSample/ │ ├── App.razor │ ├── BlazorServersideSample.csproj │ ├── Pages/ │ │ ├── Error.razor │ │ ├── _Host.cshtml │ │ └── _Imports.razor │ ├── Program.cs │ ├── Properties/ │ │ └── launchSettings.json │ ├── Services/ │ │ └── WeatherForecastGetter.cs │ ├── Startup.cs │ ├── _Imports.razor │ ├── appsettings.Development.json │ └── appsettings.json └── src/ ├── .editorconfig ├── Directory.Build.props ├── MvvmBlazor/ │ ├── MvvmBlazor.csproj │ └── Properties/ │ └── AssemblyInfo.cs ├── MvvmBlazor.CodeGenerators/ │ ├── AnalyzerReleases.Shipped.md │ ├── AnalyzerReleases.Unshipped.md │ ├── Components/ │ │ ├── MvvmComponentClassContext.cs │ │ ├── MvvmComponentGenerator.cs │ │ └── MvvmComponentSyntaxReceiver.cs │ ├── Extensions/ │ │ ├── StringBuilderExtensions.cs │ │ └── SymbolExtensions.cs │ ├── GlobalUsings.cs │ ├── MvvmBlazor.CodeGenerators.csproj │ ├── NotifyPropertyChanged/ │ │ ├── NotifyPropertyChangedContext.cs │ │ ├── NotifyPropertyChangedGenerator.cs │ │ └── NotifyPropertyChangedSyntaxReceiver.cs │ └── Properties/ │ └── AssemblyInfo.cs ├── MvvmBlazor.Core/ │ ├── Components/ │ │ ├── MvvmComponentAttribute.cs │ │ ├── MvvmComponentBase.cs │ │ └── MvvmComponentBaseT.cs │ ├── Extensions/ │ │ └── ServiceCollectionExtensions.cs │ ├── GlobalUsings.cs │ ├── Internal/ │ │ ├── Bindings/ │ │ │ ├── Binder.cs │ │ │ ├── Binding.cs │ │ │ ├── BindingException.cs │ │ │ └── BindingFactory.cs │ │ ├── Parameters/ │ │ │ ├── ParameterCache.cs │ │ │ ├── ParameterException.cs │ │ │ ├── ParameterInfo.cs │ │ │ ├── ParameterResolver.cs │ │ │ └── ViewModelParameterSetter.cs │ │ └── WeakEventListener/ │ │ ├── WeakEventListener.cs │ │ └── WeakEventManager.cs │ ├── MvvmBlazor.Core.csproj │ ├── NotifyAttribute.cs │ ├── Properties/ │ │ ├── AssemblyInfo.cs │ │ └── GlobalSuppressions.cs │ └── ViewModel/ │ └── ViewModelBase.cs ├── MvvmBlazor.Tests/ │ ├── .editorconfig │ ├── Abstractions/ │ │ ├── StrictMock.cs │ │ └── UnitTest.cs │ ├── Components/ │ │ ├── MvvmComponentBaseTTests.cs │ │ ├── MvvmComponentBaseTests.cs │ │ └── TestViewModel.cs │ ├── Extensions/ │ │ ├── ServiceCollectionExtensions.cs │ │ └── ServiceProviderExtensions.cs │ ├── Generators/ │ │ ├── MvvmComponentGeneratorTests.cs │ │ └── NotifyPropertyChangedGeneratorTests.cs │ ├── GlobalUsings.cs │ ├── Internal/ │ │ ├── Bindings/ │ │ │ ├── BinderTests.cs │ │ │ ├── BindingFactoryTests.cs │ │ │ └── BindingTests.cs │ │ ├── Parameters/ │ │ │ ├── ParameterCacheTests.cs │ │ │ ├── ParameterInfoTests.cs │ │ │ ├── ParameterResolverTests.cs │ │ │ └── ViewModelParameterSetterTests.cs │ │ └── WeakEventListener/ │ │ └── WeakEventListenerTests.cs │ ├── MvvmBlazor.Tests.csproj │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── ViewModel/ │ │ └── ViewModelBaseTests.cs │ └── xunit.runner.json ├── MvvmBlazor.sln └── MvvmBlazor.sln.DotSettings ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf trim_trailing_whitespace = true insert_final_newline = false indent_style = space indent_size = 4 # Microsoft .NET properties csharp_new_line_before_members_in_object_initializers = false csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:suggestion csharp_style_var_elsewhere = true:suggestion csharp_style_var_for_built_in_types = true:suggestion csharp_style_var_when_type_is_apparent = true:suggestion dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper = True dotnet_naming_rule.unity_serialized_field_rule.resharper_description = Unity serialized field dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef dotnet_naming_rule.unity_serialized_field_rule.severity = warning dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols dotnet_naming_style.lower_camel_case_style.capitalization = camel_case dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion dotnet_style_qualification_for_event = false:suggestion dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion # ReSharper properties resharper_autodetect_indent_settings = true resharper_use_indent_from_vs = false resharper_keep_existing_arrangement = false resharper_csharp_wrap_arguments_style = chop_if_long resharper_csharp_wrap_before_invocation_lpar = false resharper_csharp_wrap_before_invocation_rpar = true resharper_csharp_wrap_after_invocation_lpar = true resharper_csharp_wrap_parameters_style = chop_if_long resharper_csharp_wrap_before_declaration_lpar = false resharper_csharp_wrap_before_declaration_rpar = false resharper_csharp_wrap_after_declaration_lpar = true resharper_csharp_braces_for_ifelse = required resharper_csharp_wrap_chained_method_calls = chop_if_long # ReSharper inspection severities resharper_arrange_redundant_parentheses_highlighting = hint resharper_arrange_this_qualifier_highlighting = hint resharper_arrange_type_member_modifiers_highlighting = hint resharper_arrange_type_modifiers_highlighting = hint resharper_built_in_type_reference_style_for_member_access_highlighting = hint resharper_built_in_type_reference_style_highlighting = hint resharper_redundant_base_qualifier_highlighting = warning resharper_suggest_var_or_type_built_in_types_highlighting = hint resharper_suggest_var_or_type_elsewhere_highlighting = hint resharper_suggest_var_or_type_simple_types_highlighting = hint resharper_web_config_module_not_resolved_highlighting = warning resharper_web_config_type_not_resolved_highlighting = warning resharper_web_config_wrong_module_highlighting = warning dotnet_diagnostic.CA2007.severity = none # Call ConfigureAwait() dotnet_diagnostic.RS2003.severity = none # Rule is no longer shipped [{*.har,*.inputactions,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,jest.config}] indent_style = space indent_size = 4 [*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,fs,fsi,fsscript,fsx,hlsl,hlsli,hlslinc,master,ml,mli,nuspec,paml,razor,resw,resx,shader,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}] indent_style = space indent_size = 4 tab_width = 4 [*.{yaml,yml}] tab_width = 2 indent_size = 2 ================================================ FILE: .gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: .github/ISSUE_TEMPLATE/bug.yml ================================================ name: Bug Report description: File a bug report labels: ["type: bug", "status: triage"] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! - type: textarea id: description attributes: label: Bug description description: Explain the issue you have validations: required: true - type: textarea id: expectation attributes: label: Expectation description: Outline what your expectation would be validations: required: true - type: textarea id: code-sample attributes: label: Code sample description: Provide a code sample that demonstrates your issue render: C# validations: required: true - type: input id: version attributes: label: Version description: What MvvmBlazor version are you using? validations: required: true - type: dropdown id: blazor-type attributes: label: Are you using Blazor WASM or Blazor Server? multiple: false options: - Blazor WASM - Blazor Server validations: required: true - type: dropdown id: os attributes: label: What operation system are you working with? multiple: false options: - Windows - Linux - macOS - Other validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Security vulnerabilities url: https://github.com/klemmchr about: Please report security vulnerabilities via email to git+mvvmblazor@klemm.one. A link to my PGP key can be found in my profile. ================================================ FILE: .github/ISSUE_TEMPLATE/feature.yml ================================================ name: Feature request description: Propose a new feature labels: ["type: feature", "status: triage"] body: - type: textarea id: description attributes: label: Feature description description: Describe the feature you would like to have validations: required: true - type: textarea id: code-sample attributes: label: Code sample description: Provide a code sample if applicable render: C# validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/other.yml ================================================ name: Other description: All other issues labels: ["status: triage"] body: - type: textarea id: description attributes: label: Description validations: required: true - type: textarea id: code-sample attributes: label: Code sample description: Provide a code sample if applicable render: C# validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/question.yml ================================================ name: Question description: Ask a question labels: ["type: question", "status: triage"] body: - type: textarea id: description attributes: label: Question description: Outline the question you have validations: required: true - type: textarea id: code-sample attributes: label: Code sample description: Provide a code sample if applicable render: C# validations: required: false - type: input id: version attributes: label: Version description: What MvvmBlazor version are you using? validations: required: true - type: dropdown id: blazor-type attributes: label: Are you using Blazor WASM or Blazor Server? multiple: false options: - Blazor WASM - Blazor Server validations: required: true ================================================ FILE: .github/workflows/build.yaml ================================================ on: push: branches: - master pull_request: branches: - master paths: - 'src/**/*' - 'samples/**/*' - '.github/workflows/**/*' name: Build jobs: build: name: Build runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Setup .NET uses: actions/setup-dotnet@master with: dotnet-version: '6.0.x' - name: Build run: dotnet build --configuration Release -p:ContinuousIntegrationBuild=true -p:TreatWarningsAsErrors=true src - name: Test run: dotnet test -p:ContinuousIntegrationBuild=true -p:CollectCoverage=true -p:CoverletOutputFormat=lcov -p:CoverletOutput=$GITHUB_WORKSPACE/coverage.lcov src - name: Upload coverage uses: romeovs/lcov-reporter-action@master if: github.event.pull_request.base.ref == 'master' with: lcov-file: coverage.lcov delete-old-comments: true - name: Pack run: dotnet pack --configuration Release -p:ContinuousIntegrationBuild=true -p:SymbolPackageFormat=snupkg src -o out - name: Upload artifacts uses: actions/upload-artifact@v2 with: name: packages path: out/* ================================================ FILE: .github/workflows/release.yaml ================================================ on: release: types: [ published ] name: Release jobs: release: name: Release runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Setup .NET uses: actions/setup-dotnet@master with: dotnet-version: '6.0.x' - name: Build run: dotnet build -p:ContinuousIntegrationBuild=true -p:TreatWarningsAsErrors=true --configuration Release src - name: Test run: dotnet test --configuration Release src - name: Publish run: > dotnet nuget add source --username $GITHUB_REPOSITORY_OWNER --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json"; export VERSION=$(git describe --long --tags --match 'v*' | sed 's/v//' | sed -E 's/[-].+//g'); echo $VERSION; dotnet pack --configuration Release --include-source -p:ContinuousIntegrationBuild=true -p:Version=$VERSION -o out src; cd out; rm MvvmBlazor.CodeGenerators.*.symbols.nupkg; dotnet nuget push *.nupkg --skip-duplicate --force-english-output -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json; dotnet nuget push *.nupkg --skip-duplicate --force-english-output -s https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json; - name: Upload artifacts if: always() uses: actions/upload-artifact@v2 with: name: packages path: out/* ================================================ FILE: .gitignore ================================================ ## 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/ [Ww][Ii][Nn]32/ [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/ # Tye .tye/ # ASP.NET Scaffolding ScaffoldingReadMe.txt # 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 # Coverlet is a free, cross platform Code Coverage Tool coverage*.json coverage*.xml coverage*.info # 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/ # Fody - auto-generated XML schema FodyWeavers.xsd ## ## Visual studio for Mac ## # globs Makefile.in *.userprefs *.usertasks config.make config.status aclocal.m4 install-sh autom4te.cache/ *.tar.gz tarballs/ test-results/ # Mac bundle stuff *.dmg *.app # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore # Windows thumbnail cache files Thumbs.db ehthumbs.db ehthumbs_vista.db # Dump file *.stackdump # Folder config file [Dd]esktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msix *.msm *.msp # Windows shortcuts *.lnk # JetBrains Rider .idea/ *.sln.iml ## ## Visual Studio Code ## .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Christian Klemm Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ MvvmBlazor ================ [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fchris579%2FMvvmBlazor%2Fbadge&style=flat-square)](https://github.com/chris579/MvvmBlazor/actions) [![NuGet](https://img.shields.io/nuget/v/MvvmBlazor.svg?style=flat-square)](https://www.nuget.org/packages/MvvmBlazor) [![NuGet](https://img.shields.io/nuget/dt/MvvmBlazor)](https://www.nuget.org/packages/MvvmBlazor) MvvmBlazor is a small framework for building Blazor WebAssembly and Blazor Server apps. With its easy-to-use MVVM pattern you can increase your development speed while minimising the effort required to make it work. ## Getting started MvvmBlazor is available on [NuGet](https://www.nuget.org/packages/MvvmBlazor). You will need **.NET 6** to use this library. The library needs to be added to the DI container in order to use it. This is done in your `Startup` class. ```c# public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvvm(); } } ``` ## Usage ### Components Components need to inherit the base class `MvvmBlazor.Components.MvvmComponentBase` if you want to inject your view model manually. You can set a binding on any view model you like to. If you want full support use `MvvmBlazor.Components.MvvmComponentBase` and specify your view model type as a generic argument. Your view model will get auto injected into the component and set as a binding context. #### BindingSource The binding source is the default source object a binding will be made to. It is set automatically when using the base class `MvvmBlazor.Components.MvvmComponentBase` where `T` is your view model type. You can access it via the `BindingContext` property in your component. #### Bindings Bindings are achieved via the `Bind` method in the component. If the value of the bound property has changed the component will be told to rerender automatically. In this example we assume that the class `ClockViewModel` has a property called `DateTime`. ```c# @inherits MvvmComponentBase Current time: @Bind(x => x.DateTime) ``` Bindings can also be done by specifying the binding source explicitly: ```c# @inherits MvvmComponentBase @inject ClockViewModel ClockViewModel Current time: @Bind(ClockViewModel, x => x.DateTime) ``` Bindings also handle background updating automatically. No need to invoke the main thread. #### Collection Bindings If you want to have a collection that automatically notifies the component when it has changed you should use one that implements `INotifyCollectionChanged`, e.g. `ObservableCollection`. In List scenarios you often chain view models to achieve bindings for every list element on it's corresponding view model. Given this view models ```c# class MainViewModel { public ObservableCollection Items { get; } } class SubViewModel { private string _name; public string Name { get => _name; set => Set(ref, _name, value); } } ``` you can use bindings on your sub view models like this ```c# @foreach (var item in Bind(x => x.Items)) { } ``` This way the name of every list item is bound to it's corresponding entry in the view. If you change the name on any list item, it will be changed in the view too. ### EventHandlers Event handles work just the way they work in blazor. When you use the non generic base class you can bind any injected object on them. ```c# @inherits MvvmComponentBase @inject CounterViewModel CounterViewModel ``` When using the generic base class you can directly bind them to your binding context. ```c# @inherits MvvmComponentBase ``` ### ViewModel View models need to inherit the base class `MvvmBlazor.ViewModel.ViewModelBase`. If you register a view model as scoped it will be tied to the lifetime of the component and disposed when the component is disposed. This allows you to inject scoped services that should be short lived (e.g. a `DbContext`) without the need for using a factory. Note: Some services need to be resolved from the root service provider (e.g. `NavigationManager`). To do this you can access the root service provider via the `RootServiceProvider` property. #### Property implementation Bindable properties need to raise the `PropertyChanged` event on the ViewModel. The `Set`-Method is performing an equality check and is raising this event if needed. An example implementation could look like this: ```c# private int _currentCount; public int CurrentCount { get => _currentCount; set => Set(ref _currentCount, value); } ``` As an alternative you can leverage the power of source generators to do the tedious work for you. Just declare a private field inside of a view model and decorate if with the `Notify` attribute. The matching property will be auto generated for you. ```c# [Notify] private int _currentCount; ``` Note: Some third party IDEs may not recognize source generators at the current point. They could report that the property does not exist while the project builds fine. #### Lifecycle methods View models have access to the same lifecycle methods as a component when they are set as a binding context. They are documented [here](https://docs.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.0#lifecycle-methods). #### Parameters Parameters are automatically populated to the view model. Declare a parameter in your component ```c# @code { [Parameter] public string Name { get; set; } } ``` and declare the same parameter in your view model ```c# class ViewModel: ViewModelBase { [Parameter] public string Name { get; set; } } ``` Cascading parameters are supported as well. The parameter will be passed when parameters are set on the component. More information regarding the lifecycle can be found in the [Microsoft Documentation](https://docs.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-6.0#lifecycle-events) . ##### Type conversion When binding parameters in a component Blazor restricts you to a finite list of primitive types you can use. In some scenarios you might want to bind to a strong type and perform automatic conversions to it. A typical use case for this would be a strongly typed identifier that you use in your application. View model parameters support type conversion using [`TypeConverter`](https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.typeconverter?view=net-6.0). Your component parameters still needs to be a supported primitive type, however your view model parameter can be strongly typed and will get auto converted. An example for this can be found in the TypedParameter sample. #### Dispose Since ViewModels are being injected through dependency injection in the scope of the component the DI [takes care](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0#disposal-of-services) of disposing ViewModels. ## Advanced scenarios Some libraries may force you to use a different base class than `MvvmComponentBase`. To solve this issue you can create a custom mvvm component using source generators. Create a partial class and decorate it with the `MvvmComponent` attribute. ```c# [MvvmComponent] public abstract partial class CustomComponentBase : UiLibraryComponentBase { } ``` As a reference point see [`MvvmComponentBase`](https://github.com/klemmchr/MvvmBlazor/blob/master/src/MvvmBlazor.Core/Components/MvvmComponentBase.cs) and [`MvvmComponentBase`](https://github.com/klemmchr/MvvmBlazor/blob/master/src/MvvmBlazor.Core/Components/MvvmComponentBaseT.cs). Both are generated by a source generator as well. ## Examples Examples for Blazor and Serverside Blazor can be found [here](https://github.com/chris579/MvvmBlazor/tree/master/samples). You will find several projects in there - *BlazorServersideSample* A server for the blazor serverside sample - *BlazorClientsideSample.Server* The server for the blazor clientside sample - *BlazorClientsideSample.Client* The client for the blazor clientside sample These projects act as wrapper projects for the main functionality that is shared among these examples. - *BlazorSample.Components* The components and pages for the samples - *BlazorSample.ViewModels* The view models for the pages - *BlazorSample.Domain* Domain logic, stuff shared between components and view models ================================================ FILE: samples/BlazorClientsideSample.Client/App.razor ================================================ 

Sorry, there's nothing at this address.

================================================ FILE: samples/BlazorClientsideSample.Client/BlazorClientsideSample.Client.csproj ================================================  net6.0 false enable true false ================================================ FILE: samples/BlazorClientsideSample.Client/Program.cs ================================================ using BlazorClientsideSample.Client.Services; using BlazorSample.Components.Extensions; using BlazorSample.Domain.Extensions; using BlazorSample.Domain.Services; using BlazorSample.ViewModels.Extensions; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; namespace BlazorClientsideSample.Client; public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); builder.Services.AddMvvm(); builder.Services.AddDomain().AddComponents().AddViewModels(); builder.Services.AddSingleton(new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddSingleton(); var host = builder.Build(); await host.RunAsync(); } } ================================================ FILE: samples/BlazorClientsideSample.Client/Properties/launchSettings.json ================================================ { "profiles": { "BlazorClientsideSample.Client": { "commandName": "Project", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:50069/" } } } ================================================ FILE: samples/BlazorClientsideSample.Client/Services/WeatherForecastGetter.cs ================================================ using System.Net.Http.Json; using BlazorSample.Domain.Entities; using BlazorSample.Domain.Services; namespace BlazorClientsideSample.Client.Services; public class WeatherForecastGetter : IWeatherForecastGetter { private readonly HttpClient _httpClient; public WeatherForecastGetter(HttpClient httpClient) { _httpClient = httpClient; } public Task> GetForecasts() { return _httpClient.GetFromJsonAsync>("WeatherForecast")!; } } ================================================ FILE: samples/BlazorClientsideSample.Client/_Imports.razor ================================================ @using System.Net.Http @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web @using Microsoft.JSInterop @using MvvmBlazor.Components @using BlazorSample.Components.Shared ================================================ FILE: samples/BlazorClientsideSample.Client/wwwroot/index.html ================================================  BlazorSample
Loading...
================================================ FILE: samples/BlazorClientsideSample.Server/BlazorClientsideSample.Server.csproj ================================================  net6.0 enable true false ================================================ FILE: samples/BlazorClientsideSample.Server/Controllers/WeatherForecastController.cs ================================================ using BlazorSample.Domain.Entities; using Microsoft.AspNetCore.Mvc; namespace BlazorClientsideSample.Server.Controllers; [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; [HttpGet] public IEnumerable Get() { var rng = new Random(); return Enumerable.Range(1, 5) .Select( index => new WeatherForecastEntity { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] } ) .ToArray(); } } ================================================ FILE: samples/BlazorClientsideSample.Server/Program.cs ================================================ using Microsoft.AspNetCore; namespace BlazorClientsideSample.Server; public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) { return WebHost.CreateDefaultBuilder(args) .UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build()) .UseStartup() .Build(); } } ================================================ FILE: samples/BlazorClientsideSample.Server/Properties/launchSettings.json ================================================ { "profiles": { "BlazorClientsideSample.Server": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:52345/" } } } ================================================ FILE: samples/BlazorClientsideSample.Server/Startup.cs ================================================ using Microsoft.AspNetCore.ResponseCompression; namespace BlazorClientsideSample.Server; public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc().AddNewtonsoftJson(); services.AddResponseCompression( opts => { opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "application/octet-stream" }); } ); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseResponseCompression(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseWebAssemblyDebugging(); } app.UseBlazorFrameworkFiles(); app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints( endpoints => { endpoints.MapDefaultControllerRoute(); endpoints.MapFallbackToFile("index.html"); } ); } } ================================================ FILE: samples/BlazorSample.Components/BlazorSample.Components.csproj ================================================  net6.0 3.0 enable true false ================================================ FILE: samples/BlazorSample.Components/Extensions/ServiceCollectionExtensions.cs ================================================ using BlazorSample.Components.Pages; using BlazorSample.Domain.Extensions; using MatBlazor; using Microsoft.Extensions.DependencyInjection; using Index = BlazorSample.Components.Pages.Index; namespace BlazorSample.Components.Extensions; public static class ServiceCollectionExtensions { public static IServiceCollection AddComponents(this IServiceCollection serviceCollection) { serviceCollection.AddNavigationItem("Index", MatIconNames.Home); serviceCollection.AddNavigationItem("Counter", MatIconNames.Add); serviceCollection.AddNavigationItem("Weather forecasts", MatIconNames.Cloud); serviceCollection.AddNavigationItem("Clock", MatIconNames.Alarm); serviceCollection.AddNavigationItem("Parameters", MatIconNames.List); serviceCollection.AddNavigationItem("Typed Parameters", MatIconNames.List); return serviceCollection; } } ================================================ FILE: samples/BlazorSample.Components/Pages/Clock.razor ================================================ @page "/clock" @inherits CustomBaseComponent Having values updating in the background is no hassle. No need to manually invoke
StateHasChanged()
on the component inside it's synchronization context. That's done for you! Just make sure to update your values using the
Set
method in your view model.

This component demonstrates updating from a background task without user interaction.
Current time: @Bind(x => x.DateTime) ================================================ FILE: samples/BlazorSample.Components/Pages/Counter.razor ================================================ @page "/counter" @inherits CustomBaseComponent @inject ClockViewModel ClockViewModel The view model gets auto injected and can be accessed with the property
BindingContext
. You can use this property to bind event handlers on your controls.

Current count: @Bind(x => x.CurrentCount) Click me


You can also bind additional view models that you simply inject by yourself. The lifecycle methods on such view models will not be invoked by this component.
Current time: @Bind(ClockViewModel, x => x.DateTime) ================================================ FILE: samples/BlazorSample.Components/Pages/Index.razor ================================================ @page "/" Explore the tabs on the left to see some examples and explore MvvmBlazor! ================================================ FILE: samples/BlazorSample.Components/Pages/Parameters.razor ================================================ @page "/parameters/{name}" @inherits CustomBaseComponent You can also pass down parameters to your view model. If you set a binding context you can declare your parameters just as you would before but you also declare them in your view model. They will be passed down to it automatically. Enter a new value down below and click the button. See how the url changed and the parameter is shown.
My Name is @Bind(x => x.Name)
Navigate

Cascading parameters are supported as well.
@code { [Parameter] public string Name { get; set; } = "Marc"; } ================================================ FILE: samples/BlazorSample.Components/Pages/TypedParameters.razor ================================================ @page "/typed-parameters/{Id}" @inherits CustomBaseComponent Converting parameter types is supported out of the box. Visit the Microsoft Documentation to find out more about TypeConverter.
Id value @Bind(x => x.Id)
Navigate to random id @code { [Parameter] public string? Id { get; set; } } ================================================ FILE: samples/BlazorSample.Components/Pages/WeatherForecasts.razor ================================================ @page "/fetchdata" @inherits CustomBaseComponent To use a view model with your component simply inherit from the base class
MvvmComponentBase
and specify your view model type for this component as a generic argument. Your view model will have access to all lifecycle methods you also have on your component. As soon as any value changes via the
Set
method on your view model the component will refresh if needed.

In list scenarios you often nest view models to achieve bindings on the list items. This is especially needed when the data of the item changes. You can click the randomize button and see that the list gets updated.
@if (Bind(x => x.Forecasts) == null) { } else { Randomize
@foreach (var forecast in Bind(x => x.Forecasts)!) { @forecast.Date.ToShortDateString() @forecast.Summary @Bind(forecast, x => x.TemperatureC)°C (@Bind(forecast, x => x.TemperatureF)°F) } } ================================================ FILE: samples/BlazorSample.Components/Shared/CascadingComponent.razor ================================================ @inherits CustomBaseComponent Cascading value is @Bind(x => x.Name) @code { [CascadingParameter] public string? Name { get; set; } } ================================================ FILE: samples/BlazorSample.Components/Shared/CustomBaseComponent.cs ================================================ using Microsoft.AspNetCore.Components; using MvvmBlazor; namespace BlazorSample.Components.Shared; [MvvmComponent] public abstract partial class CustomBaseComponent : CustomBaseComponent { } [MvvmComponent] public abstract partial class CustomBaseComponent : ComponentBase { } ================================================ FILE: samples/BlazorSample.Components/Shared/MainLayout.razor ================================================ @inherits LayoutComponentBase @Body ================================================ FILE: samples/BlazorSample.Components/Shared/Navbar.razor ================================================ @inherits MvvmComponentBase @foreach (var item in Bind(x => x.NavbarItems)) { @if (!string.IsNullOrEmpty(item.Icon)) { } @item.DisplayName } MvvmBlazor Sample App
@ChildContent
@code { [Parameter] public RenderFragment? ChildContent { get; set; } } ================================================ FILE: samples/BlazorSample.Components/_Imports.razor ================================================ @using Microsoft.AspNetCore.Components.Web @using MvvmBlazor.Components @using BlazorSample.ViewModels @using BlazorSample.ViewModels.Navbar @using Microsoft.AspNetCore.Components.Routing @using MatBlazor @using BlazorSample.Components.Shared ================================================ FILE: samples/BlazorSample.Components/wwwroot/site.css ================================================ /* http://meyerweb.com/eric/tools/css/reset/ v4.0 | 20180602 License: none (public domain) */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, main, menu, nav, output, ruby, section, summary, time, mark, audio, video { border: 0; font: inherit; font-size: 100%; margin: 0; padding: 0; vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section { display: block; } /* HTML5 hidden-attribute fix for newer browsers */ *[hidden] { display: none; } body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } table { border-collapse: collapse; border-spacing: 0; } html, body, app { height: 100%; } .mdc-text-field { width: 100% !important; } .mdc-card__primary-action { padding: 0.5rem; } .navbar { height: 100vh; width: 100vw; } .navbar .left-sidebar { user-select: none; } .navbar .icon { margin-right: 0.5rem; } .navbar .content-container { margin: 1rem; } .navbar aside:focus { outline: none; } .navbar .mdc-drawer__content:focus { outline: none; } ================================================ FILE: samples/BlazorSample.Domain/BlazorSample.Domain.csproj ================================================  net6.0 enable true false ================================================ FILE: samples/BlazorSample.Domain/Converters/IdTypeConverter.cs ================================================ using System.ComponentModel; using System.Globalization; using BlazorSample.Domain.Entities; namespace BlazorSample.Domain.Converters; public class IdTypeConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } public override object? ConvertTo( ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) { return value is not string s || !Guid.TryParse(s, out var guid) ? null : new IdType(guid); } } ================================================ FILE: samples/BlazorSample.Domain/Entities/IdType.cs ================================================ using System.ComponentModel; using System.Text.Json; using BlazorSample.Domain.Converters; namespace BlazorSample.Domain.Entities; [TypeConverter(typeof(IdTypeConverter))] public record IdType(Guid Value) { public override string ToString() { return JsonSerializer.Serialize(this); } } ================================================ FILE: samples/BlazorSample.Domain/Entities/WeatherForecastEntity.cs ================================================ namespace BlazorSample.Domain.Entities; public class WeatherForecastEntity { public DateTime Date { get; set; } public int TemperatureC { get; set; } public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); public string? Summary { get; set; } } ================================================ FILE: samples/BlazorSample.Domain/Extensions/ServiceCollectionExtensions.cs ================================================ using BlazorSample.Domain.Services.Navbar; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; namespace BlazorSample.Domain.Extensions; public static class ServiceCollectionExtensions { public static IServiceCollection AddNavigationItem( this IServiceCollection serviceCollection, string title, string? icon = null) where TPage : ComponentBase { serviceCollection.AddSingleton(new NavbarItem(typeof(TPage), title, icon)); return serviceCollection; } public static IServiceCollection AddDomain(this IServiceCollection serviceCollection) { serviceCollection.AddScoped(); return serviceCollection; } } ================================================ FILE: samples/BlazorSample.Domain/Services/IWeatherForecastGetter.cs ================================================ using BlazorSample.Domain.Entities; namespace BlazorSample.Domain.Services; public interface IWeatherForecastGetter { Task> GetForecasts(); } ================================================ FILE: samples/BlazorSample.Domain/Services/Navbar/NavbarItem.cs ================================================ namespace BlazorSample.Domain.Services.Navbar; public class NavbarItem { public string? Icon { get; } public Type Page { get; } public string DisplayName { get; } public string? Template { get; set; } public NavbarItem(Type page, string displayName, string? icon) { Page = page; DisplayName = displayName; Icon = icon; } } ================================================ FILE: samples/BlazorSample.Domain/Services/Navbar/NavbarService.cs ================================================ using System.Collections.ObjectModel; using System.Reflection; using Microsoft.AspNetCore.Components; namespace BlazorSample.Domain.Services.Navbar; public interface INavbarService { IReadOnlyList NavbarItems { get; } } public class NavbarService : INavbarService { private readonly List _navbarItems = new(); public NavbarService(IEnumerable navbarItems) { NavbarItems = new ReadOnlyCollection(_navbarItems); LoadComponents(navbarItems); } public IReadOnlyList NavbarItems { get; } private void LoadComponents(IEnumerable navbarItems) { foreach (var item in navbarItems) { if (!(item.Page.BaseType == typeof(ComponentBase) || typeof(ComponentBase).IsAssignableFrom(item.Page.BaseType))) { throw new InvalidOperationException( $"NavItem {item.DisplayName}:{item.Page.FullName} is not a component" ); } var routeAttribute = item.Page.GetCustomAttribute(); if (routeAttribute == null) { throw new InvalidOperationException( $"NavItem {item.DisplayName}:{item.Page.FullName} has no {nameof(RouteAttribute)}" ); } item.Template = routeAttribute.Template; _navbarItems.Add(item); } } } ================================================ FILE: samples/BlazorSample.ViewModels/BlazorSample.ViewModels.csproj ================================================  net6.0 enable true false ================================================ FILE: samples/BlazorSample.ViewModels/CascadingViewModel.cs ================================================ namespace BlazorSample.ViewModels; public class CascadingViewModel : ViewModelBase { [CascadingParameter] public string? Name { get; set; } } ================================================ FILE: samples/BlazorSample.ViewModels/ClockViewModel.cs ================================================ using Timer = System.Timers.Timer; namespace BlazorSample.ViewModels; public partial class ClockViewModel : ViewModelBase, IDisposable { private readonly Timer _timer; [Notify] private DateTime _dateTime = DateTime.Now; public ClockViewModel() { _timer = new Timer(TimeSpan.FromSeconds(1).TotalMilliseconds); _timer.Elapsed += TimerOnElapsed; _timer.Start(); } public void Dispose() { GC.SuppressFinalize(this); Dispose(true); } private void TimerOnElapsed(object? sender, ElapsedEventArgs e) { DateTime = DateTime.Now; } protected virtual void Dispose(bool disposing) { if (disposing) { _timer.Dispose(); } } ~ClockViewModel() { Dispose(false); } } ================================================ FILE: samples/BlazorSample.ViewModels/CounterViewModel.cs ================================================ namespace BlazorSample.ViewModels; public partial class CounterViewModel : ViewModelBase { [Notify] private int _currentCount; public void IncrementCount() { CurrentCount++; } } ================================================ FILE: samples/BlazorSample.ViewModels/Extensions/ServiceCollectionExtensions.cs ================================================ using BlazorSample.ViewModels.Navbar; namespace BlazorSample.ViewModels.Extensions; public static class ServiceCollectionExtensions { public static IServiceCollection AddViewModels(this IServiceCollection serviceCollection) { serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddTransient(); return serviceCollection; } } ================================================ FILE: samples/BlazorSample.ViewModels/GlobalUsings.cs ================================================ global using System.Collections.ObjectModel; global using BlazorSample.Domain.Services; global using MvvmBlazor; global using MvvmBlazor.ViewModel; global using System.Timers; global using Microsoft.AspNetCore.Components; global using Microsoft.Extensions.DependencyInjection; global using BlazorSample.Domain.Entities; ================================================ FILE: samples/BlazorSample.ViewModels/Navbar/NavbarItemViewModel.cs ================================================ namespace BlazorSample.ViewModels.Navbar; public class NavbarItemViewModel : ViewModelBase { private bool _isActive; public string DisplayName { get; } public string Template { get; } public string? Icon { get; set; } public bool IsActive { get => _isActive; set => Set(ref _isActive, value, nameof(IsActive)); } public NavbarItemViewModel(string displayName, string template, string? icon) { DisplayName = displayName; Template = template; Icon = icon; } } ================================================ FILE: samples/BlazorSample.ViewModels/Navbar/NavbarViewModel.cs ================================================ using BlazorSample.Domain.Services.Navbar; namespace BlazorSample.ViewModels.Navbar; public class NavbarViewModel : ViewModelBase { private bool _isMenuOpen = true; public ObservableCollection NavbarItems { get; } public bool IsMenuOpen { get => _isMenuOpen; set => Set(ref _isMenuOpen, value, nameof(IsMenuOpen)); } public NavbarViewModel(INavbarService navbarService) { NavbarItems = new ObservableCollection( navbarService.NavbarItems.Select(x => new NavbarItemViewModel(x.DisplayName, x.Template!, x.Icon)) ); } public void ToggleMenu() { IsMenuOpen = !IsMenuOpen; } private void UpdateActiveItem(NavigationManager navigationManager) { var relativePath = navigationManager.ToBaseRelativePath(navigationManager.Uri); foreach (var navbarItem in NavbarItems) if (string.IsNullOrEmpty(relativePath)) { navbarItem.IsActive = navbarItem.Template == "/"; } else { navbarItem.IsActive = navbarItem.Template.StartsWith("/" + relativePath); } } public override void OnInitialized() { var navigationManager = RootServiceProvider.GetRequiredService(); navigationManager.LocationChanged += (_, _) => UpdateActiveItem(navigationManager); UpdateActiveItem(navigationManager); } } ================================================ FILE: samples/BlazorSample.ViewModels/ParametersViewModel.cs ================================================ namespace BlazorSample.ViewModels; public class ParametersViewModel : ViewModelBase { private NavigationManager _navigationManager = null!; [Parameter] public string? Name { get; set; } public string? NewName { get; set; } public void NavigateToNewName() { if (string.IsNullOrEmpty(NewName)) { return; } _navigationManager.NavigateTo($"/parameters/{NewName}"); } public override void OnInitialized() { _navigationManager = RootServiceProvider.GetRequiredService(); } } ================================================ FILE: samples/BlazorSample.ViewModels/TypedParametersViewModel.cs ================================================ namespace BlazorSample.ViewModels; public class TypedParametersViewModel : ViewModelBase { private NavigationManager _navigationManager = null!; [Parameter] public IdType? Id { get; set; } public void NavigateToRandomId() { _navigationManager.NavigateTo($"/typed-parameters/{Guid.NewGuid()}"); } public override void OnInitialized() { _navigationManager = RootServiceProvider.GetRequiredService(); } public override void OnParametersSet() { if (Id is null) { NavigateToRandomId(); } } } ================================================ FILE: samples/BlazorSample.ViewModels/WeatherForecastViewModel.cs ================================================ namespace BlazorSample.ViewModels; public class WeatherForecastViewModel : ViewModelBase { private readonly WeatherForecastEntity _weatherForecastEntity; private int _temperatureC; private int _temperatureF; public DateTime Date => _weatherForecastEntity.Date; public string? Summary => _weatherForecastEntity.Summary; public int TemperatureC { get => _temperatureC; set => Set(ref _temperatureC, value, nameof(TemperatureC)); } public int TemperatureF { get => _temperatureF; set => Set(ref _temperatureF, value, nameof(TemperatureF)); } public WeatherForecastViewModel(WeatherForecastEntity weatherForecastEntity) { _weatherForecastEntity = weatherForecastEntity; TemperatureC = _weatherForecastEntity.TemperatureC; TemperatureF = _weatherForecastEntity.TemperatureF; } } ================================================ FILE: samples/BlazorSample.ViewModels/WeatherForecastsViewModel.cs ================================================ namespace BlazorSample.ViewModels; public partial class WeatherForecastsViewModel : ViewModelBase { private readonly IWeatherForecastGetter _weatherForecastGetter; [Notify] private ObservableCollection? _forecasts; public WeatherForecastsViewModel(IWeatherForecastGetter weatherForecastGetter) { _weatherForecastGetter = weatherForecastGetter; } public override async Task OnInitializedAsync() { // Simulate loading time await Task.Delay(1500); var forecastData = await _weatherForecastGetter.GetForecasts(); Forecasts = new ObservableCollection( forecastData.Select(x => new WeatherForecastViewModel(x)) ); } public void RandomizeData() { var random = new Random(); foreach (var weatherForecastEntity in _forecasts!) { weatherForecastEntity.TemperatureC = random.Next(10, 40); weatherForecastEntity.TemperatureF = random.Next(50, 200); } } } ================================================ FILE: samples/BlazorServersideSample/App.razor ================================================ 

Sorry, there's nothing at this address.

================================================ FILE: samples/BlazorServersideSample/BlazorServersideSample.csproj ================================================ net6.0 enable true false ================================================ FILE: samples/BlazorServersideSample/Pages/Error.razor ================================================ @page "/error"

Error.

An error occurred while processing your request.

Development Mode

Swapping to Development environment will display more detailed information about the error that occurred.

The Development environment shouldn't be enabled for deployed applications. It can result in displaying sensitive information from exceptions to end users. For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app.

================================================ FILE: samples/BlazorServersideSample/Pages/_Host.cshtml ================================================ @page "/" @namespace BlazorServersideSample.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers BlazorServersideSample @(await Html.RenderComponentAsync(RenderMode.ServerPrerendered)) ================================================ FILE: samples/BlazorServersideSample/Pages/_Imports.razor ================================================ @inherits MvvmComponentBase ================================================ FILE: samples/BlazorServersideSample/Program.cs ================================================ namespace BlazorServersideSample; public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) { return Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); } } ================================================ FILE: samples/BlazorServersideSample/Properties/launchSettings.json ================================================ { "profiles": { "BlazorServersideSample": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: samples/BlazorServersideSample/Services/WeatherForecastGetter.cs ================================================ using BlazorSample.Domain.Entities; using BlazorSample.Domain.Services; namespace BlazorServersideSample.Services; public class WeatherForecastGetter : IWeatherForecastGetter { private static readonly string[] Summaries = { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; public Task> GetForecasts() { var rng = new Random(); return Task.FromResult( Enumerable.Range(1, 5) .Select( index => new WeatherForecastEntity { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] } ) ); } } ================================================ FILE: samples/BlazorServersideSample/Startup.cs ================================================ using BlazorSample.Components.Extensions; using BlazorSample.Domain.Extensions; using BlazorSample.Domain.Services; using BlazorSample.ViewModels.Extensions; using BlazorServersideSample.Services; namespace BlazorServersideSample; public class Startup { public IConfiguration Configuration { get; } public Startup(IConfiguration configuration) { Configuration = configuration; } // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddServerSideBlazor(); // Add mvvm to server services.AddMvvm(); services.AddDomain().AddComponents().AddViewModels(); services.AddSingleton(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints( endpoints => { endpoints.MapBlazorHub(); endpoints.MapFallbackToPage("/_Host"); } ); } } ================================================ FILE: samples/BlazorServersideSample/_Imports.razor ================================================ @using System.Net.Http @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components @using Microsoft.JSInterop @using BlazorServersideSample @using MvvmBlazor.Components @using BlazorSample.Components.Shared ================================================ FILE: samples/BlazorServersideSample/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" } } } ================================================ FILE: samples/BlazorServersideSample/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*" } ================================================ FILE: src/.editorconfig ================================================ [*.{c,c++,cc,cp,cpp,cu,cuh,cxx,h,hh,hpp,hxx,inc,inl,ino,ipp,mpp,proto,tpp}] indent_style = tab indent_size = tab tab_width = 4 [*.{asax,ascx,aspx,cs,cshtml,css,htm,html,js,jsx,master,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}] indent_style = space indent_size = 4 tab_width = 4 [*.{appxmanifest,build,config,csproj,dbml,discomap,dtd,json,jsproj,lsproj,njsproj,nuspec,proj,props,resjson,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}] indent_style = space indent_size = 2 tab_width = 2 [*] # Microsoft .NET properties csharp_new_line_before_members_in_object_initializers = false csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion csharp_style_var_elsewhere = true:hint csharp_style_var_for_built_in_types = true:hint csharp_style_var_when_type_is_apparent = true:hint dotnet_style_predefined_type_for_locals_parameters_members = true:hint dotnet_style_predefined_type_for_member_access = true:hint dotnet_style_qualification_for_event = false:warning dotnet_style_qualification_for_field = false:warning dotnet_style_qualification_for_method = false:warning dotnet_style_qualification_for_property = false:warning dotnet_style_require_accessibility_modifiers = for_non_interface_members:hint # ReSharper properties resharper_csharp_empty_block_style = together_same_line ================================================ FILE: src/Directory.Build.props ================================================  enable enable All all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: src/MvvmBlazor/MvvmBlazor.csproj ================================================ net6.0 true $(BaseIntermediateOutputPath)\GeneratedFiles true false MIT Christian Klemm A lightweight Blazor Mvvm Library $(GITHUB_SERVER_URL)/$(GITHUB_REPOSITORY) $(GITHUB_SERVER_URL)/$(GITHUB_REPOSITORY) git Blazor;Mvvm true true true ================================================ FILE: src/MvvmBlazor/Properties/AssemblyInfo.cs ================================================ [assembly: CLSCompliant(false)] ================================================ FILE: src/MvvmBlazor.CodeGenerators/AnalyzerReleases.Shipped.md ================================================ ## Release 6.0.0 ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|------- MVVMBLAZOR001 | MvvmBlazorGenerator | Error | MVVMBLAZOR001_AnalyzerName MVVMBLAZOR002 | MvvmBlazorGenerator | Error | MVVMBLAZOR002_AnalyzerName MVVMBLAZOR003 | MvvmBlazorGenerator | Error | MVVMBLAZOR003_AnalyzerName MVVMBLAZOR004 | MvvmBlazorGenerator | Error | MVVMBLAZOR004_AnalyzerName ## Release 6.0.2 ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|------- MVVMBLAZOR005 | MvvmBlazorGenerator | Error | MVVMBLAZOR005_AnalyzerName ================================================ FILE: src/MvvmBlazor.CodeGenerators/AnalyzerReleases.Unshipped.md ================================================  ================================================ FILE: src/MvvmBlazor.CodeGenerators/Components/MvvmComponentClassContext.cs ================================================ namespace MvvmBlazor.CodeGenerators.Components; internal class MvvmComponentClassContext { public ClassDeclarationSyntax ComponentClass { get; } public INamedTypeSymbol ComponentSymbol { get; } public MvvmComponentClassContext(ClassDeclarationSyntax componentClass, INamedTypeSymbol componentSymbol) { ComponentClass = componentClass; ComponentSymbol = componentSymbol; } } ================================================ FILE: src/MvvmBlazor.CodeGenerators/Components/MvvmComponentGenerator.cs ================================================ namespace MvvmBlazor.CodeGenerators.Components; [Generator] public class MvvmComponentGenerator : ISourceGenerator { private static readonly DiagnosticDescriptor ComponentNotPartialError = new( "MVVMBLAZOR001", "Component needs to be partial", "Mvvm Component class '{0}' needs to be partial", "MvvmBlazorGenerator", DiagnosticSeverity.Error, true ); private static readonly DiagnosticDescriptor ComponentWrongBaseClassError = new( "MVVMBLAZOR002", "Missing component base class", "Mvvm Component class '{0}' needs to be assignable to '{1}'", "MvvmBlazorGenerator", DiagnosticSeverity.Error, true ); private static readonly DiagnosticDescriptor ComponentWrongTypeParameterError = new( "MVVMBLAZOR005", "Wrong type parameter", "Mvvm Component class '{0}' needs to have exactly one type parameter named '{1}'", "MvvmBlazorGenerator", DiagnosticSeverity.Error, true ); public void Execute(GeneratorExecutionContext context) { if (context.SyntaxContextReceiver is not MvvmComponentSyntaxReceiver syntaxReceiver || syntaxReceiver.ComponentClassContexts.Count == 0) { return; } foreach (var componentClassContext in syntaxReceiver.ComponentClassContexts) ProcessComponent(context, componentClassContext); } public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(() => new MvvmComponentSyntaxReceiver()); } private static void ProcessComponent( GeneratorExecutionContext context, MvvmComponentClassContext componentClassContext) { var componentClass = componentClassContext.ComponentClass; var isPartial = componentClass.Modifiers.Any(SyntaxKind.PartialKeyword); if (!isPartial) { context.ReportDiagnostic( Diagnostic.Create( ComponentNotPartialError, Location.Create( componentClass.SyntaxTree, TextSpan.FromBounds(componentClass.SpanStart, componentClass.SpanStart) ), componentClass.Identifier ) ); return; } var componentBaseType = context.Compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.ComponentBase")!; if (!componentClassContext.ComponentSymbol.InheritsFrom(componentBaseType)) { context.ReportDiagnostic( Diagnostic.Create( ComponentWrongBaseClassError, Location.Create( componentClass.SyntaxTree, TextSpan.FromBounds(componentClass.SpanStart, componentClass.SpanStart) ), componentClass.Identifier, componentBaseType.GetMetadataName() ) ); return; } if (componentClass.TypeParameterList is null || componentClass.TypeParameterList.Parameters.Count == 0) { AddComponent(context, componentClassContext, componentClass); return; } AddGenericComponent(context, componentClassContext, componentClass); } private static void AddComponent( GeneratorExecutionContext context, MvvmComponentClassContext componentClassContext, BaseTypeDeclarationSyntax componentClass) { var componentSourceText = SourceText.From(GenerateComponentCode(componentClassContext), Encoding.UTF8); context.AddSource(componentClass.Identifier + ".g.cs", componentSourceText); } private static void AddGenericComponent( GeneratorExecutionContext context, MvvmComponentClassContext componentClassContext, TypeDeclarationSyntax componentClass) { const string typeParameterName = "T"; var genericComponentSourceText = SourceText.From( GenerateGenericComponentCode(componentClassContext, typeParameterName), Encoding.UTF8 ); var typeParameterList = componentClass.TypeParameterList; if (typeParameterList is null || typeParameterList.Parameters.Count != 1 || typeParameterList.Parameters[0].Identifier.ValueText != typeParameterName) { context.ReportDiagnostic( Diagnostic.Create( ComponentWrongTypeParameterError, Location.Create( componentClass.SyntaxTree, TextSpan.FromBounds(componentClass.SpanStart, componentClass.SpanStart) ), componentClass.Identifier, typeParameterName ) ); } context.AddSource(componentClass.Identifier + "T.g.cs", genericComponentSourceText); } private static string GenerateComponentCode(MvvmComponentClassContext componentClassContext) { var componentNamespace = componentClassContext.ComponentSymbol.ContainingNamespace; var componentClassName = componentClassContext.ComponentClass.Identifier; return $@" #nullable enable using System; using System.Linq.Expressions; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using MvvmBlazor.Components; using MvvmBlazor.ViewModel; namespace {componentNamespace} {{ partial class {componentClassName} : IDisposable, IAsyncDisposable {{ private AsyncServiceScope? _scope; [Inject] IServiceScopeFactory ScopeFactory {{ get; set; }} = default!; [Inject] protected IServiceProvider RootServiceProvider {{ get; set; }} = default!; protected bool IsDisposed {{ get; private set; }} public MvvmBlazor.Internal.Bindings.IBinder Binder {{ get; private set; }} = null!; #pragma warning disable CS0109 protected new IServiceProvider ScopedServices #pragma warning restore CS0109 {{ get {{ if (ScopeFactory == null) {{ throw new InvalidOperationException(""Services cannot be accessed before the component is initialized.""); }} if (IsDisposed) {{ throw new ObjectDisposedException(GetType().Name); }} _scope ??= ScopeFactory.CreateAsyncScope(); return _scope.Value.ServiceProvider; }} }} #pragma warning disable CS8618 protected internal {componentClassName}(IServiceProvider services) #pragma warning restore CS8618 {{ RootServiceProvider = services; ScopeFactory = services.GetRequiredService(); InitializeDependencies(); }} #pragma warning disable CS8618 protected {componentClassName}() #pragma warning restore CS8618 {{ }} private void InitializeDependencies() {{ Binder = ScopedServices.GetRequiredService(); Binder.ValueChangedCallback = BindingOnBindingValueChanged; }} protected internal TValue Bind(TViewModel viewModel, Expression> property) where TViewModel : ViewModelBase {{ return AddBinding(viewModel, property); }} public virtual TValue AddBinding(TViewModel viewModel, Expression> propertyExpression) where TViewModel : ViewModelBase {{ return Binder.Bind(viewModel, propertyExpression); }} protected override void OnInitialized() {{ base.OnInitialized(); InitializeDependencies(); }} internal virtual void BindingOnBindingValueChanged(object sender, EventArgs e) {{ InvokeAsync(StateHasChanged); }} public void Dispose() {{ if (!IsDisposed) {{ _scope?.Dispose(); _scope = null; Dispose(true); GC.SuppressFinalize(this); IsDisposed = true; }} }} protected virtual void Dispose(bool disposing) {{ }} public async ValueTask DisposeAsync() {{ if (!IsDisposed) {{ if (_scope is not null) {{ await _scope.Value.DisposeAsync(); _scope = null; }} await DisposeAsyncCore(); Dispose(false); GC.SuppressFinalize(this); IsDisposed = true; }} }} protected virtual ValueTask DisposeAsyncCore() {{ return ValueTask.CompletedTask; }} }} }} "; } private static string GenerateGenericComponentCode(MvvmComponentClassContext componentClassContext, string typeParameterName) { var componentNamespace = componentClassContext.ComponentSymbol.ContainingNamespace; var componentClassName = componentClassContext.ComponentClass.Identifier; return $@" #nullable enable using System; using System.Linq.Expressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using MvvmBlazor.Internal.Parameters; using MvvmBlazor.ViewModel; namespace {componentNamespace} {{ partial class {componentClassName}<{typeParameterName}> where T : ViewModelBase {{ private MvvmBlazor.Internal.Parameters.IViewModelParameterSetter? _viewModelParameterSetter; #pragma warning disable CS8618 protected internal {componentClassName}(IServiceProvider serviceProvider) : base(serviceProvider) #pragma warning restore CS8618 {{ SetBindingContext(); }} #pragma warning disable CS8618 protected {componentClassName}() #pragma warning restore CS8618 {{ }} protected T BindingContext {{ get; set; }} private void SetBindingContext() {{ BindingContext ??= ScopedServices.GetRequiredService(); BindingContext.RootServiceProvider = RootServiceProvider; }} private void SetParameters() {{ if (IsDisposed) return; if (BindingContext is null) throw new InvalidOperationException($""{{nameof(BindingContext)}} is not set""); _viewModelParameterSetter ??= ScopedServices.GetRequiredService(); _viewModelParameterSetter.ResolveAndSet(this, BindingContext); }} protected internal TValue Bind(Expression> property) {{ if (BindingContext is null) throw new InvalidOperationException($""{{nameof(BindingContext)}} is not set""); return AddBinding(BindingContext, property); }} /// protected override void OnInitialized() {{ SetBindingContext(); base.OnInitialized(); BindingContext?.OnInitialized(); }} /// protected override async Task OnInitializedAsync() {{ await base.OnInitializedAsync(); await BindingContext!.OnInitializedAsync(); }} /// protected override void OnParametersSet() {{ SetParameters(); base.OnParametersSet(); BindingContext?.OnParametersSet(); }} /// protected override async Task OnParametersSetAsync() {{ await base.OnParametersSetAsync(); await BindingContext.OnParametersSetAsync(); }} /// protected override bool ShouldRender() {{ return BindingContext!.ShouldRender(); }} /// protected override void OnAfterRender(bool firstRender) {{ base.OnAfterRender(firstRender); BindingContext!.OnAfterRender(firstRender); }} /// protected override async Task OnAfterRenderAsync(bool firstRender) {{ await base.OnAfterRenderAsync(firstRender); await BindingContext!.OnAfterRenderAsync(firstRender); }} /// public override async Task SetParametersAsync(ParameterView parameters) {{ await base.SetParametersAsync(parameters); if (BindingContext != null) {{ await BindingContext.SetParametersAsync(parameters); }} }} }} }} "; } } ================================================ FILE: src/MvvmBlazor.CodeGenerators/Components/MvvmComponentSyntaxReceiver.cs ================================================ namespace MvvmBlazor.CodeGenerators.Components; internal class MvvmComponentSyntaxReceiver : ISyntaxContextReceiver { public List ComponentClassContexts { get; } = new(); public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { if (context.Node is not ClassDeclarationSyntax cds || cds.AttributeLists.Count == 0) { return; } var symbol = context.SemanticModel.GetDeclaredSymbol(context.Node); if (symbol is not INamedTypeSymbol typeSymbol) { return; } var isDecoratedWithAttribute = typeSymbol.GetAttributes() .Any(ad => ad.AttributeClass is { Name: "MvvmComponentAttribute" }); if (!isDecoratedWithAttribute) { return; } ComponentClassContexts.Add(new MvvmComponentClassContext(cds, typeSymbol)); } } ================================================ FILE: src/MvvmBlazor.CodeGenerators/Extensions/StringBuilderExtensions.cs ================================================ using System.Globalization; namespace MvvmBlazor.CodeGenerators.Extensions; internal static class StringBuilderExtensions { public static void AppendLineFormat(this StringBuilder stringBuilder, string format, object arg0) { stringBuilder.AppendFormat(CultureInfo.InvariantCulture, format, arg0); stringBuilder.AppendLine(); } public static void AppendLineFormat(this StringBuilder stringBuilder, string format, object arg0, object arg1) { stringBuilder.AppendFormat(CultureInfo.InvariantCulture, format, arg0, arg1); stringBuilder.AppendLine(); } } ================================================ FILE: src/MvvmBlazor.CodeGenerators/Extensions/SymbolExtensions.cs ================================================ namespace MvvmBlazor.CodeGenerators.Extensions; internal static class SymbolExtensions { public static string GetMetadataName(this ISymbol symbol) { return string.Join(".", symbol.ContainingNamespace, symbol.MetadataName); } public static bool InheritsFrom(this ITypeSymbol symbol, ISymbol baseType) { if (symbol.BaseType is null) { return false; } if (symbol.BaseType.GetMetadataName() == baseType.GetMetadataName()) { return true; } return symbol.BaseType.InheritsFrom(baseType); } } ================================================ FILE: src/MvvmBlazor.CodeGenerators/GlobalUsings.cs ================================================ global using System.Text; global using Microsoft.CodeAnalysis; global using Microsoft.CodeAnalysis.CSharp; global using Microsoft.CodeAnalysis.CSharp.Syntax; global using Microsoft.CodeAnalysis.Text; global using MvvmBlazor.CodeGenerators.Extensions; global using System.Collections.Generic; global using System.Linq; ================================================ FILE: src/MvvmBlazor.CodeGenerators/MvvmBlazor.CodeGenerators.csproj ================================================  netstandard2.0 10.0 false false false MIT Christian Klemm Code generators for a lightweight Blazor Mvvm Library $(GITHUB_SERVER_URL)/$(GITHUB_REPOSITORY) $(GITHUB_SERVER_URL)/$(GITHUB_REPOSITORY) git Blazor;Mvvm all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: src/MvvmBlazor.CodeGenerators/NotifyPropertyChanged/NotifyPropertyChangedContext.cs ================================================ namespace MvvmBlazor.CodeGenerators.NotifyPropertyChanged; internal class NotifyPropertyChangedContext { public FieldDeclarationSyntax Field { get; } public IFieldSymbol FieldSymbol { get; } public NotifyPropertyChangedContext(FieldDeclarationSyntax field, IFieldSymbol fieldSymbol) { Field = field; FieldSymbol = fieldSymbol; } } ================================================ FILE: src/MvvmBlazor.CodeGenerators/NotifyPropertyChanged/NotifyPropertyChangedGenerator.cs ================================================ namespace MvvmBlazor.CodeGenerators.NotifyPropertyChanged; [Generator] public class NotifyPropertyChangedGenerator : ISourceGenerator { private static readonly DiagnosticDescriptor ViewModelNotPartialError = new( "MVVMBLAZOR003", "View model needs to be partial", "View model class '{0}' needs to be partial", "MvvmBlazorGenerator", DiagnosticSeverity.Error, true ); private static readonly DiagnosticDescriptor ViewModelMissingBaseClass = new( "MVVMBLAZOR004", "Missing view model base class", "View model class '{0}' needs to be assignable to '{1}'", "MvvmBlazorGenerator", DiagnosticSeverity.Error, true ); public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(() => new NotifyPropertyChangedSyntaxReceiver()); } public void Execute(GeneratorExecutionContext context) { if (context.SyntaxContextReceiver is not NotifyPropertyChangedSyntaxReceiver syntaxReceiver || syntaxReceiver.Contexts.Count == 0) { return; } foreach (var fieldContexts in syntaxReceiver.Contexts) ProcessViewModel(context, fieldContexts.Value); } private static void ProcessViewModel( GeneratorExecutionContext context, IReadOnlyCollection fieldContexts) { var viewModelClass = fieldContexts.First().Field.Ancestors().OfType().First(); var isPartial = viewModelClass.Modifiers.Any(SyntaxKind.PartialKeyword); if (!isPartial) { context.ReportDiagnostic( Diagnostic.Create( ViewModelNotPartialError, Location.Create( viewModelClass.SyntaxTree, TextSpan.FromBounds(viewModelClass.SpanStart, viewModelClass.SpanStart) ), viewModelClass.Identifier ) ); return; } var viewModelType = fieldContexts.First().FieldSymbol.ContainingType; var viewModelBaseType = context.Compilation.GetTypeByMetadataName("MvvmBlazor.ViewModel.ViewModelBase")!; if (!viewModelType.InheritsFrom(viewModelBaseType)) { context.ReportDiagnostic( Diagnostic.Create( ViewModelMissingBaseClass, Location.Create( viewModelClass.SyntaxTree, TextSpan.FromBounds(viewModelClass.SpanStart, viewModelClass.SpanStart) ), viewModelClass.Identifier ) ); return; } AddViewModel(context, fieldContexts, viewModelType, viewModelClass); } private static void AddViewModel( GeneratorExecutionContext context, IReadOnlyCollection fieldContexts, INamedTypeSymbol viewModelType, ClassDeclarationSyntax viewModelClass) { var viewModelSourceText = SourceText.From( GenerateViewModelCode(fieldContexts, viewModelType, viewModelClass), Encoding.UTF8 ); context.AddSource(viewModelClass.Identifier + ".Generated.cs", viewModelSourceText); } private static string GenerateViewModelCode( IReadOnlyCollection fieldContexts, INamedTypeSymbol viewModelType, ClassDeclarationSyntax viewModelClass) { var viewModelNamespace = viewModelType.ContainingNamespace; var viewModelClassName = viewModelClass.Identifier; var genericArgumentString = string.Empty; var genericArguments = viewModelType.TypeArguments.Select(x => x.Name).ToList(); if (genericArguments.Count > 0) { genericArgumentString = "<" + string.Join(", ", genericArguments) + ">"; } var sb = new StringBuilder(); sb.AppendLine("#nullable enable"); sb.AppendLine(); sb.AppendLineFormat("namespace {0}", viewModelNamespace); sb.AppendLine("{"); sb.AppendLineFormat(" partial class {0}{1}", viewModelClassName, genericArgumentString); sb.AppendLine(" {"); foreach (var fieldContext in fieldContexts) { var propertyType = fieldContext.FieldSymbol.Type; var fieldName = fieldContext.FieldSymbol.Name; var propertyName = GetPropertyName(fieldName); sb.AppendLineFormat(" public {0} {1}", propertyType, propertyName); sb.AppendLine(" {"); sb.AppendLineFormat(" get => {0};", fieldName); sb.AppendLineFormat(" set => Set(ref {0}, value, \"{1}\");", fieldName, propertyName); sb.AppendLine(" }"); } sb.AppendLine(" }"); sb.AppendLine("}"); return sb.ToString(); } private static string GetPropertyName(string fieldName) { var fieldNameWithoutUnderscore = fieldName.TrimStart('_'); var firstChar = fieldNameWithoutUnderscore.Substring(0, 1); return firstChar.ToUpperInvariant() + fieldNameWithoutUnderscore.Substring(1); } } ================================================ FILE: src/MvvmBlazor.CodeGenerators/NotifyPropertyChanged/NotifyPropertyChangedSyntaxReceiver.cs ================================================ namespace MvvmBlazor.CodeGenerators.NotifyPropertyChanged; internal class NotifyPropertyChangedSyntaxReceiver : ISyntaxContextReceiver { public Dictionary> Contexts { get; } = new(); public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { if (context.Node is not FieldDeclarationSyntax fds || fds.AttributeLists.Count == 0) { return; } foreach (var variable in fds.Declaration.Variables) { var symbol = context.SemanticModel.GetDeclaredSymbol(variable); if (symbol is not IFieldSymbol fieldSymbol) { continue; } var isDecoratedWithAttribute = fieldSymbol.GetAttributes() .Any(ad => ad.AttributeClass is { Name: "NotifyAttribute" }); if (!isDecoratedWithAttribute) { return; } var className = fieldSymbol.ContainingType.Name; if (!Contexts.TryGetValue(className, out var fields)) { fields = new List(); Contexts.Add(className, fields); } fields.Add(new NotifyPropertyChangedContext(fds, fieldSymbol)); } } } ================================================ FILE: src/MvvmBlazor.CodeGenerators/Properties/AssemblyInfo.cs ================================================ [assembly: CLSCompliant(false)] ================================================ FILE: src/MvvmBlazor.Core/Components/MvvmComponentAttribute.cs ================================================ // ReSharper disable once CheckNamespace namespace MvvmBlazor; [AttributeUsage(AttributeTargets.Class)] public sealed class MvvmComponentAttribute : Attribute { } ================================================ FILE: src/MvvmBlazor.Core/Components/MvvmComponentBase.cs ================================================ namespace MvvmBlazor.Components; [MvvmComponent] // ReSharper disable once PartialTypeWithSinglePart public abstract partial class MvvmComponentBase : ComponentBase { } ================================================ FILE: src/MvvmBlazor.Core/Components/MvvmComponentBaseT.cs ================================================ namespace MvvmBlazor.Components; [MvvmComponent] // ReSharper disable once PartialTypeWithSinglePart public abstract partial class MvvmComponentBase : MvvmComponentBase where T : ViewModelBase { } ================================================ FILE: src/MvvmBlazor.Core/Extensions/ServiceCollectionExtensions.cs ================================================ using Binder = MvvmBlazor.Internal.Bindings.Binder; // ReSharper disable once CheckNamespace namespace Microsoft.Extensions.DependencyInjection; public static class ServiceCollectionExtensions { public static IServiceCollection AddMvvm(this IServiceCollection serviceCollection) { serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); return serviceCollection; } } ================================================ FILE: src/MvvmBlazor.Core/GlobalUsings.cs ================================================ global using System.ComponentModel; global using System.Linq.Expressions; global using System.Reflection; global using System.Runtime.CompilerServices; global using Microsoft.AspNetCore.Components; global using MvvmBlazor.Internal.Bindings; global using System.Collections.Specialized; global using MvvmBlazor.Internal.WeakEventListener; global using System.Runtime.Serialization; global using MvvmBlazor.ViewModel; global using MvvmBlazor.Components; global using MvvmBlazor.Internal.Parameters; ================================================ FILE: src/MvvmBlazor.Core/Internal/Bindings/Binder.cs ================================================ namespace MvvmBlazor.Internal.Bindings; public interface IBinder { Action? ValueChangedCallback { get; set; } TValue Bind(TViewModel viewModel, Expression> propertyExpression) where TViewModel : ViewModelBase; } internal class Binder : IBinder, IDisposable { private readonly IBindingFactory _bindingFactory; private readonly HashSet _bindings = new(); private readonly IWeakEventManager _weakEventManager; private bool _isDisposed; public Binder(IBindingFactory bindingFactory, IWeakEventManager weakEventManager) { _bindingFactory = bindingFactory; _weakEventManager = weakEventManager; } public Action? ValueChangedCallback { get; set; } public TValue Bind( TViewModel viewModel, Expression> propertyExpression) where TViewModel : ViewModelBase { ThrowIfDisposed(); if (ValueChangedCallback is null) { throw new BindingException($"{nameof(ValueChangedCallback)} is null"); } var propertyInfo = ValidateAndResolveBindingContext(viewModel, propertyExpression); var binding = _bindingFactory.Create(viewModel, propertyInfo, _weakEventManager); if (_bindings.Contains(binding)) { return (TValue)binding.GetValue(); } _weakEventManager.AddWeakEventListener(binding, nameof(Binding.BindingValueChanged), ValueChangedCallback); binding.Initialize(); _bindings.Add(binding); return (TValue)binding.GetValue(); } protected static PropertyInfo ValidateAndResolveBindingContext( TViewModel viewModel, Expression> property) where TViewModel : ViewModelBase { if (viewModel is null) { throw new BindingException("ViewModelType is null"); } if (property is null) { throw new BindingException("Property expression is null"); } if (property.Body is not MemberExpression { Member: PropertyInfo p }) { throw new BindingException("Binding member needs to be a property"); } if (typeof(TViewModel).GetProperty(p.Name) is null) { throw new BindingException($"Cannot find property {p.Name} in type {viewModel.GetType().FullName}"); } return p; } #region IDisposable public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { ThrowIfDisposed(); _isDisposed = true; DisposeBindings(); } } private void DisposeBindings() { foreach (var binding in _bindings) { _weakEventManager.RemoveWeakEventListener(binding); binding.Dispose(); } } private void ThrowIfDisposed() { if (_isDisposed) { throw new ObjectDisposedException(nameof(Binder)); } } ~Binder() { Dispose(false); } #endregion } ================================================ FILE: src/MvvmBlazor.Core/Internal/Bindings/Binding.cs ================================================ namespace MvvmBlazor.Internal.Bindings; public interface IBinding : IDisposable { INotifyPropertyChanged Source { get; } PropertyInfo PropertyInfo { get; } event EventHandler? BindingValueChanged; void Initialize(); object GetValue(); } internal class Binding : IBinding { private readonly IWeakEventManager _weakEventManager; private INotifyCollectionChanged? _boundCollection; private bool _isCollection; public Binding(INotifyPropertyChanged source, PropertyInfo propertyInfo, IWeakEventManager weakEventManager) { _weakEventManager = weakEventManager; Source = source; PropertyInfo = propertyInfo; } public INotifyPropertyChanged Source { get; } public PropertyInfo PropertyInfo { get; } public event EventHandler? BindingValueChanged; public void Initialize() { _isCollection = typeof(INotifyCollectionChanged).IsAssignableFrom(PropertyInfo.PropertyType); _weakEventManager.AddWeakEventListener(Source, SourceOnPropertyChanged); AddCollectionBindings(); } public object GetValue() { return PropertyInfo.GetValue(Source, null)!; } private void AddCollectionBindings() { if (!_isCollection || GetValue() is not INotifyCollectionChanged collection) { return; } _weakEventManager.AddWeakEventListener(collection, CollectionOnCollectionChanged); _boundCollection = collection; } private void SourceOnPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName is null) { BindingValueChanged?.Invoke(this, EventArgs.Empty); return; } // This should just listen to the bindings property if (e.PropertyName != PropertyInfo.Name) { return; } if (_isCollection) { // If our binding is a collection binding we need to remove the event // and reinitialize the collection bindings if (_boundCollection != null) { _weakEventManager.RemoveWeakEventListener(_boundCollection); } AddCollectionBindings(); } BindingValueChanged?.Invoke(this, EventArgs.Empty); } private void CollectionOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { BindingValueChanged?.Invoke(this, EventArgs.Empty); } #region IDisposable Support public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { if (_boundCollection != null) { _weakEventManager.RemoveWeakEventListener(_boundCollection); } _weakEventManager.RemoveWeakEventListener(Source); } } #endregion #region Base overrides public override string ToString() { return $"{PropertyInfo?.DeclaringType?.Name}.{PropertyInfo?.Name}"; } public override bool Equals(object? obj) { return obj is Binding b && ReferenceEquals(b.Source, Source) && b.PropertyInfo.Name == PropertyInfo.Name; } public override int GetHashCode() { var hash = 13; hash = hash * 7 + Source.GetHashCode(); hash = hash * 7 + PropertyInfo.Name.GetHashCode(StringComparison.InvariantCulture); return hash; } #endregion } ================================================ FILE: src/MvvmBlazor.Core/Internal/Bindings/BindingException.cs ================================================ namespace MvvmBlazor.Internal.Bindings; public class BindingException : Exception { public BindingException() { } protected BindingException(SerializationInfo info, StreamingContext context) : base(info, context) { } public BindingException(string message) : base(message) { } public BindingException(string message, Exception innerException) : base(message, innerException) { } } ================================================ FILE: src/MvvmBlazor.Core/Internal/Bindings/BindingFactory.cs ================================================ namespace MvvmBlazor.Internal.Bindings; internal interface IBindingFactory { IBinding Create(INotifyPropertyChanged source, PropertyInfo propertyInfo, IWeakEventManager weakEventManager); } internal class BindingFactory : IBindingFactory { public IBinding Create(INotifyPropertyChanged source, PropertyInfo propertyInfo, IWeakEventManager weakEventManager) { return new Binding(source, propertyInfo, weakEventManager); } } ================================================ FILE: src/MvvmBlazor.Core/Internal/Parameters/ParameterCache.cs ================================================ using System.Collections.Concurrent; namespace MvvmBlazor.Internal.Parameters; internal interface IParameterCache { ParameterInfo? Get(Type type); void Set(Type type, ParameterInfo info); } internal class ParameterCache : IParameterCache { private readonly ConcurrentDictionary _cache = new(); public ParameterInfo? Get(Type type) { return _cache.TryGetValue(type, out var info) ? info : null; } public void Set(Type type, ParameterInfo info) { ArgumentNullException.ThrowIfNull(info); _cache[type] = info; } } ================================================ FILE: src/MvvmBlazor.Core/Internal/Parameters/ParameterException.cs ================================================ namespace MvvmBlazor.Internal.Parameters; [Serializable] public class ParameterException : Exception { public ParameterException() { } protected ParameterException(SerializationInfo info, StreamingContext context) : base(info, context) { } public ParameterException(string? message) : base(message) { } public ParameterException(string? message, Exception? innerException) : base(message, innerException) { } } ================================================ FILE: src/MvvmBlazor.Core/Internal/Parameters/ParameterInfo.cs ================================================ namespace MvvmBlazor.Internal.Parameters; internal record ParameterInfo { private readonly Dictionary _parameters = new(); public IReadOnlyDictionary Parameters => _parameters; public ParameterInfo(IEnumerable componentProperties, IEnumerable viewModelProperties) { var componentPropertyDict = componentProperties.ToDictionary(x => x.Name); foreach (var viewModelProperty in viewModelProperties.OrderBy(x => x.Name)) { if (!componentPropertyDict.TryGetValue(viewModelProperty.Name, out var componentProperty)) { throw new ParameterException( $"Failed to find matching component parameter {viewModelProperty.Name} for view model {viewModelProperty.DeclaringType!.FullName}" ); } _parameters.Add(componentProperty, viewModelProperty); } } } ================================================ FILE: src/MvvmBlazor.Core/Internal/Parameters/ParameterResolver.cs ================================================ namespace MvvmBlazor.Internal.Parameters; internal interface IParameterResolver { ParameterInfo ResolveParameters(Type componentType, Type viewModelType); } internal class ParameterResolver : IParameterResolver { private readonly IParameterCache _parameterCache; public ParameterResolver(IParameterCache parameterCache) { _parameterCache = parameterCache; } public ParameterInfo ResolveParameters(Type componentType, Type viewModelType) { var parameterInfo = _parameterCache.Get(componentType); if (parameterInfo is not null) { return parameterInfo; } var componentParameters = ResolveTypeParameters(componentType); var viewModelParameters = ResolveTypeParameters(viewModelType); parameterInfo = new ParameterInfo(componentParameters, viewModelParameters); _parameterCache.Set(componentType, parameterInfo); return parameterInfo; } private static IEnumerable ResolveTypeParameters(Type memberType) { var componentProperties = memberType.GetProperties(); var resolvedComponentProperties = new List(); foreach (var componentProperty in componentProperties) { // Skip if property has no public setter if (componentProperty.GetSetMethod() is null) { continue; } // If the property is marked as a parameter add it to the list ParameterAttribute? GetParameterAttribute() => componentProperty.GetCustomAttribute(); CascadingParameterAttribute? GetCascadingParameterAttribute() => componentProperty.GetCustomAttribute(); if (GetParameterAttribute() != null || GetCascadingParameterAttribute() != null) { resolvedComponentProperties.Add(componentProperty); } } return resolvedComponentProperties; } } ================================================ FILE: src/MvvmBlazor.Core/Internal/Parameters/ViewModelParameterSetter.cs ================================================ namespace MvvmBlazor.Internal.Parameters; public interface IViewModelParameterSetter { void ResolveAndSet(ComponentBase component, ViewModelBase viewModel); } internal class ViewModelParameterSetter : IViewModelParameterSetter { private readonly IParameterResolver _parameterResolver; public ViewModelParameterSetter(IParameterResolver parameterResolver) { _parameterResolver = parameterResolver; } public void ResolveAndSet(ComponentBase component, ViewModelBase viewModel) { var componentType = component.GetType(); var viewModelType = viewModel.GetType(); var parameterInfo = _parameterResolver.ResolveParameters(componentType, viewModelType); foreach (var (componentProperty, viewModelProperty) in parameterInfo.Parameters) { var value = componentProperty.GetValue(component); var parameterTypeDiffers = componentProperty.PropertyType != viewModelProperty.PropertyType; if (value != null && parameterTypeDiffers) { value = ConvertValue(componentProperty.PropertyType, viewModelProperty.PropertyType, value); } viewModelProperty.SetValue(viewModel, value); } } private static object? ConvertValue(Type componentType, Type viewModelType, object value) { var converter = TypeDescriptor.GetConverter(viewModelType); return converter.CanConvertFrom(componentType) ? converter.ConvertTo(value, viewModelType) : value; } } ================================================ FILE: src/MvvmBlazor.Core/Internal/WeakEventListener/WeakEventListener.cs ================================================ namespace MvvmBlazor.Internal.WeakEventListener; internal interface IWeakEventListener { bool IsAlive { get; } object? Source { get; } Delegate? Handler { get; } void StopListening(); } internal abstract class WeakEventListenerBase : IWeakEventListener where T : class where TArgs : EventArgs { private readonly WeakReference> _handler; private readonly WeakReference _source; protected WeakEventListenerBase(T source, Action handler) { ArgumentNullException.ThrowIfNull(source); ArgumentNullException.ThrowIfNull(handler); _source = new WeakReference(source); _handler = new WeakReference>(handler); } public bool IsAlive => _handler.TryGetTarget(out _) && _source.TryGetTarget(out _); public object? Source { get { if (_source.TryGetTarget(out var source)) { return source; } return null; } } public Delegate? Handler { get { if (_handler.TryGetTarget(out var handler)) { return handler; } return null; } } public void StopListening() { if (_source.TryGetTarget(out var source)) { StopListening(source); } } protected void HandleEvent(object sender, TArgs e) { if (_handler.TryGetTarget(out var handler)) { handler((T)sender, e); } else { StopListening(); } } protected abstract void StopListening(T source); } internal class TypedWeakEventListener : WeakEventListenerBase where T : class where TArgs : EventArgs { private readonly Action> _unregister; public TypedWeakEventListener( T source, Action> register, Action> unregister, Action handler) : base(source, handler) { ArgumentNullException.ThrowIfNull(register); ArgumentNullException.ThrowIfNull(unregister); _unregister = unregister; register(source, HandleEvent!); } protected override void StopListening(T source) { _unregister(source, HandleEvent!); } } internal class PropertyChangedWeakEventListener : WeakEventListenerBase where T : class, INotifyPropertyChanged { public PropertyChangedWeakEventListener(T source, Action handler) : base( source, handler ) { source.PropertyChanged += HandleEvent!; } protected override void StopListening(T source) { source.PropertyChanged -= HandleEvent!; } } internal class CollectionChangedWeakEventListener : WeakEventListenerBase where T : class, INotifyCollectionChanged { public CollectionChangedWeakEventListener(T source, Action handler) : base( source, handler ) { source.CollectionChanged += HandleEvent!; } protected override void StopListening(T source) { source.CollectionChanged -= HandleEvent!; } } internal class WeakEventListener : WeakEventListenerBase where T : class where TArgs : EventArgs { private readonly EventInfo _eventInfo; public WeakEventListener(T source, string eventName, Action handler) : base(source, handler) { _eventInfo = source.GetType().GetEvent(eventName) ?? throw new ArgumentException("Unknown Event Name", nameof(eventName)); if (_eventInfo.EventHandlerType == typeof(EventHandler)) { _eventInfo.AddEventHandler(source, new EventHandler(HandleEvent!)); } else //the event type isn't just an EventHandler<> so we have to create the delegate using reflection { _eventInfo.AddEventHandler( source, Delegate.CreateDelegate(_eventInfo.EventHandlerType!, this, nameof(HandleEvent)) ); } } protected override void StopListening(T source) { if (_eventInfo.EventHandlerType == typeof(EventHandler)) { _eventInfo.RemoveEventHandler(source, new EventHandler(HandleEvent!)); } else { _eventInfo.RemoveEventHandler( source, Delegate.CreateDelegate(_eventInfo.EventHandlerType!, this, nameof(HandleEvent)) ); } } } ================================================ FILE: src/MvvmBlazor.Core/Internal/WeakEventListener/WeakEventManager.cs ================================================ namespace MvvmBlazor.Internal.WeakEventListener; internal interface IWeakEventManager { /// /// Registers the given delegate as a handler for the event specified by `eventName` on the given source. /// void AddWeakEventListener(T source, string eventName, Action handler) where T : class where TArgs : EventArgs; /// /// Registers the given delegate as a handler for the INotifyPropertyChanged.PropertyChanged event /// void AddWeakEventListener(T source, Action handler) where T : class, INotifyPropertyChanged; /// /// Registers the given delegate as a handler for the INotifyCollectionChanged.CollectionChanged event /// void AddWeakEventListener(T source, Action handler) where T : class, INotifyCollectionChanged; /// /// Unregisters any previously registered weak event handlers on the given source object /// void RemoveWeakEventListener(T source) where T : class; /// /// Unregisters all weak event listeners that have been registered by this weak event manager instance /// void ClearWeakEventListeners(); } public class WeakEventManager : IWeakEventManager { private readonly Dictionary _listeners = new(); /// /// Registers the given delegate as a handler for the event specified by `eventName` on the given source. /// public void AddWeakEventListener(T source, string eventName, Action handler) where T : class where TArgs : EventArgs { ArgumentNullException.ThrowIfNull(source); _listeners.Add(new WeakEventListener(source, eventName, handler), handler); } /// /// Registers the given delegate as a handler for the INotifyPropertyChanged.PropertyChanged event /// public void AddWeakEventListener(T source, Action handler) where T : class, INotifyPropertyChanged { ArgumentNullException.ThrowIfNull(source); _listeners.Add(new PropertyChangedWeakEventListener(source, handler), handler); } /// /// Registers the given delegate as a handler for the INotifyCollectionChanged.CollectionChanged event /// public void AddWeakEventListener(T source, Action handler) where T : class, INotifyCollectionChanged { ArgumentNullException.ThrowIfNull(source); _listeners.Add(new CollectionChangedWeakEventListener(source, handler), handler); } /// /// Unregisters any previously registered weak event handlers on the given source object /// public void RemoveWeakEventListener(T source) where T : class { ArgumentNullException.ThrowIfNull(source); var toRemove = new List(); foreach (var listener in _listeners.Keys) if (!listener.IsAlive) { toRemove.Add(listener); } else if (listener.Source == source) { listener.StopListening(); toRemove.Add(listener); } foreach (var item in toRemove) _listeners.Remove(item); } /// /// Unregisters all weak event listeners that have been registered by this weak event manager instance /// public void ClearWeakEventListeners() { foreach (var listener in _listeners.Keys) if (listener.IsAlive) { listener.StopListening(); } _listeners.Clear(); } } ================================================ FILE: src/MvvmBlazor.Core/MvvmBlazor.Core.csproj ================================================  net6.0 true $(BaseIntermediateOutputPath)\GeneratedFiles MvvmBlazor true false MIT Christian Klemm A lightweight Blazor Mvvm Library $(GITHUB_SERVER_URL)/$(GITHUB_REPOSITORY) $(GITHUB_SERVER_URL)/$(GITHUB_REPOSITORY) git Blazor;Mvvm true true true all runtime; build; native; contentfiles; analyzers ================================================ FILE: src/MvvmBlazor.Core/NotifyAttribute.cs ================================================ // ReSharper disable once CheckNamespace namespace MvvmBlazor; [AttributeUsage(AttributeTargets.Field)] public sealed class NotifyAttribute : Attribute { } ================================================ FILE: src/MvvmBlazor.Core/Properties/AssemblyInfo.cs ================================================ [assembly: InternalsVisibleTo("MvvmBlazor.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] [assembly: CLSCompliant(false)] ================================================ FILE: src/MvvmBlazor.Core/Properties/GlobalSuppressions.cs ================================================ // This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters")] ================================================ FILE: src/MvvmBlazor.Core/ViewModel/ViewModelBase.cs ================================================ namespace MvvmBlazor.ViewModel; public abstract class ViewModelBase : INotifyPropertyChanged { private readonly Dictionary>> _subscriptions = new(); public IServiceProvider RootServiceProvider { get; set; } = null!; public event PropertyChangedEventHandler? PropertyChanged; protected bool Set(ref T field, T value, [CallerMemberName] string? propertyName = null) { return Set(ref field, value, EqualityComparer.Default, propertyName); } protected bool Set(ref T field, T value, IEqualityComparer equalityComparer, [CallerMemberName] string? propertyName = null) { ArgumentNullException.ThrowIfNull(equalityComparer); if (!equalityComparer.Equals(field, value)) { field = value; OnPropertyChanged(propertyName!); if (!_subscriptions.ContainsKey(propertyName!)) { return true; } foreach (var action in _subscriptions[propertyName!]) action(value!); return true; } return false; } public virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) { ArgumentNullException.ThrowIfNull(propertyName); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected void Subscribe(Expression>? expression, Action action) { SubscribeAsync( expression, arg => { action(arg); return Task.CompletedTask; } ); } protected void SubscribeAsync(Expression>? property, Func func) { if (property is null) { throw new BindingException("Property cannot be null"); } if (!(property.Body is MemberExpression m)) { throw new BindingException("Subscription member must be a property"); } if (!(m.Member is PropertyInfo propertyInfo)) { throw new BindingException("Subscription member must be a property"); } var propertyName = propertyInfo.Name; if (!_subscriptions.ContainsKey(propertyName)) { _subscriptions[propertyName] = new List>(); } _subscriptions[propertyName].Add(async value => await func((T)value).ConfigureAwait(false)); } #region Lifecycle Methods /// /// Method invoked when the component is ready to start, having received its /// initial parameters from its parent in the render tree. /// public virtual void OnInitialized() { } /// /// Method invoked when the component is ready to start, having received its /// initial parameters from its parent in the render tree. /// Override this method if you will perform an asynchronous operation and /// want the component to refresh when that operation is completed. /// /// A representing any asynchronous operation. public virtual Task OnInitializedAsync() { return Task.CompletedTask; } /// /// Method invoked when the component has received parameters from its parent in /// the render tree, and the incoming values have been assigned to properties. /// public virtual void OnParametersSet() { } /// /// Method invoked when the component has received parameters from its parent in /// the render tree, and the incoming values have been assigned to properties. /// /// A representing any asynchronous operation. public virtual Task OnParametersSetAsync() { return Task.CompletedTask; } /// /// Notifies the component that its state has changed. When applicable, this will /// cause the component to be re-rendered. /// protected void StateHasChanged() { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null)); } /// /// Returns a flag to indicate whether the component should render. /// /// public virtual bool ShouldRender() { return true; } /// /// Method invoked after each time the component has been rendered. /// /// /// Set to true if this is the first time /// has been invoked /// on this component instance; otherwise false. /// /// /// The and /// lifecycle methods /// are useful for performing interop, or interacting with values received from @ref. /// Use the parameter to ensure that initialization work is only performed /// once. /// public virtual void OnAfterRender(bool firstRender) { } /// /// Method invoked after each time the component has been rendered. Note that the component does /// not automatically re-render after the completion of any returned , /// because /// that would cause an infinite render loop. /// /// /// Set to true if this is the first time /// has been invoked /// on this component instance; otherwise false. /// /// A representing any asynchronous operation. /// /// The and /// lifecycle methods /// are useful for performing interop, or interacting with values received from @ref. /// Use the parameter to ensure that initialization work is only performed /// once. /// public virtual Task OnAfterRenderAsync(bool firstRender) { return Task.CompletedTask; } /// /// Sets parameters supplied by the component's parent in the render tree. /// /// The parameters. /// /// A that completes when the component has finished updating and /// rendering itself. /// /// /// /// The /// /// method should be passed the entire set of parameter values each /// time /// /// is called. It not required that the caller supply a parameter /// value for all parameters that are logically understood by the component. /// /// /// The default implementation of /// /// will set the value of each property /// decorated with or /// that has /// a corresponding value in the . Parameters that do /// not have a corresponding value /// will be unchanged. /// /// public virtual Task SetParametersAsync(ParameterView parameters) { return Task.CompletedTask; } #endregion } ================================================ FILE: src/MvvmBlazor.Tests/.editorconfig ================================================ [*.cs] dotnet_diagnostic.CA1707.severity = none dotnet_diagnostic.CA1812.severity = none ================================================ FILE: src/MvvmBlazor.Tests/Abstractions/StrictMock.cs ================================================ namespace MvvmBlazor.Tests.Abstractions; internal class StrictMock : Mock where T : class { public StrictMock() : base(MockBehavior.Strict) { } public StrictMock(params object[] args) : base(MockBehavior.Strict, args) { } public StrictMock(Expression> newExpression) : base(newExpression, MockBehavior.Strict) { } } ================================================ FILE: src/MvvmBlazor.Tests/Abstractions/UnitTest.cs ================================================ namespace MvvmBlazor.Tests.Abstractions; public abstract class UnitTest : IDisposable { private readonly ServiceProvider _services; protected IServiceProvider Services => _services; protected UnitTest(ITestOutputHelper outputHelper) { _services = BuildProvider(outputHelper); } private ServiceProvider BuildProvider(ITestOutputHelper testOutputHelper) { var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(testOutputHelper); RegisterServices(serviceCollection); return serviceCollection.BuildServiceProvider(); } protected virtual void RegisterServices(IServiceCollection services) { } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { _services.Dispose(); } } } ================================================ FILE: src/MvvmBlazor.Tests/Components/MvvmComponentBaseTTests.cs ================================================ namespace MvvmBlazor.Tests.Components; public class MvvmComponentBaseTTests : UnitTest { public MvvmComponentBaseTTests(ITestOutputHelper outputHelper) : base(outputHelper) { } protected override void RegisterServices(IServiceCollection services) { var binder = services.StrictMock(); services.StrictMock(); services.StrictMock(); services.AddSingleton(); services.AddSingleton(); binder.SetupSet(x => x.ValueChangedCallback = It.IsAny>()).Verifiable(); } [Fact] public void AfterRender_called_On_binding_context() { var viewModel = Services.GetMock(); viewModel.Setup(x => x.OnAfterRender(true)).Verifiable(); var component = Services.GetRequiredService(); component.AfterRender(true); viewModel.Verify(); } [Fact] public async Task AfterRenderAsync_called_on_binding_context() { var task = Task.CompletedTask; var viewModel = Services.GetMock(); var component = Services.GetRequiredService(); viewModel.Setup(x => x.OnAfterRenderAsync(It.IsAny())).Returns(task).Verifiable(); await component.AfterRenderAsync(true); viewModel.Verify(); } [Fact] public void Bind_binds_binding_context() { var viewModel = Services.GetRequiredService(); var binder = Services.GetMock(); binder.Setup(x => x.Bind(viewModel, x => x.TestProperty)); var component = new Mock>(Services.GetRequiredService()); component.Setup(x => x.AddBinding(viewModel, x => x.TestProperty)).Verifiable(); component.Object.Bind(x => x.TestProperty); component.Verify(); component.VerifyNoOtherCalls(); } [Fact] public void OnInitialized_called_on_binding_context() { var viewModel = Services.GetMock(); var viewModelParameterSetter = Services.GetMock(); var component = Services.GetRequiredService(); viewModel.Setup(x => x.OnInitialized()).Verifiable(); viewModelParameterSetter.Setup(x => x.ResolveAndSet(component, viewModel.Object)).Verifiable(); component.Initialized(); viewModel.Verify(); } [Fact] public async Task OnInitializedAsync_called_on_binding_context() { var task = Task.CompletedTask; var viewModel = Services.GetMock(); var component = Services.GetRequiredService(); viewModel.Setup(x => x.OnInitializedAsync()).Returns(task).Verifiable(); await component.InitializedAsync(); viewModel.Verify(); } [Fact] public void OnParametersSet_sets_viewmodel_parameters() { var viewModel = Services.GetMock(); var viewModelParameterSetter = Services.GetMock(); var component = Services.GetRequiredService(); viewModel.Setup(x => x.OnParametersSet()).Verifiable(); viewModelParameterSetter.Setup(x => x.ResolveAndSet(component, viewModel.Object)).Verifiable(); component.ParametersSet(); viewModelParameterSetter.Verify(); } [Fact] public async Task OnParametersSetAsync_called_on_binding_context() { var task = Task.CompletedTask; var viewModel = Services.GetMock(); var component = Services.GetRequiredService(); viewModel.Setup(x => x.OnParametersSetAsync()).Returns(task).Verifiable(); await component.ParametersSetAsync(); viewModel.Verify(); } [Fact] public void Sets_binding_context() { var viewModel = Services.GetMock(); var component = Services.GetRequiredService(); component.Context.ShouldBe(viewModel.Object); } [Fact] public void ShouldRender_called_on_binding_context() { var viewModel = Services.GetMock(); var component = Services.GetRequiredService(); viewModel.Setup(x => x.ShouldRender()).Returns(true).Verifiable(); var res = component.Render(); res.ShouldBe(true); viewModel.Verify(); } private class MockMvvmComponentBase : MvvmComponentBase { public ViewModelBase Context => BindingContext; public MockMvvmComponentBase(IServiceProvider services) : base(services) { } public void Initialized() { OnInitialized(); } public Task InitializedAsync() { return OnInitializedAsync(); } public void ParametersSet() { OnParametersSet(); } public Task ParametersSetAsync() { return OnParametersSetAsync(); } public bool Render() { return ShouldRender(); } public void AfterRender(bool firstRender) { OnAfterRender(firstRender); } public Task AfterRenderAsync(bool firstRender) { return OnAfterRenderAsync(firstRender); } } } ================================================ FILE: src/MvvmBlazor.Tests/Components/MvvmComponentBaseTests.cs ================================================ namespace MvvmBlazor.Tests.Components; public class MvvmComponentBaseTests : UnitTest { public MvvmComponentBaseTests(ITestOutputHelper outputHelper) : base(outputHelper) { } protected override void RegisterServices(IServiceCollection services) { var binder = services.StrictMock(); services.AddSingleton(); binder.SetupSet(x => x.ValueChangedCallback = It.IsAny>()).Verifiable(); } [Fact] public void AddBinding_adds_Binding() { var viewModel = new TestViewModel(); var binder = Services.GetMock(); binder.Setup(x => x.Bind(viewModel, y => y.TestProperty)).Returns("Test").Verifiable(); var component = Services.GetRequiredService(); var res = component.AddBinding(viewModel, x => x.TestProperty); res.ShouldBe("Test"); binder.Verify(); } [Fact] public void Bind_adds_Binding() { var viewModel = new TestViewModel(); var component = new Mock(Services.GetRequiredService()); component.Setup(x => x.AddBinding(viewModel, y => y.TestProperty)).Returns("Test").Verifiable(); var res = component.Object.Bind(viewModel, x => x.TestProperty); res.ShouldBe("Test"); component.Verify(); } internal class TestComponent : MvvmComponentBase { public Action? BindingChangedAction { get; set; } public TestComponent(IServiceProvider services) : base(services) { } internal override void BindingOnBindingValueChanged(object sender, EventArgs e) { BindingChangedAction?.Invoke(); } } } ================================================ FILE: src/MvvmBlazor.Tests/Components/TestViewModel.cs ================================================ namespace MvvmBlazor.Tests.Components; public class TestViewModel : ViewModelBase { private string? _testProperty; public string? TestProperty { get => _testProperty; set => Set(ref _testProperty, value); } } ================================================ FILE: src/MvvmBlazor.Tests/Extensions/ServiceCollectionExtensions.cs ================================================ // ReSharper disable once CheckNamespace namespace Microsoft.Extensions.DependencyInjection; internal static class ServiceCollectionExtensions { public static Mock Mock(this IServiceCollection services, params object[] args) where T : class { var mock = new Mock(args); services.AddSingleton(mock); services.AddSingleton(mock.Object); return mock; } public static Mock StrictMock(this IServiceCollection services) where T : class { var mock = new StrictMock(); services.AddSingleton>(mock); services.AddSingleton(mock.Object); return mock; } public static IServiceCollection Provide(this IServiceCollection services) where T : class { return services.AddSingleton(); } } ================================================ FILE: src/MvvmBlazor.Tests/Extensions/ServiceProviderExtensions.cs ================================================ // ReSharper disable once CheckNamespace namespace Microsoft.Extensions.DependencyInjection; internal static class ServiceProviderExtensions { public static Mock GetMock(this IServiceProvider provider) where T : class { return provider.GetRequiredService>(); } } ================================================ FILE: src/MvvmBlazor.Tests/Generators/MvvmComponentGeneratorTests.cs ================================================ using Binder = System.Reflection.Binder; namespace MvvmBlazor.Tests.Generators; public class MvvmComponentGeneratorTests { [Fact] public void Generates_error_when_component_is_not_partial() { var inputCompilation = CreateCompilation( @$" [{nameof(MvvmComponentAttribute)}] public class TestComponent : Microsoft.AspNetCore.Components.ComponentBase {{}} " ); var generator = new MvvmComponentGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); diagnostics.ShouldNotBeEmpty(); diagnostics.First().Id.ShouldBe("MVVMBLAZOR001"); } [Fact] public void Generates_error_when_component_is_not_inheriting() { var inputCompilation = CreateCompilation( @$" [{nameof(MvvmComponentAttribute)}] public partial class TestComponent {{}} " ); var generator = new MvvmComponentGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); diagnostics.ShouldNotBeEmpty(); diagnostics.First().Id.ShouldBe("MVVMBLAZOR002"); } [Fact] public void Generates_error_when_generic_type_parameter_count_is_wrong() { var inputCompilation = CreateCompilation( @$" [{nameof(MvvmComponentAttribute)}] public partial class TestComponent : Microsoft.AspNetCore.Components.ComponentBase {{}} " ); var generator = new MvvmComponentGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); diagnostics.ShouldNotBeEmpty(); diagnostics.First().Id.ShouldBe("MVVMBLAZOR005"); } [Fact] public void Generates_error_when_generic_type_parameter_name_is_wrong() { var inputCompilation = CreateCompilation( @$" [{nameof(MvvmComponentAttribute)}] public partial class TestComponent : Microsoft.AspNetCore.Components.ComponentBase {{}} " ); var generator = new MvvmComponentGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); diagnostics.ShouldNotBeEmpty(); diagnostics.First().Id.ShouldBe("MVVMBLAZOR005"); } [Fact] public void Generates_component_with_component_base_class() { var inputCompilation = CreateCompilation( @$" [{nameof(MvvmComponentAttribute)}] public partial class TestComponent : Microsoft.AspNetCore.Components.ComponentBase {{}} " ); var generator = new MvvmComponentGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); diagnostics.ShouldBeEmpty(); } [Fact] public void Generates_component_with_owning_component_base_class() { var inputCompilation = CreateCompilation( @$" [{nameof(MvvmComponentAttribute)}] public partial class TestComponent : Microsoft.AspNetCore.Components.OwningComponentBase {{}} " ); var generator = new MvvmComponentGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); diagnostics.ShouldBeEmpty(); } [Fact] public void Generates_generic_component() { var inputCompilation = CreateCompilation( @$" [{nameof(MvvmComponentAttribute)}] public partial class TestComponent : Microsoft.AspNetCore.Components.OwningComponentBase {{}} " ); var generator = new MvvmComponentGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); diagnostics.ShouldBeEmpty(); } private static Compilation CreateCompilation(string source) { return CSharpCompilation.Create( "compilation", new[] { CSharpSyntaxTree.ParseText(source) }, new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location), MetadataReference.CreateFromFile(typeof(ComponentBase).GetTypeInfo().Assembly.Location) }, new CSharpCompilationOptions(OutputKind.ConsoleApplication) ); } } ================================================ FILE: src/MvvmBlazor.Tests/Generators/NotifyPropertyChangedGeneratorTests.cs ================================================ namespace MvvmBlazor.Tests.Generators; public class NotifyPropertyChangedGeneratorTests { [Fact] public void Generates_error_when_viewmodel_is_not_partial() { var inputCompilation = CreateCompilation( @$" public class TestViewModel : MvvmBlazor.ViewModel.ViewModelBase {{ [{typeof(NotifyAttribute)}] private bool _test; }} " ); var generator = new NotifyPropertyChangedGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); diagnostics.ShouldNotBeEmpty(); diagnostics.First().Id.ShouldBe("MVVMBLAZOR003"); } [Fact] public void Generates_error_when_view_model_is_not_inheriting() { var inputCompilation = CreateCompilation( @$" public partial class TestViewModel {{ [{typeof(NotifyAttribute)}] private bool _test; }} " ); var generator = new NotifyPropertyChangedGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); diagnostics.ShouldNotBeEmpty(); diagnostics.First().Id.ShouldBe("MVVMBLAZOR004"); } [Fact] public void Generates_viewmodel() { var inputCompilation = CreateCompilation( @$" public partial class TestViewModel : MvvmBlazor.ViewModel.ViewModelBase {{ [{typeof(NotifyAttribute)}] private bool _test; public TestViewModel() {{ Test = true; }} }} " ); var generator = new NotifyPropertyChangedGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); diagnostics.ShouldBeEmpty(); } [Fact] public void Generates_viewmodel_for_abstract_class() { var inputCompilation = CreateCompilation( @$" public abstract partial class TestViewModel : MvvmBlazor.ViewModel.ViewModelBase {{ [{typeof(NotifyAttribute)}] private bool _test; }} public TestViewModel() {{ Test = true; }} " ); var generator = new NotifyPropertyChangedGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); diagnostics.ShouldBeEmpty(); } [Fact] public void Generates_viewmodel_for_abstract_class_with_inheritance() { var inputCompilation = CreateCompilation( @$" public abstract partial class BaseViewModel : MvvmBlazor.ViewModel.ViewModelBase {{ [{typeof(NotifyAttribute)}] private bool _foo; }} public abstract partial class TestViewModel : BaseViewModel {{ [{typeof(NotifyAttribute)}] private bool _test; }} public TestViewModel() {{ Test = true; Foo = true; }} " ); var generator = new NotifyPropertyChangedGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); diagnostics.ShouldBeEmpty(); } [Fact] public void Generates_viewmodel_for_generic_class() { var inputCompilation = CreateCompilation( @$" public abstract partial class TestViewModel : MvvmBlazor.ViewModel.ViewModelBase {{ [{typeof(NotifyAttribute)}] private bool _test; }} public TestViewModel() {{ Test = true; }} " ); var generator = new NotifyPropertyChangedGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); diagnostics.ShouldBeEmpty(); } [Fact] public void Generates_viewmodel_for_abstract_generic_class() { var inputCompilation = CreateCompilation( @$" public abstract partial class TestViewModel : MvvmBlazor.ViewModel.ViewModelBase {{ [{typeof(NotifyAttribute)}] private bool _test; }} public TestViewModel() {{ Test = true; }} " ); var generator = new NotifyPropertyChangedGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); diagnostics.ShouldBeEmpty(); } private static Compilation CreateCompilation(string source) { return CSharpCompilation.Create( "compilation", new[] { CSharpSyntaxTree.ParseText(source) }, new[] { MetadataReference.CreateFromFile(typeof(MvvmComponentBase).GetTypeInfo().Assembly.Location) }, new CSharpCompilationOptions(OutputKind.ConsoleApplication) ); } } ================================================ FILE: src/MvvmBlazor.Tests/GlobalUsings.cs ================================================ global using System; global using Microsoft.Extensions.DependencyInjection; global using Moq; global using MvvmBlazor.Components; global using MvvmBlazor.Internal.Bindings; global using MvvmBlazor.Tests.Abstractions; global using Shouldly; global using Xunit; global using Xunit.Abstractions; global using System.Linq; global using System.Reflection; global using Microsoft.AspNetCore.Components; global using Microsoft.CodeAnalysis; global using Microsoft.CodeAnalysis.CSharp; global using System.Threading.Tasks; global using MvvmBlazor.Internal.Parameters; global using MvvmBlazor.ViewModel; global using System.Linq.Expressions; global using System.Collections.Specialized; global using System.ComponentModel; global using MvvmBlazor.Internal.WeakEventListener; global using MvvmBlazor.CodeGenerators.Components; global using MvvmBlazor.CodeGenerators.NotifyPropertyChanged; ================================================ FILE: src/MvvmBlazor.Tests/Internal/Bindings/BinderTests.cs ================================================ using Binder = MvvmBlazor.Internal.Bindings.Binder; namespace MvvmBlazor.Tests.Internal.Bindings; public class BinderTests : UnitTest { public BinderTests(ITestOutputHelper outputHelper) : base(outputHelper) { } protected override void RegisterServices(IServiceCollection services) { services.StrictMock(); services.StrictMock(); services.Provide(); services.Provide(); } [Fact] public void Bind_throws_when_callback_is_null() { var viewModel = Services.GetRequiredService(); var binder = Services.GetRequiredService(); Should.Throw(() => binder.Bind(viewModel, x => x.Test)); } [Fact] public void Bind_throws_when_view_model_is_null() { var binder = Services.GetRequiredService(); var callback = (IBinding b, EventArgs a) => { }; binder.ValueChangedCallback = callback; Should.Throw(() => binder.Bind(null!, x => x.Test!)); } [Fact] public void Bind_throws_when_property_expression_is_null() { var viewModel = Services.GetRequiredService(); var binder = Services.GetRequiredService(); var callback = (IBinding b, EventArgs a) => { }; binder.ValueChangedCallback = callback; Should.Throw(() => binder.Bind(viewModel, null!)); } [Fact] public void Bind_throws_when_property_expression_is_not_property() { var viewModel = Services.GetRequiredService(); var binder = Services.GetRequiredService(); var callback = (IBinding b, EventArgs a) => { }; binder.ValueChangedCallback = callback; Should.Throw(() => binder.Bind(viewModel, x => x.PublicField)); } [Fact] public void Bind_binds_property() { var bindingFactory = Services.GetMock(); var weakEventManager = Services.GetMock(); var viewModel = Services.GetRequiredService(); var binding = new Mock(); bindingFactory.Setup(x => x.Create(viewModel, It.IsAny(), weakEventManager.Object)) .Returns(binding.Object) .Verifiable(); binding.Setup(x => x.GetValue()).Returns("test").Verifiable(); weakEventManager.Setup( x => x.AddWeakEventListener( binding.Object, nameof(Binding.BindingValueChanged), It.IsAny>() ) ) .Verifiable(); weakEventManager.Setup(x => x.RemoveWeakEventListener(binding.Object)); var callback = (IBinding b, EventArgs a) => { }; var binder = Services.GetRequiredService(); binder.ValueChangedCallback = callback; var res = binder.Bind(viewModel, x => x.Test); res.ShouldBe("test"); bindingFactory.Verify(); binding.Verify(); weakEventManager.Verify(); } [Fact] public void Bind_binds_property_exactly_once() { var bindingFactory = Services.GetMock(); var weakEventManager = Services.GetMock(); var viewModel = Services.GetRequiredService(); var binding = new Mock(); bindingFactory.Setup(x => x.Create(viewModel, It.IsAny(), weakEventManager.Object)) .Returns(binding.Object) .Verifiable(); binding.Setup(x => x.GetValue()).Returns("test").Verifiable(); weakEventManager.Setup( x => x.AddWeakEventListener( binding.Object, nameof(Binding.BindingValueChanged), It.IsAny>() ) ) .Verifiable(); weakEventManager.Setup(x => x.RemoveWeakEventListener(binding.Object)); var callback = (IBinding b, EventArgs a) => { }; var binder = Services.GetRequiredService(); binder.ValueChangedCallback = callback; var res = binder.Bind(viewModel, x => x.Test); res.ShouldBe("test"); res = binder.Bind(viewModel, x => x.Test); res.ShouldBe("test"); weakEventManager.Verify( x => x.AddWeakEventListener( binding.Object, nameof(Binding.BindingValueChanged), It.IsAny>() ), Times.Once ); binding.Verify(); bindingFactory.Verify(); weakEventManager.Verify(); } private class TestViewModel : ViewModelBase { #pragma warning disable CA1051 #pragma warning disable CS0649 public string? PublicField; #pragma warning restore CS0649 #pragma warning restore CA1051 public string? Test { get; set; } } } ================================================ FILE: src/MvvmBlazor.Tests/Internal/Bindings/BindingFactoryTests.cs ================================================ namespace MvvmBlazor.Tests.Internal.Bindings; public class BindingFactoryTests { [Fact] public void Create_returns_binding() { var source = new Mock(); var propertyInfo = new Mock(); var wem = new Mock(); var factory = new BindingFactory(); using var res = factory.Create(source.Object, propertyInfo.Object, wem.Object); res.ShouldNotBeNull(); res.PropertyInfo.ShouldBe(propertyInfo.Object); res.Source.ShouldBe(source.Object); } } ================================================ FILE: src/MvvmBlazor.Tests/Internal/Bindings/BindingTests.cs ================================================ using System.Collections.ObjectModel; namespace MvvmBlazor.Tests.Internal.Bindings; public class BindingTests { [Fact] public void Adds_collection_event_listener_when_initializing() { const string propertyName = "propertyName"; var source = new Mock(); var collection = new Mock(); var wem = new Mock(); var propertyInfo = new Mock(); propertyInfo.Setup(x => x.Name).Returns(propertyName); propertyInfo.Setup(x => x.PropertyType).Returns(typeof(INotifyCollectionChanged)); propertyInfo.SetupSequence(x => x.GetValue(It.IsAny(), null)) .Returns(collection.Object); using var binding = new Binding(source.Object, propertyInfo.Object, wem.Object); binding.Initialize(); wem.Verify( x => x.AddWeakEventListener( It.IsAny(), It.IsAny>() ) ); } [Fact] public void Adds_collection_event_listener_when_property_changes_to_not_null() { const string propertyName = "propertyName"; var source = new Mock(); var collection = new Mock(); var wem = new Mock(); var propertyInfo = new Mock(); propertyInfo.Setup(x => x.Name).Returns(propertyName); propertyInfo.Setup(x => x.PropertyType).Returns(typeof(INotifyCollectionChanged)); wem.Setup( x => x.AddWeakEventListener( It.IsAny(), It.IsAny>() ) ) .Callback>( (sender, action) => sender.PropertyChanged += (s, a) => action((INotifyPropertyChanged)s!, a) ); propertyInfo.Setup(x => x.GetValue(It.IsAny(), null)).Returns(collection.Object); using var binding = new Binding(source.Object, propertyInfo.Object, wem.Object); binding.Initialize(); source.Raise(x => x.PropertyChanged += null, new PropertyChangedEventArgs(propertyName)); wem.Verify( x => x.AddWeakEventListener( It.IsAny(), It.IsAny>() ) ); } [Fact] public void Dispose_removes_collection_listener() { const string propertyName = "propertyName"; var source = new Mock(); var collection = new Mock(); var wem = new Mock(); var propertyInfo = new Mock(); propertyInfo.Setup(x => x.Name).Returns(propertyName); propertyInfo.Setup(x => x.PropertyType).Returns(typeof(INotifyCollectionChanged)); wem.Setup( x => x.AddWeakEventListener( It.IsAny(), It.IsAny>() ) ) .Callback>( (sender, action) => sender.PropertyChanged += (s, a) => action((INotifyPropertyChanged)s!, a) ); propertyInfo.SetupSequence(x => x.GetValue(It.IsAny(), null)) .Returns(collection.Object) .Returns((object?)null); using (var binding = new Binding(source.Object, propertyInfo.Object, wem.Object)) { binding.Initialize(); } wem.Verify(x => x.RemoveWeakEventListener(collection.Object)); } [Fact] public void Dispose_removes_event_listener() { const string propertyName = "propertyName"; var source = new Mock(); var collection = new Mock(); var wem = new Mock(); var propertyInfo = new Mock(); propertyInfo.Setup(x => x.Name).Returns(propertyName); propertyInfo.Setup(x => x.PropertyType).Returns(typeof(INotifyCollectionChanged)); wem.Setup( x => x.AddWeakEventListener( It.IsAny(), It.IsAny>() ) ) .Callback>( (sender, action) => sender.PropertyChanged += (s, a) => action((INotifyPropertyChanged)s!, a) ); propertyInfo.Setup(x => x.GetValue(It.IsAny(), null)).Returns(collection.Object); using (var binding = new Binding(source.Object, propertyInfo.Object, wem.Object)) { binding.Initialize(); } wem.Verify(x => x.RemoveWeakEventListener(source.Object)); } [Fact] public void Equals_not_different_source_and_property() { const string propertyName = "propertyName"; var source1 = new Mock(); var source2 = new Mock(); var propertyInfo1 = new Mock(); propertyInfo1.Setup(x => x.Name).Returns(propertyName); var wem = new Mock(); var propertyInfo2 = new Mock(); propertyInfo2.Setup(x => x.Name).Returns(propertyName + "Foo"); using var binding1 = new Binding(source1.Object, propertyInfo1.Object, wem.Object); using var binding2 = new Binding(source2.Object, propertyInfo2.Object, wem.Object); binding1.GetHashCode().ShouldNotBe(binding2.GetHashCode()); binding1.Equals(binding2).ShouldBeFalse(); } [Fact] public void Equals_same_source_and_property() { const string propertyName = "propertyName"; var source = new Mock(); var wem = new Mock(); var propertyInfo = new Mock(); propertyInfo.Setup(x => x.Name).Returns(propertyName); using var binding1 = new Binding(source.Object, propertyInfo.Object, wem.Object); using var binding2 = new Binding(source.Object, propertyInfo.Object, wem.Object); binding1.GetHashCode().ShouldBe(binding2.GetHashCode()); binding1.Equals(binding2).ShouldBeTrue(); } [Fact] public void Ignores_PropertyChangedEvent_when_property_name_does_not_match() { const string propertyName = "propertyName"; var source = new Mock(); var collection = new Mock(); var wem = new Mock(); var propertyInfo = new Mock(); propertyInfo.Setup(x => x.Name).Returns(propertyName); propertyInfo.Setup(x => x.PropertyType).Returns(typeof(INotifyCollectionChanged)); wem.Setup( x => x.AddWeakEventListener( It.IsAny(), It.IsAny>() ) ) .Callback>( (sender, action) => sender.PropertyChanged += (s, a) => action((INotifyPropertyChanged)s!, a) ); propertyInfo.Setup(x => x.GetValue(It.IsAny(), null)).Returns(collection.Object); var bindingValueChangedRaised = false; using var binding = new Binding(source.Object, propertyInfo.Object, wem.Object); binding.Initialize(); binding.BindingValueChanged += (s, e) => bindingValueChangedRaised = true; source.Raise(x => x.PropertyChanged += null, new PropertyChangedEventArgs("foo")); bindingValueChangedRaised.ShouldBeFalse(); } [Fact] public void Does_not_raise_BindingValueChanged_when_uninitialized() { const string propertyName = "propertyName"; var source = new Mock(); var wem = new Mock(); var propertyInfo = new Mock(); propertyInfo.Setup(x => x.Name).Returns(propertyName); var hasChanged = false; using var binding = new Binding(source.Object, propertyInfo.Object, wem.Object); binding.BindingValueChanged += (sender, args) => { hasChanged = true; }; source.Raise(x => x.PropertyChanged += null, new PropertyChangedEventArgs(propertyName)); hasChanged.ShouldBeFalse(); } [Fact] public void Raises_BindingValueChanged_when_collection_changed() { const string propertyName = "propertyName"; var source = new Mock(); var collection = new Mock>(); var wem = new Mock(); var propertyInfo = new Mock(); propertyInfo.Setup(x => x.Name).Returns(propertyName); propertyInfo.Setup(x => x.PropertyType).Returns(typeof(INotifyCollectionChanged)); propertyInfo.Setup(x => x.GetValue(It.IsAny(), It.IsAny())).Returns(collection.Object); wem.Setup( x => x.AddWeakEventListener( It.IsAny(), It.IsAny>() ) ) .Callback>( (sender, action) => sender.CollectionChanged += (s, a) => action((INotifyCollectionChanged)s!, a) ); var hasChanged = false; using var binding = new Binding(source.Object, propertyInfo.Object, wem.Object); binding.Initialize(); binding.BindingValueChanged += (sender, args) => { sender.ShouldBe(binding); args.ShouldBe(EventArgs.Empty); hasChanged = true; }; collection.Raise( x => x.CollectionChanged += null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List { new() }) ); hasChanged.ShouldBeTrue(); } [Fact] public void Raises_BindingValueChanged_when_property_changed() { const string propertyName = "propertyName"; var source = new Mock(); var wem = new Mock(); var propertyInfo = new Mock(); propertyInfo.Setup(x => x.Name).Returns(propertyName); wem.Setup( x => x.AddWeakEventListener( It.IsAny(), It.IsAny>() ) ) .Callback>( (sender, action) => sender.PropertyChanged += (s, a) => action((INotifyPropertyChanged)s!, a) ); var hasChanged = false; using var binding = new Binding(source.Object, propertyInfo.Object, wem.Object); binding.Initialize(); binding.BindingValueChanged += (sender, args) => { sender.ShouldBe(binding); args.ShouldBe(EventArgs.Empty); hasChanged = true; }; source.Raise(x => x.PropertyChanged += null, new PropertyChangedEventArgs(propertyName)); hasChanged.ShouldBeTrue(); } [Fact] public void Removes_collection_event_listener_when_property_changes_to_null() { const string propertyName = "propertyName"; var source = new Mock(); var collection = new Mock(); var wem = new Mock(); var propertyInfo = new Mock(); propertyInfo.Setup(x => x.Name).Returns(propertyName); propertyInfo.Setup(x => x.PropertyType).Returns(typeof(INotifyCollectionChanged)); wem.Setup( x => x.AddWeakEventListener( It.IsAny(), It.IsAny>() ) ) .Callback>( (sender, action) => sender.PropertyChanged += (s, a) => action((INotifyPropertyChanged)s!, a) ); propertyInfo.SetupSequence(x => x.GetValue(It.IsAny(), null)) .Returns(collection.Object) .Returns((object?)null); using var binding = new Binding(source.Object, propertyInfo.Object, wem.Object); binding.Initialize(); source.Raise(x => x.PropertyChanged += null, new PropertyChangedEventArgs(propertyName)); wem.Verify(x => x.RemoveWeakEventListener(It.IsAny())); } [Fact] public void Should_not_raise_BindingValueChanged_on_collection_when_uninitialized() { const string propertyName = "propertyName"; var source = new Mock(); var collection = new Mock>(); var wem = new Mock(); var propertyInfo = new Mock(); propertyInfo.Setup(x => x.Name).Returns(propertyName); propertyInfo.Setup(x => x.PropertyType).Returns(typeof(INotifyCollectionChanged)); propertyInfo.Setup(x => x.GetValue(It.IsAny(), It.IsAny())).Returns(collection.Object); var hasChanged = false; using var binding = new Binding(source.Object, propertyInfo.Object, wem.Object); binding.BindingValueChanged += (_, __) => hasChanged = true; collection.Raise( x => x.CollectionChanged += null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List { new() }) ); hasChanged.ShouldBeFalse(); } } ================================================ FILE: src/MvvmBlazor.Tests/Internal/Parameters/ParameterCacheTests.cs ================================================ using ParameterInfo = MvvmBlazor.Internal.Parameters.ParameterInfo; namespace MvvmBlazor.Tests.Internal.Parameters; public class ParameterCacheTests { [Fact] public void Get_gets_cached_entry() { var type = typeof(ParameterCacheTests); var parameterInfo = new ParameterInfo(new List(), new List()); var cache = new ParameterCache(); cache.Set(type, parameterInfo); cache.Get(type).ShouldBeSameAs(parameterInfo); } } ================================================ FILE: src/MvvmBlazor.Tests/Internal/Parameters/ParameterInfoTests.cs ================================================ using ParameterInfo = MvvmBlazor.Internal.Parameters.ParameterInfo; namespace MvvmBlazor.Tests.Internal.Parameters; public class ParameterInfoTests { private static Mock GenerateProperty(string propertyName) { var property = new StrictMock(); property.SetupGet(x => x.Name).Returns(propertyName); property.SetupGet(x => x.DeclaringType).Returns(typeof(ParameterInfoTests)); property.Setup(x => x.GetHashCode()).Returns(propertyName.GetHashCode(StringComparison.OrdinalIgnoreCase)); property.Setup(x => x.Equals(It.IsAny())).Returns((obj) => obj is PropertyInfo p && p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)); return property; } [Fact] public void Ignores_missing_property_on_viewmodel() { var p1 = GenerateProperty("p1"); var p2 = GenerateProperty("p2"); var componentProperties = new List { p1.Object, p2.Object }; var vmp1 = GenerateProperty("p1"); var viewModelProperties = new List { vmp1.Object }; var info = new ParameterInfo(componentProperties, viewModelProperties); info.Parameters.ShouldNotBeNull(); info.Parameters.Count.ShouldBe(1); info.Parameters.ElementAt(0).Key.ShouldBe(p1.Object); info.Parameters.ElementAt(0).Value.ShouldBe(vmp1.Object); } [Fact] public void Throws_for_missing_property_on_component() { var p1 = GenerateProperty("p1"); var p2 = GenerateProperty("p2"); var componentProperties = new List { p2.Object }; var vmp1 = GenerateProperty("p1"); var viewModelProperties = new List { vmp1.Object }; Should.Throw(() => new ParameterInfo(componentProperties, viewModelProperties)); } [Fact] public void Sorts_properties() { var p1 = GenerateProperty("p1"); var p2 = GenerateProperty("p2"); var componentProperties = new List { p1.Object, p2.Object }; var vmp1 = GenerateProperty("p1"); var vmp2 = GenerateProperty("p2"); var viewModelProperties = new List { vmp2.Object, vmp1.Object }; var info = new ParameterInfo(componentProperties, viewModelProperties); info.Parameters.ShouldNotBeNull(); info.Parameters.Count.ShouldBe(2); info.Parameters.ElementAt(0).Key.ShouldBe(p1.Object); info.Parameters.ElementAt(0).Value.ShouldBe(vmp1.Object); info.Parameters.ElementAt(1).Key.ShouldBe(p2.Object); info.Parameters.ElementAt(1).Value.ShouldBe(vmp2.Object); } } ================================================ FILE: src/MvvmBlazor.Tests/Internal/Parameters/ParameterResolverTests.cs ================================================ namespace MvvmBlazor.Tests.Internal.Parameters; public class ParameterResolverTests : UnitTest { public ParameterResolverTests(ITestOutputHelper outputHelper) : base(outputHelper) { } protected override void RegisterServices(IServiceCollection services) { services.Mock(); services.Provide(); } [Fact] public void ResolveParameters_ignores_properties_without_attribute() { var resolver = Services.GetRequiredService(); var res = resolver.ResolveParameters(typeof(S3), typeof(S3)); res.Parameters.Count.ShouldBe(1); res.Parameters.ElementAt(0).Key.Name.ShouldBe(nameof(S3.Test)); res.Parameters.ElementAt(0).Key.PropertyType.ShouldBe(typeof(string)); } [Fact] public void ResolveParameters_ignores_properties_without_public_setter() { var resolver = Services.GetRequiredService(); var res = resolver.ResolveParameters(typeof(S4), typeof(S4)); res.Parameters.Count.ShouldBe(1); res.Parameters.ElementAt(0).Key.Name.ShouldBe(nameof(S4.Test)); res.Parameters.ElementAt(0).Key.PropertyType.ShouldBe(typeof(string)); } [Fact] public void ResolveParameters_resolves_multiple_parameters() { var resolver = Services.GetRequiredService(); var res = resolver.ResolveParameters(typeof(S2), typeof(S2)); res.Parameters.Count.ShouldBe(2); res.Parameters.ElementAt(0).Key.Name.ShouldBe(nameof(S2.Foo)); res.Parameters.ElementAt(0).Key.PropertyType.ShouldBe(typeof(int)); res.Parameters.ElementAt(1).Key.Name.ShouldBe(nameof(S2.Test)); res.Parameters.ElementAt(1).Key.PropertyType.ShouldBe(typeof(string)); } [Fact] public void ResolveParameters_resolves_single_parameter() { var resolver = Services.GetRequiredService(); var res = resolver.ResolveParameters(typeof(S1), typeof(S1)); res.Parameters.Count.ShouldBe(1); res.Parameters.ElementAt(0).Key.Name.ShouldBe(nameof(S1.Test)); res.Parameters.ElementAt(0).Key.PropertyType.ShouldBe(typeof(string)); } [Fact] public void ResolveParameters_resolves_cascading_parameter() { var resolver = Services.GetRequiredService(); var res = resolver.ResolveParameters(typeof(S5), typeof(S5)); res.Parameters.Count.ShouldBe(1); res.Parameters.ElementAt(0).Key.Name.ShouldBe(nameof(S5.Test)); res.Parameters.ElementAt(0).Key.PropertyType.ShouldBe(typeof(string)); } private class S1 { [Parameter] public string? Test { get; set; } } private class S2 { [Parameter] public string? Test { get; set; } [Parameter] public int Foo { get; set; } } private class S3 { [Parameter] public string? Test { get; set; } public int Foo { get; set; } } private class S4 { [Parameter] public string? Test { get; set; } public int Foo { get; } public int Doo { get; private set; } public int Boo { get; internal set; } public int Loo { get; protected set; } public int Moo { get; protected internal set; } } private class S5 { [CascadingParameter] public string? Test { get; set; } } } ================================================ FILE: src/MvvmBlazor.Tests/Internal/Parameters/ViewModelParameterSetterTests.cs ================================================ using System.Globalization; using ParameterInfo = MvvmBlazor.Internal.Parameters.ParameterInfo; namespace MvvmBlazor.Tests.Internal.Parameters; public class ViewModelParameterSetterTests : UnitTest { private static readonly StronglyTypedParameter TestParameter = new(); public ViewModelParameterSetterTests(ITestOutputHelper outputHelper) : base(outputHelper) { } protected override void RegisterServices(IServiceCollection services) { services.StrictMock(); services.StrictMock(); services.StrictMock(); services.Provide(); } private static Mock GenerateProperty(string propertyName, Type propertyType) { var property = new Mock(); property.Setup(x => x.Name).Returns(propertyName).Verifiable(); property.SetupGet(x => x.PropertyType).Returns(propertyType).Verifiable(); return property; } [Fact] public void ResolveAndSet_resolves_parameters_of_same_type() { const string propName = "p1"; const string componentValue = "foo"; var cp1 = GenerateProperty(propName, componentValue.GetType()); var componentProperties = new List { cp1.Object }; var vmp1 = GenerateProperty(propName, componentValue.GetType()); var viewModelProperties = new List { vmp1.Object }; var parameterInfo = new ParameterInfo(componentProperties, viewModelProperties); var resolver = Services.GetMock(); var component = Services.GetMock(); var viewModel = Services.GetMock(); resolver.Setup(x => x.ResolveParameters(component.Object.GetType(), viewModel.Object.GetType())) .Returns(parameterInfo) .Verifiable(); cp1.Setup(x => x.GetValue(component.Object, null)).Returns(componentValue).Verifiable(); vmp1.Setup(x => x.SetValue(viewModel.Object, componentValue, null)).Verifiable(); var setter = Services.GetRequiredService(); setter.ResolveAndSet(component.Object, viewModel.Object); cp1.Verify(); vmp1.Verify(); resolver.Verify(); } [Fact] public void ResolveAndSet_resolves_parameters_of_convertible_types() { const string propName = "p1"; const int componentValue = 42; var cp1 = GenerateProperty(propName, componentValue.GetType()); var componentProperties = new List { cp1.Object }; var vmp1 = GenerateProperty(propName, typeof(StronglyTypedParameter)); var viewModelProperties = new List { vmp1.Object }; var parameterInfo = new ParameterInfo(componentProperties, viewModelProperties); var resolver = Services.GetMock(); var component = Services.GetMock(); var viewModel = Services.GetMock(); resolver.Setup(x => x.ResolveParameters(component.Object.GetType(), viewModel.Object.GetType())) .Returns(parameterInfo) .Verifiable(); cp1.Setup(x => x.GetValue(component.Object, null)).Returns(componentValue).Verifiable(); vmp1.Setup(x => x.SetValue(viewModel.Object, TestParameter, null)).Verifiable(); var setter = Services.GetRequiredService(); setter.ResolveAndSet(component.Object, viewModel.Object); cp1.Verify(); vmp1.Verify(); resolver.Verify(); } [TypeConverter(typeof(StronglyTypedParameterConverter))] private class StronglyTypedParameter { } private class StronglyTypedParameterConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(int); } public override object ConvertTo( ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) { return TestParameter; } } } ================================================ FILE: src/MvvmBlazor.Tests/Internal/WeakEventListener/WeakEventListenerTests.cs ================================================ namespace MvvmBlazor.Tests.Internal.WeakEventListener; public class WeakEventManagerTests { [Fact] public void AddWeakEventListener_collection_fires_event() { var collectionAddObject = new object(); var source = new Mock(); var invocations = 0; var wem = new WeakEventManager(); wem.AddWeakEventListener( source.Object, (s, a) => { s.ShouldBe(source.Object); a.Action.ShouldBe(NotifyCollectionChangedAction.Add); a.NewItems.ShouldNotBeNull(); a.NewItems.Count.ShouldBe(1); a.NewItems[0].ShouldBe(collectionAddObject); invocations++; } ); source.Raise( x => x.CollectionChanged += null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collectionAddObject) ); invocations.ShouldBe(1); } [Fact] public void AddWeakEventListener_custom_fires_event() { var source = new Mock(); var invocations = 0; var wem = new WeakEventManager(); wem.AddWeakEventListener( source.Object, nameof(INotifyPropertyChanged.PropertyChanged), (s, a) => { s.ShouldBe(source.Object); invocations++; } ); source.Raise(x => x.PropertyChanged += null, new PropertyChangedEventArgs("Foo")); invocations.ShouldBe(1); } [Fact] public void AddWeakEventListener_fires_event_after_GC() { var source = new Mock(); var invocations = 0; var wem = new WeakEventManager(); wem.AddWeakEventListener( source.Object, nameof(INotifyPropertyChanged.PropertyChanged), (s, a) => { s.ShouldBe(source.Object); invocations++; } ); GC.Collect(); source.Raise(x => x.PropertyChanged += null, new PropertyChangedEventArgs("Foo")); invocations.ShouldBe(1); } [Fact] public void AddWeakEventListener_property_fires_event() { const string propertyName = "TestProperty"; var source = new Mock(); var invocations = 0; var wem = new WeakEventManager(); wem.AddWeakEventListener( source.Object, (s, a) => { s.ShouldBe(source.Object); a.PropertyName.ShouldBe(propertyName); invocations++; } ); source.Raise(x => x.PropertyChanged += null, new PropertyChangedEventArgs(propertyName)); invocations.ShouldBe(1); } [Fact] public void ClearWeakEventListeners_event_no_longer_fires() { var source = new Mock(); var invocations = 0; var wem = new WeakEventManager(); wem.AddWeakEventListener( source.Object, nameof(INotifyPropertyChanged.PropertyChanged), (s, a) => { s.ShouldBe(source.Object); invocations++; } ); wem.ClearWeakEventListeners(); source.Raise(x => x.PropertyChanged += null, new PropertyChangedEventArgs("Foo")); invocations.ShouldBe(0); } [Fact] public void RemoveWeakEventListener_event_no_longer_fires() { var source = new Mock(); var invocations = 0; var wem = new WeakEventManager(); wem.AddWeakEventListener( source.Object, nameof(INotifyPropertyChanged.PropertyChanged), (s, a) => { s.ShouldBe(source.Object); invocations++; } ); wem.RemoveWeakEventListener(source.Object); source.Raise(x => x.PropertyChanged += null, new PropertyChangedEventArgs("Foo")); invocations.ShouldBe(0); } } ================================================ FILE: src/MvvmBlazor.Tests/MvvmBlazor.Tests.csproj ================================================ net6.0 false all runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive Always ================================================ FILE: src/MvvmBlazor.Tests/Properties/AssemblyInfo.cs ================================================ [assembly: CLSCompliant(false)] ================================================ FILE: src/MvvmBlazor.Tests/ViewModel/ViewModelBaseTests.cs ================================================ namespace MvvmBlazor.Tests.ViewModel; public class ViewModelBaseTests { [Fact] public void Set_returns_false_on_reference_equal() { var obj = new object(); var invoked = false; var vm = new TestViewModel(); vm.PropertyChanged += (_, __) => invoked = true; var res = vm.SetProperty(ref obj, obj, "Foo"); res.ShouldBeFalse(); invoked.ShouldBeFalse(); } [Fact] public void Set_returns_false_on_value_equal() { void TestField(ref T field, T value) { var invoked = false; var vm = new TestViewModel(); vm.PropertyChanged += (_, __) => invoked = true; var res = vm.SetProperty(ref field, value, "Foo"); res.ShouldBeFalse(); invoked.ShouldBeFalse(); } var int1 = 1; TestField(ref int1, 1); var string1 = "test"; TestField(ref string1, "test"); var double1 = 23.3; TestField(ref double1, 23.3); } [Fact] public void Set_returns_true_on_value_not_equal() { void TestField(ref T field, T value) { var invoked = 0; var vm = new TestViewModel(); vm.PropertyChanged += (s, a) => { s.ShouldBe(vm); a.PropertyName.ShouldBe("Foo"); invoked++; }; var res = vm.SetProperty(ref field, value, "Foo"); res.ShouldBeTrue(); invoked.ShouldBe(1); field.ShouldBe(value); } var int1 = 1; TestField(ref int1, 2); var string1 = "test"; TestField(ref string1, "test1"); var double1 = 23.3; TestField(ref double1, 2.3); } [Fact] public void Set_returns_False_with_custom_equality_comparer() { var mockEq= new StrictMock>(); mockEq.Setup(x => x.Equals(It.IsAny(), It.IsAny())) .Returns(true) .Verifiable(); var vm = new TestViewModel(); var int1 = 1; var res = vm.SetProperty(ref int1, 2, mockEq.Object, "Foo"); res.ShouldBe(false); mockEq.Verify(); } [Fact] public void Set_returns_true_with_custom_equality_comparer() { var mockEq= new StrictMock>(); mockEq.Setup(x => x.Equals(It.IsAny(), It.IsAny())) .Returns(false) .Verifiable(); var vm = new TestViewModel(); var int1 = 1; var res = vm.SetProperty(ref int1, 1, mockEq.Object, "Foo"); res.ShouldBe(true); mockEq.Verify(); } private class TestViewModel : ViewModelBase { public bool SetProperty(ref T field, T value, string? propertyName = null) { return Set(ref field, value, propertyName); } public bool SetProperty(ref T field, T value, IEqualityComparer equalityComparer, string? propertyName = null) { return Set(ref field, value, equalityComparer, propertyName); } } } ================================================ FILE: src/MvvmBlazor.Tests/xunit.runner.json ================================================ { "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", "methodDisplayOptions": "replaceUnderscoreWithSpace,useOperatorMonikers" } ================================================ FILE: src/MvvmBlazor.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28803.352 MinimumVisualStudioVersion = 15.0.26124.0 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvvmBlazor.Core", "MvvmBlazor.Core\MvvmBlazor.Core.csproj", "{B4E1DEC6-44FF-434C-8AA9-79B3F2FE50DA}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3454D681-0991-4626-A291-D4D9D26E91FF}" ProjectSection(SolutionItems) = preProject ..\.gitattributes = ..\.gitattributes ..\.gitignore = ..\.gitignore ..\.github\workflows\build.yaml = ..\.github\workflows\build.yaml ..\LICENSE = ..\LICENSE ..\README.md = ..\README.md ..\.github\workflows\release.yaml = ..\.github\workflows\release.yaml ..\.editorconfig = ..\.editorconfig EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{4A4D5D8E-6CD2-4EAB-96E7-B2473C3B2925}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvvmBlazor.Tests", "MvvmBlazor.Tests\MvvmBlazor.Tests.csproj", "{1D60EC2D-CA52-4AA7-AB93-CA21CB85479F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorServersideSample", "..\samples\BlazorServersideSample\BlazorServersideSample.csproj", "{AFAA8C8F-ACB6-41E1-B14C-6B91E074F44E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorSample.Components", "..\samples\BlazorSample.Components\BlazorSample.Components.csproj", "{35A25A6A-B6F2-475B-91D2-FB94E7D03FA6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorSample.ViewModels", "..\samples\BlazorSample.ViewModels\BlazorSample.ViewModels.csproj", "{E6A464A4-911B-4AD3-9F84-9923CD4AD124}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorSample.Domain", "..\samples\BlazorSample.Domain\BlazorSample.Domain.csproj", "{00E97789-4AB7-4254-BC9A-6134E16DB2B2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{6F179065-0CE7-4786-9A92-5AB130D333A6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorClientsideSample.Client", "..\samples\BlazorClientsideSample.Client\BlazorClientsideSample.Client.csproj", "{0774AD8F-ADC6-4293-B456-D7B080FD17C3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorClientsideSample.Server", "..\samples\BlazorClientsideSample.Server\BlazorClientsideSample.Server.csproj", "{33D6D643-2C45-4B4B-9691-7D0D67D0E304}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvvmBlazor.CodeGenerators", "MvvmBlazor.CodeGenerators\MvvmBlazor.CodeGenerators.csproj", "{42B24F4F-5B4E-4172-9EB2-8083DFEB1042}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvvmBlazor", "MvvmBlazor\MvvmBlazor.csproj", "{8CE5C38E-9308-431C-BAFA-BAB3A0F1DC1C}" 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 {B4E1DEC6-44FF-434C-8AA9-79B3F2FE50DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B4E1DEC6-44FF-434C-8AA9-79B3F2FE50DA}.Debug|Any CPU.Build.0 = Debug|Any CPU {B4E1DEC6-44FF-434C-8AA9-79B3F2FE50DA}.Debug|x64.ActiveCfg = Debug|Any CPU {B4E1DEC6-44FF-434C-8AA9-79B3F2FE50DA}.Debug|x64.Build.0 = Debug|Any CPU {B4E1DEC6-44FF-434C-8AA9-79B3F2FE50DA}.Debug|x86.ActiveCfg = Debug|Any CPU {B4E1DEC6-44FF-434C-8AA9-79B3F2FE50DA}.Debug|x86.Build.0 = Debug|Any CPU {B4E1DEC6-44FF-434C-8AA9-79B3F2FE50DA}.Release|Any CPU.ActiveCfg = Release|Any CPU {B4E1DEC6-44FF-434C-8AA9-79B3F2FE50DA}.Release|Any CPU.Build.0 = Release|Any CPU {B4E1DEC6-44FF-434C-8AA9-79B3F2FE50DA}.Release|x64.ActiveCfg = Release|Any CPU {B4E1DEC6-44FF-434C-8AA9-79B3F2FE50DA}.Release|x64.Build.0 = Release|Any CPU {B4E1DEC6-44FF-434C-8AA9-79B3F2FE50DA}.Release|x86.ActiveCfg = Release|Any CPU {B4E1DEC6-44FF-434C-8AA9-79B3F2FE50DA}.Release|x86.Build.0 = Release|Any CPU {1D60EC2D-CA52-4AA7-AB93-CA21CB85479F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1D60EC2D-CA52-4AA7-AB93-CA21CB85479F}.Debug|Any CPU.Build.0 = Debug|Any CPU {1D60EC2D-CA52-4AA7-AB93-CA21CB85479F}.Debug|x64.ActiveCfg = Debug|Any CPU {1D60EC2D-CA52-4AA7-AB93-CA21CB85479F}.Debug|x64.Build.0 = Debug|Any CPU {1D60EC2D-CA52-4AA7-AB93-CA21CB85479F}.Debug|x86.ActiveCfg = Debug|Any CPU {1D60EC2D-CA52-4AA7-AB93-CA21CB85479F}.Debug|x86.Build.0 = Debug|Any CPU {1D60EC2D-CA52-4AA7-AB93-CA21CB85479F}.Release|Any CPU.ActiveCfg = Release|Any CPU {1D60EC2D-CA52-4AA7-AB93-CA21CB85479F}.Release|Any CPU.Build.0 = Release|Any CPU {1D60EC2D-CA52-4AA7-AB93-CA21CB85479F}.Release|x64.ActiveCfg = Release|Any CPU {1D60EC2D-CA52-4AA7-AB93-CA21CB85479F}.Release|x64.Build.0 = Release|Any CPU {1D60EC2D-CA52-4AA7-AB93-CA21CB85479F}.Release|x86.ActiveCfg = Release|Any CPU {1D60EC2D-CA52-4AA7-AB93-CA21CB85479F}.Release|x86.Build.0 = Release|Any CPU {AFAA8C8F-ACB6-41E1-B14C-6B91E074F44E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AFAA8C8F-ACB6-41E1-B14C-6B91E074F44E}.Debug|Any CPU.Build.0 = Debug|Any CPU {AFAA8C8F-ACB6-41E1-B14C-6B91E074F44E}.Debug|x64.ActiveCfg = Debug|Any CPU {AFAA8C8F-ACB6-41E1-B14C-6B91E074F44E}.Debug|x64.Build.0 = Debug|Any CPU {AFAA8C8F-ACB6-41E1-B14C-6B91E074F44E}.Debug|x86.ActiveCfg = Debug|Any CPU {AFAA8C8F-ACB6-41E1-B14C-6B91E074F44E}.Debug|x86.Build.0 = Debug|Any CPU {AFAA8C8F-ACB6-41E1-B14C-6B91E074F44E}.Release|Any CPU.ActiveCfg = Release|Any CPU {AFAA8C8F-ACB6-41E1-B14C-6B91E074F44E}.Release|Any CPU.Build.0 = Release|Any CPU {AFAA8C8F-ACB6-41E1-B14C-6B91E074F44E}.Release|x64.ActiveCfg = Release|Any CPU {AFAA8C8F-ACB6-41E1-B14C-6B91E074F44E}.Release|x64.Build.0 = Release|Any CPU {AFAA8C8F-ACB6-41E1-B14C-6B91E074F44E}.Release|x86.ActiveCfg = Release|Any CPU {AFAA8C8F-ACB6-41E1-B14C-6B91E074F44E}.Release|x86.Build.0 = Release|Any CPU {35A25A6A-B6F2-475B-91D2-FB94E7D03FA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {35A25A6A-B6F2-475B-91D2-FB94E7D03FA6}.Debug|Any CPU.Build.0 = Debug|Any CPU {35A25A6A-B6F2-475B-91D2-FB94E7D03FA6}.Debug|x64.ActiveCfg = Debug|Any CPU {35A25A6A-B6F2-475B-91D2-FB94E7D03FA6}.Debug|x64.Build.0 = Debug|Any CPU {35A25A6A-B6F2-475B-91D2-FB94E7D03FA6}.Debug|x86.ActiveCfg = Debug|Any CPU {35A25A6A-B6F2-475B-91D2-FB94E7D03FA6}.Debug|x86.Build.0 = Debug|Any CPU {35A25A6A-B6F2-475B-91D2-FB94E7D03FA6}.Release|Any CPU.ActiveCfg = Release|Any CPU {35A25A6A-B6F2-475B-91D2-FB94E7D03FA6}.Release|Any CPU.Build.0 = Release|Any CPU {35A25A6A-B6F2-475B-91D2-FB94E7D03FA6}.Release|x64.ActiveCfg = Release|Any CPU {35A25A6A-B6F2-475B-91D2-FB94E7D03FA6}.Release|x64.Build.0 = Release|Any CPU {35A25A6A-B6F2-475B-91D2-FB94E7D03FA6}.Release|x86.ActiveCfg = Release|Any CPU {35A25A6A-B6F2-475B-91D2-FB94E7D03FA6}.Release|x86.Build.0 = Release|Any CPU {E6A464A4-911B-4AD3-9F84-9923CD4AD124}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E6A464A4-911B-4AD3-9F84-9923CD4AD124}.Debug|Any CPU.Build.0 = Debug|Any CPU {E6A464A4-911B-4AD3-9F84-9923CD4AD124}.Debug|x64.ActiveCfg = Debug|Any CPU {E6A464A4-911B-4AD3-9F84-9923CD4AD124}.Debug|x64.Build.0 = Debug|Any CPU {E6A464A4-911B-4AD3-9F84-9923CD4AD124}.Debug|x86.ActiveCfg = Debug|Any CPU {E6A464A4-911B-4AD3-9F84-9923CD4AD124}.Debug|x86.Build.0 = Debug|Any CPU {E6A464A4-911B-4AD3-9F84-9923CD4AD124}.Release|Any CPU.ActiveCfg = Release|Any CPU {E6A464A4-911B-4AD3-9F84-9923CD4AD124}.Release|Any CPU.Build.0 = Release|Any CPU {E6A464A4-911B-4AD3-9F84-9923CD4AD124}.Release|x64.ActiveCfg = Release|Any CPU {E6A464A4-911B-4AD3-9F84-9923CD4AD124}.Release|x64.Build.0 = Release|Any CPU {E6A464A4-911B-4AD3-9F84-9923CD4AD124}.Release|x86.ActiveCfg = Release|Any CPU {E6A464A4-911B-4AD3-9F84-9923CD4AD124}.Release|x86.Build.0 = Release|Any CPU {00E97789-4AB7-4254-BC9A-6134E16DB2B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {00E97789-4AB7-4254-BC9A-6134E16DB2B2}.Debug|Any CPU.Build.0 = Debug|Any CPU {00E97789-4AB7-4254-BC9A-6134E16DB2B2}.Debug|x64.ActiveCfg = Debug|Any CPU {00E97789-4AB7-4254-BC9A-6134E16DB2B2}.Debug|x64.Build.0 = Debug|Any CPU {00E97789-4AB7-4254-BC9A-6134E16DB2B2}.Debug|x86.ActiveCfg = Debug|Any CPU {00E97789-4AB7-4254-BC9A-6134E16DB2B2}.Debug|x86.Build.0 = Debug|Any CPU {00E97789-4AB7-4254-BC9A-6134E16DB2B2}.Release|Any CPU.ActiveCfg = Release|Any CPU {00E97789-4AB7-4254-BC9A-6134E16DB2B2}.Release|Any CPU.Build.0 = Release|Any CPU {00E97789-4AB7-4254-BC9A-6134E16DB2B2}.Release|x64.ActiveCfg = Release|Any CPU {00E97789-4AB7-4254-BC9A-6134E16DB2B2}.Release|x64.Build.0 = Release|Any CPU {00E97789-4AB7-4254-BC9A-6134E16DB2B2}.Release|x86.ActiveCfg = Release|Any CPU {00E97789-4AB7-4254-BC9A-6134E16DB2B2}.Release|x86.Build.0 = Release|Any CPU {0774AD8F-ADC6-4293-B456-D7B080FD17C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0774AD8F-ADC6-4293-B456-D7B080FD17C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {0774AD8F-ADC6-4293-B456-D7B080FD17C3}.Debug|x64.ActiveCfg = Debug|Any CPU {0774AD8F-ADC6-4293-B456-D7B080FD17C3}.Debug|x64.Build.0 = Debug|Any CPU {0774AD8F-ADC6-4293-B456-D7B080FD17C3}.Debug|x86.ActiveCfg = Debug|Any CPU {0774AD8F-ADC6-4293-B456-D7B080FD17C3}.Debug|x86.Build.0 = Debug|Any CPU {0774AD8F-ADC6-4293-B456-D7B080FD17C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {0774AD8F-ADC6-4293-B456-D7B080FD17C3}.Release|Any CPU.Build.0 = Release|Any CPU {0774AD8F-ADC6-4293-B456-D7B080FD17C3}.Release|x64.ActiveCfg = Release|Any CPU {0774AD8F-ADC6-4293-B456-D7B080FD17C3}.Release|x64.Build.0 = Release|Any CPU {0774AD8F-ADC6-4293-B456-D7B080FD17C3}.Release|x86.ActiveCfg = Release|Any CPU {0774AD8F-ADC6-4293-B456-D7B080FD17C3}.Release|x86.Build.0 = Release|Any CPU {33D6D643-2C45-4B4B-9691-7D0D67D0E304}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {33D6D643-2C45-4B4B-9691-7D0D67D0E304}.Debug|Any CPU.Build.0 = Debug|Any CPU {33D6D643-2C45-4B4B-9691-7D0D67D0E304}.Debug|x64.ActiveCfg = Debug|Any CPU {33D6D643-2C45-4B4B-9691-7D0D67D0E304}.Debug|x64.Build.0 = Debug|Any CPU {33D6D643-2C45-4B4B-9691-7D0D67D0E304}.Debug|x86.ActiveCfg = Debug|Any CPU {33D6D643-2C45-4B4B-9691-7D0D67D0E304}.Debug|x86.Build.0 = Debug|Any CPU {33D6D643-2C45-4B4B-9691-7D0D67D0E304}.Release|Any CPU.ActiveCfg = Release|Any CPU {33D6D643-2C45-4B4B-9691-7D0D67D0E304}.Release|Any CPU.Build.0 = Release|Any CPU {33D6D643-2C45-4B4B-9691-7D0D67D0E304}.Release|x64.ActiveCfg = Release|Any CPU {33D6D643-2C45-4B4B-9691-7D0D67D0E304}.Release|x64.Build.0 = Release|Any CPU {33D6D643-2C45-4B4B-9691-7D0D67D0E304}.Release|x86.ActiveCfg = Release|Any CPU {33D6D643-2C45-4B4B-9691-7D0D67D0E304}.Release|x86.Build.0 = Release|Any CPU {42B24F4F-5B4E-4172-9EB2-8083DFEB1042}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {42B24F4F-5B4E-4172-9EB2-8083DFEB1042}.Debug|Any CPU.Build.0 = Debug|Any CPU {42B24F4F-5B4E-4172-9EB2-8083DFEB1042}.Debug|x64.ActiveCfg = Debug|Any CPU {42B24F4F-5B4E-4172-9EB2-8083DFEB1042}.Debug|x64.Build.0 = Debug|Any CPU {42B24F4F-5B4E-4172-9EB2-8083DFEB1042}.Debug|x86.ActiveCfg = Debug|Any CPU {42B24F4F-5B4E-4172-9EB2-8083DFEB1042}.Debug|x86.Build.0 = Debug|Any CPU {42B24F4F-5B4E-4172-9EB2-8083DFEB1042}.Release|Any CPU.ActiveCfg = Release|Any CPU {42B24F4F-5B4E-4172-9EB2-8083DFEB1042}.Release|Any CPU.Build.0 = Release|Any CPU {42B24F4F-5B4E-4172-9EB2-8083DFEB1042}.Release|x64.ActiveCfg = Release|Any CPU {42B24F4F-5B4E-4172-9EB2-8083DFEB1042}.Release|x64.Build.0 = Release|Any CPU {42B24F4F-5B4E-4172-9EB2-8083DFEB1042}.Release|x86.ActiveCfg = Release|Any CPU {42B24F4F-5B4E-4172-9EB2-8083DFEB1042}.Release|x86.Build.0 = Release|Any CPU {8CE5C38E-9308-431C-BAFA-BAB3A0F1DC1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8CE5C38E-9308-431C-BAFA-BAB3A0F1DC1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {8CE5C38E-9308-431C-BAFA-BAB3A0F1DC1C}.Debug|x64.ActiveCfg = Debug|Any CPU {8CE5C38E-9308-431C-BAFA-BAB3A0F1DC1C}.Debug|x64.Build.0 = Debug|Any CPU {8CE5C38E-9308-431C-BAFA-BAB3A0F1DC1C}.Debug|x86.ActiveCfg = Debug|Any CPU {8CE5C38E-9308-431C-BAFA-BAB3A0F1DC1C}.Debug|x86.Build.0 = Debug|Any CPU {8CE5C38E-9308-431C-BAFA-BAB3A0F1DC1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {8CE5C38E-9308-431C-BAFA-BAB3A0F1DC1C}.Release|Any CPU.Build.0 = Release|Any CPU {8CE5C38E-9308-431C-BAFA-BAB3A0F1DC1C}.Release|x64.ActiveCfg = Release|Any CPU {8CE5C38E-9308-431C-BAFA-BAB3A0F1DC1C}.Release|x64.Build.0 = Release|Any CPU {8CE5C38E-9308-431C-BAFA-BAB3A0F1DC1C}.Release|x86.ActiveCfg = Release|Any CPU {8CE5C38E-9308-431C-BAFA-BAB3A0F1DC1C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {AFAA8C8F-ACB6-41E1-B14C-6B91E074F44E} = {4A4D5D8E-6CD2-4EAB-96E7-B2473C3B2925} {35A25A6A-B6F2-475B-91D2-FB94E7D03FA6} = {6F179065-0CE7-4786-9A92-5AB130D333A6} {E6A464A4-911B-4AD3-9F84-9923CD4AD124} = {6F179065-0CE7-4786-9A92-5AB130D333A6} {00E97789-4AB7-4254-BC9A-6134E16DB2B2} = {6F179065-0CE7-4786-9A92-5AB130D333A6} {6F179065-0CE7-4786-9A92-5AB130D333A6} = {4A4D5D8E-6CD2-4EAB-96E7-B2473C3B2925} {0774AD8F-ADC6-4293-B456-D7B080FD17C3} = {4A4D5D8E-6CD2-4EAB-96E7-B2473C3B2925} {33D6D643-2C45-4B4B-9691-7D0D67D0E304} = {4A4D5D8E-6CD2-4EAB-96E7-B2473C3B2925} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C8D55576-3422-405F-866A-7651C879FD8E} EndGlobalSection EndGlobal ================================================ FILE: src/MvvmBlazor.sln.DotSettings ================================================  True True True True