[
  {
    "path": ".gitattributes",
    "content": "###############################################################################\n# Set default behavior to automatically normalize line endings.\n###############################################################################\n* text=auto\n\n###############################################################################\n# Set default behavior for command prompt diff.\n#\n# This is need for earlier builds of msysgit that does not have it on by\n# default for csharp files.\n# Note: This is only used by command line\n###############################################################################\n#*.cs     diff=csharp\n\n###############################################################################\n# Set the merge driver for project and solution files\n#\n# Merging from the command prompt will add diff markers to the files if there\n# are conflicts (Merging from VS is not affected by the settings below, in VS\n# the diff markers are never inserted). Diff markers may cause the following \n# file extensions to fail to load in VS. An alternative would be to treat\n# these files as binary and thus will always conflict and require user\n# intervention with every merge. To do so, just uncomment the entries below\n###############################################################################\n#*.sln       merge=binary\n#*.csproj    merge=binary\n#*.vbproj    merge=binary\n#*.vcxproj   merge=binary\n#*.vcproj    merge=binary\n#*.dbproj    merge=binary\n#*.fsproj    merge=binary\n#*.lsproj    merge=binary\n#*.wixproj   merge=binary\n#*.modelproj merge=binary\n#*.sqlproj   merge=binary\n#*.wwaproj   merge=binary\n\n###############################################################################\n# behavior for image files\n#\n# image files are treated as binary by default.\n###############################################################################\n#*.jpg   binary\n#*.png   binary\n#*.gif   binary\n\n###############################################################################\n# diff behavior for common document formats\n# \n# Convert binary document formats to text before diffing them. This feature\n# is only available from the command line. Turn it on by uncommenting the \n# entries below.\n###############################################################################\n#*.doc   diff=astextplain\n#*.DOC   diff=astextplain\n#*.docx  diff=astextplain\n#*.DOCX  diff=astextplain\n#*.dot   diff=astextplain\n#*.DOT   diff=astextplain\n#*.pdf   diff=astextplain\n#*.PDF   diff=astextplain\n#*.rtf   diff=astextplain\n#*.RTF   diff=astextplain\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\n[Xx]64/\n[Xx]86/\n[Bb]uild/\nbld/\n[Bb]in/\n[Oo]bj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# DNX\nproject.lock.json\nartifacts/\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n\n# TODO: Un-comment the next line if you do not want to checkin \n# your web deploy settings because they may include unencrypted\n# passwords\n#*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n# NuGet v3's project.json files produces more ignoreable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n[Ss]tyle[Cc]op.*\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\nnode_modules/\norleans.codegen.cs\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# LightSwitch generated files\nGeneratedArtifacts/\nModelManifest.xml\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/"
  },
  {
    "path": "LICENSE.txt",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "PagerDemo.md",
    "content": "# ASP.NET Core Data Paging and Pager Usage Guide\n\n*Note: here is the guide of version 2. For the version 1 guide, please click [here](PagerDemov1.md).*\n\nThis page will give you a step-by-step guide to use ASP.NET Core MVC Data Paging and Pager features. This guide includes the following topics:\n- How to paging your data source\n- How to enumerate your data page and get paging information\n- How to show a pager in your ASP.NET Core MVC Web Page\n\nThe following packages are required for this guide:\n- `Sakura.AspNetCore.PagedList`: for data paging\n- `Sakura.AspNetCore.PagedList.Async`: for async data paging\n- `Sakura.AspNetCore.Mvc.PagedList`: for HTML pager generation\n\n## Data Paging\n\nBefore using the ASP.NET Core MVC Pager, usually you may want to paging your data source (which may come from a EntityFramework or memory query) first. The `Sakura.AspNetCore.PagedList` package provides a interface named `IPagedList` to represent as a paged data source. The easiest way to create an instance of it is to use the extension methods defined in `Sakura.AspNetCore.PagedListCreationHelper` class. The following code shows the basic usage:\n\n```C#\n// Import extension methods in PagedListCreationHelper class.\nusing Sakura.AspNetCore;\n\nvar pageNumber = 1; // Note that page number starts from 1 (not zero!)\nvar pageSize = 10;\n\n// data is assumed as coming from an EntityFramework DbSet here. Any object with type IEnumerable<T> or IQueryable<T> is supported with different implementations.\nvar data = from i in SportsManageModel.Players select i;\n\n// The created IPagedList object, which contains a partial view for the current page, and the paging information.\nvar pagedData = data.ToPagedList(pageSize, pageNumber);\n```\n\n**Note:** actually, the content of `IEnumerable<T>` or `IQueryable<T>` may vary if they represent as a unstable source (e.g. the result a query from a database may change when another thread added new data). However, the the `ToPagedList` method always creates a static snapshot from the source at the creation time, which means the all the information of the `IPagedList` (including the total page count and the content of current page) never changes after you created it. If you want to capture a dynamic reference of the source, please see the next section.\n\n### Dynamic Data Paging\n\nFor advanced scenes, you may try to track the change of the data source. Under such circumstance, you may use `IDynamicPagedList`. Another additional feature of this interface is you can change paging settings (page size and page index) even after you created it and receive data in new page. In order to use create a dynamic paged list, you may use `ToDynamicPagedList` extension method on either `IEnumerable<T>` or `IQueryable<T>` object.\n\n### Async Data Paging\n\nEntity Framework provides some extention method to quey data asynchronously (e.g. `ToArrayAsync`). If you would like to take advantage for these async operations, you may add the `Sakura.AspNetCore.PagedList.Async` package into your project, and use `ToPagedListAsync` method to generate `IPagedList` asynchronously.\n\n**Note:** it is similar with the `ToPageList` method that the async method also creates snapshot for data source. Async operation on  dynamic paged lists is currently not supported, since there is no way to set a property value asynchronously.\n\n## Paged Data Access\n\nIn MVC Projects, you may pass the paged data source from your controller into your view, and iterates the current pages easily. The following code shows the basic way:\n```C#\n// In MVC Controller\npublic IActionResult MyDataView()\n{\n  // Code for data source access is omitted here.\n  var pagedData = data.ToPagedList(pageSize, pageNumber);\n  return View(pagedData);\n}\n```\n\n```C#\n// In MVC View\n@model Sakura.AspNetCore.IPagedList<Player>\n\n@* The TotalCount property of a IPagedList is used to indicate the count of all (non-paged) items. *@\n<span>Total Players: @Model.TotalCount </span>\n\n<ul>\n  @* Note: IPagedList implements the enumration for current page. (data not in current page will not be accessed.) *@\n  @foreach (var i in Model)\n  {\n    <li>@i.Name</li>\n  }\n</ul>\n\n```\n\n*Note: Actually, the `IPagedList` is not related with the MVC platform, which means it can be used in any .NET Core Platform targeted project (Including ASP.NET Core Class Library and ASP.NET Core Web Application).*\n\n## Pager\n\nPager feature is much more complex, since it is related with data handling, HTML generation, and MVC view code writing. The detailed step-by-step configuration is list as following.\n\n### Simple Usage\n\nFirst of all, you need to install the package for MVC pager, you should add the package `Sakura.AspNetCore.Mvc.PagedList` into your `project.json`.\n\nAnd then, for the most simple usage, you may add the following code into your `Startup.cs` file:\n```C#\n// Add the following line at the top area of the file to import extension methods.\nusing Sakura.AspNetCore.Mvc;\n\npublic void ConfigureServices(IServiceCollection services)\n{\n  // .. Other configuration codes in you application\n \n  // Add default bootstrap-styled pager implementation\n  services.AddBootstrapPagerGenerator(options =>\n  {\n    // Use default pager options.\n    options.ConfigureDefault();\n   });\n}\n```\nNext, you should add the `PagerTagHelper` into your view processing pipeline, you may add a new line in the `_ViewImports.cs` like:\n```HTML\n@addTagHelper *, Sakura.AspNetCore.Mvc.PagedList\n```\nNote the above line will enabled the `<pager>` tag for all view pages. If you want to enable it only in a few views, you may add this line to each view file invididually.\n\nFinally, in your MVC View (.cshtml) file, use the following code to display a pager.\n```HTML\n<!-- The \"source\" attribute must be a C# expression with return type of `IPagedList` (no \"@\" perfix is needed) -->\n<pager source=\"youDataModel\" />\n```\n**Note: The `<pager>` tag must use the self-closing mode. Adding any content to this tag is not supported.**\n\nIf the model type of your view is just `IPagedList` or any inheritted type, you can even omit the \"source\" attribute and show a pager like this:\n```HTML\n<pager />\n```\n\n### Pager without Source\n\nIn most cases, your pager source is a value with type of `IPagedList`. The usage of the pager source is shown as above. However sometimes you may want to generate a pager with out a paged list source. In this case, you can use the `current-page` and `total-page` attribute on the tag to generate a static pager as following:\n```HTML\n<pager current-page=\"3\" total-page=\"10\" />\n```\nThe above code will generate a pager with 10 pages, and the 3rd page will be current page and displays in an active style.\n\n**Note: You must set both `current-page` and `total-page` attribute to generate a static pager. In addition, these 2 attribute cannot  be used together with the `source` attribute, otherwise you will receive an error message during the pager generating.**\n###\n\n### Generation Result and Partial Generation\nThe default bootstrap style pager generator will generate a `<ul class=\"pagination\">` HTML element, and each pager item will be an `<li>` element inside it. According to the state of the page, there will be an `<a>` element (for most of pages) or an `<span>` element (for active page or any page without an effective link) as the content of the `<li>` element.\n\nThe original `<pager>` element will be removed from the final HTML page.\n\nIn some cases, you may want to use your own container for your pager, or add some custom items in the list. In this cases, You may change the generation mode of the pager to `PagerGenerationMode.ListOnly`. On this mode, only `<li>` elements for each pager items will be generated, a examples may be following:\n```HTML\n<!-- You must provide a container manually --> \n<ul class=\"pagination\">\n  <li>My Custom Item</li>\n  <pager generation-mode=\"ListOnly\" />\n  <li>Another Custom Item</li>\n</ul>\n```\n### PagerOptions Configuration\nTo customize the pager genreation, the most effective way is change the options for the pager. The options are designed as a type named `PagerOptions`, and you can set a custom options to your pager using the `options` attribute like below (the detailed description for options settings will be introduced later in this page):\n```HTML\n<!-- the `myOptions` must be a C# expression with return type of PagerOptions, no \"@\" perfix is needed. -->\n<pager options=\"myOptions\" />\n```\n\n#### `PagerOptions` Settings Description\nThere are many settings you can control in the `PagerOptions` class, a brief inroduction for these settings are listed below:\n\nSetting|Description|Typical Usage\n-------|-----------|-------------\n`ExpandPageItemsForCurrentPage`|How many pages arround the current page (in both side) will be displayed. Set to 1 means one extra page for both left and right side will be generated. Set to 0 will display no extra pages (the current page is always displayed).|2\n`PagerItemsForEndings`|How many pages at the ending will be displayed. Set to 1 means only the 1st first and last page will be displayed. Set to 0 will disable this feature.|3\n`Layout`|The layout of the pager controls the element(s) will be displayed in the pager and their display order. For more information, please see the documentation of `PagerLayoutElement` class.|`PagerLayouts.Default`, `PargerLayouts.Custom()`\n`IsReversed`|If true, all pager items (including number items and special buttons) will be reversed. |*N/A*\n`HideOnSinglePage`|If true, the pager will supress all output when the pager source only has one page.|*N/A*\n`ItemOptions`|Controls the content and link generation for different types of pager items.|*See below*\n`AdditionalSettings`|Provide additional settings used for 3rd and expaned handlers.|An example can be found in `Enabling Ajax` section\n\nIn the `PagerOptions.ItemOptions` property, there are series of different individual properties to control the different pager element, each of them is an instance of `PagerItemOptions` class. The list of all items are as below:\n\nItem|Usage|Base Item\n----|-----|---------\n`Default`|Shared settings for all pager items|*None*\n`Normal`|Settings for all numbered pager links|`Default`\n`Active`|Settings for current active (highlighted) page|`Normal`\n`Omitted`|Settings for omitted page placehoders|`Default`\n`FirstPageButton`|Settings for invidiual \"Go to first page\" button|`Default`\n`LastPageButton`|Settings for invidiual \"Go to last page\" button|`Default`\n`PreviousPageButton`|Settings for invidiual \"Go to previous page\" button|`Default`\n`NextPageButton`|Settings for invidiual \"Go to next page\" button|`Default`\n\nIn the above table, `Base Item` means the setting will be generated will the specified base item. e.g. The lost settings in `Active` mode will be merged with values in `Normal` mode, and then merged again with `Default` mode. It means you do not need to rewrite the settings which are same as the base item.\n\nThe settings in the `PagerItemOptions` are described as below:\n\nSetting|Description|Typical Usage\n-------|-----------|-------------\n`Content`|A content generator used to generate the content of each pager item|`PagerItemContentGenerators.XXX`\n`Link`|A link generator used to generate the link of each pager item, if the generator returns a null string, the pager will has no link effect|`PagerItemLinkGenerators.XXX`\n`InactiveBehavior`|How to handle the pager item if its current state is not meanful (e.g. the state of \"go to next page\" button when you are already in the last page), this setting only affects \"go to first/last/next/previous page button\"|*Please see documentation*\n`ActiveMode`|How to determine if the current pager item is meanful (for further handling of `InactiveBehavior` setting), this setting only affect \"go to first/last page button\"|*Please see documentation*\n\n#### Configure Default Options using Setup Actions\n\nYou do not need to set `options` attribute for each pager; You can set a default options at application startup time. The ASP.NET Core framework has provide the `Configure` extension method to save a application-level options value, you may use code like bellow to set the default `PagerOptions`:\n```C#\npublic void ConfigureServices(IServiceCollection services)\n{\n  // .. Other configuration codes in you application\n\n  services.Configure<PagerOptions>(options =>\n  {\n    // This following line is an example to set an option value\n    options.PagerLinksForEnding = 2;\n  });\n}\n```\nYou may also to to use the setup delegate in the `AddBootstrapPagerGenerator` method (see the example in the `Simple Usage` section), it is a shortcut manner with the same effect as `Configure` method.\n\n#### Configure Options using Config File\nASP.NET Core application provide the ability to load configuration from setting files (usually named as `appsettings.json`). This pager framework also supports set most of the options in the settings file, a example is shown as below:\n\nIn `appsettings.json`:\n```JSON\n{\n  \"Pager\": {\n    \"ExpandPageItemsForCurrentPage\" : 2,\n    \"PageItemsForEnding\": 3,\n    \"Layout\": \"Default\",\n    \"AdditionalSettings\": {\n      \"my-setting-one\": \"1\"\n    },\n    \n    \"ItemOptions\": {\n      \"Default\": {\n        \"Content\": \"TextFormat:{0}\",\n        \"Link\": \"QueryName:page\",\n        \"InactiveBehavior\": \"Hide\",\n        \"ActiveMode\": \"Always\"\n      },\n      \"GoToLastPage\": {\n        \"Content\": \"Text:Go To Last Page\"\n      }\n    }\n  }\n}\n```\nIn `Startup.cs`:\n```C#\n// You need to the \"Microsoft.Extensions.Options.ConfigurationExtensions\" package, which is pre-loaded in ASP.NET Core RTM.\n\npublic void ConfigureServices(IServiceCollection services)\n{\n  // .. Other configuration codes in you application\n\n  // Loading the \"pager\" section in the config file and set as the default pager options.\n  services.Configure<PagerOptions>(Configuration.GetSection(\"Pager\"));\n}\n```\n\nThe ASP.NET Core framework can mapp JSON key and values into strong typed propreteis of all simple types (int, string, etc.), enums, and `Dictionary<string,string>` automatically. However, the `PagerOptions.Layout`, `PagerItemOptions.Content` and `PagerItemOptions.Link` are complex types, thus the package provides different converters to convert string based values into actual implementations, the supported configuration string a listed below (allow constant string in `Format` column is case insensitive):\n\n##### For `PagerOptions.Layout`\n\nFormat|Description|Example\n------|-----------|-------\n`Default`|Use default layout setting, please see `PagerLayouts.Default`|*N/A*\n`Custom:{items}`|Use a custom layout, `{items}` is a comma `,` splitted string, in which each term is one enum item of `PagerLayoutElement`(case insensitive)|`Custom:GoToFirstPageButton,Items`: The pager will contains a standalone \"go to first page\" button and a list of pages, no other buttons will be displayed.\n\n##### For `PagerItemOptions.Content`\nFormat|Description|Example\n------|-----------|-------\n`Text:{text}`|Use a fixed text as item content, string will be HTML-encoded before write to page.|`Text:Go to First` will display string \"Go to First\" in the button\n`Html:{html}`|Use a fixed text as item content, string will **NOT** be HTML-encoded before write to page.|`Html:&lsaquo;` will display a single left quote mark (\"<\") in the button\n`TextFormat:{format}`|Use a format string as item content, the placeholder `{0}` will be the page number.string will be HTML-encoded before write to page.|`TextFormat:Page {0:d}` will display `Page 3` for the 3rd page\n`HtmlFormat:{format}`|Same as above, but the content will **NOT** be HTML-encoded.|*N/A*\n`Default`|Use the default settings which display the page number only (equivelent to `Text:{0:d}`)|*N/A*\n\n*Note: There are many other generators in `PagerItemContentGenerators` and `PagerItemLinkGenerators` class, however, many of them cannot be describe and created from a string configuration. In order to use them, you must craete them in your code.*\n\n##### For `PagerItemOptions.Link`\nFormat|Description|Example\n------|-----------|-------\n`Query:{name}={format}`|Add a query parameter with a fixed name and a formatted query parameter value as link.|`Query:page={0:d}` will generate append(or replace) a query parameter `page=3` of the 3rd page on the current URL\n`QueryName:{name}`|Add a query parameter with a fixed name, the value of the parameter will be the page number.|`QueryName:page`  will generate append(or replace) a query parameter `page=3` of the 3rd page on the current URL\n`Default`|Use the default setting (equivelent to `QueryName:page`)|*N/A*\n`Format`|Use a format string as item link, the placeholder `{0}` will be the page number.|`Format:/Index?page={0:d}` will generate a link `/Index?page=3` for the 3rd page\n`Fixed`|Use a fixed string as item link.|`Fixed:/Index/Home` will generate a link `/Index/Home` for the pager item\n`Disabled`|Generate a null string as link, which will cause the button non clickable|*N/A*\n\n#### Additional Settings\nBoth `PagerOptions` and `PagerItemOptions` contains a property named `AdditioanlSettings`. This settings are automatically inheritted, which means the settings is the `PagerOptions` will be merged into each `PagerItemOptions` at runtime, and the different types of `PagerItemOptions` will also be merged as the same behavior for other properties described above.\n\nThis settings are not used in `PagerTagHelper` itself, however, the generators may use them for extra customization. In the next section you will see how the default `BootstrapPagerHtmlGenerator` use it for customize element generation.\n\n#### Shortcut Settings\nCreating a `PagerOptions` at each time is boring. In order to shorten the developement time, the `<pager>` tag provide several shortcut attribute in order to partial customize the options. Currently the available attributes are:\n\nName|Description\n----|-----------\n`item-default-content`|The default content generator. It refers `PagerOptions.ItemOptions.Default.Content`\n`item-default-link`|The defualt link generator. It refers `PagerOptoins.ItemOptions.Default.Link`\n`settings` and `setting-*`|Additional settings for `PagerOptions.` You may use `settings` to set the entire dictionary, or use `setting-*` to set one item. e.g. You can use `setting-mydata=\"1\"` to set item \"mydata\" with value \"1\" (Just like the design of MVC `asp-route-*` attributes).\n\nHere's a sample to use shortcut settings:\n```HTML\n<pager item-default-link='PagerItemLinkGenerators.QueryName(\"datapage\")' />\n```\n\nIn the above code, the pager will change use a custom link generator instead of the default generator configured globally.\n\n### Generators and Customization\n\nPagers are complex objects. Its building process consists of many steps, including calculating page numbers, generating button links and contents, and building-up HTML strutures. All these work is done by a service named `IPagerGenrator`. In order to generate a pager, you must register one implementation of this service at application startup time, or specify one in the tag like:\n```HTML\n<pager generator=\"yourGenrator\" />\n```\nThe default generator is named `DefaultPagerGenrator`, which is included in this package. If you would like to use another generator to get a pager of new style, you may register a new one and replace it.\n\n#### Default Generator and Partial Customization\nHere, I would like to provide a brief introduction to the default generator. It uses 3 different sub-services to finally generate the complete pager HTML. These services are:\n\n##### `IPagerListGenerator`\nThis service is used to generate a logical `PagerList` according to the pager information provided in the tag context. The logical `PagerList` describe the type and page number (if any) of each pager item, and their content and link generation manner. However, the visual information (e.g. the visual state and actual content) is not included. The `DefaultPagerListGenerator` class implements it by default.\n\n##### `IPagerRenderingListGenerator`\nThis service is used to generate the visual `PagerRenderingList`, in which the visual information for the pager is generated, including its content HTML ,its link address, and visual state. The logical information like page number is no longer accessible. The `DefaultPagerRenderingListGenerator` class implements it by default.\n\n##### `IPagerHtmlGenerator`\nThis service is used to finally build-up the entire HTML result. Since the `PagerRenderingList` only describe the information for the pager elements, it is still lack of the entire HTML structure, while this service take the responsibility for generating them. This service may take a lot of HTML operations, and the style of the final result is usually very limited by its implementation. If you just want to generate a pager with a different style, you may consider to find a new implementation for this service and register it before the default `BootstrapPagerHtmlGenerator` works.\n\n#### Customization for Default Implementation\n\nThe default `BootstrapPagerHtmlGenerator` generate a bootstrap style pager, which includes a `<ul>` container, a series of `<li>` items, and each of them contains an `<a>` or `<span>` element accroding to the pager button's style.\n\nIn order to enhance the customization ability for this pager, the `BootstrapPagerHtmlGenerator` is designed to accept the following additional settings:\n- Any setting with a name start with `list-attr-` will be append to the root `<ul>` element\n- Any setting with a name start with `item-attr-` will be append to the `<li>` element\n- Any setting with a name start with `link-attr-` will be append to the `<a>` and `<span>` element\n\nFor example, you may use the following code to set the `id` of the generated `<ul>` element as `myPager`:\n```HTML\n<pager setting-list-attr-id=\"myPager\" />\n```\n\n#### Enabling AJAX\nAJAX navigation is an common feature for a lots of mordern web applications. It should be pointed out that navigation is not the core business for a pager, but a feature on HTML interaction. Thus, this feature is not provided in the `PagerOptions` level, and you should ask the `IPagerHtmlGenerator` service to support AJAX navigation.\n\nFor the default `BootstrapPagerHtmlGenerator`, it is suggested that use the **Microsoft jQuery Unobtrusive Ajax** library as your AJAX support library. This library allows you to add static HTML attributes to control the AJAX behavior, which is highly compatible with this package. For example, a simple way to just enable AJAX nagivation is like:\n```HTML\n<pager setting-link-attr-data-ajax=\"true\" />\n```\nThe above code will generate all `<a>` element with a setting of `data-ajax=\"true\"`, which can enable the AJAX navigation for links. For more detailed information about AJAX behaviour controlling, please read the offical documentation of **Microsoft jQuery Unobtrusive Ajax** library.\n\n#### Bootstrap Version Compatibility Notes\nBootstrap V4 has changed the HTML class requirement for pagination elements, which requires `page-item` and `page-link` classes must be explicitly specified on the `<li>` and `<a>`(or `<span>`) element in the pager. Since the package version 2.0.11 of `Sakura.AspNetCore.Mvc.PagedList`, the default `BootstrapPagerHtmlGenerator` will automatically adds these classes on all related pager elements. This behaviour usually does not make downside effects on pages using Boostrap version 3 since these classes are not defined in the Version 3 CSS files, and thus no actual UI effect will be raised. However, if these classes are defined in your web site's style files and they DO affect your pager appearance, you may use the following setting to disable these behaviour:\n```HTML\n<pager setting-disable-bootstrap-v4-class=\"true\" />\n```\n\n### Future Works and Contribution\n\nThe author is planned to add the following new features:\n- [ ] Hash (Fragment) based URL generators\n- [ ] `FormatProvider` controlling for generators\n- [x] `IsReversed` property for `PagerOptions`\n- [ ] Extenders for pager generators\n\nAnyone who want to help improve the library is very welcome~\n"
  },
  {
    "path": "PagerDemov1.md",
    "content": "# ASP.NET 5 Data Paging and Pager Usage Guide\n\n*NOTE: This is a documenation old version, we recommend you to use version newest of `Sakura.AspNetCore.Mvc.PagedList` package. The documentation for the newest package can be found [here](PagerDemo.md).*\n\nThis page will give you a step-by-step guide to use ASP.NET 5 MVC6 Data Paging and Pager features. This guide includes the following topics:\n- How to paging your data source\n- How to enumerate your data page and get paging information\n- How to show a pager in your MVC6 Web Page\n\n## Data Paging\n\nTo use MVC6 Pager, you must first paging your data source (which may come from a EntityFramework or memory query) into an `IPagedList` instance. The easiest way to create it is using the extension methods defined in `Sakura.AspNet.PagedListCreationHelper` class. The following code shows the basic way to use them:\n\n```C#\n// Import extension methods in PagedListCreationHelper class.\nusing Sakura.AspNet;\n\nvar pageNumber = 1; // Note that page number starts from 1 (not zero!)\nvar pageSize = 10;\n\n// data is assumed as coming from an EntityFramework DbSet here. All data source with IEnumerable<T> and IQueryable<T> are both supported with different implementations.\nvar data = from i in SportsManageModel.Players select i;\n\n// The IPagedList type, which contains a partial view for the current page, and the paging information.\nvar pagedData = data.ToPagedList(pageSize, pageNumber);\n```\n## Paged Data Access\n\nIn MVC Projects, you may pass the paged data source from your controller into your view, and iterates the current pages easily. The following code shows the basic way:\n```C#\n// In MVC Controller\npublic IActionResult MyDataView()\n{\n  // Code for data source access is omitted here.\n  var pagedData = data.ToPagedList(pageSize, pageNumber);\n  return View(pagedData);\n}\n```\n\n```C#\n// In MVC View\n@model Sakura.AspNet.IPagedList<Player>\n\n@* The TotalCount property of a IPagedList is used to indicate the count of all (non-paged) items. *@\n<span>Total Players: @Model.TotalCount </span>\n\n<ul>\n  @* Note: IPagedList implements the enumration for current page. (data not in current page will not be accessed.) *@\n  @foreach (var i in Model)\n  {\n    <li>@i.Name</li>\n  }\n</ul>\n\n```\n\n*Note: Actually, the `IPagedList` is not related with the MVC platform, which means it can be used in any .NET Core Platform targeted project (Including ASP.NET Class Library and ASP.NET Web Application).*\n\n## Pager\n\nPager feature is much more complex, since it is related with data handling, HTML generation, and MVC view code writing. The detailed step-by-step configuration is list as following.\n\n### Pager Configuration\nBefore you use pager feature, you must first configure pager options. Pager options is represented using `Sakura.AspNet.Mvc.PagerOptions` class, which controls the behavior for pager generation. A simple pager options defination may created as below:\n```C#\nusing Sakura.AspNet.Mvc;\n\nvar pagerOptions = new PagerOptions\n{\n  ExpandPageLinksForCurrentPage = 2, // Will display more 2 pager buttons before and after current page.\n  PageLinksForEndings = 2, // Will display 2 pager buttons for first and last pages.\n  Layout = PagedListPagerLayouts.Default, // Layout controls which elements will be displayed in the pager. For more information, please read the documentation.\n\n  // Options for all pager items.\n  Items = new PagerItemOptions\n  {\n    TextFormat = \"{0}\", // The format for the pager button text, here means the content is just the actual page number. This property is used with string.Format method.\n    LinkParameterName = \"page\", // This property measn the generated pager button url will append the \"page={pageNumber}\" to the current URL.\n  }, \n  \n  // Configure for \"go to next\" button\n  NextButton = new SpecialPagerItemOptions \n  {\n    Text = \"Next\",\n    InactiveBehavior = SpecialPagerItemInactiveBehavior.Disable, // When there is no next page, disable this button\n\t\tLinkParameterName = \"page\"\n  }, \n\t\n\t// Configure for \"go to previous\" button\n\tPreviousButton = new SpecialPagerItemOptions\n\t{\n    Text = \"Previous\",\n    InactiveBehavior = SpecialPagerItemInactiveBehavior.Disable, // When there is no next page, disable this button\n\t\tLinkParameterName = \"page\"\n\t},\n\t\n\t// Configure for \"go to first page\" button\n\tFirstButton = new FirstAndLastPagerItemOptions\n\t{\n\t\tText = \"First\",\n\t\tActiveMode = FirstAndLastPagerItemActiveMode.Always,\n\t\tInactiveBehavior = SpecialPagerItemInactiveBehavior.Disable,\n\t\tLinkParameterName = \"page\",\n\t},\n\t\n  // Configure for \"go to last page\" button\n\tLastButton = new FirstAndLastPagerItemOptions\n\t{\n\t\tText = \"Last\",\n\t\tActiveMode = FirstAndLastPagerItemActiveMode.Always,\n\t\tInactiveBehavior = SpecialPagerItemInactiveBehavior.Disable,\n\t\tLinkParameterName = \"page\",\n\t},\n\t\n\t// Configure for omitted buttons (placeholder button when there's too many pages)\n\tOmitted = new PagerItemOptions\n\t{\n\t\tText = \"...\",\n\t\tLink = string.Empty // disable link\n\t},\n}\n```\n### Pager Generation Service\nNext, you also must have an `IPagerHtmlGenerator` service registered in your ASP.NET 5 application. The easiest way is using the default implementation using bootstrap theme. You can active this service in the `ConfigureServices` method in your ASP.NET 5 Startup class as below:\n\n```C#\npublic class Startup\n{\n  public void ConfigureServices(IServiceCollection services)\n  {\n    // other service configuration of your application\n    services.UseBootstrapPagerGenerator();\n  }\n}\n\n```\nIf you uses different theme or frontend framework in your project, you may implement your own `IPagerHtmlGenerator` for custom UI.\n\n### Pager Tag-Helper Directive\nAfter all these above steps, now you are ready to show a pager in your rozar page. First, you must add tag helpers for pager directive, in `_ViewImports.cs`, add the folloing line:\n```Rozar\n@addTagHelper \"*, Sakura.AspNet.Mvc.PagedList\"\n```\nThen, you can enable pager UI for any <nav> element in your HTML page. The method is very simple as:\n```HTML\n<nav asp-pager-source=\"myPagedList\" asp-pager-options=\"myPagerOptions\" ></nav>\n```\nIn the above example, `myPagedList` are an `IPagedList` object which represent the paged data, and `myPagerOptions` are your pager options (no @ perfix is needed because the value of both attribute are defined as C# object). You can define the pager options anywhere before this directive. In most cases, your paged data is just the model of your view, thus your can change the above code as:\n```HTML\n<nav asp-pager-source=\"Model\" asp-pager-options=\"myPagerOptions\" ></nav>\n```\n\nIf you want to use an unified pager option for all of your pages, you can configure the default pager options in `UseBootstrapPagerGenerator` method like:\n```C#\nservices.UseBootstrapPagerGenerator(pagerOptions => \n{\n  pagerOptions.Items = new PagerItemOptions { /* ... */ };\n  // ...\n});\n```\nThen you can omit the `asp-pager-options` attribute, and your final code may be\n```HTML\n<nav asp-pager-source=\"Model\"></nav>\n```\n-------------------\n# FAQ\n\n- *Is there any build-in AJAX support for this library?*\nUnfortunately there is no build-in AJAX support in the current version. You may use JQuery scripts to enable AJAX nagivation for linkes, a sample may be:\n```Javascript\n$(document).ready(function(){\n  // Add the 'data-ajax' attribute to all anchor in the pager \n  $('#your-pager-id').find('a').attr('data-ajax', 'true');\n});\n```\nThe author a designing a new version of this library, and in the new version AJAX support will be added as an build-in feature.\n"
  },
  {
    "path": "README.md",
    "content": "# ASP.NET Core Utility\n\nThis solution add some useful features for ASP.NET Core projects. All projects in this solution are designed for cross-platform  \"netstandard\" and triditional .NET Full \"net\" frameworks.\n\nThis solution contains the following aspects:\n\n* Tag Helpers\n* Middlewares\n* Utilities\n\n---\n\n## Current Project List\n\nThis section lists all projects in the repo. The list will always update in time.\n\n### External Cookie Services\n\n*Nuget Package Name: `Sakura.AspNetCore.Authentication.ExternalCookie`*\n\nASP.NET Core Identity Service (`Microsoft.AspNet.Identity` package) already added support for external cookie services, which is required for 3rd application authentication scheme (e.g. Microsoft of Fackbook account). You may use `AddIdentity` and `UseIdentity` method to enable external cookie services. However, in sometimes you may wish to enable external services indivindually without full ASP.NET Identity Service enabled. This project seperates the external cookie services from ASP.NET Identity Services, and may be used in more complex authentication scenes.\n\nThe major feature of this project contains:\n\n* Configure ASP.NET Core Application to support ApplicationCookie and ExternalCookie services (provides extension methods can be used in `ConfigureServices` method of ASP.NET Startup class)\n* Provide ExternalSignInManager service to simpify external cookie ticket management.\n\n\n### ASP.NET Core MVC TagHelper Extension Library\n\n*Nuget Package Name: `Sakura.AspNetCore.Mvc.TagHelpers`*\n\nProvide new tag helpers in order to simplify MVC code writing. For more details, please visit the [Tag Helper Demo](TagHelperDemo.md) page.\n\n### ASP.NET TempData Extension Package\n\n*Nuget Package Name: `Sakura.AspNetCore.Mvc.TempDataExtensions`*\n\nThis project provides the `EnhancedSessionStateTempDataProvider` service provider, with can replace the original `SessionBasedTempDataProvider`, in order to enhance the type compatibility for session data. The original TempData provider can only work with primitive types, arrays, and one-level plain-objects, objects with complex properties are not supported. The `EnhancedSessionStateTempDataProvider` can successfully work with most data objects with arbitray level structures.\n\n Internally, it uses certain serializer techinque to convert complex data into string value, and store it together with its type full name. When application loading its content, it will deserialize the string to recover the object data structure. The default implmementation uses `JsonObjectSerializer`, and you may also replace it with your own implementation if necessary.\n\n\n### ASP.NET Core MVC Messages Package\n\n*Nuget Package Name: `Sakura.AspNetCore.Mvc.Messages`*\n\nThis project add the feature of common operation message response in web applications, as well as tag helpers to simplify message presentations. The detailed features includes:\n\n* `OperationMessage` definitions and different `OperationMessageLevel` enum items.\n* `IOperationMessageAccessor` service, which can be inject in both views and controllers to access operation message data.\n* `MessageTagHelper` helper class, and you may use `asp-message-list` attribute on `div` element to generate message list UI, with various styles and additional layout options can be specified.\n* `IOperationMessageHtmlGenerator` service, wich is used internally for generating message list UI, and default bootstrap style generator are built-in implemented.\n\n*NOTE: To use this package, your `ITempDataProvider` implementation must have the ability to store and load `ICollection<OperationMessage>` instance. Defaultly, the ASP.NET5 `SessionStateTempDataProvider` cannot support data operation of complex objects. You may change into another `ITempDataProvider` implementation, or just use `EnhancedSessionStateTempDataProvider` in the `ASP.NET TempData Extension Package` project.\n\n### ASP.NET Core PagedList Packages\n\n*Nuget Package Name:*\n- *`Sakura.AspNetCore.PagedList`*\n- *`Sakura.AspNetCore.PagedList.Async`*\n- *`Sakura.AspNetCore.Mvc.PagedList`*\n\nThe `Sakura.AspNetCore.PagedList` package provides the `IPagedList` core interface to represent as a data page for a large data source. Some extension methods are also provided to generate instance from any `IEnumerable<T>` or `IQueryable<T>` data sources.\n\nThe `Sakura.AspNetCore.PagedList.Async` package helpes you to generate `IPagedList` using async extension method (`ToArrayAsync`, etc.) defined in `Microsoft.EntityFrameworkCore` package.\n\nThe `Sakura.AspNetCore.Mvc.PagedList` allows you to use `<pager>` tag in your MVC view page to generate a full featured pager structure.\n\n*For detailed usage, please visit the [Demo](PagerDemo.md) page. Notice: this package has been updated to version 2 (the recommended version)． For usage of version 1, please visit the [Version 1 Demo](PagerDemov1.md) page.*\n\n### ASP.NET ActionResult Extensions Package\n\n*Nuget Package Name: `Sakura.AspNetCore.Mvc.ActionResultExceptionExtensions`*\n\nIn MVC Projects, you need to return a instance of `IActionResult` to finish the action pipeline, this design made it difficult to add common helper functions to make argument or permission checking and then report a specified status code directly to end user. This library allows you to terminate action executing pipeline directly with a specified result through the standard exception handling system. \n\nIn order to enable this feature, all you need is adding an `EnableActionResultException` attribute on a controller or action, and then you can throw an `ActionResultException`instance to terminate a action executing pipeline directly and provide the final action result. If you need to enable this feature globally, you can use `EnableActionResultExceptionFilter` extension method on `MvcOptions` parameter when you add the MVC middleware.\n\n### ASP.NET Core MVC Dyanmic Localizer Package\n\n*Nuget Package Name: `Sakura.AspNetCore.DynamicLocalizer`*\n\nASP.NET Core 1.0 introduced a new localization design model, which allow developers to access localized resources using `IStringLocalizer`, `IHtmlLocalizer` or `IViewLocalizer` service instances. In order to keep compatibilty and reduce the time cost for switching the developing time single language website into production multiple language implementation, ASP.NET Core team suggests developers to use string context itself as the language resource keys. e.g. the following code\n```HTML\n@ViewLocalizer[\"Hello World\"]\n```\nwhile output the key string \"hello world\" when there's no resource files defined or the resource for current culture is unavialable.\n\nAlthough this design reduces the time cost of enabling multiple language support, lots of developers may still build the website with multiple language support from the beginning. The above manner may not be optimized for these scenes, since under such circumstance, developers may choose to use identifier-like word as keys for long messages, e.g. the final code in the CSHTML file maybe: \n```HTML\n@ViewLocalizer[\"HelloWorldMessage\"]\n```\nHowever, actually developers may love the following code style much more:\n```HTML\n@ViewLocalizer.HelloWorldMessage\n```\nThe above code style looks with much more object-oriented code style, and may also take a lot of editing and compiling time benefits such as identifier checking, searching and intellisence. This behaviour is also favorite for old-time .NET Resource Manager based localizaable applications, in which each resources is named with an identifier and the resource file generators will help you to generate classes for resources, and developers may use strong named propreties to access resources. \n\nThe `Sakura.AspNetCore.DynamicLocalizer` package now provides a simplified simulation for object-oreinted resource accessing style using .NET dynamic objects. For a simple usage, you may install this package and add the necessary services on startup using the following code:\n\n```C#\npublic void ConfigureServices(IServiceCollection services)\n{\n  // other service configurations here\n  \n  // Note: the following base services are necessary and must be also added manually in your startup code\n  services.AddLocalization();\n  services.AddMvc().AddViewLocalization();\n  \n  // add core services used for dynamic localizer\n  services.AddDynamicLocalizer();\n}\n```\n\nAnd now in MVC views, you may using dyanmic object as localization services, the basic usage is shown in the following code:\n\n```HTML\n@inject Sakura.AspNetCore.IDynamicViewLocalizer ViewLocalizer\n@inject Sakura.AspNetCore.IDynamicHtmlLocalizer<MyResource> MyResourceLocalizer\n\n<p>@ViewLocalizer.Html.WelcomeMessage</p>\n<p>@ViewLocalizer.Html.UserNameFormat(ViewBag.UserName)</p>\n\n<p>@MyResourceLocalizer.Html.ImportantTip<Hello>\n```\nMore specifically, the relationship between original localizers and dynamic localizers are shown as bellow:\n\n|Original|Dynamic|\n|--------|-------|\n|`IViewLocalizer`|`IDyanmicViewLocalizer`|\n|`IHtmlLocalizer<T>`|`IDynamicHtmlLocalizer<T>`|\n|`IStringLocalizer<T>`|`IDynamicStringLocalizer<T>`|\n\nThe following tables showes the supported invocation syntax for dynamic localizers (words in braces are identfier placeholders):\n\n|Syntax|Equivelant Orignal Syntax|Notes|\n|------|-------------------------|-----|\n|`localizer.Html.{Key}`|`localizer.GetHtml(\"{Key}\")]`|This method is not available in `IStringLocalizer`|\n|`localizer.Html.{Key}({Value1}, {Value2})`|`localizer.GetHtml(\"{Key}\", Value1, Value2)]`|This method is not available in `IStringLocalizer`|\n|`localizer.Text.{Key}`|`localizer.GetString(\"{Key}\")`|Allowed in all localizers|\n|`localizer.Text.{Key}({Value1}, {Value2})`|`localizer.GetString(\"{Key}\", Value1, Value2)]`|Allowed in all localizers|\n\nNote: The behaviour default index-style syntax (e.g. `localizer[\"Key\", Value1, Value2]`) depends on the type of the localizer. For `IViewLocalizer` and `IHtmlLocalizer`, it is equivelant to `GetHtml`,  while for `IStringLocalizer`, it's equivelant to `GetString`.\n\nFor compatbility reason, the dynamic localizer also support the index style syntax, which will generate the same effect for the new syntax, e.g. `localizer.Html[\"{Key}\", Value1, Value2]` are equivelant to `localizer.Html.Key(Value1, Value2)`.\n\n### ASP.NET Entity Framework Core FromSql Extension Package\n\n*Nuget Package Name: `Sakura.EntityFrameworkCore.FromSqlExtensions`*\n\nIn Entity Framework Core 2.1, the concept of the new `QueryType` is introduced, which allows developers to mapping non-table-based query results (e.g. data comes from a database view or a stored procedure) to CLR types. However, in order to use query types, you must first include it in your model. This restriction will cause you to take a lot of unnecessary work when you want to executing a raw SQL statement directly.\n\nMore specifically, let's see an example. You want to query some data using a stored procedure named \"GetTopStudents\" from your database, you know the procedure will produce the top 10 students with highest scores along with their name, and then you may define a query type as well as the following executing codes:\n\n```C#\npublic class TopStudentInfo\n{\n  public string Name { get; set; }\n  public int Score { get; set; }\n}\n\n// The following code is used to extract the top student infomation.\nvar result = MyDbContext.Query<TopStudentInfo>().FromSql(\"EXEC [GetTopStudents]\");\n```\n\nHowever, when you try to execute the code above, Entity Framework Core will tell you that the `TopStudentInfo` is not included in the model. In order to fix this exception, you must first include it just like:\n\n```C#\npublic class StudentDbContext : DbContext\n{\n  public virtual DbQuery<TopStudentInfo> TopStudents { get; set; }\n}\n```\n\nAnd then you can get the result using either \n```C#\nMyDbContext.Query<TopStudentInfo>().FromSql(\"EXEC [GetTopStudents]\")\n```\nor\n```C#\nMyDbContext.TopStudents.FromSql(\"EXEC [GetTopStudents]\")\n```\n\nThe reason why EF requires you to include the query type is to support a chained-query just like `from i in MyDbContext.TopStudents select i`. Under such circumstance, the query type must be included and mapped with either `ToView` or `ToQuery` to declare its original data source. However, if you just want to take usage of the `FromSql` method, the mapping step will be reduntant, but you still need to define the query type in your `DbContext` class. \n\nConsidering that you may executing serveral different raw SQL queries or stored procedures in your project. For each of them, you must include the query type into your model individually and this requirement will be a quite boring work. Now the `Sakura.EntityFrameworkCore.FromSqlExtensions` package provides extension methods for including query types and executing raw SQL statements directly, now you may using the following code:\n\n```C#\nvar result = MyDbContext.FromSql<TopStudents>(\"EXEC [GetTopStudents]\");\n```\n\nto get the result without explicitly defining a context-level `DbQuery` instance.\n\n*Note: Internally, this extension method will register the query type into the model globally when you call it if it not exists before, and thus after you call this method sereval times with different types, there will be a huge number of query types added in your model. It may seems to be a side-effect (although adding a new query type actually does not generate performance burden for EF Core), while is problem is caused by the internal design of the EF Core and currently it is not possible to implement query-level type registrations.*\n\n---\n\n## Contribution and Discussion\n\nYou are welcome to add issues and advices, if you want to contribute to the project, please fell free to make pull requests.\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Directory.Build.props",
    "content": "﻿<Project>\n    <PropertyGroup>\n        <LangVersion>latest</LangVersion>\n        <Nullable>enable</Nullable>\n        <CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>\n        <WarningLevel>9999</WarningLevel>\n    </PropertyGroup>\n</Project>"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.AsyncPagedList/AsyncCacheable.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\n\nnamespace Sakura.AspNetCore\n{\n\t/// <summary>\n\t///     Represent as a cachable data.\n\t/// </summary>\n\t/// <typeparam name=\"T\">The type of the data to be caching。</typeparam>\n\t/// <seealso cref=\"ICacheControl\" />\n\tpublic class AsyncCacheable<T> : ICacheControl\n\t{\n\t\t/// <summary>\n\t\t///     Auto refreshing counter used by refreshing control feature.\n\t\t/// </summary>\n\t\tprivate int _AutoRefreshControlCount;\n\n\t\t/// <summary>\n\t\t///     Initialize a new instance using specified information.\n\t\t/// </summary>\n\t\t/// <param name=\"getDataCallback\">The callback delegate to obtain the data to be caching.</param>\n\t\t/// <param name=\"cacheCallback\">\n\t\t///     A optional callback delegate to generate a cached copy for original data. If this parameter\n\t\t///     is <c>null</c>, <see cref=\"DefaultCacheCallback\" /> will be used.\n\t\t/// </param>\n\t\t/// <param name=\"cacheMode\">\n\t\t///     The default cache mode of the cache. The default value of this parameter is\n\t\t///     <see cref=\"CacheMode.Manual\" />.\n\t\t/// </param>\n\t\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"getDataCallback\" /> is <c>null</c>.</exception>\n\t\tpublic AsyncCacheable(Func<Task<T>> getDataCallback, Func<T, T> cacheCallback = null, CacheMode cacheMode = CacheMode.Manual)\n\t\t{\n\t\t\tif (getDataCallback == null)\n\t\t\t{\n\t\t\t\tthrow new ArgumentNullException(nameof(getDataCallback));\n\t\t\t}\n\n\t\t\tGetDataCallback = getDataCallback;\n\n\t\t\t// Set the caching callback.\n\t\t\tCacheCallback = cacheCallback ?? DefaultCacheCallback;\n\n\t\t\t// Set the caching mode.\n\t\t\tCacheMode = cacheMode;\n\n\t\t\t// Trigger the first dismiss notification.\n\t\t\tNotifyDismiss();\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Get the callback delegate for generating a cached copy.\n\t\t/// </summary>\n\t\tprivate Func<T, T> CacheCallback { get; }\n\n\t\t/// <summary>\n\t\t///     Get the callback delegate for get the data to be caching.\n\t\t/// </summary>\n\t\tprivate Func<Task<T>> GetDataCallback { get; }\n\n\t\t/// <summary>\n\t\t///     Get the cached data. If no cache is available, the default value of <typeparamref name=\"T\" /> will be returned.\n\t\t/// </summary>\n\t\tpublic T CachedData { get; private set; }\n\n\t\t/// <summary>\n\t\t///     Get a value that indicates whether the cache is available now.\n\t\t/// </summary>\n\t\tpublic bool IsCached { get; private set; }\n\n\n\t\tpublic async Task<T> GetDataAsync()\n\t\t{\n\t\t\t// Alaways return the cache if available.\n\t\t\tif (IsCached)\n\t\t\t{\n\t\t\t\treturn CachedData;\n\t\t\t}\n\n\t\t\t// No caching if caching mode is set to \"manual\".\n\t\t\tif (CacheMode == CacheMode.Manual)\n\t\t\t{\n\t\t\t\treturn await GetDataDirectlyAsync();\n\t\t\t}\n\n\t\t\t// Otherwise, reload the cache and return the result.\n\t\t\tawait ReloadAsync();\n\t\t\treturn CachedData;\n\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Forcely remove the current cache and reload it.\n\t\t/// </summary>\n\t\tpublic async Task ReloadAsync()\n\t\t{\n\t\t\tCachedData = CacheCallback(await GetDataCallback());\n\t\t\tIsCached = true;\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Generate a cache from the data source. If the data is already cached, this method will do nothing.\n\t\t/// </summary>\n\t\tpublic async Task CacheAsync()\n\t\t{\n\t\t\tif (!IsCached)\n\t\t\t{\n\t\t\t\tawait ReloadAsync();\n\t\t\t}\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Uncache the current value. This method will make cache dismissed, but will not automatically remove or refresh the\n\t\t///     cached data\n\t\t/// </summary>\n\t\tpublic Task UncacheAsync()\n\t\t{\n\t\t\tIsCached = false;\n\t\t\treturn Task.CompletedTask;\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Get or set the cache mode of the current object.\n\t\t/// </summary>\n\t\tpublic CacheMode CacheMode { get; set; }\n\n\t\t/// <summary>\n\t\t///     The default value for <see cref=\"CacheCallback\" />\n\t\t/// </summary>\n\t\t/// <param name=\"item\">The data to be cached.</param>\n\t\t/// <returns>The cached data.</returns>\n\t\t/// <remarks>This method will do nothing.</remarks>\n\t\tprivate static T DefaultCacheCallback(T item) => item;\n\n\t\t/// <summary>\n\t\t///     Get data directly from the source and ignore any cache.\n\t\t/// </summary>\n\t\t/// <returns>The data got directly from the source.</returns>\n\t\tpublic Task<T> GetDataDirectlyAsync() => GetDataCallback();\n\n\t\t/// <summary>\n\t\t///     Notify the cached data is dismissed, and reload it if necessary.\n\t\t/// </summary>\n\t\tpublic async Task NotifyDismissAsync()\n\t\t{\n\t\t\t// Make the cache unavailable.\n\t\t\tIsCached = false;\n\n\t\t\t// Automatically reload the cache if cache mode is set to \"prefetch\" and currently caching refresh is enabled.\n\t\t\tif (CacheMode == CacheMode.AutoWithPrefetch && _AutoRefreshControlCount == 0)\n\t\t\t{\n\t\t\t\tawait ReloadAsync();\n\t\t\t}\n\t\t}\n\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.AsyncPagedList/AsyncPagedList.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace Sakura.AspNetCore\n{\n\tpublic class AsyncPagedList<T> : IAsyncEnumerable<T>\n\t{\n\t\t#region Constructors\n\n\t\t/// <summary>\n\t\t///     Initialize a new instance with specified information.\n\t\t/// </summary>\n\t\t/// <param name=\"source\">The data source to be paging.</param>\n\t\t/// <param name=\"pageSize\">The size of each page.</param>\n\t\t/// <param name=\"pageIndex\">The index of the current page, start from 1.</param>\n\t\t/// <param name=\"cacheOptions\">Additional cacheOptions for the paged list.</param>\n\t\tpublic AsyncPagedList(IAsyncEnumerable<T> source, int pageSize, int pageIndex = 1,\n\t\t\tDynamicPagedListCacheOptions cacheOptions = null)\n\t\t{\n\t\t\t// Exception handling\n\t\t\tif (source == null)\n\t\t\t{\n\t\t\t\tthrow new ArgumentNullException(nameof(source));\n\t\t\t}\n\n\t\t\t// Default parameters\n\t\t\tvar defaultOptions = new DynamicPagedListCacheOptions\n\t\t\t{\n\t\t\t\tCurrentPageCacheMode = CacheMode.Manual,\n\t\t\t\tTotalCountCacheMode = CacheMode.Manual\n\t\t\t};\n\n\t\t\t// Merge cacheOptions\n\t\t\tcacheOptions = cacheOptions ?? defaultOptions;\n\n\t\t\t// Initialize the data source\n\t\t\tSource = source;\n\n\n\t\t\t// Set Paging information\n\t\t\tPageSize = pageSize;\n\t\t\tPageIndex = pageIndex;\n\n\n\t\t\t// Caching total count for first time\n\t\t\tTotalCountCacheInternal = new AsyncCacheable<Task<int>>(GetTotalCountAsync, cacheMode: cacheOptions.TotalCountCacheMode);\n\t\t\t// Caching current page for first time\n\t\t\tCurrentPageCacheInternal = new AsyncCacheable<IAsyncEnumerable<T>>(GetCurrentPageAsync, CacheDataAsync, cacheOptions.CurrentPageCacheMode);\n\n\t\t\t// Set initialized flag\n\t\t\tIsInitialized = true;\n\t\t}\n\n\t\t#endregion\n\n\t\t#region Data Source\n\n\t\t/// <summary>\n\t\t///     Get the original data source.\n\t\t/// </summary>\n\t\tpublic IAsyncEnumerable<T> Source { get; }\n\n\t\t#endregion\n\n\t\t#region Core Features must be implemented in Derived Classes\n\n\t\t/// <summary>\n\t\t///     When be derived, returns the total count of the data source.\n\t\t/// </summary>\n\t\t/// <returns>The total count of the data source.</returns>\n\t\tprotected virtual Task<int> GetTotalCountAsync() => Source.Count();\n\n\t\t/// <summary>\n\t\t///     When be derived, get the elements in the current page.\n\t\t/// </summary>\n\t\t/// <returns>The collection of elements in the current page.</returns>\n\t\tprotected virtual IAsyncEnumerable<T> GetCurrentPageAsync()\n\t\t{\n\t\t\tvar skipValue = PageSize * (PageIndex - 1);\n\t\t\tvar takeValue = PageSize;\n\n\t\t\treturn Source.Skip(skipValue).Take(takeValue);\n\t\t}\n\n\t\t/// <summary>\n\t\t///     When be derived, Cache the data page.\n\t\t/// </summary>\n\t\t/// <param name=\"source\">The data page to be cached.</param>\n\t\t/// <returns>The cached copy of <paramref name=\"source\" />.</returns>\n\t\tprotected virtual async Task<IAsyncEnumerable<T>> CacheDataAsync(IAsyncEnumerable<T> source)\n\t\t{\n\t\t\treturn (await source.ToArray()).ToAsyncEnumerable();\n\t\t}\n\n\t\t#endregion\n\n\t\t#region Data Caching and Controllers\n\n\t\t/// <summary>\n\t\t///     Get the internal caching object for total count.\n\t\t/// </summary>\n\t\tprivate AsyncCacheable<int> TotalCountCacheInternal { get; }\n\n\t\t/// <summary>\n\t\t///     Get the internal caching object for current page.\n\t\t/// </summary>\n\t\tprivate AsyncCacheable<IAsyncEnumerable<T>> CurrentPageCacheInternal { get; }\n\n\t\t/// <summary>\n\t\t///     Get the cache controller for the total count cache.\n\t\t/// </summary>\n\t\tpublic ICacheControl TotalCountCache => TotalCountCacheInternal;\n\n\t\t/// <summary>\n\t\t///     Get the cache controller for the current page cache.\n\t\t/// </summary>\n\t\tpublic ICacheControl CurrentPageCache => CurrentPageCacheInternal;\n\n\t\t#endregion\n\n\t\t#region Cached Core Data Properties\n\n\t\t/// <summary>\n\t\t///     Get the data in current page.\n\t\t/// </summary>\n\t\tpublic Task<IAsyncEnumerable<T>> CurrentPage => CurrentPageCacheInternal.GetDataAsync();\n\n\t\t/// <summary>\n\t\t///     Get the total count of the data source.\n\t\t/// </summary>\n\t\tpublic Task<int> GetTotalCountAsync() => TotalCountCacheInternal.GetDataAsync();\n\n\t\t#endregion\n\n\t\t#region Paging Core Features\n\n\t\t/// <summary>\n\t\t///     The current page index.\n\t\t/// </summary>\n\t\tprivate int _PageIndex;\n\n\t\t/// <summary>\n\t\t///     The size of each page.\n\t\t/// </summary>\n\t\tprivate int _PageSize;\n\n\t\t/// <summary>\n\t\t///     Get or set the current page index. The page index is started from 1.\n\t\t/// </summary>\n\t\tpublic int PageIndex\n\t\t{\n\t\t\tget { return _PageIndex; }\n\t\t\tset\n\t\t\t{\n\t\t\t\t// If initialized, make detailed value check\n\t\t\t\tif (IsInitialized)\n\t\t\t\t{\n\t\t\t\t\t// Range check\n\t\t\t\t\tif (value < 1 || value > TotalPage)\n\t\t\t\t\t{\n\t\t\t\t\t\tthrow new ArgumentOutOfRangeException(nameof(value), value, \"The page index is out of valid range.\");\n\t\t\t\t\t}\n\n\t\t\t\t\tif (_PageIndex != value)\n\t\t\t\t\t{\n\t\t\t\t\t\t_PageIndex = value;\n\n\t\t\t\t\t\t// Notify data dismiss\n\t\t\t\t\t\tCurrentPageCacheInternal.NotifyDismiss();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// When initializing, skip the detailed check\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// Basic range check\n\t\t\t\t\tif (value < 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tthrow new ArgumentOutOfRangeException(nameof(value), value, \"The page index is out of valid range.\");\n\t\t\t\t\t}\n\n\t\t\t\t\t_PageIndex = value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Get or set the size of each page.\n\t\t/// </summary>\n\t\t/// <remarks>\n\t\t///     Modify this property will reset the <see cref=\"PageIndex\" /> to <c>1</c>. This may raise data refreshing. If you\n\t\t///     will change <see cref=\"PageIndex\" /> later, please condiser call <see cref=\"ICacheControl.DisableAutoRefresh\" /> on\n\t\t///     <see cref=\"CurrentPageCache\" /> property in order to improve the performance.\n\t\t/// </remarks>\n\t\tpublic int PageSize\n\t\t{\n\t\t\tget { return _PageSize; }\n\t\t\tset\n\t\t\t{\n\t\t\t\tif (value <= 0)\n\t\t\t\t{\n\t\t\t\t\tthrow new ArgumentOutOfRangeException(nameof(value), value, \"Page size must be positive integer\");\n\t\t\t\t}\n\n\t\t\t\t_PageSize = value;\n\t\t\t\tPageIndex = 1;\n\t\t\t}\n\t\t}\n\n\t\t#endregion\n\n\t\t#region Additional propreties\n\n\t\t/// <summary>\n\t\t///     Get a value that indicates if the object has been initialized.\n\t\t/// </summary>\n\t\tpublic bool IsInitialized { get; }\n\n\t\t/// <summary>\n\t\t///     Get the total page count of the data source.\n\t\t/// </summary>\n\t\tpublic int TotalPage => (TotalCount - 1) / PageSize + 1;\n\n\t\t/// <summary>\n\t\t///     Get the count of current page.\n\t\t/// </summary>\n\t\tpublic int Count\n\t\t{\n\t\t\tget\n\t\t\t{\n\t\t\t\t// Cache property value.\n\t\t\t\tvar totalCount = TotalCount;\n\n\t\t\t\t// Total page\n\t\t\t\t// NOTE: Cannot use propreties since it may cause duplicated computation\n\t\t\t\tvar totalPage = (totalCount - 1) / PageSize + 1;\n\n\t\t\t\t// Only the last page should be calculated.\n\t\t\t\treturn PageIndex != totalPage ? PageSize : totalCount - PageSize * (totalPage - 1);\n\t\t\t}\n\t\t}\n\n\t\t#endregion\n\n\t\t#region Interface Implementations\n\n\t\t/// <summary>\n\t\t///     Get the enumerator for the current object.\n\t\t/// </summary>\n\t\t/// <returns>The enumerator for the current object.</returns>\n\t\tpublic IAsyncEnumerator<T> GetEnumerator()\n\t\t{\n\t\t\treturn CurrentPage.GetEnumerator();\n\t\t}\n\n\n\t\t/// <summary>\n\t\t///     Get the data at the specified location in the current page.\n\t\t/// </summary>\n\t\t/// <param name=\"index\">The location index in the current page, starting from zero.</param>\n\t\t/// <returns>The element at the specified location in the current page.</returns>\n\t\tpublic Task<T> GetAsync(int index) => CurrentPage.ElementAt(index);\n\n\t\t#endregion\n\t}\n}\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.AsyncPagedList/IAsyncPagedList.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading.Tasks;\n\nnamespace Sakura.AspNetCore\n{\n\tpublic interface IAsyncPagedList<T> : IAsyncEnumerable<T>\n\t{\n\t\tTask<int> CountAsync();\n\t\tTask<int> TotalCountAsync();\n\n\t\tint PageSize { get; }\n\t\tint PageIndex { get; }\n\n\t\tTask<T> GetAsync(int index);\n\t}\n}\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.AsyncPagedList/PagedListCreationHelper.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing JetBrains.Annotations;\n// ReSharper disable PossibleMultipleEnumeration\nnamespace Sakura.AspNetCore\n{\n\t/// <summary>\n\t///     Provide extension methods for creating <see cref=\"IPagedList{T}\" /> instances. This class is static.\n\t/// </summary>\n\t[PublicAPI]\n\tpublic static class PagedListCreationHelper\n\t{\n\t\t/// <summary>\n\t\t///     Create a snapshot for one page of a <see cref=\"IAsyncEnumerable{T}\" /> object asynchronously.\n\t\t/// </summary>\n\t\t/// <typeparam name=\"T\">The element type in the data source.</typeparam>\n\t\t/// <param name=\"source\">The source <see cref=\"IEnumerable{T}\" /> object to be converting.</param>\n\t\t/// <param name=\"pageSize\">The size of each page.</param>\n\t\t/// <param name=\"pageIndex\">The index of the current page. Page index starts from 1.</param>\n\t\t/// <returns>A <see cref=\"PagedList{TSource,TElement}\" /> object created by paging the <paramref name=\"source\" /> object asynchronously.</returns>\n\t\tpublic static async Task<PagedList<IAsyncEnumerable<T>, T>> ToPagedListAsync<T>([NotNull] this IAsyncEnumerable<T> source, int pageSize, int pageIndex = 1)\n\t\t{\n\t\t\tif (source == null)\n\t\t\t{\n\t\t\t\tthrow new ArgumentNullException(nameof(source));\n\t\t\t}\n\n\t\t\tif (pageSize <= 0)\n\t\t\t{\n\t\t\t\tthrow new ArgumentOutOfRangeException(nameof(pageSize), pageSize, \"The page size must be positive.\");\n\t\t\t}\n\n\t\t\tif (pageIndex <= 0)\n\t\t\t{\n\t\t\t\tthrow new ArgumentOutOfRangeException(nameof(pageIndex), pageIndex, \"The page index must be positive.\");\n\t\t\t}\n\n\t\t\tvar skipValue = pageSize * (pageIndex - 1);\n\t\t\tvar takeValue = pageSize;\n\n\t\t\tvar currentPage = await source.Skip(skipValue).Take(takeValue).ToArray();\n\t\t\tvar totalCount = await source.Count();\n\t\t\tvar totalPage = (totalCount - 1) / pageSize + 1;\n\n\t\t\treturn new PagedList<IAsyncEnumerable<T>, T>(currentPage, source, pageSize, pageIndex, totalCount, totalPage);\n\t\t}\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.AsyncPagedList/Properties/Annotations.cs",
    "content": "﻿/* MIT License\n\nCopyright (c) 2016 JetBrains http://www.jetbrains.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE. */\n\nusing System;\nusing System.Diagnostics;\n\n#pragma warning disable 1591\n// ReSharper disable UnusedMember.Global\n// ReSharper disable MemberCanBePrivate.Global\n// ReSharper disable UnusedAutoPropertyAccessor.Global\n// ReSharper disable IntroduceOptionalParameters.Global\n// ReSharper disable MemberCanBeProtected.Global\n// ReSharper disable InconsistentNaming\n// ReSharper disable once CheckNamespace\n\nnamespace JetBrains.Annotations\n{\n\t/// <summary>\n\t///     Indicates that the value of the marked element could be <c>null</c> sometimes,\n\t///     so the check for <c>null</c> is necessary before its usage.\n\t/// </summary>\n\t/// <example>\n\t///     <code>\n\t/// [CanBeNull] object Test() => null;\n\t/// \n\t/// void UseTest() {\n\t///   var p = Test();\n\t///   var s = p.ToString(); // Warning: Possible 'System.NullReferenceException'\n\t/// }\n\t/// </code>\n\t/// </example>\n\t[AttributeUsage(\n\t\t AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |\n\t\t AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event |\n\t\t AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class CanBeNullAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Indicates that the value of the marked element could never be <c>null</c>.\n\t/// </summary>\n\t/// <example>\n\t///     <code>\n\t/// [NotNull] object Foo() {\n\t///   return null; // Warning: Possible 'null' assignment\n\t/// }\n\t/// </code>\n\t/// </example>\n\t[AttributeUsage(\n\t\t AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |\n\t\t AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event |\n\t\t AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class NotNullAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task\n\t///     and Lazy classes to indicate that the value of a collection item, of the Task.Result property\n\t///     or of the Lazy.Value property can never be null.\n\t/// </summary>\n\t[AttributeUsage(\n\t\t AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |\n\t\t AttributeTargets.Delegate | AttributeTargets.Field)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class ItemNotNullAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task\n\t///     and Lazy classes to indicate that the value of a collection item, of the Task.Result property\n\t///     or of the Lazy.Value property can be null.\n\t/// </summary>\n\t[AttributeUsage(\n\t\t AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |\n\t\t AttributeTargets.Delegate | AttributeTargets.Field)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class ItemCanBeNullAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Implicitly apply [NotNull]/[ItemNotNull] annotation to all the of type members and parameters\n\t///     in particular scope where this annotation is used (type declaration or whole assembly).\n\t/// </summary>\n\t[AttributeUsage(\n\t\t AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Assembly)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class ImplicitNotNullAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Indicates that the marked method builds string by format pattern and (optional) arguments.\n\t///     Parameter, which contains format string, should be given in constructor. The format string\n\t///     should be in <see cref=\"string.Format(IFormatProvider,string,object[])\" />-like form.\n\t/// </summary>\n\t/// <example>\n\t///     <code>\n\t/// [StringFormatMethod(\"message\")]\n\t/// void ShowError(string message, params object[] args) { /* do something */ }\n\t/// \n\t/// void Foo() {\n\t///   ShowError(\"Failed: {0}\"); // Warning: Non-existing argument in format string\n\t/// }\n\t/// </code>\n\t/// </example>\n\t[AttributeUsage(\n\t\t AttributeTargets.Constructor | AttributeTargets.Method |\n\t\t AttributeTargets.Property | AttributeTargets.Delegate)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class StringFormatMethodAttribute : Attribute\n\t{\n\t\t/// <param name=\"formatParameterName\">\n\t\t///     Specifies which parameter of an annotated method should be treated as format-string\n\t\t/// </param>\n\t\tpublic StringFormatMethodAttribute([NotNull] string formatParameterName)\n\t\t{\n\t\t\tFormatParameterName = formatParameterName;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic string FormatParameterName { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     For a parameter that is expected to be one of the limited set of values.\n\t///     Specify fields of which type should be used as values for this parameter.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class ValueProviderAttribute : Attribute\n\t{\n\t\tpublic ValueProviderAttribute([NotNull] string name)\n\t\t{\n\t\t\tName = name;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic string Name { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     Indicates that the function argument should be string literal and match one\n\t///     of the parameters of the caller function. For example, ReSharper annotates\n\t///     the parameter of <see cref=\"System.ArgumentNullException\" />.\n\t/// </summary>\n\t/// <example>\n\t///     <code>\n\t/// void Foo(string param) {\n\t///   if (param == null)\n\t///     throw new ArgumentNullException(\"par\"); // Warning: Cannot resolve symbol\n\t/// }\n\t/// </code>\n\t/// </example>\n\t[AttributeUsage(AttributeTargets.Parameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class InvokerParameterNameAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Indicates that the method is contained in a type that implements\n\t///     <c>System.ComponentModel.INotifyPropertyChanged</c> interface and this method\n\t///     is used to notify that some property value changed.\n\t/// </summary>\n\t/// <remarks>\n\t///     The method should be non-static and conform to one of the supported signatures:\n\t///     <list>\n\t///         <item>\n\t///             <c>NotifyChanged(string)</c>\n\t///         </item>\n\t///         <item>\n\t///             <c>NotifyChanged(params string[])</c>\n\t///         </item>\n\t///         <item>\n\t///             <c>NotifyChanged{T}(Expression{Func{T}})</c>\n\t///         </item>\n\t///         <item>\n\t///             <c>NotifyChanged{T,U}(Expression{Func{T,U}})</c>\n\t///         </item>\n\t///         <item>\n\t///             <c>SetProperty{T}(ref T, T, string)</c>\n\t///         </item>\n\t///     </list>\n\t/// </remarks>\n\t/// <example>\n\t///     <code>\n\t///  public class Foo : INotifyPropertyChanged {\n\t///    public event PropertyChangedEventHandler PropertyChanged;\n\t///  \n\t///    [NotifyPropertyChangedInvocator]\n\t///    protected virtual void NotifyChanged(string propertyName) { ... }\n\t/// \n\t///    string _name;\n\t///  \n\t///    public string Name {\n\t///      get { return _name; }\n\t///      set { _name = value; NotifyChanged(\"LastName\"); /* Warning */ }\n\t///    }\n\t///  }\n\t///  </code>\n\t///     Examples of generated notifications:\n\t///     <list>\n\t///         <item>\n\t///             <c>NotifyChanged(\"Property\")</c>\n\t///         </item>\n\t///         <item>\n\t///             <c>NotifyChanged(() =&gt; Property)</c>\n\t///         </item>\n\t///         <item>\n\t///             <c>NotifyChanged((VM x) =&gt; x.Property)</c>\n\t///         </item>\n\t///         <item>\n\t///             <c>SetProperty(ref myField, value, \"Property\")</c>\n\t///         </item>\n\t///     </list>\n\t/// </example>\n\t[AttributeUsage(AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class NotifyPropertyChangedInvocatorAttribute : Attribute\n\t{\n\t\tpublic NotifyPropertyChangedInvocatorAttribute()\n\t\t{\n\t\t}\n\n\t\tpublic NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName)\n\t\t{\n\t\t\tParameterName = parameterName;\n\t\t}\n\n\t\t[CanBeNull]\n\t\tpublic string ParameterName { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     Describes dependency between method input and output.\n\t/// </summary>\n\t/// <syntax>\n\t///     <p>Function Definition Table syntax:</p>\n\t///     <list>\n\t///         <item>FDT      ::= FDTRow [;FDTRow]*</item>\n\t///         <item>FDTRow   ::= Input =&gt; Output | Output &lt;= Input</item>\n\t///         <item>Input    ::= ParameterName: Value [, Input]*</item>\n\t///         <item>Output   ::= [ParameterName: Value]* {halt|stop|void|nothing|Value}</item>\n\t///         <item>Value    ::= true | false | null | notnull | canbenull</item>\n\t///     </list>\n\t///     If method has single input parameter, it's name could be omitted.<br />\n\t///     Using <c>halt</c> (or <c>void</c>/<c>nothing</c>, which is the same)\n\t///     for method output means that the methos doesn't return normally.<br />\n\t///     <c>canbenull</c> annotation is only applicable for output parameters.<br />\n\t///     You can use multiple <c>[ContractAnnotation]</c> for each FDT row,\n\t///     or use single attribute with rows separated by semicolon.<br />\n\t/// </syntax>\n\t/// <examples>\n\t///     <list>\n\t///         <item>\n\t///             <code>\n\t/// [ContractAnnotation(\"=> halt\")]\n\t/// public void TerminationMethod()\n\t/// </code>\n\t///         </item>\n\t///         <item>\n\t///             <code>\n\t/// [ContractAnnotation(\"halt &lt;= condition: false\")]\n\t/// public void Assert(bool condition, string text) // regular assertion method\n\t/// </code>\n\t///         </item>\n\t///         <item>\n\t///             <code>\n\t/// [ContractAnnotation(\"s:null => true\")]\n\t/// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty()\n\t/// </code>\n\t///         </item>\n\t///         <item>\n\t///             <code>\n\t/// // A method that returns null if the parameter is null,\n\t/// // and not null if the parameter is not null\n\t/// [ContractAnnotation(\"null => null; notnull => notnull\")]\n\t/// public object Transform(object data) \n\t/// </code>\n\t///         </item>\n\t///         <item>\n\t///             <code>\n\t/// [ContractAnnotation(\"s:null=>false; =>true,result:notnull; =>false, result:null\")]\n\t/// public bool TryParse(string s, out Person result)\n\t/// </code>\n\t///         </item>\n\t///     </list>\n\t/// </examples>\n\t[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class ContractAnnotationAttribute : Attribute\n\t{\n\t\tpublic ContractAnnotationAttribute([NotNull] string contract)\n\t\t\t: this(contract, false)\n\t\t{\n\t\t}\n\n\t\tpublic ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates)\n\t\t{\n\t\t\tContract = contract;\n\t\t\tForceFullStates = forceFullStates;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic string Contract { get; private set; }\n\n\t\tpublic bool ForceFullStates { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     Indicates that marked element should be localized or not.\n\t/// </summary>\n\t/// <example>\n\t///     <code>\n\t/// [LocalizationRequiredAttribute(true)]\n\t/// class Foo {\n\t///   string str = \"my string\"; // Warning: Localizable string\n\t/// }\n\t/// </code>\n\t/// </example>\n\t[AttributeUsage(AttributeTargets.All)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class LocalizationRequiredAttribute : Attribute\n\t{\n\t\tpublic LocalizationRequiredAttribute() : this(true)\n\t\t{\n\t\t}\n\n\t\tpublic LocalizationRequiredAttribute(bool required)\n\t\t{\n\t\t\tRequired = required;\n\t\t}\n\n\t\tpublic bool Required { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     Indicates that the value of the marked type (or its derivatives)\n\t///     cannot be compared using '==' or '!=' operators and <c>Equals()</c>\n\t///     should be used instead. However, using '==' or '!=' for comparison\n\t///     with <c>null</c> is always permitted.\n\t/// </summary>\n\t/// <example>\n\t///     <code>\n\t/// [CannotApplyEqualityOperator]\n\t/// class NoEquality { }\n\t/// \n\t/// class UsesNoEquality {\n\t///   void Test() {\n\t///     var ca1 = new NoEquality();\n\t///     var ca2 = new NoEquality();\n\t///     if (ca1 != null) { // OK\n\t///       bool condition = ca1 == ca2; // Warning\n\t///     }\n\t///   }\n\t/// }\n\t/// </code>\n\t/// </example>\n\t[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class CannotApplyEqualityOperatorAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     When applied to a target attribute, specifies a requirement for any type marked\n\t///     with the target attribute to implement or inherit specific type or types.\n\t/// </summary>\n\t/// <example>\n\t///     <code>\n\t/// [BaseTypeRequired(typeof(IComponent)] // Specify requirement\n\t/// class ComponentAttribute : Attribute { }\n\t/// \n\t/// [Component] // ComponentAttribute requires implementing IComponent interface\n\t/// class MyComponent : IComponent { }\n\t/// </code>\n\t/// </example>\n\t[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]\n\t[BaseTypeRequired(typeof(Attribute))]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class BaseTypeRequiredAttribute : Attribute\n\t{\n\t\tpublic BaseTypeRequiredAttribute([NotNull] Type baseType)\n\t\t{\n\t\t\tBaseType = baseType;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic Type BaseType { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library),\n\t///     so this symbol will not be marked as unused (as well as by other usage inspections).\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.All)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class UsedImplicitlyAttribute : Attribute\n\t{\n\t\tpublic UsedImplicitlyAttribute()\n\t\t\t: this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default)\n\t\t{\n\t\t}\n\n\t\tpublic UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags)\n\t\t\t: this(useKindFlags, ImplicitUseTargetFlags.Default)\n\t\t{\n\t\t}\n\n\t\tpublic UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags)\n\t\t\t: this(ImplicitUseKindFlags.Default, targetFlags)\n\t\t{\n\t\t}\n\n\t\tpublic UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)\n\t\t{\n\t\t\tUseKindFlags = useKindFlags;\n\t\t\tTargetFlags = targetFlags;\n\t\t}\n\n\t\tpublic ImplicitUseKindFlags UseKindFlags { get; private set; }\n\t\tpublic ImplicitUseTargetFlags TargetFlags { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes\n\t///     as unused (as well as by other usage inspections)\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class MeansImplicitUseAttribute : Attribute\n\t{\n\t\tpublic MeansImplicitUseAttribute()\n\t\t\t: this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default)\n\t\t{\n\t\t}\n\n\t\tpublic MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags)\n\t\t\t: this(useKindFlags, ImplicitUseTargetFlags.Default)\n\t\t{\n\t\t}\n\n\t\tpublic MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags)\n\t\t\t: this(ImplicitUseKindFlags.Default, targetFlags)\n\t\t{\n\t\t}\n\n\t\tpublic MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)\n\t\t{\n\t\t\tUseKindFlags = useKindFlags;\n\t\t\tTargetFlags = targetFlags;\n\t\t}\n\n\t\t[UsedImplicitly]\n\t\tpublic ImplicitUseKindFlags UseKindFlags { get; private set; }\n\n\t\t[UsedImplicitly]\n\t\tpublic ImplicitUseTargetFlags TargetFlags { get; private set; }\n\t}\n\n\t[Flags]\n\tinternal enum ImplicitUseKindFlags\n\t{\n\t\tDefault = Access | Assign | InstantiatedWithFixedConstructorSignature,\n\n\t\t/// <summary>Only entity marked with attribute considered used.</summary>\n\t\tAccess = 1,\n\n\t\t/// <summary>Indicates implicit assignment to a member.</summary>\n\t\tAssign = 2,\n\n\t\t/// <summary>\n\t\t///     Indicates implicit instantiation of a type with fixed constructor signature.\n\t\t///     That means any unused constructor parameters won't be reported as such.\n\t\t/// </summary>\n\t\tInstantiatedWithFixedConstructorSignature = 4,\n\n\t\t/// <summary>Indicates implicit instantiation of a type.</summary>\n\t\tInstantiatedNoFixedConstructorSignature = 8\n\t}\n\n\t/// <summary>\n\t///     Specify what is considered used implicitly when marked\n\t///     with <see cref=\"MeansImplicitUseAttribute\" /> or <see cref=\"UsedImplicitlyAttribute\" />.\n\t/// </summary>\n\t[Flags]\n\tinternal enum ImplicitUseTargetFlags\n\t{\n\t\tDefault = Itself,\n\t\tItself = 1,\n\n\t\t/// <summary>Members of entity marked with attribute are considered used.</summary>\n\t\tMembers = 2,\n\n\t\t/// <summary>Entity marked with attribute and all its members considered used.</summary>\n\t\tWithMembers = Itself | Members\n\t}\n\n\t/// <summary>\n\t///     This attribute is intended to mark publicly available API\n\t///     which should not be removed and so is treated as used.\n\t/// </summary>\n\t[MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class PublicAPIAttribute : Attribute\n\t{\n\t\tpublic PublicAPIAttribute()\n\t\t{\n\t\t}\n\n\t\tpublic PublicAPIAttribute([NotNull] string comment)\n\t\t{\n\t\t\tComment = comment;\n\t\t}\n\n\t\t[CanBeNull]\n\t\tpublic string Comment { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     Tells code analysis engine if the parameter is completely handled when the invoked method is on stack.\n\t///     If the parameter is a delegate, indicates that delegate is executed while the method is executed.\n\t///     If the parameter is an enumerable, indicates that it is enumerated while the method is executed.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class InstantHandleAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Indicates that a method does not make any observable state changes.\n\t///     The same as <c>System.Diagnostics.Contracts.PureAttribute</c>.\n\t/// </summary>\n\t/// <example>\n\t///     <code>\n\t/// [Pure] int Multiply(int x, int y) => x * y;\n\t/// \n\t/// void M() {\n\t///   Multiply(123, 42); // Waring: Return value of pure method is not used\n\t/// }\n\t/// </code>\n\t/// </example>\n\t[AttributeUsage(AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class PureAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Indicates that the return value of method invocation must be used.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class MustUseReturnValueAttribute : Attribute\n\t{\n\t\tpublic MustUseReturnValueAttribute()\n\t\t{\n\t\t}\n\n\t\tpublic MustUseReturnValueAttribute([NotNull] string justification)\n\t\t{\n\t\t\tJustification = justification;\n\t\t}\n\n\t\t[CanBeNull]\n\t\tpublic string Justification { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     Indicates the type member or parameter of some type, that should be used instead of all other ways\n\t///     to get the value that type. This annotation is useful when you have some \"context\" value evaluated\n\t///     and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one.\n\t/// </summary>\n\t/// <example>\n\t///     <code>\n\t/// class Foo {\n\t///   [ProvidesContext] IBarService _barService = ...;\n\t/// \n\t///   void ProcessNode(INode node) {\n\t///     DoSomething(node, node.GetGlobalServices().Bar);\n\t///     //              ^ Warning: use value of '_barService' field\n\t///   }\n\t/// }\n\t/// </code>\n\t/// </example>\n\t[AttributeUsage(\n\t\t AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method |\n\t\t AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class ProvidesContextAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Indicates that a parameter is a path to a file or a folder within a web project.\n\t///     Path can be relative or absolute, starting from web root (~).\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class PathReferenceAttribute : Attribute\n\t{\n\t\tpublic PathReferenceAttribute()\n\t\t{\n\t\t}\n\n\t\tpublic PathReferenceAttribute([NotNull, PathReference] string basePath)\n\t\t{\n\t\t\tBasePath = basePath;\n\t\t}\n\n\t\t[CanBeNull]\n\t\tpublic string BasePath { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     An extension method marked with this attribute is processed by ReSharper code completion\n\t///     as a 'Source Template'. When extension method is completed over some expression, it's source code\n\t///     is automatically expanded like a template at call site.\n\t/// </summary>\n\t/// <remarks>\n\t///     Template method body can contain valid source code and/or special comments starting with '$'.\n\t///     Text inside these comments is added as source code when the template is applied. Template parameters\n\t///     can be used either as additional method parameters or as identifiers wrapped in two '$' signs.\n\t///     Use the <see cref=\"MacroAttribute\" /> attribute to specify macros for parameters.\n\t/// </remarks>\n\t/// <example>\n\t///     In this example, the 'forEach' method is a source template available over all values\n\t///     of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block:\n\t///     <code>\n\t/// [SourceTemplate]\n\t/// public static void forEach&lt;T&gt;(this IEnumerable&lt;T&gt; xs) {\n\t///   foreach (var x in xs) {\n\t///      //$ $END$\n\t///   }\n\t/// }\n\t/// </code>\n\t/// </example>\n\t[AttributeUsage(AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class SourceTemplateAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Allows specifying a macro for a parameter of a <see cref=\"SourceTemplateAttribute\">source template</see>.\n\t/// </summary>\n\t/// <remarks>\n\t///     You can apply the attribute on the whole method or on any of its additional parameters. The macro expression\n\t///     is defined in the <see cref=\"MacroAttribute.Expression\" /> property. When applied on a method, the target\n\t///     template parameter is defined in the <see cref=\"MacroAttribute.Target\" /> property. To apply the macro silently\n\t///     for the parameter, set the <see cref=\"MacroAttribute.Editable\" /> property value = -1.\n\t/// </remarks>\n\t/// <example>\n\t///     Applying the attribute on a source template method:\n\t///     <code>\n\t/// [SourceTemplate, Macro(Target = \"item\", Expression = \"suggestVariableName()\")]\n\t/// public static void forEach&lt;T&gt;(this IEnumerable&lt;T&gt; collection) {\n\t///   foreach (var item in collection) {\n\t///     //$ $END$\n\t///   }\n\t/// }\n\t/// </code>\n\t///     Applying the attribute on a template method parameter:\n\t///     <code>\n\t/// [SourceTemplate]\n\t/// public static void something(this Entity x, [Macro(Expression = \"guid()\", Editable = -1)] string newguid) {\n\t///   /*$ var $x$Id = \"$newguid$\" + x.ToString();\n\t///   x.DoSomething($x$Id); */\n\t/// }\n\t/// </code>\n\t/// </example>\n\t[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class MacroAttribute : Attribute\n\t{\n\t\t/// <summary>\n\t\t///     Allows specifying a macro that will be executed for a <see cref=\"SourceTemplateAttribute\">source template</see>\n\t\t///     parameter when the template is expanded.\n\t\t/// </summary>\n\t\tpublic string Expression { get; set; }\n\n\t\t/// <summary>\n\t\t///     Allows specifying which occurrence of the target parameter becomes editable when the template is deployed.\n\t\t/// </summary>\n\t\t/// <remarks>\n\t\t///     If the target parameter is used several times in the template, only one occurrence becomes editable;\n\t\t///     other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence,\n\t\t///     use values >= 0. To make the parameter non-editable when the template is expanded, use -1.\n\t\t/// </remarks>\n\t\t/// >\n\t\tpublic int Editable { get; set; }\n\n\t\t/// <summary>\n\t\t///     Identifies the target parameter of a <see cref=\"SourceTemplateAttribute\">source template</see> if the\n\t\t///     <see cref=\"MacroAttribute\" /> is applied on a template method.\n\t\t/// </summary>\n\t\tpublic string Target { get; set; }\n\t}\n\n\t[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute\n\t{\n\t\tpublic AspMvcAreaMasterLocationFormatAttribute([NotNull] string format)\n\t\t{\n\t\t\tFormat = format;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic string Format { get; private set; }\n\t}\n\n\t[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute\n\t{\n\t\tpublic AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format)\n\t\t{\n\t\t\tFormat = format;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic string Format { get; private set; }\n\t}\n\n\t[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcAreaViewLocationFormatAttribute : Attribute\n\t{\n\t\tpublic AspMvcAreaViewLocationFormatAttribute([NotNull] string format)\n\t\t{\n\t\t\tFormat = format;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic string Format { get; private set; }\n\t}\n\n\t[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcMasterLocationFormatAttribute : Attribute\n\t{\n\t\tpublic AspMvcMasterLocationFormatAttribute(string format)\n\t\t{\n\t\t\tFormat = format;\n\t\t}\n\n\t\tpublic string Format { get; private set; }\n\t}\n\n\t[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcPartialViewLocationFormatAttribute : Attribute\n\t{\n\t\tpublic AspMvcPartialViewLocationFormatAttribute([NotNull] string format)\n\t\t{\n\t\t\tFormat = format;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic string Format { get; private set; }\n\t}\n\n\t[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcViewLocationFormatAttribute : Attribute\n\t{\n\t\tpublic AspMvcViewLocationFormatAttribute([NotNull] string format)\n\t\t{\n\t\t\tFormat = format;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic string Format { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter\n\t///     is an MVC action. If applied to a method, the MVC action name is calculated\n\t///     implicitly from the context. Use this attribute for custom wrappers similar to\n\t///     <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String)</c>.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcActionAttribute : Attribute\n\t{\n\t\tpublic AspMvcActionAttribute()\n\t\t{\n\t\t}\n\n\t\tpublic AspMvcActionAttribute([NotNull] string anonymousProperty)\n\t\t{\n\t\t\tAnonymousProperty = anonymousProperty;\n\t\t}\n\n\t\t[CanBeNull]\n\t\tpublic string AnonymousProperty { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     ASP.NET MVC attribute. Indicates that a parameter is an MVC area.\n\t///     Use this attribute for custom wrappers similar to\n\t///     <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String)</c>.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcAreaAttribute : Attribute\n\t{\n\t\tpublic AspMvcAreaAttribute()\n\t\t{\n\t\t}\n\n\t\tpublic AspMvcAreaAttribute([NotNull] string anonymousProperty)\n\t\t{\n\t\t\tAnonymousProperty = anonymousProperty;\n\t\t}\n\n\t\t[CanBeNull]\n\t\tpublic string AnonymousProperty { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is\n\t///     an MVC controller. If applied to a method, the MVC controller name is calculated\n\t///     implicitly from the context. Use this attribute for custom wrappers similar to\n\t///     <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String)</c>.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcControllerAttribute : Attribute\n\t{\n\t\tpublic AspMvcControllerAttribute()\n\t\t{\n\t\t}\n\n\t\tpublic AspMvcControllerAttribute([NotNull] string anonymousProperty)\n\t\t{\n\t\t\tAnonymousProperty = anonymousProperty;\n\t\t}\n\n\t\t[CanBeNull]\n\t\tpublic string AnonymousProperty { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. Use this attribute\n\t///     for custom wrappers similar to <c>System.Web.Mvc.Controller.View(String, String)</c>.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcMasterAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute\n\t///     for custom wrappers similar to <c>System.Web.Mvc.Controller.View(String, Object)</c>.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcModelTypeAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC\n\t///     partial view. If applied to a method, the MVC partial view name is calculated implicitly\n\t///     from the context. Use this attribute for custom wrappers similar to\n\t///     <c>System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String)</c>.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcPartialViewAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcSuppressViewErrorAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     ASP.NET MVC attribute. Indicates that a parameter is an MVC display template.\n\t///     Use this attribute for custom wrappers similar to\n\t///     <c>System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String)</c>.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcDisplayTemplateAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template.\n\t///     Use this attribute for custom wrappers similar to\n\t///     <c>System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String)</c>.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcEditorTemplateAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     ASP.NET MVC attribute. Indicates that a parameter is an MVC template.\n\t///     Use this attribute for custom wrappers similar to\n\t///     <c>System.ComponentModel.DataAnnotations.UIHintAttribute(System.String)</c>.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcTemplateAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter\n\t///     is an MVC view component. If applied to a method, the MVC view name is calculated implicitly\n\t///     from the context. Use this attribute for custom wrappers similar to\n\t///     <c>System.Web.Mvc.Controller.View(Object)</c>.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcViewAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter\n\t///     is an MVC view component name.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcViewComponentAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter\n\t///     is an MVC view component view. If applied to a method, the MVC view component view name is default.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcViewComponentViewAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     ASP.NET MVC attribute. When applied to a parameter of an attribute,\n\t///     indicates that this parameter is an MVC action name.\n\t/// </summary>\n\t/// <example>\n\t///     <code>\n\t/// [ActionName(\"Foo\")]\n\t/// public ActionResult Login(string returnUrl) {\n\t///   ViewBag.ReturnUrl = Url.Action(\"Foo\"); // OK\n\t///   return RedirectToAction(\"Bar\"); // Error: Cannot resolve action\n\t/// }\n\t/// </code>\n\t/// </example>\n\t[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMvcActionSelectorAttribute : Attribute\n\t{\n\t}\n\n\t[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class HtmlElementAttributesAttribute : Attribute\n\t{\n\t\tpublic HtmlElementAttributesAttribute()\n\t\t{\n\t\t}\n\n\t\tpublic HtmlElementAttributesAttribute([NotNull] string name)\n\t\t{\n\t\t\tName = name;\n\t\t}\n\n\t\t[CanBeNull]\n\t\tpublic string Name { get; private set; }\n\t}\n\n\t[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class HtmlAttributeValueAttribute : Attribute\n\t{\n\t\tpublic HtmlAttributeValueAttribute([NotNull] string name)\n\t\t{\n\t\t\tName = name;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic string Name { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     Razor attribute. Indicates that a parameter or a method is a Razor section.\n\t///     Use this attribute for custom wrappers similar to\n\t///     <c>System.Web.WebPages.WebPageBase.RenderSection(String)</c>.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class RazorSectionAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Indicates how method, constructor invocation or property access\n\t///     over collection type affects content of the collection.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class CollectionAccessAttribute : Attribute\n\t{\n\t\tpublic CollectionAccessAttribute(CollectionAccessType collectionAccessType)\n\t\t{\n\t\t\tCollectionAccessType = collectionAccessType;\n\t\t}\n\n\t\tpublic CollectionAccessType CollectionAccessType { get; private set; }\n\t}\n\n\t[Flags]\n\tinternal enum CollectionAccessType\n\t{\n\t\t/// <summary>Method does not use or modify content of the collection.</summary>\n\t\tNone = 0,\n\n\t\t/// <summary>Method only reads content of the collection but does not modify it.</summary>\n\t\tRead = 1,\n\n\t\t/// <summary>Method can change content of the collection but does not add new elements.</summary>\n\t\tModifyExistingContent = 2,\n\n\t\t/// <summary>Method can add new elements to the collection.</summary>\n\t\tUpdatedContent = ModifyExistingContent | 4\n\t}\n\n\t/// <summary>\n\t///     Indicates that the marked method is assertion method, i.e. it halts control flow if\n\t///     one of the conditions is satisfied. To set the condition, mark one of the parameters with\n\t///     <see cref=\"AssertionConditionAttribute\" /> attribute.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AssertionMethodAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Indicates the condition parameter of the assertion method. The method itself should be\n\t///     marked by <see cref=\"AssertionMethodAttribute\" /> attribute. The mandatory argument of\n\t///     the attribute is the assertion type.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AssertionConditionAttribute : Attribute\n\t{\n\t\tpublic AssertionConditionAttribute(AssertionConditionType conditionType)\n\t\t{\n\t\t\tConditionType = conditionType;\n\t\t}\n\n\t\tpublic AssertionConditionType ConditionType { get; private set; }\n\t}\n\n\t/// <summary>\n\t///     Specifies assertion type. If the assertion method argument satisfies the condition,\n\t///     then the execution continues. Otherwise, execution is assumed to be halted.\n\t/// </summary>\n\tinternal enum AssertionConditionType\n\t{\n\t\t/// <summary>Marked parameter should be evaluated to true.</summary>\n\t\tIS_TRUE = 0,\n\n\t\t/// <summary>Marked parameter should be evaluated to false.</summary>\n\t\tIS_FALSE = 1,\n\n\t\t/// <summary>Marked parameter should be evaluated to null value.</summary>\n\t\tIS_NULL = 2,\n\n\t\t/// <summary>Marked parameter should be evaluated to not null value.</summary>\n\t\tIS_NOT_NULL = 3\n\t}\n\n\t/// <summary>\n\t///     Indicates that the marked method unconditionally terminates control flow execution.\n\t///     For example, it could unconditionally throw exception.\n\t/// </summary>\n\t[Obsolete(\"Use [ContractAnnotation('=> halt')] instead\")]\n\t[AttributeUsage(AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class TerminatesProgramAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select,\n\t///     .Where). This annotation allows inference of [InstantHandle] annotation for parameters\n\t///     of delegate type by analyzing LINQ method chains.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class LinqTunnelAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Indicates that IEnumerable, passed as parameter, is not enumerated.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class NoEnumerationAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Indicates that parameter is regular expression pattern.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Parameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class RegexPatternAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     XAML attribute. Indicates the type that has <c>ItemsSource</c> property and should be treated\n\t///     as <c>ItemsControl</c>-derived type, to enable inner items <c>DataContext</c> type resolve.\n\t/// </summary>\n\t[AttributeUsage(AttributeTargets.Class)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class XamlItemsControlAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     XAML attribute. Indicates the property of some <c>BindingBase</c>-derived type, that\n\t///     is used to bind some item of <c>ItemsControl</c>-derived type. This annotation will\n\t///     enable the <c>DataContext</c> type resolve for XAML bindings for such properties.\n\t/// </summary>\n\t/// <remarks>\n\t///     Property should have the tree ancestor of the <c>ItemsControl</c> type or\n\t///     marked with the <see cref=\"XamlItemsControlAttribute\" /> attribute.\n\t/// </remarks>\n\t[AttributeUsage(AttributeTargets.Property)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class XamlItemBindingOfItemsControlAttribute : Attribute\n\t{\n\t}\n\n\t[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspChildControlTypeAttribute : Attribute\n\t{\n\t\tpublic AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType)\n\t\t{\n\t\t\tTagName = tagName;\n\t\t\tControlType = controlType;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic string TagName { get; private set; }\n\n\t\t[NotNull]\n\t\tpublic Type ControlType { get; private set; }\n\t}\n\n\t[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspDataFieldAttribute : Attribute\n\t{\n\t}\n\n\t[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspDataFieldsAttribute : Attribute\n\t{\n\t}\n\n\t[AttributeUsage(AttributeTargets.Property)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspMethodPropertyAttribute : Attribute\n\t{\n\t}\n\n\t[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspRequiredAttributeAttribute : Attribute\n\t{\n\t\tpublic AspRequiredAttributeAttribute([NotNull] string attribute)\n\t\t{\n\t\t\tAttribute = attribute;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic string Attribute { get; private set; }\n\t}\n\n\t[AttributeUsage(AttributeTargets.Property)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class AspTypePropertyAttribute : Attribute\n\t{\n\t\tpublic AspTypePropertyAttribute(bool createConstructorReferences)\n\t\t{\n\t\t\tCreateConstructorReferences = createConstructorReferences;\n\t\t}\n\n\t\tpublic bool CreateConstructorReferences { get; private set; }\n\t}\n\n\t[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class RazorImportNamespaceAttribute : Attribute\n\t{\n\t\tpublic RazorImportNamespaceAttribute([NotNull] string name)\n\t\t{\n\t\t\tName = name;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic string Name { get; private set; }\n\t}\n\n\t[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class RazorInjectionAttribute : Attribute\n\t{\n\t\tpublic RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName)\n\t\t{\n\t\t\tType = type;\n\t\t\tFieldName = fieldName;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic string Type { get; private set; }\n\n\t\t[NotNull]\n\t\tpublic string FieldName { get; private set; }\n\t}\n\n\t[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class RazorDirectiveAttribute : Attribute\n\t{\n\t\tpublic RazorDirectiveAttribute([NotNull] string directive)\n\t\t{\n\t\t\tDirective = directive;\n\t\t}\n\n\t\t[NotNull]\n\t\tpublic string Directive { get; private set; }\n\t}\n\n\t[AttributeUsage(AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class RazorHelperCommonAttribute : Attribute\n\t{\n\t}\n\n\t[AttributeUsage(AttributeTargets.Property)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class RazorLayoutAttribute : Attribute\n\t{\n\t}\n\n\t[AttributeUsage(AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class RazorWriteLiteralMethodAttribute : Attribute\n\t{\n\t}\n\n\t[AttributeUsage(AttributeTargets.Method)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class RazorWriteMethodAttribute : Attribute\n\t{\n\t}\n\n\t[AttributeUsage(AttributeTargets.Parameter)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class RazorWriteMethodParameterAttribute : Attribute\n\t{\n\t}\n\n\t/// <summary>\n\t///     Prevents the Member Reordering feature from tossing members of the marked class.\n\t/// </summary>\n\t/// <remarks>\n\t///     The attribute must be mentioned in your member reordering patterns\n\t/// </remarks>\n\t[AttributeUsage(AttributeTargets.All)]\n\t[Conditional(\"JETBRAINS_ANNOTATIONS\")]\n\tinternal sealed class NoReorder : Attribute\n\t{\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.AsyncPagedList/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled through the following\n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"Sakura.AspNetCore.AsyncPagedList\")]\n[assembly: AssemblyTrademark(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible\n// to COM components.  If you need to access a type in this assembly from\n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n// The following GUID is for the ID of the typelib if this project is exposed to COM\n[assembly: Guid(\"7581ce0a-0080-4d53-b22e-18e6cf251f3a\")]\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.AsyncPagedList/Sakura.AspNetCore.AsyncPagedList.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>netstandard1.6</TargetFramework>\n    <AssemblyName>Sakura.AspNetCore.AsyncPagedList</AssemblyName>\n    <PackageId>Sakura.AspNetCore.AsyncPagedList</PackageId>\n    <NetStandardImplicitPackageVersion>1.6.0</NetStandardImplicitPackageVersion>\n    <PackageTargetFallback>$(PackageTargetFallback);dnxcore50</PackageTargetFallback>\n    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>\n    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>\n    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\Sakura.AspNetCore.PagedList\\Sakura.AspNetCore.PagedList.csproj\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"System.Interactive.Async\" Version=\"3.0.0\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Authentication.ExternalCookie/ApplicationBuilderExtensions.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Identity;\n#if !NETSTANDARD2_0\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Options;\n#endif\n\n// ReSharper disable once CheckNamespace\nnamespace Microsoft.AspNet.Builder;\n\n/// <summary>\n///     Provide extension methods for <see cref=\"IApplicationBuilder\" />. This class is static.\n/// </summary>\npublic static class ApplicationBuilderExtensions\n{\n\t/// <summary>\n\t///     Configure a application to enable all cookie-related authentication schemes.\n\t/// </summary>\n\t/// <param name=\"app\">The <see cref=\"IApplicationBuilder\" /> object.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"app\" /> is <c>null</c>.</exception>\n#if NETSTANDARD2_0 || NETCOREAPP3_0\n\t[PublicAPI]\n\t[Obsolete(\"This API is obsolete. Plase use UseAuthentication method provided by ASP.NET Core 2 directly.\")]\n\tpublic static void UseAllCookies([NotNull] this IApplicationBuilder app)\n\t{\n\t\tif (app == null)\n\t\t\tthrow new ArgumentNullException(nameof(app));\n\n\t\tapp.UseAuthentication();\n\t}\n#else\n\t\t[PublicAPI]\n\t\tpublic static void UseAllCookies([NotNull] this IApplicationBuilder app)\n\t\t{\n\t\t\tif (app == null)\n\t\t\t\tthrow new ArgumentNullException(nameof(app));\n\n\t\t\t// Get the identity options\n\t\t\tvar identityOptions = app.ApplicationServices.GetService<IOptions<IdentityOptions>>().Value;\n\n\t\t\t// Configure cookie authentications with specified order\n\t\t\tapp.UseCookieAuthentication(identityOptions.Cookies.ExternalCookie);\n\t\t\tapp.UseCookieAuthentication(identityOptions.Cookies.TwoFactorRememberMeCookie);\n\t\t\tapp.UseCookieAuthentication(identityOptions.Cookies.TwoFactorUserIdCookie);\n\t\t\tapp.UseCookieAuthentication(identityOptions.Cookies.ApplicationCookie);\n\t\t}\n\t\n#endif\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Authentication.ExternalCookie/ExternalSecurityStampValidator.cs",
    "content": "﻿using System.Security.Claims;\nusing System.Threading.Tasks;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Authentication.Cookies;\nusing Microsoft.AspNetCore.Identity;\n\nnamespace Sakura.AspNetCore.Authentication;\n\n/// <inheritdoc />\n/// <summary>\n///     Provide default implementation for <see cref=\"T:Microsoft.AspNetCore.Identity.ISecurityStampValidator\" /> service.\n/// </summary>\n[UsedImplicitly]\npublic class ExternalSecurityStampValidator : ISecurityStampValidator\n{\n\t/// <summary>\n\t///     Validates a security stamp of an identity as an asynchronous operation, and rebuilds the identity if the validation\n\t///     succeeds, otherwise rejects\n\t///     the identity.\n\t/// </summary>\n\t/// <param name=\"context\">\n\t///     The context containing the <see cref=\"ClaimsPrincipal\" />\n\t///     and <see cref=\"AuthenticationProperties\" /> to validate.\n\t/// </param>\n\t/// <returns>The <see cref=\"Task\" /> that represents the asynchronous validation operation.</returns>\n\tpublic Task ValidateAsync(CookieValidatePrincipalContext context)\n\t{\n\t\treturn Task.FromResult(0);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Authentication.ExternalCookie/ExternalSignInManager.Net1.cs",
    "content": "﻿#if NET451 || NETSTANDARD1_3\n\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Security.Claims;\nusing System.Threading.Tasks;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Http.Authentication;\nusing Microsoft.Extensions.Options;\n\nnamespace Sakura.AspNetCore.Authentication\n{\n\n\tpublic partial class ExternalSignInManager\n\t{\n\t\t/// <summary>\n\t\t///     Initialize a new instance.\n\t\t/// </summary>\n\t\t/// <param name=\"httpContextAccessor\">HTTP context accessor.</param>\n\t\t/// <param name=\"identityOptions\">Identity options.</param>\n\t\tpublic ExternalSignInManager(IHttpContextAccessor httpContextAccessor, IOptions<IdentityOptions> identityOptions)\n\t\t{\n\t\t\tAuthentication = httpContextAccessor.HttpContext.Authentication;\n\t\t\tIdentityOptions = identityOptions.Value;\n\t\t}\n\n\n\t\t/// <summary>\n\t\t///     Get the <see cref=\"IdentityOptions\" /> instance.\n\t\t/// </summary>\n\t\tprivate IdentityOptions IdentityOptions { get; }\n\n\t\tprivate AuthenticationManager Authentication { get; }\n\n\t\t/// <summary>\n\t\t///     Sign in current user using stored external cookie information.\n\t\t/// </summary>\n\t\t/// <returns>\n\t\t///     If signing-in is succeeded, returns the generated <see cref=\"ClaimsPrincipal\" /> object for current user;\n\t\t///     otherwise, returns <c>null</c>.\n\t\t/// </returns>\n\t\t[PublicAPI]\n\t\tpublic async Task<ClaimsPrincipal> SignInFromExternalCookieAsync()\n\t\t{\n\t\t\tvar externalLoginInfo = await GetExternalPrincipalAsync();\n\n\t\t\tif (externalLoginInfo == null)\n\t\t\t\treturn null;\n\n\t\t\t// the new schame to replace the old one\n\t\t\tvar newScheme = IdentityOptions.Cookies.ApplicationCookieAuthenticationScheme;\n\n\t\t\tawait Authentication.SignInAsync(newScheme, externalLoginInfo.CloneAs(newScheme));\n\t\t\treturn externalLoginInfo;\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Get the external principal stored in the external cookie.\n\t\t/// </summary>\n\t\t/// <returns>A <see cref=\"ClaimsPrincipal\" /> object represents as the external principal.</returns>\n\t\t[PublicAPI]\n\t\tpublic Task<ClaimsPrincipal> GetExternalPrincipalAsync()\n\t\t{\n\t\t\treturn Authentication.AuthenticateAsync(IdentityOptions.Cookies.ExternalCookieAuthenticationScheme);\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Sign out current user.\n\t\t/// </summary>\n\t\t/// <returns>A task represents as an async operation.</returns>\n\t\t[PublicAPI]\n\t\tpublic async Task SignOutAsync()\n\t\t{\n\t\t\tawait Authentication.SignOutAsync(IdentityOptions.Cookies.ApplicationCookieAuthenticationScheme);\n\t\t\tawait Authentication.SignOutAsync(IdentityOptions.Cookies.ExternalCookieAuthenticationScheme);\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Get a value that indicates whether the current user is signed-in with application's primary authentication scheme.\n\t\t/// </summary>\n\t\t/// <param name=\"principal\">The user principal instance.</param>\n\t\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"principal\" /> is <c>null</c>.</exception>\n\t\t/// <returns>\n\t\t///     If the current user is signed-in with application's primary authentication scheme, returns <c>true</c>;\n\t\t///     otherwise, returns <c>false</c>.\n\t\t/// </returns>\n\t\t[PublicAPI]\n\t\tpublic bool IsSignedIn([NotNull] ClaimsPrincipal principal)\n\t\t{\n\t\t\tif (principal == null)\n\t\t\t\tthrow new ArgumentNullException(nameof(principal));\n\n\t\t\treturn principal.Identities.Any(i => i.AuthenticationType == IdentityOptions.Cookies.ApplicationCookieAuthenticationScheme);\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Get all external authentication schemes registered in the current appliation.\n\t\t/// </summary>\n\t\t/// <returns>The collection of <see cref=\"AuthenticationDescription\" /> for all registered external authentication schemes.</returns>\n\t\t[PublicAPI]\n\t\t[NotNull]\n\t\t[ItemNotNull]\n\t\tpublic IEnumerable<AuthenticationDescription> GetExternalAuthenticationSchemes()\n\t\t{\n\t\t\treturn Authentication.GetAuthenticationSchemes().Where(i => !string.IsNullOrEmpty(i.DisplayName));\n\t\t}\n\n\t}\n}\n\n#endif\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Authentication.ExternalCookie/ExternalSignInManager.Net2.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Security.Claims;\nusing System.Threading.Tasks;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Authentication;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.Extensions.Options;\n\nnamespace Sakura.AspNetCore.Authentication;\n#if NETSTANDARD2_0 || NETCOREAPP3_0\n\n\tpublic partial class ExternalSignInManager\n\t{\n\t\t/// <summary>\n\t\t///     Initialize a new instance of <see cref=\"ExternalSignInManager\" />.\n\t\t/// </summary>\n\t\t/// <param name=\"httpContextAccessor\">The <see cref=\"IHttpContextAccessor\" /> service instance.</param>\n\t\t/// <param name=\"authenticationSchemeProvider\">the <see cref=\"IAuthenticationSchemeProvider\" /> service instance.</param>\n\t\t/// <param name=\"identityOptions\">The Options of <see cref=\"Microsoft.AspNetCore.Identity.IdentityOptions\" />.</param>\n\t\tpublic ExternalSignInManager(IHttpContextAccessor httpContextAccessor,\n\t\t\tIAuthenticationSchemeProvider authenticationSchemeProvider, IOptions<IdentityOptions> identityOptions)\n\t\t{\n\t\t\tHttpContext = httpContextAccessor.HttpContext;\n\t\t\tAuthenticationSchemeProvider = authenticationSchemeProvider;\n\t\t\tIdentityOptions = identityOptions.Value;\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Get the <see cref=\"Microsoft.AspNetCore.Http.HttpContext\" /> instance.\n\t\t/// </summary>\n\t\tprivate HttpContext HttpContext { get; }\n\n\t\t/// <summary>\n\t\t///     Get thse <see cref=\"Microsoft.AspNetCore.Identity.IdentityOptions\" /> value.\n\t\t/// </summary>\n\t\tprivate IdentityOptions IdentityOptions { get; }\n\n\t\t/// <summary>\n\t\t///     Get the <see cref=\"IAuthenticationSchemeProvider\" /> service.\n\t\t/// </summary>\n\t\tprivate IAuthenticationSchemeProvider AuthenticationSchemeProvider { get; }\n\n\t\t/// <summary>\n\t\t///     Sign in current user using stored external cookie information.\n\t\t/// </summary>\n\t\t/// <returns>\n\t\t///     If signing-in is succeeded, returns the generated <see cref=\"ClaimsPrincipal\" /> object for current user;\n\t\t///     otherwise, returns <c>null</c>.\n\t\t/// </returns>\n\t\t[PublicAPI]\n\t\tpublic async Task<ClaimsPrincipal> SignInFromExternalCookieAsync()\n\t\t{\n\t\t\tvar externalLoginInfo = await GetExternalPrincipalAsync();\n\n\t\t\tif (externalLoginInfo == null)\n\t\t\t\treturn null;\n\n\t\t\t// the new schame to replace the old one\n\t\t\tvar newScheme = IdentityConstants.ApplicationScheme;\n\n\t\t\tawait HttpContext.SignInAsync(externalLoginInfo.CloneAs(newScheme));\n\t\t\treturn externalLoginInfo;\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Get the external principal stored in the external cookie.\n\t\t/// </summary>\n\t\t/// <returns>A <see cref=\"ClaimsPrincipal\" /> object represents as the external principal.</returns>\n\t\t[PublicAPI]\n\t\tpublic async Task<ClaimsPrincipal> GetExternalPrincipalAsync()\n\t\t{\n\t\t\tvar result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);\n\t\t\treturn result.Principal;\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Sign out current user.\n\t\t/// </summary>\n\t\t/// <returns>A task represents as an async operation.</returns>\n\t\t[PublicAPI]\n\t\tpublic async Task SignOutAsync()\n\t\t{\n\t\t\tawait HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme);\n\t\t\tawait HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Get a value that indicates whether the current user is signed-in with application's primary authentication scheme.\n\t\t/// </summary>\n\t\t/// <param name=\"principal\">The user principal instance.</param>\n\t\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"principal\" /> is <c>null</c>.</exception>\n\t\t/// <returns>\n\t\t///     If the current user is signed-in with application's primary authentication scheme, returns <c>true</c>;\n\t\t///     otherwise, returns <c>false</c>.\n\t\t/// </returns>\n\t\t[PublicAPI]\n\t\tpublic bool IsSignedIn([NotNull] ClaimsPrincipal principal)\n\t\t{\n\t\t\tif (principal == null)\n\t\t\t\tthrow new ArgumentNullException(nameof(principal));\n\n\t\t\treturn principal.Identities.Any(i => i.AuthenticationType == IdentityConstants.ApplicationScheme);\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Get all external authentication schemes registered in the current appliation.\n\t\t/// </summary>\n\t\t/// <returns>The collection of <see cref=\"AuthenticationDescription\" /> for all registered external authentication schemes.</returns>\n\t\t[PublicAPI]\n\t\t[NotNull]\n\t\t[ItemNotNull]\n\t\tpublic async Task<IEnumerable<AuthenticationScheme>> GetExternalAuthenticationSchemesAsync()\n\t\t{\n\t\t\tvar allSchemes = await AuthenticationSchemeProvider.GetAllSchemesAsync();\n\t\t\treturn allSchemes.Where(i => !string.IsNullOrEmpty(i.DisplayName));\n\t\t}\n\t}\n\n#endif"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Authentication.ExternalCookie/ExternalSignInManager.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Security.Claims;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Identity;\n\nnamespace Sakura.AspNetCore.Authentication;\n\n/// <summary>\n///     Provide methods used for external signing-in co-operations.\n/// </summary>\npublic partial class ExternalSignInManager\n{\n\t/// <summary>\n\t///     Get the user name from a <see cref=\"ClaimsIdentity\" />.\n\t/// </summary>\n\t/// <param name=\"identity\">The <see cref=\"ClaimsIdentity\" /> instance.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"identity\" /> is <c>null</c>.</exception>\n\t/// <returns>\n\t///     The user name included in the <paramref name=\"identity\" />. If no user name is included, this method will\n\t///     return <c>null</c>.\n\t/// </returns>\n\t[PublicAPI]\n\tpublic string? GetUserName([NotNull] ClaimsIdentity identity)\n\t{\n\t\tif (identity == null)\n\t\t\tthrow new ArgumentNullException(nameof(identity));\n\n\t\treturn identity.FindFirst(IdentityOptions.ClaimsIdentity.UserNameClaimType)?.Value;\n\t}\n\n\t/// <summary>\n\t///     Get the user name from a <see cref=\"ClaimsPrincipal\" />.\n\t/// </summary>\n\t/// <param name=\"principal\">The <see cref=\"ClaimsPrincipal\" /> instance.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"principal\" /> is <c>null</c>.</exception>\n\t/// <returns>\n\t///     The user name included in the <paramref name=\"principal\" />. If no user name is included, this method will\n\t///     return <c>null</c>.\n\t/// </returns>\n\t[PublicAPI]\n\tpublic string? GetUserName([NotNull] ClaimsPrincipal principal)\n\t{\n\t\tif (principal == null)\n\t\t\tthrow new ArgumentNullException(nameof(principal));\n\n\t\treturn principal.FindFirst(IdentityOptions.ClaimsIdentity.UserNameClaimType)?.Value;\n\t}\n\n\t/// <summary>\n\t///     Get the user identifier from a <see cref=\"ClaimsIdentity\" />.\n\t/// </summary>\n\t/// <param name=\"identity\">The <see cref=\"ClaimsIdentity\" /> instance.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"identity\" /> is <c>null</c>.</exception>\n\t/// <returns>\n\t///     The user identifier included in the <paramref name=\"identity\" />. If no user identifier is included, this\n\t///     method will return <c>null</c>.\n\t/// </returns>\n\t[PublicAPI]\n\tpublic string? GetUserId([NotNull] ClaimsIdentity identity)\n\t{\n\t\tif (identity == null)\n\t\t\tthrow new ArgumentNullException(nameof(identity));\n\n\t\treturn identity.FindFirst(IdentityOptions.ClaimsIdentity.UserIdClaimType)?.Value;\n\t}\n\n\n\t/// <summary>\n\t///     Get the user identifier from a <see cref=\"ClaimsPrincipal\" />.\n\t/// </summary>\n\t/// <param name=\"principal\">The <see cref=\"ClaimsPrincipal\" /> instance.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"principal\" /> is <c>null</c>.</exception>\n\t/// <returns>\n\t///     The user identifier included in the <paramref name=\"principal\" />. If no user identifier is included, this\n\t///     method will return <c>null</c>.\n\t/// </returns>\n\t[PublicAPI]\n\tpublic string? GetUserId([NotNull] ClaimsPrincipal principal)\n\t{\n\t\tif (principal == null)\n\t\t\tthrow new ArgumentNullException(nameof(principal));\n\n\t\treturn principal.FindFirst(IdentityOptions.ClaimsIdentity.UserIdClaimType)?.Value;\n\t}\n\n\t/// <summary>\n\t///     Get the the collection of all roles for a <see cref=\"ClaimsPrincipal\" />.\n\t/// </summary>\n\t/// <param name=\"principal\">The <see cref=\"ClaimsPrincipal\" /> instance.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"principal\" /> is <c>null</c>.</exception>\n\t/// <returns>\n\t///     A collection that including all the roles for the <paramref name=\"principal\" />. If the user has no roles,\n\t///     this method will return a empty collection.\n\t/// </returns>\n\t[NotNull]\n\t[PublicAPI]\n\tpublic IEnumerable<string> GetUserRoles([NotNull] ClaimsPrincipal principal)\n\t{\n\t\tif (principal == null)\n\t\t\tthrow new ArgumentNullException(nameof(principal));\n\n\t\treturn principal.FindAll(IdentityOptions.ClaimsIdentity.RoleClaimType).Select(i => i.Value);\n\t}\n\n\t/// <summary>\n\t///     Get the the collection of all roles for a <see cref=\"ClaimsIdentity\" />.\n\t/// </summary>\n\t/// <param name=\"identity\">The <see cref=\"ClaimsIdentity\" /> instance.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"identity\" /> is <c>null</c>.</exception>\n\t/// <returns>\n\t///     A collection that including all the roles for the <paramref name=\"identity\" />. If the user has no roles, this\n\t///     method will return a empty collection.\n\t/// </returns>\n\t[NotNull]\n\t[PublicAPI]\n\tpublic IEnumerable<string> GetUserRoles([NotNull] ClaimsIdentity identity)\n\t{\n\t\tif (identity == null)\n\t\t\tthrow new ArgumentNullException(nameof(identity));\n\n\t\treturn identity.FindAll(IdentityOptions.ClaimsIdentity.RoleClaimType).Select(i => i.Value);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Authentication.ExternalCookie/IdentityHelper.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Security.Claims;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore.Authentication;\n\n/// <summary>\n///     Provide utility methods for identity operations. This class is static.\n/// </summary>\n[PublicAPI]\npublic static class IdentityHelper\n{\n\t/// <summary>\n\t///     Create a new <see cref=\"ClaimsIdentity\" /> based on a existing <see cref=\"ClaimsIdentity\" /> with a new\n\t///     authentication type.\n\t/// </summary>\n\t/// <param name=\"identity\">The identity to be cloned.</param>\n\t/// <param name=\"authenticationType\">The authentication type of the new identity.</param>\n\t/// <returns>The cloned new identity.</returns>\n\t/// <exception cref=\"ArgumentNullException\">\n\t///     The <paramref name=\"identity\" /> or <paramref name=\"authenticationType\" /> is\n\t///     <c>null</c>.\n\t/// </exception>\n\tpublic static ClaimsIdentity CloneAs(this ClaimsIdentity identity, string authenticationType)\n\t{\n\t\tif (identity == null)\n\t\t\tthrow new ArgumentNullException(nameof(identity));\n\t\tif (authenticationType == null)\n\t\t\tthrow new ArgumentNullException(nameof(authenticationType));\n\n\t\treturn new ClaimsIdentity(identity.Claims, authenticationType, identity.NameClaimType,\n\t\t\tidentity.RoleClaimType);\n\t}\n\n\t/// <summary>\n\t///     Create a new <see cref=\"ClaimsPrincipal\" /> based on a existing <see cref=\"ClaimsPrincipal\" /> with a new\n\t///     authentication type for all of its identities.\n\t/// </summary>\n\t/// <param name=\"principal\">The principal to be cloned.</param>\n\t/// <param name=\"authenticationType\">The authentication type of the new identity.</param>\n\t/// <returns>The cloned new principal.</returns>\n\t/// <exception cref=\"ArgumentNullException\">\n\t///     The <paramref name=\"principal\" /> or <paramref name=\"authenticationType\" /> is\n\t///     <c>null</c>.\n\t/// </exception>\n\tpublic static ClaimsPrincipal CloneAs(this ClaimsPrincipal principal, string authenticationType)\n\t{\n\t\tif (principal == null)\n\t\t\tthrow new ArgumentNullException(nameof(principal));\n\t\tif (authenticationType == null)\n\t\t\tthrow new ArgumentNullException(nameof(authenticationType));\n\n\t\tvar newIdentities = principal.Identities.Select(i => i.CloneAs(authenticationType));\n\n\t\treturn new ClaimsPrincipal(newIdentities);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Authentication.ExternalCookie/Sakura.AspNetCore.Authentication.ExternalCookie.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <Description>This library is used to simplify the pipeline to use external cookie identity as application identity directly.</Description>\n    <AssemblyTitle>ASP.NET Core External Cookie Library</AssemblyTitle>\n    <VersionPrefix>2.0.0</VersionPrefix>\n    <Authors>Iris Sakura</Authors>\n    <TargetFrameworks>netstandard1.3;net451;netstandard2.0;netcoreapp3.0</TargetFrameworks>\n    <AssemblyName>Sakura.AspNetCore.Authentication.ExternalCookie</AssemblyName>\n    <PackageId>Sakura.AspNetCore.Authentication.ExternalCookie</PackageId>\n    <PackageTags>ASP.NET;ASP.NETCore;Identity;External Cookie</PackageTags>\n    <PackageReleaseNotes>Add support for ASP.NET Core 3.0</PackageReleaseNotes>\n    <PackageProjectUrl>https://github.com/sgjsakura/AspNetCore</PackageProjectUrl>\n    <PackageLicenseUrl></PackageLicenseUrl>\n    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>\n    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>\n    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>\n    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n    <PackageIconUrl></PackageIconUrl>\n    <RepositoryUrl>https://github.com/sgjsakura/AspNetCore.git</RepositoryUrl>\n    <RootNamespace>Sakura.AspNetCore.Authentication</RootNamespace>\n    <Version>2.2.0</Version>\n    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n  </PropertyGroup>\n\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'netstandard1.3' \">\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Identity\" Version=\"1.0.6\" />\n\t</ItemGroup>\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'netcoreapp3.0' \">\n\t\t<FrameworkReference Include=\"Microsoft.AspNetCore.App\" />\n\t</ItemGroup>\n\n  <PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n    <GenerateDocumentationFile>true</GenerateDocumentationFile>\n  </PropertyGroup>\n\n  <ItemGroup Condition=\" '$(TargetFramework)' == 'net451' \">\n    <Reference Include=\"System\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Identity\" Version=\"1.0.6\" />\n  </ItemGroup>\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'netstandard2.0' \">\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Identity\" Version=\"2.0.0\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t  <PackageReference Include=\"JetBrains.Annotations\" Version=\"2024.3.0\" />\n\t</ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Authentication.ExternalCookie/ServiceCollectionExtensions.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.Extensions.DependencyInjection.Extensions;\nusing Sakura.AspNetCore.Authentication;\n\n#if !NETSTANDARD2_0\nusing Microsoft.AspNetCore.Builder;\n#endif\n\n// ReSharper disable once CheckNamespace\n\nnamespace Microsoft.Extensions.DependencyInjection;\n\n/// <summary>\n///     Provide helper methods to adding external signing-in service to application. This class is static.\n/// </summary>\npublic static class ServiceCollectionExtensions\n{\n\t/// <summary>\n\t///     Add external signing-in manager service for application.\n\t/// </summary>\n\t/// <param name=\"services\">The <see cref=\"IServiceCollection\" /> instance.</param>\n\t/// <param name=\"setupOptions\">A setup method for configuring identity options.</param>\n\t[PublicAPI]\n\tpublic static void AddExternalSignInManager([NotNull] this IServiceCollection services,\n\t\tAction<IdentityOptions> setupOptions = null)\n\t{\n\t\tif (services == null)\n\t\t\tthrow new ArgumentNullException(nameof(services));\n\n\t\tservices.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();\n\t\tservices.TryAddSingleton<ISecurityStampValidator, ExternalSecurityStampValidator>();\n\t\tservices.TryAddScoped<ExternalSignInManager>();\n\n\t\tif (setupOptions != null)\n\t\t\tservices.Configure(setupOptions);\n\n#if NETSTANDARD2_0\n\n\t\t\tservices.AddAuthentication(IdentityConstants.ApplicationScheme)\n\t\t\t\t.AddCookie(IdentityConstants.ApplicationScheme)\n\t\t\t\t.AddCookie(IdentityConstants.ExternalScheme)\n\t\t\t\t.AddCookie(IdentityConstants.TwoFactorUserIdScheme)\n\t\t\t\t.AddCookie(IdentityConstants.TwoFactorRememberMeScheme);\n#endif\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.DynamicLocalizer/IDynamicHtmlLocalizer.cs",
    "content": "﻿using Microsoft.AspNetCore.Mvc.Localization;\n\nnamespace Sakura.AspNetCore.Localization;\n\n/// <summary>\n///     Define the necessary feature for dynamic style HTML resource accessing.\n/// </summary>\npublic interface IDynamicHtmlLocalizer\n{\n\t/// <summary>\n\t///     Get the dynamic object used to access resource strings as HTML format.\n\t/// </summary>\n\tdynamic Html { get; }\n\n\t/// <summary>\n\t///     Get the dynamic object used to access resource strings as text format.\n\t/// </summary>\n\tdynamic Text { get; }\n\n\t/// <summary>\n\t///     Get the internal <see cref=\"IHtmlLocalizer\" /> service instance.\n\t/// </summary>\n\tIHtmlLocalizer Localizer { get; }\n}\n\n\n/// <summary>\n///     Provide strong typed resource class for dynamic style HTML resource accessing.\n/// </summary>\n/// <typeparam name=\"TResource\">The resource type.</typeparam>\npublic interface IDynamicHtmlLocalizer<TResource> : IDynamicHtmlLocalizer\n{\n\t/// <summary>\n\t///     Get the strong typed internal <see cref=\"IHtmlLocalizer{TResource}\" /> service instance.\n\t/// </summary>\n\tnew IHtmlLocalizer<TResource> Localizer { get; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.DynamicLocalizer/IDynamicStringLocalizer.cs",
    "content": "﻿using Microsoft.Extensions.Localization;\n\nnamespace Sakura.AspNetCore.Localization;\n\n/// <summary>\n///     Provide strong typed resource class for dynamic style text resource accessing.\n/// </summary>\n/// <typeparam name=\"TResource\">The resource type.</typeparam>\npublic interface IDynamicStringLocalizer<TResource> : IDynamicStringLocalizer\n{\n\t/// <summary>\n\t///     Get the internal <see cref=\"IStringLocalizer{T}\" /> service instance.\n\t/// </summary>\n\tnew IStringLocalizer<TResource> Localizer { get; }\n}\n\n/// <summary>\n///     Define the necessary feature for dynamic style text resource accessing.\n/// </summary>\npublic interface IDynamicStringLocalizer\n{\n\t/// <summary>\n\t///     Get the dynamic object used to access resource strings as text format.\n\t/// </summary>\n\tdynamic Text { get; }\n\n\t/// <summary>\n\t///     Get the internal <see cref=\"IStringLocalizer\" /> service instance.\n\t/// </summary>\n\tIStringLocalizer Localizer { get; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.DynamicLocalizer/IDynamicViewLocalizer.cs",
    "content": "using Microsoft.AspNetCore.Mvc.Localization;\n\nnamespace Sakura.AspNetCore.Localization;\n\n/// <summary>\n///     Provide dynamic style localizable string resource accessibility for <see cref=\"IViewLocalizer\" /> service.\n/// </summary>\npublic interface IDynamicViewLocalizer : IDynamicHtmlLocalizer\n{\n\t/// <summary>\n\t///     Get the internal <see cref=\"IViewLocalizer\" /> service instance.\n\t/// </summary>\n\tnew IViewLocalizer Localizer { get; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.DynamicLocalizer/Internal/DynamicHtmlLocalizer.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Mvc.Localization;\n\nnamespace Sakura.AspNetCore.Localization.Internal;\n\n/// <summary>\n///     Provide the default implementation for <see cref=\"IDynamicHtmlLocalizer\" /> service.\n/// </summary>\npublic abstract class DynamicHtmlLocalizer : IDynamicHtmlLocalizer\n{\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"DynamicHtmlLocalizer\" /> service.\n\t/// </summary>\n\t/// <param name=\"localizer\">The internal <see cref=\"IHtmlLocalizer\" /> service.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"localizer\" /> is <c>null</c>.</exception>\n\tprotected DynamicHtmlLocalizer([NotNull] IHtmlLocalizer localizer)\n\t{\n\t\tLocalizer = localizer ?? throw new ArgumentNullException(nameof(localizer));\n\n\t\tHtml = new DynamicHtmlLocalizerWrapper(localizer);\n\t\tText = new DynamicHtmlTextLocalizerWrapper(localizer);\n\t}\n\n\t/// <inheritdoc />\n\tpublic dynamic Html { get; }\n\n\t/// <inheritdoc />\n\tpublic dynamic Text { get; }\n\n\t/// <inheritdoc />\n\tpublic IHtmlLocalizer Localizer { get; }\n}\n\n/// <summary>\n///     Provide the default implementation for <see cref=\"IDynamicHtmlLocalizer{TResource}\" /> service.\n/// </summary>\npublic class DynamicHtmlLocalizer<TResource> : DynamicHtmlLocalizer, IDynamicHtmlLocalizer<TResource>\n{\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"DynamicHtmlLocalizer{TResource}\" /> service.\n\t/// </summary>\n\t/// <param name=\"localizer\">The internal <see cref=\"IHtmlLocalizer{TResource}\" /> service.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"localizer\" /> is <c>null</c>.</exception>\n\t[UsedImplicitly]\n\tpublic DynamicHtmlLocalizer([NotNull] IHtmlLocalizer<TResource> localizer)\n\t\t: base(localizer)\n\t{\n\t\tLocalizer = localizer;\n\t}\n\n\t/// <inheritdoc />\n\tpublic new IHtmlLocalizer<TResource> Localizer { get; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.DynamicLocalizer/Internal/DynamicHtmlLocalizerWrapper.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Dynamic;\nusing System.Linq;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Mvc.Localization;\n\nnamespace Sakura.AspNetCore.Localization.Internal;\n\n/// <summary>\n///     Provide the dynamic style implementation for <see cref=\"IHtmlLocalizer\" /> object.\n/// </summary>\npublic class DynamicHtmlLocalizerWrapper : DynamicObject, IDynamicLocalizerWrapper\n{\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"DynamicHtmlLocalizerWrapper\" /> object.\n\t/// </summary>\n\t/// <param name=\"innerLocalizer\">The internal <see cref=\"IHtmlLocalizer\" /> object.</param>\n\tpublic DynamicHtmlLocalizerWrapper([NotNull] IHtmlLocalizer innerLocalizer)\n\t{\n\t\tInnerLocalizer = innerLocalizer ?? throw new ArgumentNullException(nameof(innerLocalizer));\n\t}\n\n\t/// <summary>\n\t///     Get the internal <see cref=\"IHtmlLocalizer\" /> service.\n\t/// </summary>\n\tprivate IHtmlLocalizer InnerLocalizer { get; }\n\n\t/// <inheritdoc />\n\tpublic override bool TryGetMember(GetMemberBinder binder, out object result)\n\t{\n\t\tresult = InnerLocalizer[binder.Name];\n\t\treturn true;\n\t}\n\n\t/// <inheritdoc />\n\tpublic override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)\n\t{\n\t\tresult = InnerLocalizer[binder.Name, args];\n\t\treturn true;\n\t}\n\n\t/// <inheritdoc />\n\tpublic override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)\n\t{\n\t\tif (indexes.Length == 0)\n\t\t\tthrow new ArgumentException(nameof(indexes), \"The length of index array cannot be zero.\");\n\n\t\tif (indexes[0] is string name)\n\t\t\tresult = InnerLocalizer[name, indexes.Skip(1).ToArray()];\n\t\telse\n\t\t\tthrow new ArgumentException(nameof(indexes), \"The first index value must be a string.\");\n\n\t\treturn true;\n\t}\n\n\t/// <inheritdoc />\n\tpublic override IEnumerable<string> GetDynamicMemberNames()\n\t{\n\t\treturn InnerLocalizer.GetAllStrings().Select(i => i.Name);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.DynamicLocalizer/Internal/DynamicHtmlTextLocalizerWrapper.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Dynamic;\nusing System.Linq;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Mvc.Localization;\n\nnamespace Sakura.AspNetCore.Localization.Internal;\n\n/// <summary>\n///     Provide the dynamic style implementation for <see cref=\"IHtmlLocalizer\" /> object.\n/// </summary>\npublic class DynamicHtmlTextLocalizerWrapper : DynamicObject, IDynamicLocalizerWrapper\n{\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"DynamicHtmlLocalizerWrapper\" /> object.\n\t/// </summary>\n\t/// <param name=\"innerLocalizer\">The internal <see cref=\"IHtmlLocalizer\" /> object.</param>\n\tpublic DynamicHtmlTextLocalizerWrapper([NotNull] IHtmlLocalizer innerLocalizer)\n\t{\n\t\tInnerLocalizer = innerLocalizer ?? throw new ArgumentNullException(nameof(innerLocalizer));\n\t}\n\n\t/// <summary>\n\t///     Get the internal <see cref=\"IHtmlLocalizer\" /> service.\n\t/// </summary>\n\tprivate IHtmlLocalizer InnerLocalizer { get; }\n\n\t/// <inheritdoc />\n\tpublic override bool TryGetMember(GetMemberBinder binder, out object result)\n\t{\n\t\tresult = InnerLocalizer.GetString(binder.Name);\n\t\treturn true;\n\t}\n\n\t/// <inheritdoc />\n\tpublic override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)\n\t{\n\t\tresult = InnerLocalizer.GetString(binder.Name, args);\n\t\treturn true;\n\t}\n\n\t/// <inheritdoc />\n\tpublic override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)\n\t{\n\t\tif (indexes.Length == 0)\n\t\t\tthrow new ArgumentException(nameof(indexes), \"The length of index array cannot be zero.\");\n\n\t\tif (indexes[0] is string name)\n\t\t\tresult = InnerLocalizer.GetString(name, indexes.Skip(1).ToArray());\n\t\telse\n\t\t\tthrow new ArgumentException(nameof(indexes), \"The first index value must be a string.\");\n\n\t\treturn true;\n\t}\n\n\t/// <inheritdoc />\n\tpublic override IEnumerable<string> GetDynamicMemberNames()\n\t{\n\t\treturn InnerLocalizer.GetAllStrings().Select(i => i.Name);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.DynamicLocalizer/Internal/DynamicStringLocalizer.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.Extensions.Localization;\n\nnamespace Sakura.AspNetCore.Localization.Internal;\n\n/// <summary>\n///     Provide the default implementation for <see cref=\"IDynamicStringLocalizer\" /> service.\n/// </summary>\npublic class DynamicStringLocalizer : IDynamicStringLocalizer\n{\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"DynamicStringLocalizer\" /> service.\n\t/// </summary>\n\t/// <param name=\"localizer\">The internal <see cref=\"IStringLocalizer\" /> service.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"localizer\" /> is <c>null</c>.</exception>\n\tpublic DynamicStringLocalizer([NotNull] IStringLocalizer localizer)\n\t{\n\t\tLocalizer = localizer ?? throw new ArgumentNullException(nameof(localizer));\n\t\tText = new DynamicStringLocalizerWrapper(localizer);\n\t}\n\n\t/// <inheritdoc />\n\tpublic dynamic Text { get; }\n\n\t/// <inheritdoc />\n\tpublic IStringLocalizer Localizer { get; }\n}\n\n/// <summary>\n///     Provide the default implementation for <see cref=\"DynamicStringLocalizer{TResource}\" /> service.\n/// </summary>\npublic class DynamicStringLocalizer<TResource> : DynamicStringLocalizer, IDynamicStringLocalizer<TResource>\n{\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"DynamicStringLocalizer{TResource}\" /> service.\n\t/// </summary>\n\t/// <param name=\"localizer\">The internal <see cref=\"IStringLocalizer{TResource}\" /> service.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"localizer\" /> is <c>null</c>.</exception>\n\t[UsedImplicitly]\n\tpublic DynamicStringLocalizer([NotNull] IStringLocalizer<TResource> localizer) : base(localizer)\n\t{\n\t\tLocalizer = localizer;\n\t}\n\n\t/// <inheritdoc />\n\tpublic new IStringLocalizer<TResource> Localizer { get; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.DynamicLocalizer/Internal/DynamicStringLocalizerWrapper.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Dynamic;\nusing System.Linq;\nusing Microsoft.Extensions.Localization;\n\nnamespace Sakura.AspNetCore.Localization.Internal;\n\n/// <summary>\n///     Provide the dynamic style implementation for <see cref=\"IStringLocalizer\" /> object.\n/// </summary>\npublic class DynamicStringLocalizerWrapper : DynamicObject\n{\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"DynamicStringLocalizerWrapper\" /> object.\n\t/// </summary>\n\t/// <param name=\"innerLocalizer\">The internal <see cref=\"IStringLocalizer\" /> object.</param>\n\tpublic DynamicStringLocalizerWrapper(IStringLocalizer innerLocalizer)\n\t{\n\t\tInnerLocalizer = innerLocalizer;\n\t}\n\n\t/// <summary>\n\t///     Get the internal <see cref=\"IStringLocalizer\" /> service.\n\t/// </summary>\n\tprivate IStringLocalizer InnerLocalizer { get; }\n\n\t/// <inheritdoc />\n\tpublic override bool TryGetMember(GetMemberBinder binder, out object result)\n\t{\n\t\tresult = InnerLocalizer[binder.Name];\n\t\treturn true;\n\t}\n\n\t/// <inheritdoc />\n\tpublic override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)\n\t{\n\t\tresult = InnerLocalizer[binder.Name, args];\n\t\treturn true;\n\t}\n\n\t/// <inheritdoc />\n\tpublic override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)\n\t{\n\t\tif (indexes.Length == 0)\n\t\t\tthrow new ArgumentException(nameof(indexes), \"The length of index array cannot be zero.\");\n\n\t\tif (indexes[0] is string name)\n\t\t\tresult = InnerLocalizer[name, indexes.Skip(1).ToArray()];\n\t\telse\n\t\t\tthrow new ArgumentException(nameof(indexes), \"The first index value must be a string.\");\n\n\t\treturn true;\n\t}\n\n\t/// <inheritdoc />\n\tpublic override IEnumerable<string> GetDynamicMemberNames()\n\t{\n\t\treturn InnerLocalizer.GetAllStrings().Select(i => i.Name);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.DynamicLocalizer/Internal/DynamicViewLocalizer.cs",
    "content": "using JetBrains.Annotations;\nusing Microsoft.AspNetCore.Mvc.Localization;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\n\nnamespace Sakura.AspNetCore.Localization.Internal;\n\n/// <summary>\n///     Provide the dynamic style implementation for <see cref=\"IHtmlLocalizer\" /> object.\n/// </summary>\npublic class DynamicViewLocalizer : DynamicHtmlLocalizer, IDynamicViewLocalizer, IViewContextAware\n{\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"DynamicViewLocalizer\" /> object.\n\t/// </summary>\n\t/// <param name=\"localizer\">The internal <see cref=\"IViewLocalizer\" /> object.</param>\n\t[UsedImplicitly]\n\tpublic DynamicViewLocalizer(IViewLocalizer localizer)\n\t\t: base(localizer)\n\t{\n\t\tLocalizer = localizer;\n\t}\n\n\t/// <inheritdoc />\n\tpublic new IViewLocalizer Localizer { get; }\n\n\t/// <inheritdoc />\n\tvoid IViewContextAware.Contextualize(ViewContext viewContext)\n\t{\n\t\tif (Localizer is IViewContextAware viewLocalizer)\n\t\t\tviewLocalizer.Contextualize(viewContext);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.DynamicLocalizer/Internal/IDynamicLocalizerWrapper.cs",
    "content": "using System.Dynamic;\n\nnamespace Sakura.AspNetCore.Localization.Internal;\n\n/// <summary>\n///     Provide common base interface for dynamic localizer services. This interface is for internal usage only and should\n///     not be used in user code.\n/// </summary>\npublic interface IDynamicLocalizerWrapper : IDynamicMetaObjectProvider\n{\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.DynamicLocalizer/Sakura.AspNetCore.DynamicLocalizer.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n\t<PropertyGroup>\n\t\t<TargetFrameworks>netstandard1.6;netcoreapp3.0</TargetFrameworks>\n\t\t<RootNamespace>Sakura.AspNetCore.Localization</RootNamespace>\n\t\t<GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n\t\t<Title>ASP.NET Core MVC Dynamic Localizer Service</Title>\n\t\t<Authors>Iris Sakura</Authors>\n\t\t<Company>Iris Sakura</Company>\n\t\t<Product>Sakura.AspNetCore.Extensions</Product>\n\t\t<Description>This package provide dynamic style localiazation resource accesing ability.</Description>\n\t\t<PackageReleaseNotes>Update framework reference usage.</PackageReleaseNotes>\n\t\t<PackageLicenseUrl></PackageLicenseUrl>\n\t\t<PackageProjectUrl>https://github.com/sgjsakura/AspNetCore</PackageProjectUrl>\n\t\t<RepositoryUrl>https://github.com/sgjsakura/AspNetCore.git</RepositoryUrl>\n\t\t<PackageTags>ASP.NET;ASP.NETCore;MVC;MVCCore;Localization</PackageTags>\n\t\t<Version>2.2.0</Version>\n\t\t<PackageLicenseFile></PackageLicenseFile>\n\t\t<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n\t\t<GenerateDocumentationFile>True</GenerateDocumentationFile>\n\t</PropertyGroup>\n\n\t<PropertyGroup>\n\t  <WarningLevel>9999</WarningLevel>\n\t  <CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>\n\t</PropertyGroup>\n\n\t<ItemGroup Condition=\" $(TargetFramework) == 'netstandard1.6' \">\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Mvc.Localization\" Version=\"1.0.0\" />\n\t</ItemGroup>\n\n\t<ItemGroup Condition=\" $(TargetFramework) == 'netcoreapp3.0' \">\n\t\t<FrameworkReference Include=\"Microsoft.AspNetCore.App\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t  <PackageReference Include=\"JetBrains.Annotations\" Version=\"2024.3.0\" />\n\t</ItemGroup>\n\n</Project>"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.DynamicLocalizer/ServiceCollectionExtensions.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.DependencyInjection.Extensions;\nusing Sakura.AspNetCore.Localization.Internal;\n\nnamespace Sakura.AspNetCore.Localization;\n\n/// <summary>\n///     Provide extension methods to add services. This class is static.\n/// </summary>\npublic static class ServiceCollectionExtensions\n{\n\t/// <summary>\n\t///     Add default implementation for all dynamic resource accessing services.\n\t/// </summary>\n\t/// <param name=\"services\">The service collection container.</param>\n\tpublic static void AddDynamicLocalizer([NotNull] this IServiceCollection services)\n\t{\n\t\tif (services == null)\n\t\t\tthrow new ArgumentNullException(nameof(services));\n\n\t\tservices.TryAddTransient<IDynamicViewLocalizer, DynamicViewLocalizer>();\n\t\tservices.TryAddTransient(typeof(IDynamicStringLocalizer<>), typeof(DynamicStringLocalizer<>));\n\t\tservices.TryAddTransient(typeof(IDynamicHtmlLocalizer<>), typeof(DynamicHtmlLocalizer<>));\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Extensions.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.3.32819.101\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Sakura.AspNetCore.Authentication.ExternalCookie\", \"Sakura.AspNetCore.Authentication.ExternalCookie\\Sakura.AspNetCore.Authentication.ExternalCookie.csproj\", \"{590CF0F1-94D9-452E-850B-F4317948F964}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Sakura.AspNetCore.Messages\", \"Sakura.AspNetCore.Messages\\Sakura.AspNetCore.Messages.csproj\", \"{0503C0D3-7C16-4197-8A45-55A8D9FAF336}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Sakura.AspNetCore.Messages.Abstractions\", \"Sakura.AspNetCore.Messages.Abstractions\\Sakura.AspNetCore.Messages.Abstractions.csproj\", \"{376B4F2C-4F5B-45C8-8622-1EF87915CA2D}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Sakura.AspNetCore.Mvc.ActionResultExceptionExtensions\", \"Sakura.AspNetCore.Mvc.ActionResultExceptionExtensions\\Sakura.AspNetCore.Mvc.ActionResultExceptionExtensions.csproj\", \"{57C6025D-CA1C-47FB-A3E0-14F00E5CA42D}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Sakura.AspNetCore.Mvc.Messages\", \"Sakura.AspNetCore.Mvc.Messages\\Sakura.AspNetCore.Mvc.Messages.csproj\", \"{F33A9546-DCEE-4587-B4C3-6BC7C0A15781}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Sakura.AspNetCore.Mvc.PagedList\", \"Sakura.AspNetCore.Mvc.PagedList\\Sakura.AspNetCore.Mvc.PagedList.csproj\", \"{074CC00D-46C5-4ACF-8FE8-DA11FA6C62D2}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Sakura.AspNetCore.Mvc.TagHelpers\", \"Sakura.AspNetCore.Mvc.TagHelpers\\Sakura.AspNetCore.Mvc.TagHelpers.csproj\", \"{5CE9F1EB-B73B-424E-96BE-E1ADE49D1A87}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Sakura.AspNetCore.Mvc.TempDataExtensions\", \"Sakura.AspNetCore.Mvc.TempDataExtensions\\Sakura.AspNetCore.Mvc.TempDataExtensions.csproj\", \"{5C995A93-2867-4230-BCCB-9F966BAF9BB4}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Sakura.AspNetCore.PagedList\", \"Sakura.AspNetCore.PagedList\\Sakura.AspNetCore.PagedList.csproj\", \"{D6F2F3B1-A8F7-414A-888A-A6F8524D465B}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Sakura.AspNetCore.PagedList.Abstractions\", \"Sakura.AspNetCore.PagedList.Abstractions\\Sakura.AspNetCore.PagedList.Abstractions.csproj\", \"{A5254FCE-857E-44A3-A161-062358486099}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Sakura.AspNetCore.PagedList.Async\", \"Sakura.AspNetCore.PagedList.Async\\Sakura.AspNetCore.PagedList.Async.csproj\", \"{B192C312-8D65-4CDE-9146-A3379FD71F47}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Sakura.AspNetCore.DynamicLocalizer\", \"Sakura.AspNetCore.DynamicLocalizer\\Sakura.AspNetCore.DynamicLocalizer.csproj\", \"{A622B3F3-458E-4813-95F9-2AD4BDDC8A49}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Sakura.EntityFrameworkCore.FromSqlExtensions\", \"Sakura.EntityFrameworkCore.FromSqlExtensions\\Sakura.EntityFrameworkCore.FromSqlExtensions.csproj\", \"{56143555-9A12-4FD0-9447-A6831C7FEB17}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Sakura.AspNetCore.Mvc.VisualStyles\", \"Sakura.AspNetCore.Mvc.VisualStyles\\Sakura.AspNetCore.Mvc.VisualStyles.csproj\", \"{79EB3475-55AB-4546-A043-83CFD5DB3723}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"解决方案项\", \"解决方案项\", \"{4DB05341-A92F-4FDB-AFC2-6C1FFF0065D4}\"\n\tProjectSection(SolutionItems) = preProject\n\t\tDirectory.Build.props = Directory.Build.props\n\tEndProjectSection\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{590CF0F1-94D9-452E-850B-F4317948F964}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{590CF0F1-94D9-452E-850B-F4317948F964}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{590CF0F1-94D9-452E-850B-F4317948F964}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{590CF0F1-94D9-452E-850B-F4317948F964}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{0503C0D3-7C16-4197-8A45-55A8D9FAF336}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{0503C0D3-7C16-4197-8A45-55A8D9FAF336}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{0503C0D3-7C16-4197-8A45-55A8D9FAF336}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{0503C0D3-7C16-4197-8A45-55A8D9FAF336}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{376B4F2C-4F5B-45C8-8622-1EF87915CA2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{376B4F2C-4F5B-45C8-8622-1EF87915CA2D}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{376B4F2C-4F5B-45C8-8622-1EF87915CA2D}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{376B4F2C-4F5B-45C8-8622-1EF87915CA2D}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{57C6025D-CA1C-47FB-A3E0-14F00E5CA42D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{57C6025D-CA1C-47FB-A3E0-14F00E5CA42D}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{57C6025D-CA1C-47FB-A3E0-14F00E5CA42D}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{57C6025D-CA1C-47FB-A3E0-14F00E5CA42D}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{F33A9546-DCEE-4587-B4C3-6BC7C0A15781}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{F33A9546-DCEE-4587-B4C3-6BC7C0A15781}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{F33A9546-DCEE-4587-B4C3-6BC7C0A15781}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{F33A9546-DCEE-4587-B4C3-6BC7C0A15781}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{074CC00D-46C5-4ACF-8FE8-DA11FA6C62D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{074CC00D-46C5-4ACF-8FE8-DA11FA6C62D2}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{074CC00D-46C5-4ACF-8FE8-DA11FA6C62D2}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{074CC00D-46C5-4ACF-8FE8-DA11FA6C62D2}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{5CE9F1EB-B73B-424E-96BE-E1ADE49D1A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{5CE9F1EB-B73B-424E-96BE-E1ADE49D1A87}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{5CE9F1EB-B73B-424E-96BE-E1ADE49D1A87}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{5CE9F1EB-B73B-424E-96BE-E1ADE49D1A87}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{5C995A93-2867-4230-BCCB-9F966BAF9BB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{5C995A93-2867-4230-BCCB-9F966BAF9BB4}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{5C995A93-2867-4230-BCCB-9F966BAF9BB4}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{5C995A93-2867-4230-BCCB-9F966BAF9BB4}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{D6F2F3B1-A8F7-414A-888A-A6F8524D465B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{D6F2F3B1-A8F7-414A-888A-A6F8524D465B}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{D6F2F3B1-A8F7-414A-888A-A6F8524D465B}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{D6F2F3B1-A8F7-414A-888A-A6F8524D465B}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{A5254FCE-857E-44A3-A161-062358486099}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{A5254FCE-857E-44A3-A161-062358486099}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{A5254FCE-857E-44A3-A161-062358486099}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{A5254FCE-857E-44A3-A161-062358486099}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{B192C312-8D65-4CDE-9146-A3379FD71F47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{B192C312-8D65-4CDE-9146-A3379FD71F47}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{B192C312-8D65-4CDE-9146-A3379FD71F47}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{B192C312-8D65-4CDE-9146-A3379FD71F47}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{A622B3F3-458E-4813-95F9-2AD4BDDC8A49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{A622B3F3-458E-4813-95F9-2AD4BDDC8A49}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{A622B3F3-458E-4813-95F9-2AD4BDDC8A49}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{A622B3F3-458E-4813-95F9-2AD4BDDC8A49}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{56143555-9A12-4FD0-9447-A6831C7FEB17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{56143555-9A12-4FD0-9447-A6831C7FEB17}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{56143555-9A12-4FD0-9447-A6831C7FEB17}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{56143555-9A12-4FD0-9447-A6831C7FEB17}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{79EB3475-55AB-4546-A043-83CFD5DB3723}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{79EB3475-55AB-4546-A043-83CFD5DB3723}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{79EB3475-55AB-4546-A043-83CFD5DB3723}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{79EB3475-55AB-4546-A043-83CFD5DB3723}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {80507D33-E191-4B8A-9100-13E2BB921706}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Extensions.sln.DotSettings",
    "content": "﻿<wpf:ResourceDictionary xml:space=\"preserve\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:s=\"clr-namespace:System;assembly=mscorlib\" xmlns:ss=\"urn:shemas-jetbrains-com:settings-storage-xaml\" xmlns:wpf=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue\">UI</s:String></wpf:ResourceDictionary>"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Localization.Dictionary/DictionaryStringLocalizer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing System.Text;\nusing Microsoft.Extensions.Localization;\n\nnamespace Sakura.AspNetCore.Localization\n{\n\t/// <summary>\n\t/// Provide localizable resource string from a dictiony.\n\t/// </summary>\n\tpublic class DictionaryStringLocalizer : IStringLocalizer\n\t{\n\t\tpublic IReadOnlyDictionary<string, string> LocalizedStrings { get; set; }\n\n\t\t/// <inheritdoc />\n\t\tpublic IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)\n\t\t{\n\t\t\tthrow new NotImplementedException();\n\t\t}\n\n\t\t/// <inheritdoc />\n\t\tpublic IStringLocalizer WithCulture(CultureInfo culture)\n\t\t{\n\t\t\tthrow new NotImplementedException();\n\t\t}\n\n\t\t/// <inheritdoc />\n\t\tLocalizedString IStringLocalizer.this[string name]\n\t\t{\n\t\t\tget\n\t\t\t{\n\t\t\t\tvar culture = new CultureInfo();\n\t\t\t\tvar found = TryFetchStringCore(LocalizedStrings, )\n\t\t\t}\n\t\t}\n\n\t\t/// <inheritdoc />\n\t\tLocalizedString IStringLocalizer.this[string name, params object[] arguments]\n\t\t{\n\t\t\tget { throw new NotImplementedException(); }\n\t\t}\n\n\t\t/// <returns></returns>\n\t\tprivate static bool TryFetchStringCore(IReadOnlyDictionary<string, string> data, CultureInfo culture, out string result)\n\t\t{\n\t\t\tdo\n\t\t\t{\n\t\t\t\tif (data.TryGetValue(culture.Name, out var value))\n\t\t\t\t{\n\t\t\t\t\tresult = value;\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tculture = culture.Parent;\n\t\t\t} while (culture.Equals(culture.Parent));\n\n\t\t\tresult = null;\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Localization.Dictionary/Sakura.AspNetCore.Localization.Dictionary.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>netstandard1.4</TargetFramework>\n    <RootNamespace>Sakura.AspNetCore.Localization</RootNamespace>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.Extensions.Localization.Abstractions\" Version=\"1.0.0\" />\n  </ItemGroup>\n\n</Project>"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Messages/DefaultOperationMessageAccessor.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\nusing Microsoft.Extensions.Options;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Provide the default implementation for <see cref=\"IOperationMessageAccessor\" /> service.\n/// </summary>\npublic class DefaultOperationMessageAccessor : IOperationMessageAccessor\n{\n\t/// <summary>\n\t///     Initialize a new service instance with required services.\n\t/// </summary>\n\t/// <param name=\"httpContextAccessor\">The <see cref=\"IHttpContextAccessor\" /> service.</param>\n\t/// <param name=\"tempDataFactory\">The TempData dictionary factory in the context.</param>\n\t/// <param name=\"options\">The <see cref=\"OperationMessageOptions\" /> instance used to configure the accessor.</param>\n\tpublic DefaultOperationMessageAccessor(IHttpContextAccessor httpContextAccessor,\n\t\tITempDataDictionaryFactory tempDataFactory, IOptions<OperationMessageOptions> options)\n\t{\n\t\tOptions = options.Value;\n\t\tTempData = tempDataFactory.GetTempData(httpContextAccessor.HttpContext);\n\t}\n\n\n\t/// <summary>\n\t///     Get the ASP.NET TempData dictionary in the context.\n\t/// </summary>\n\t[PublicAPI]\n\tprotected ITempDataDictionary TempData { get; }\n\n\t/// <summary>\n\t///     Get the options for this accessor.\n\t/// </summary>\n\t[PublicAPI]\n\tprotected OperationMessageOptions Options { get; }\n\n\t/// <summary>\n\t///     Get the collection to store all messages in the current execution context.\n\t/// </summary>\n\t/// <exception cref=\"InvalidOperationException\">\n\t///     The configured TempDataKeyName is <c>null</c>; Or, the temp data dictionary\n\t///     already contains an value associated with the key used for operation message, however the value cannot be converted\n\t///     into <see cref=\"ICollection{T}\" /> type.\n\t/// </exception>\n\tpublic ICollection<OperationMessage> Messages\n\t{\n\t\tget\n\t\t{\n\t\t\t// Retrieve and check the key\n\t\t\tvar key = Options.TempDataKeyName;\n\n\t\t\tif (key == null)\n\t\t\t\tthrow new InvalidOperationException(\n\t\t\t\t\t\"The TempDataKeyName property in the configured OperationMessageOptions instance cannot be null。\");\n\n\t\t\tICollection<OperationMessage> result = null;\n\n\t\t\t// Retrieve the data and convert to the collection type\n\t\t\tif (TempData.TryGetValue(key, out var value))\n\t\t\t{\n\t\t\t\tresult = value as ICollection<OperationMessage>;\n\n\t\t\t\t// Check type and raise exception\n\t\t\t\tif (result == null && !Options.AutomaticOverwriteOnTypeError)\n\t\t\t\t\tthrow new InvalidOperationException(\n\t\t\t\t\t\t$\"TempData has an value with the key name '{key}', however it cannot be converted to ICollection<OperationMessage> type.\");\n\t\t\t}\n\n\t\t\t// Automatically create a new collection when the valuect is not existed or cannot be converted to the target type\n\t\t\tif (result == null)\n\t\t\t{\n\t\t\t\tresult = new List<OperationMessage>();\n\t\t\t\tTempData[key] = result;\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Messages/OperationMessageOptions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Provide the configuration options for operation message services.\n/// </summary>\npublic class OperationMessageOptions\n{\n\t/// <summary>\n\t///     Define the default value for the <see cref=\"TempDataKeyName\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string DefaultTempDataKeyName = \"ASP_OperationMessages\";\n\n\t/// <summary>\n\t///     Get or set the key used to store the message collections in TempData dictionary. This key must not be used in any\n\t///     other manner. The default value of this property is <see cref=\"DefaultTempDataKeyName\" />.\n\t/// </summary>\n\t[PublicAPI]\n\tpublic string TempDataKeyName { get; set; } = DefaultTempDataKeyName;\n\n\t/// <summary>\n\t///     Get of set a value that control the behavior when there is an value stored associated with the key defined as\n\t///     <see cref=\"TempDataKeyName\" /> but it cannot be converted into <see cref=\"ICollection{T}\" /> type. If the property\n\t///     is set to <c>true</c>, the original value will be automatically discarded and replaced with a new instance of\n\t///     <see cref=\"ICollection{OperationMessage}\" /> type. Otherwise, <see cref=\"InvalidOperationException\" /> will be\n\t///     threw.\n\t/// </summary>\n\t[PublicAPI]\n\tpublic bool AutomaticOverwriteOnTypeError { get; set; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Messages/OperationMessageServiceCollectionExtensions.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.Extensions.DependencyInjection.Extensions;\nusing Sakura.AspNetCore;\n\n// ReSharper disable once CheckNamespace\n\nnamespace Microsoft.Extensions.DependencyInjection;\n\n/// <summary>\n///     Provide extension method for add or configure the operation message service. This class is static.\n/// </summary>\n[PublicAPI]\npublic static class OperationMessageServiceCollectionExtensions\n{\n\t/// <summary>\n\t///     Add the operation message services to the specified service container.\n\t/// </summary>\n\t/// <param name=\"services\">The <see cref=\"IServiceCollection\" /> to add the service to.</param>\n\t/// <param name=\"configureOptions\">A optional action for configure this service.</param>\n\t/// <returns>The <paramref name=\"services\" /> parameter.</returns>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"services\" /> is <c>null</c>.</exception>\n\tpublic static IServiceCollection AddOperationMessageAccessor(this IServiceCollection services,\n\t\tAction<OperationMessageOptions>? configureOptions = null)\n\t{\n\t\t// Check argument\n\t\tif (services == null)\n\t\t\tthrow new ArgumentNullException(nameof(services));\n\n\t\t// Try to add service\n\t\tservices.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();\n\t\tservices.TryAddScoped<IOperationMessageAccessor, DefaultOperationMessageAccessor>();\n\n\t\t// Configure the service\n\t\tif (configureOptions != null)\n\t\t\tservices.Configure(configureOptions);\n\n\n\t\treturn services;\n\t}\n\n\t/// <summary>\n\t///     Configure the options for <see cref=\"IOperationMessageAccessor\" /> service.\n\t/// </summary>\n\t/// <param name=\"services\">The service container to store the service configuration.</param>\n\t/// <param name=\"configureOptions\">A action for configuring the service.</param>\n\t/// <returns>The <paramref name=\"services\" /> parameter.</returns>\n\t/// <exception cref=\"ArgumentNullException\">\n\t///     Either <paramref name=\"services\" /> or <paramref name=\"configureOptions\" /> is\n\t///     <c>null</c>.\n\t/// </exception>\n\tpublic static IServiceCollection ConfigureOperationMessages(this IServiceCollection services,\n\t\tAction<OperationMessageOptions> configureOptions)\n\t{\n\t\t// Check argument\n\t\tif (services == null)\n\t\t\tthrow new ArgumentNullException(nameof(services));\n\n\t\tif (configureOptions == null)\n\t\t\tthrow new ArgumentNullException(nameof(configureOptions));\n\n\t\tservices.Configure(configureOptions);\n\n\t\treturn services;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Messages/Sakura.AspNetCore.Messages.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <Description>This library contains the default implementation for operation message services for ASP.NET Core projects.</Description>\n    <AssemblyTitle>ASP.NET Core Operation Message Core Library</AssemblyTitle>\n    <Authors>Iris Sakura</Authors>\n    <TargetFrameworks>netstandard1.6;net451;netcoreapp3.0</TargetFrameworks>\n    <AssemblyName>Sakura.AspNetCore.Messages</AssemblyName>\n    <PackageId>Sakura.AspNetCore.Messages</PackageId>\n    <PackageTags>ASP.NET;ASP.NETCore;Message;Messages</PackageTags>\n    <PackageReleaseNotes>Update to support ASP.NET Core 3.0</PackageReleaseNotes>\n    <PackageProjectUrl>https://github.com/sgjsakura/AspNetCore</PackageProjectUrl>\n    <PackageLicenseUrl></PackageLicenseUrl>\n    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>\n    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>\n    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>\n    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n    <RepositoryUrl>https://github.com/sgjsakura/AspNetCore.git</RepositoryUrl>\n    <Version>1.2.0</Version>\n    <RootNamespace>Sakura.AspNetCore</RootNamespace>\n    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\Sakura.AspNetCore.Messages.Abstractions\\Sakura.AspNetCore.Messages.Abstractions.csproj\" />\n  </ItemGroup>\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' != 'netcoreapp3.0' \">\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Mvc.ViewFeatures\" Version=\"1.0.0\" />\n  </ItemGroup>\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'netcoreapp3.0' \">\n\t\t<FrameworkReference Include=\"Microsoft.AspNetCore.App\" />\t\n\t</ItemGroup>\n\n  <PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n    <GenerateDocumentationFile>true</GenerateDocumentationFile>\n  </PropertyGroup>\n\n  <ItemGroup Condition=\" '$(TargetFramework)' == 'net451' \">\n    <Reference Include=\"System\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Messages.Abstractions/IOperationMessageAccessor.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Provide the service for getting the messages from the current execution context.\n/// </summary>\npublic interface IOperationMessageAccessor\n{\n\t/// <summary>\n\t///     Get all the messages in the current context. If no messages are currently set, this property will return an empty\n\t///     collection.\n\t/// </summary>\n\tICollection<OperationMessage> Messages { get; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Messages.Abstractions/OperationMessage.cs",
    "content": "﻿using JetBrains.Annotations;\nusing Microsoft.AspNetCore.Html;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Represent as an operation message.\n/// </summary>\n[PublicAPI]\npublic class OperationMessage\n{\n\t/// <summary>\n\t///     Initialize a new instance of operation message.\n\t/// </summary>\n\tpublic OperationMessage()\n\t{\n\t}\n\n\t/// <summary>\n\t///     Initialize a new instance of operation message with the specified information.\n\t/// </summary>\n\t/// <param name=\"level\">The level of the message.</param>\n\t/// <param name=\"title\">The title of the message.</param>\n\t/// <param name=\"description\">The detailed description of the message. The default value of this parameter is <c>null</c>.</param>\n\tpublic OperationMessage(\n\t\tOperationMessageLevel level,\n\t\t[LocalizationRequired] IHtmlContent? title,\n\t\t[LocalizationRequired] IHtmlContent? description = null)\n\t{\n\t\tLevel = level;\n\t\tTitle = title;\n\t\tDescription = description;\n\t}\n\n\t/// <summary>\n\t///     Get or set the detailed description of the message.\n\t/// </summary>\n\t[LocalizationRequired]\n\tpublic IHtmlContent? Description { get; set; }\n\n\t/// <summary>\n\t///     Get or set the level of the message.\n\t/// </summary>\n\tpublic OperationMessageLevel Level { get; set; }\n\n\t/// <summary>\n\t///     Get or set the title of the message.\n\t/// </summary>\n\t[LocalizationRequired]\n\tpublic IHtmlContent? Title { get; set; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Messages.Abstractions/OperationMessageExtensions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\n\nusing JetBrains.Annotations;\n\nusing Microsoft.AspNetCore.Html;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Provide extension method for adding messages. This class is static.\n/// </summary>\n[PublicAPI]\npublic static class OperationMessageExtensions\n{\n\t/// <summary>\n\t///     Add a new message into the message collection.\n\t/// </summary>\n\t/// <param name=\"messageAccessor\">The collection of messages to be adding the new message.</param>\n\t/// <param name=\"level\">The level of the new message.</param>\n\t/// <param name=\"title\">The title of the new message.</param>\n\t/// <param name=\"description\">The detailed description of the new message.</param>\n\t/// <returns>The newly added <see cref=\"OperationMessage\" /> object.</returns>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"messageAccessor\" /> is <c>null</c>.</exception>\n\tpublic static OperationMessage Add([NotNull] this IOperationMessageAccessor messageAccessor,\n\t\tOperationMessageLevel level, [LocalizationRequired] IHtmlContent? title,\n\t\t[LocalizationRequired] IHtmlContent? description = null)\n\t{\n\t\tif (messageAccessor == null)\n\t\t\tthrow new ArgumentNullException(nameof(messageAccessor));\n\n\t\tvar item = new OperationMessage(level, title, description);\n\t\tmessageAccessor.Messages.Add(item);\n\n\t\treturn item;\n\t}\n\n\t/// <summary>\n\t///     Add a new message into the message collection.\n\t/// </summary>\n\t/// <param name=\"messageAccessor\">The collection of messages to be adding the new message.</param>\n\t/// <param name=\"level\">The level of the new message.</param>\n\t/// <param name=\"title\">The title of the new message.</param>\n\t/// <param name=\"description\">The detailed description of the new message.</param>\n\t/// <returns>The newly added <see cref=\"OperationMessage\" /> object.</returns>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"messageAccessor\" /> is <c>null</c>.</exception>\n\tpublic static OperationMessage Add([NotNull] this IOperationMessageAccessor messageAccessor,\n\t\tOperationMessageLevel level, [LocalizationRequired] string? title,\n\t\t[LocalizationRequired] string? description = null)\n\t{\n\t\tif (messageAccessor == null)\n\t\t\tthrow new ArgumentNullException(nameof(messageAccessor));\n\n\t\tvar item = new OperationMessage(level, EncodeToHtml(title), EncodeToHtml(description));\n\t\tmessageAccessor.Messages.Add(item);\n\n\t\treturn item;\n\t}\n\n\tprivate static IHtmlContent EncodeToHtml(string value)\n\t{\n\t\treturn new HtmlContentBuilder().Append(value);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Messages.Abstractions/OperationMessageLevel.cs",
    "content": "﻿namespace Sakura.AspNetCore;\n\n/// <summary>\n///     Define the level of messages.\n/// </summary>\n/// <seealso cref=\"OperationMessage.Level\" />\npublic enum OperationMessageLevel\n{\n\t/// <summary>\n\t///     The level of the message is not classified.\n\t/// </summary>\n\tNone = 0,\n\n\t/// <summary>\n\t///     The message is in debug level.\n\t/// </summary>\n\tDebug,\n\n\t/// <summary>\n\t///     The message is in verbose level.\n\t/// </summary>\n\tVerbose,\n\n\t/// <summary>\n\t///     The message is in information level, which means it can be ignored safely.\n\t/// </summary>\n\tInfo,\n\n\t/// <summary>\n\t///     The message is in success level, it represent as a successfully operation result.\n\t/// </summary>\n\tSuccess,\n\n\t/// <summary>\n\t///     The message is in warning level, it represents as an non-critical incorrect runtime behavior.\n\t/// </summary>\n\tWarning,\n\n\t/// <summary>\n\t///     The message is in error level, it means there is some problem during the system executing.\n\t/// </summary>\n\tError,\n\n\t/// <summary>\n\t///     The message is in critical level, the system may be badly broken and cannot continue for execution.\n\t/// </summary>\n\tCritical\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Messages.Abstractions/Sakura.AspNetCore.Messages.Abstractions.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <Description>This library contains the operation message type definitions and service interfaces for ASP.NET Core projects.</Description>\n    <AssemblyTitle>ASP.NET Core Operation Message Abstraction Layer</AssemblyTitle>\n    <VersionPrefix>1.0.1</VersionPrefix>\n    <Authors>Iris Sakura</Authors>\n    <TargetFrameworks>netstandard1.0;netcoreapp3.0</TargetFrameworks>\n    <GenerateDocumentationFile>true</GenerateDocumentationFile>\n    <AssemblyName>Sakura.AspNetCore.Messages.Abstractions</AssemblyName>\n    <PackageId>Sakura.AspNetCore.Messages.Abstractions</PackageId>\n    <PackageTags>ASP.NET;ASP.NETCore;Message;Messages</PackageTags>\n    <PackageReleaseNotes>Fixed security problem using HtmlString.</PackageReleaseNotes>\n    <PackageProjectUrl>https://github.com/sgjsakura/AspNetCore</PackageProjectUrl>\n    <PackageLicenseUrl></PackageLicenseUrl>\n    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>\n    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>\n    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>\n    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n    <RepositoryUrl>https://github.com/sgjsakura/AspNetCore.git</RepositoryUrl>\n    <Version>2.0.1</Version>\n    <RootNamespace>Sakura.AspNetCore</RootNamespace>\n    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n  </PropertyGroup>\n\n\t<ItemGroup Condition=\"$(TargetFramework) == 'netstandard1.0'\">\n\t  <PackageReference Include=\"Microsoft.AspNetCore.Html.Abstractions\" Version=\"1.0.0\" />\n\t</ItemGroup>\n\n\t<ItemGroup Condition=\"$(TargetFramework) == 'netcoreapp3.0'\">\n\t\t<FrameworkReference Include=\"Microsoft.AspNetCore.App\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t  <PackageReference Include=\"JetBrains.Annotations\" Version=\"2024.3.0\" />\n\t</ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.ActionResultExceptionExtensions/ActionResultException.cs",
    "content": "﻿using System;\nusing System.Net;\nusing Microsoft.AspNetCore.Mvc;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Represent as a special exception used to return an <see cref=\"IActionResult\" /> directly during the MVC executing\n///     pipeline.\n/// </summary>\n/// <remarks>\n///     To enable this special action, please mark <see cref=\"EnableActionResultExceptionAttribute\" /> on a controller or\n///     action, or register it globally in your MVC configuration code.\n/// </remarks>\npublic class ActionResultException : Exception\n{\n\t/// <summary>\n\t///     Initialize a new <see cref=\"ActionResultException\" /> with specified <see cref=\"IActionResult\" />.\n\t/// </summary>\n\t/// <param name=\"result\">The <see cref=\"IActionResult\" /> object which will be returned when this exception is handled.</param>\n\tpublic ActionResultException(IActionResult result)\n\t{\n\t\tResult = result ?? throw new ArgumentNullException(nameof(result));\n\t}\n\n\t/// <summary>\n\t///     Initialize a new <see cref=\"ActionResultException\" /> with specified <see cref=\"IActionResult\" /> and message.\n\t/// </summary>\n\t/// <param name=\"message\">The message for this exception.</param>\n\t/// <param name=\"result\">The <see cref=\"IActionResult\" /> object which will be returned when this exception is handled.</param>\n\tpublic ActionResultException(string message, IActionResult result) : base(message)\n\t{\n\t\tResult = result ?? throw new ArgumentNullException(nameof(result));\n\t}\n\n\t/// <summary>\n\t///     Initialize a new <see cref=\"ActionResultException\" /> with specified <see cref=\"IActionResult\" /> and information.\n\t/// </summary>\n\t/// <param name=\"message\">The message for this exception.</param>\n\t/// <param name=\"inner\">The inner exception for this exception.</param>\n\t/// <param name=\"result\">The <see cref=\"IActionResult\" /> object which will be returned when this exception is handled.</param>\n\tpublic ActionResultException(string message, Exception inner, IActionResult result) : base(message, inner)\n\t{\n\t\tResult = result ?? throw new ArgumentNullException(nameof(result));\n\t}\n\n\t/// <summary>\n\t///     Get or the result will be returned to the MVC pipeline when this exception is handled.\n\t/// </summary>\n\tpublic IActionResult Result { get; }\n\n\t#region Shortcut Constructors\n\n\t/// <summary>\n\t///     Create a new <see cref=\"ActionResultException\" /> with specified HTTP status code.\n\t/// </summary>\n\t/// <param name=\"statusCode\">The value of the HTTP status code.</param>\n\tpublic ActionResultException(int statusCode)\n\t{\n\t\tResult = new StatusCodeResult(statusCode);\n\t}\n\n\t/// <summary>\n\t///     Create a new <see cref=\"ActionResultException\" /> with specified HTTP status code.\n\t/// </summary>\n\t/// <param name=\"statusCode\">The value of the HTTP status code.</param>\n\tpublic ActionResultException(HttpStatusCode statusCode)\n\t{\n\t\tResult = new StatusCodeResult((int) statusCode);\n\t}\n\n\t/// <summary>\n\t///     Create a new <see cref=\"ActionResultException\" /> with specified HTTP status code and content.\n\t/// </summary>\n\t/// <param name=\"statusCode\">The value of the HTTP status code.</param>\n\t/// <param name=\"value\">The result content object.</param>\n\tpublic ActionResultException(int statusCode, object value)\n\t{\n\t\tResult = new ObjectResult(value) {StatusCode = statusCode};\n\t}\n\n\t/// <summary>\n\t///     Create a new <see cref=\"ActionResultException\" /> with specified HTTP status code and content.\n\t/// </summary>\n\t/// <param name=\"statusCode\">The value of the HTTP status code.</param>\n\t/// <param name=\"value\">The result content object.</param>\n\tpublic ActionResultException(HttpStatusCode statusCode, object value)\n\t{\n\t\tResult = new ObjectResult(value) {StatusCode = (int) statusCode};\n\t}\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.ActionResultExceptionExtensions/EnableActionResultExceptionAttribute.cs",
    "content": "﻿using Microsoft.AspNetCore.Mvc.Filters;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Enable special handling for <see cref=\"ActionResultException\" />.\n/// </summary>\npublic class EnableActionResultExceptionAttribute : ExceptionFilterAttribute\n{\n\t/// <inheritdoc />\n\tpublic override void OnException(ExceptionContext context)\n\t{\n\t\tif (context.Exception is ActionResultException actionResultException)\n\t\t{\n\t\t\tcontext.ExceptionHandled = true;\n\t\t\tcontext.Result = actionResultException.Result;\n\t\t}\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.ActionResultExceptionExtensions/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled through the following\n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"Iris Sakura\")]\n[assembly: AssemblyProduct(\"Sakura.AspNetCore.Extensions\")]\n[assembly: AssemblyTrademark(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible\n// to COM components.  If you need to access a type in this assembly from\n// COM, set the ComVisible attribute to true on that type.\n\n[assembly: ComVisible(false)]\n\n// The following GUID is for the ID of the typelib if this project is exposed to COM\n\n[assembly: Guid(\"07d7e7cf-f66a-4f0d-b061-1136b3bfc16b\")]"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.ActionResultExceptionExtensions/Sakura.AspNetCore.Mvc.ActionResultExceptionExtensions.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <Description>This library classes and helper methods which can be used to return action results directly during the MVC executing pipeline.</Description>\n    <AssemblyTitle>ASP.NET Core ActionResult Extension Library</AssemblyTitle>\n    <Authors>Iris Sakura</Authors>\n    <TargetFrameworks>net451;netstandard1.6;netcoreapp3.0</TargetFrameworks>\n    <GenerateDocumentationFile>true</GenerateDocumentationFile>\n    <AssemblyName>Sakura.AspNetCore.Mvc.ActionResultExceptionExtensions</AssemblyName>\n    <PackageId>Sakura.AspNetCore.Mvc.ActionResultExceptionExtensions</PackageId>\n    <PackageTags>ASP.NET;ASP.NETCore;MVC;MVCCore;Exception</PackageTags>\n    <PackageReleaseNotes>Update to support ASP.NET Core 3.0</PackageReleaseNotes>\n    <PackageProjectUrl>https://github.com/sgjsakura/AspNetCore</PackageProjectUrl>\n    <PackageLicenseUrl></PackageLicenseUrl>\n    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>\n    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>\n    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>\n    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n    <RepositoryUrl>https://github.com/sgjsakura/AspNetCore.git</RepositoryUrl>\n    <Version>1.1.0</Version>\n    <RootNamespace>Sakura.AspNetCore.Mvc</RootNamespace>\n    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n  </PropertyGroup>\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'netcoreapp3.0' \">\n\t\t<FrameworkReference Include=\"Microsoft.AspNetCore.App\" />\t\n\t</ItemGroup>\n\n  <ItemGroup Condition=\" '$(TargetFramework)' != 'netcoreapp3.0' \">\n    <PackageReference Include=\"Microsoft.AspNetCore.Mvc.Core\" Version=\"1.0.4\" />\n  </ItemGroup>\n\n  <ItemGroup Condition=\" '$(TargetFramework)' == 'net451' \">\n    <Reference Include=\"System\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n  </ItemGroup>\n\n</Project>"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.ActionResultExceptionExtensions/ServiceExtensions.cs",
    "content": "﻿using System;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.Filters;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Provide extension methods for register <see cref=\"EnableActionResultExceptionAttribute\" /> globally. This class is\n///     static.\n/// </summary>\npublic static class ServiceExtensions\n{\n\t/// <summary>\n\t///     Add <see cref=\"EnableActionResultExceptionAttribute\" /> as a exception filter in the\n\t///     <paramref name=\"filterCollection\" />.\n\t/// </summary>\n\t/// <param name=\"filterCollection\">The <see cref=\"FilterCollection\" /> object.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"filterCollection\" /> is <c>null</c>.</exception>\n\tpublic static void EnableActionResultExceptionFilter(this FilterCollection filterCollection)\n\t{\n\t\tif (filterCollection == null)\n\t\t\tthrow new ArgumentNullException(nameof(filterCollection));\n\n\t\tfilterCollection.Add(typeof(EnableActionResultExceptionAttribute));\n\t}\n\n\t/// <summary>\n\t///     Add <see cref=\"EnableActionResultExceptionAttribute\" /> as a exception filter in the <paramref name=\"mvcOptions\" />\n\t///     .\n\t/// </summary>\n\t/// <param name=\"mvcOptions\">The <see cref=\"MvcOptions\" /> object.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"mvcOptions\" /> is <c>null</c>.</exception>\n\tpublic static void EnableActionResultExceptionFilter(this MvcOptions mvcOptions)\n\t{\n\t\tif (mvcOptions == null)\n\t\t\tthrow new ArgumentNullException(nameof(mvcOptions));\n\n\t\tmvcOptions.Filters.EnableActionResultExceptionFilter();\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/CssClassBasedIconMapper.cs",
    "content": "﻿using Microsoft.AspNetCore.Html;\nusing Microsoft.AspNetCore.Mvc.Rendering;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n/// Generate HTML content for an icon based on the CSS class name.\n/// </summary>\n/// <param name=\"cssClassMapper\"></param>\npublic class CssClassBasedIconMapper(IIconToCssClassMapper cssClassMapper)\n    : IIconMapper\n{\n    /// <summary>\n    /// The base css classes for the icon element. Will always be added to the generated HTML tag. \n    /// </summary>\n    /// <remarks>This property will be used in the default implementation of <see cref=\"GenerateBaseTag\"/> method.</remarks>\n    protected virtual string? BaseCssClasses { get; } = null;\n\n    /// <summary>\n    /// The base tag name for the icon element. Default is \"i\".\n    /// </summary>\n    /// <remarks>This property will be used in the default implementation of <see cref=\"GenerateBaseTag\"/> method.</remarks>\n    protected virtual string BaseTagName { get; } = \"i\";\n\n    /// <summary>\n    /// Generate the HTML tag for the icon element.\n    /// </summary>\n    /// <returns>The generated <see cref=\"TagBuilder\"/> instance.</returns>\n    /// <remarks>The default implementation of this method uses both <see cref=\"BaseTagName\"/> and <see cref=\"BaseCssClasses\"/> during the generation process.</remarks>\n    protected virtual TagBuilder GenerateBaseTag()\n    {\n        var tag = new TagBuilder(BaseTagName);\n        tag.AddCssClass(BaseCssClasses ?? string.Empty);\n\n        return tag;\n    }\n\n    /// <inheritdoc />\n    public IHtmlContent? GetIconHtml(OperationMessageLevel level)\n    {\n        var iconCssClass = cssClassMapper.GetIconCssClass(level);\n\n        if (iconCssClass is null)\n        {\n            return null;\n        }\n\n        var tag = GenerateBaseTag();\n        tag.AddCssClass(iconCssClass);\n        return tag;\n    }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/IIconMapper.cs",
    "content": "﻿using Microsoft.AspNetCore.Html;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n/// Used to map an <see cref=\"OperationMessageLevel\"/> to an icon HTML content.\n/// </summary>\npublic interface IIconMapper\n{\n    /// <summary>\n    /// Get the HTML content for the icon corresponding to the specified operation message level.\n    /// </summary>\n    /// <param name=\"level\">The operation message level.</param>\n    /// <returns>The generated HTML content for <paramref name=\"level\"/>. If the return value is <see langword=\"null\"/>, no icon should be displayed.</returns>\n    IHtmlContent? GetIconHtml(OperationMessageLevel level);\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/IIconToCssClassMapper.cs",
    "content": "﻿using System.Runtime.InteropServices.ComTypes;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n/// Used to map an <see cref=\"OperationMessageLevel\"/> to a CSS class name for an icon.\n/// </summary>\npublic interface IIconToCssClassMapper\n{\n    /// <summary>\n    /// Try to get the CSS class name for the icon based on the operation message level.\n    /// </summary>\n    /// <param name=\"level\">The operation message level.</param>\n    /// <returns>The CSS class for the <see cref=\"level\"/>. <see langword=\"null\"/> return value means no icon should be generated. </returns>\n    string? GetIconCssClass(OperationMessageLevel level);\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/IOperationMessageHtmlGenerator.cs",
    "content": "﻿using System.Collections.Generic;\n\nusing Microsoft.AspNetCore.Html;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Define the necessary feature for generating HTML content for <see cref=\"OperationMessage\" /> list.\n/// </summary>\npublic interface IOperationMessageHtmlGenerator\n{\n\t/// <summary>\n\t///     Generate HTML content for one or more <see cref=\"OperationMessage\" /> objects.\n\t/// </summary>\n\t/// <param name=\"messages\">The list of message to generating the HTML content.</param>\n\t/// <param name=\"context\">The context of the <see cref=\"TagHelper\"/>.</param>\n\t/// <returns>The generated <see cref=\"IHtmlContent\"/>.</returns>\n\tIHtmlContent GenerateList(IEnumerable<OperationMessage> messages, TagHelperContext context);\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/IOperationMessageLevelToStyleMapper.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Provide methods to convert <see cref=\"OperationMessageLevel\" /> into primary style names. The style names may be used to generate CSS classes for displaying operation messages in a web application.\n/// </summary>\npublic interface IOperationMessageLevelToStyleMapper\n{\n\t/// <summary>\n\t///     Get base style name of a <see cref=\"OperationMessageLevel\" />.\n\t/// </summary>\n\t/// <param name=\"level\">The level of an operation message.</param>\n\t/// <returns>The base style of the <paramref name=\"level\"/>. Returns <see langword=\"null\"/> if no style should be applied.</returns>\n\tstring? GetLevelStyleName(OperationMessageLevel level);\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/Bootstrap5AlertMessageContentLayout.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.Implementations;\n\n/// <summary>\n/// Control the layout of the title and content in a Bootstrap 5 alert message.\n/// </summary>\npublic enum Bootstrap5AlertMessageContentLayout\n{\n    /// <summary>\n    /// The title and content is displayed in a single row.\n    /// </summary>\n    SingleRow,\n    /// <summary>\n    /// The title and content is displayed in two rows, without a separator between them.\n    /// </summary>\n    DoubleRow,\n    /// <summary>\n    /// The title and content is displayed in two rows, with a separator between them. Note if either of them is empty, the separator will not be shown.\n    /// </summary>\n    DoubleRowWithSeparator,\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/Bootstrap5AlertMessageHtmlGenerator.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\n\nusing Microsoft.AspNetCore.Html;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\nusing Microsoft.Extensions.Localization;\nusing Microsoft.Extensions.Options;\n\nnamespace Sakura.AspNetCore.Mvc.Implementations;\n\n/// <summary>\n///     Provide default implementation for <see cref=\"IOperationMessageHtmlGenerator\" />.\n/// </summary>\n/// <param name=\"levelToStyleMapper\">The message-level to CSS class mapper.</param>\npublic class Bootstrap5AlertMessageHtmlGenerator(IOperationMessageLevelToStyleMapper levelToStyleMapper, IIconMapper iconMapper, IOptions<Bootstrap5AlertMessageHtmlGeneratorOptions> options, IStringLocalizer<Bootstrap5AlertMessageHtmlGeneratorOptions> localizer)\n\t: IOperationMessageHtmlGenerator\n{\n\t/// <inheritdoc />\n\tpublic IHtmlContent GenerateList(IEnumerable<OperationMessage> messages, TagHelperContext context)\n\t{\n\t\tvar container = new TagBuilder(\"div\");\n\t\tcontainer.MergeAttributes(context.AllAttributes.ToDictionary(i => i.Name, object? (i) => i.Value));\n\t\t\n\t\tforeach (var message in messages)\n\t\t{\n\t\t\tvar alertItem = GenerateAlertItem(message);\n\t\t\tif (alertItem != null)\n\t\t\t{\n\t\t\t\tcontainer.InnerHtml.AppendHtml(alertItem);\n\t\t\t}\n\t\t}\n\n\t\treturn container;\n\t}\n\n\t/// <summary>\n\t///     Generate the alert dialog for a <see cref=\"OperationMessage\" />.\n\t/// </summary>\n\t/// <param name=\"message\">The <see cref=\"OperationMessage\" /> instance.</param>\n\t/// <returns>The generated HTML content which represent as a HTML alert dialog.</returns>\n\tprivate IHtmlContent? GenerateAlertItem(OperationMessage message)\n\t{\n\t\t// Get the base style.\n\t\tvar baseStyle = levelToStyleMapper.GetLevelStyleName(message.Level);\n\n\t\t// No base style means no alert dialog should be generated.\n\t\tif (baseStyle == null)\n\t\t{\n\t\t\treturn null;\n\t\t}\n\n\t\tvar container = new TagBuilder(\"div\")\n\t\t{\n\t\t\tAttributes =\n\t\t\t{\n\t\t\t\t[\"role\"] = \"alert\"\n\t\t\t}\n\t\t};\n\n\t\tcontainer.AddCssClass($\"alert alert-{baseStyle}\");\n\t\tif (options.Value.Dismissible)\n\t\t{\n\t\t\tcontainer.AddCssClass(\"alert-dismissible fade show\");\n\t\t}\n\n\t\tif (options.Value.ShowIcon)\n\t\t{\n\t\t\t// The icon should be shown in the alert dialog.\n\t\t\tvar iconHtml = iconMapper.GetIconHtml(message.Level);\n\n\t\t\tif (iconHtml != null)\n\t\t\t{\n\t\t\t\tcontainer.InnerHtml.AppendHtml(iconHtml).AppendLine();\n\t\t\t}\n\t\t}\n\n\t\tif (message.Title != null)\n\t\t{\n\t\t\tvar titleElement = new TagBuilder(\"strong\");\n\t\t\ttitleElement.InnerHtml.AppendHtml(message.Title);\n\n\t\t\tcontainer.InnerHtml.AppendHtml(titleElement).AppendLine();\n\t\t}\n\n\t\t// separator, only show if both title and description exist.\n\t\tif (message is { Title: not null, Description: not null })\n\t\t{\n\t\t\tvar separator = GenerateSeparator(options.Value.ContentLayout);\n\t\t\tcontainer.InnerHtml.AppendHtml(separator);\n\t\t}\n\n\t\tif (message.Description != null)\n\t\t{\n\t\t\tcontainer.InnerHtml.AppendHtml(message.Description).AppendLine();\n\t\t}\n\n\t\tif (options.Value.Dismissible)\n\t\t{\n\t\t\tvar closeButton = new TagBuilder(\"button\")\n\t\t\t{\n\t\t\t\tAttributes =\n\t\t\t\t{\n\t\t\t\t\t[\"class\"] = \"btn-close\",\n\t\t\t\t\t[\"type\"] = \"button\",\n\t\t\t\t\t[\"data-bs-dismiss\"] = \"alert\",\n\t\t\t\t\t[\"aria-label\"] = localizer[options.Value.DismissAriaLabel]\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tcontainer.InnerHtml.AppendHtml(closeButton).AppendLine();\n\t\t}\n\n\t\treturn container;\n\t}\n\n\t/// <summary>\n\t/// Generate the separator HTML content based on the specified layout.\n\t/// </summary>\n\t/// <param name=\"layout\">The layout used of the alert message.</param>\n\t/// <returns>The separator HTML content.</returns>\n\t/// <exception cref=\"ArgumentOutOfRangeException\">The <paramref name=\"layout\"/> is not a valid enum item.</exception>\n\tprivate static IHtmlContent GenerateSeparator(Bootstrap5AlertMessageContentLayout layout)\n\t{\n\t\tvar htmlString = layout switch\n\t\t{\n\t\t\tBootstrap5AlertMessageContentLayout.SingleRow => \" \",\n\t\t\tBootstrap5AlertMessageContentLayout.DoubleRow => \"<br />\",\n\t\t\tBootstrap5AlertMessageContentLayout.DoubleRowWithSeparator => \"<hr />\",\n\t\t\t_ => throw new ArgumentOutOfRangeException(nameof(layout), layout, \"The argument is not a valid enum item.\")\n\t\t};\n\n\t\treturn new HtmlString(htmlString);\n\t}\n\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/Bootstrap5AlertMessageHtmlGeneratorOptions.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.Implementations;\n\n/// <summary>\n/// Provide options for <see cref=\"Bootstrap5AlertMessageHtmlGenerator\"/>.\n/// </summary>\npublic record  Bootstrap5AlertMessageHtmlGeneratorOptions\n{\n/// <summary>\n    /// Get or set whether an icon should be shown in the message dialog.\n    /// </summary>\n    public bool ShowIcon { get; set; } = true;\n\n    /// <summary>\n    /// Get or set whether the alert message is dismissible (closable).\n    /// </summary>\n    public bool Dismissible { get; set; } = true;\n    /// <summary>\n    /// The text used in \"aria-label\" attribute of the dismiss button in the alert message. This is used for accessibility purposes.\n    /// </summary>\n\n    public string DismissAriaLabel { get; set; } = \"Close\";\n\n    /// <summary>\n    /// Get or set the layout of the title and content in the alert message.\n    /// </summary>\n    public Bootstrap5AlertMessageContentLayout ContentLayout { get; set; } = Bootstrap5AlertMessageContentLayout.SingleRow;\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/Bootstrap5MessagesServiceBuilder.cs",
    "content": "﻿using Microsoft.Extensions.DependencyInjection;\n\nnamespace Sakura.AspNetCore.Mvc.Implementations;\n\n/// <summary>\n/// Used to configure the Bootstrap 5 alert messages service.\n/// </summary>\n/// <param name=\"service\">The <see cref=\"IServiceCollection\"/> instance.</param>\npublic sealed class Bootstrap5MessagesServiceBuilder(IServiceCollection service)\n : IBootstrapIconServiceBuilder\n{\n    /// <summary>\n    /// Get the service collection.\n    /// </summary>\n    public IServiceCollection Services { get; } = service;\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/Bootstrap5ToastMessageHtmlGenerator.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nusing Microsoft.AspNetCore.Html;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\nusing Microsoft.Extensions.Localization;\nusing Microsoft.Extensions.Options;\n\nnamespace Sakura.AspNetCore.Mvc.Implementations;\npublic class Bootstrap5ToastMessageHtmlGenerator(IOperationMessageLevelToStyleMapper styleMapper, IIconMapper iconMapper, IOptions<Bootstrap5ToastMessageHtmlGeneratorOptions> options, IStringLocalizer<Bootstrap5ToastMessageHtmlGeneratorOptions> localizer)\n: IOperationMessageHtmlGenerator\n{\n\t/// <inheritdoc />\n\tpublic IHtmlContent GenerateList(IEnumerable<OperationMessage> messages, TagHelperContext context)\n\t{\n\t\tvar container = new TagBuilder(\"div\");\n\n\t\t// Merge attributes from the context to the container.\n\t\tcontainer.MergeAttributes(context.AllAttributes.ToDictionary(i => i.Name, object? (i) => i.Value));\n\t\t\n\t\t// Fixed class for toast container\n\t\tcontainer.AddCssClass(\"toast-container position-fixed\");\n\t\t\n\t\t// Alignment class\n\t\tcontainer.AddCssClasses(GetAlignmentClasses());\n\n\t\t// Container\n\t\tforeach (var item in messages)\n\t\t{\n\t\t\tcontainer.InnerHtml.AppendHtml(GenerateItem(item));\n\t\t}\n\n\t\treturn container;\n\t}\n\n\tprivate IHtmlContent GenerateItem(OperationMessage message)\n\t{\n\t\tvar container = new TagBuilder(\"div\")\n\t\t{\n\t\t\tAttributes =\n\t\t\t{\n\t\t\t\t[\"class\"] = \"toast fade\",\n\t\t\t\t[\"role\"] = \"alert\",\n\t\t\t\t[\"aria-live\"] = \"assertive\",\n\t\t\t\t[\"aria-atomic\"] = \"true\",\n\t\t\t\t[\"data-bs-animation\"] = options.Value.Animation.ToJavaScriptString(),\n\t\t\t\t[\"data-bs-autohide\"] = options.Value.AutoHide.ToJavaScriptString(),\n\t\t\t\t[\"data-bs-delay\"] = options.Value.Delay.TotalMilliseconds.ToString(\"F\")\n\t\t\t}\n\t\t};\n\n\t\tvar header = new TagBuilder(\"div\")\n\t\t{\n\t\t\tAttributes =\n\t\t\t{\n\t\t\t\t[\"class\"] = \"toast-header\"\n\t\t\t}\n\t\t};\n\n\n\t\t// strong-styled title\n\t\tvar titleElement = new TagBuilder(\"strong\")\n\t\t{\n\t\t\tAttributes =\n\t\t\t{\n\t\t\t\t[\"class\"] = \"me-auto\"\n\t\t\t}\n\t\t};\n\n\t\t// Show Icon\n\t\tif (options.Value.ShowIcon)\n\t\t{\n\t\t\tvar iconElement = iconMapper.GetIconHtml(message.Level);\n\n\t\t\t// Append Icon only if exists\n\t\t\tif (iconElement != null)\n\t\t\t{\n\t\t\t\ttitleElement.InnerHtml.AppendHtml(iconElement).AppendLine();\n\t\t\t}\n\t\t}\n\n\t\t// Title\n\t\ttitleElement.InnerHtml.AppendHtml(message.Title).AppendLine();\n\n\t\theader.InnerHtml.AppendHtml(titleElement);\n\n\t\t// Dismiss button\n\t\tif (options.Value.Dismissible)\n\t\t{\n\t\t\tvar closeButton = new TagBuilder(\"button\")\n\t\t\t{\n\t\t\t\tAttributes =\n\t\t\t\t{\n\t\t\t\t\t[\"type\"] = \"button\",\n\t\t\t\t\t[\"class\"] = \"btn-close\",\n\t\t\t\t\t[\"data-bs-dismiss\"] = \"toast\",\n\t\t\t\t\t[\"aria-label\"] = localizer[options.Value.DismissAriaLabel]\n\t\t\t\t}\n\t\t\t};\n\n\t\t\theader.InnerHtml.AppendHtml(closeButton).AppendLine();\n\t\t}\n\n\t\t// Body\n\t\tvar body = new TagBuilder(\"div\")\n\t\t{\n\t\t\tAttributes =\n\t\t\t{\n\t\t\t\t[\"class\"] = \"toast-body\"\n\t\t\t}\n\t\t};\n\t\tbody.InnerHtml.AppendHtml(message.Description);\n\n\t\t// Apply style\n\t\tvar style = styleMapper.GetLevelStyleName(message.Level);\n\t\tif (style != null)\n\t\t{\n\t\t\theader.AddCssClass($\"text-bg-{style}\");\n\t\t\tbody.AddCssClass($\"bg-{style}-subtle text-{style}-emphasis\");\n\t\t}\n\n\t\tcontainer.InnerHtml.AppendHtml(header).AppendHtml(body);\n\t\treturn container;\n\t}\n\n\tprivate IEnumerable<string> GetAlignmentClasses()\n\t{\n\t\tyield return GeneratePositionClassForHorizontalAlignment(options.Value.HorizontalAlignment);\n\t\tyield return GeneratePositionClassForVerticalAlignment(options.Value.VerticalAlignment);\n\t}\n\n\tprivate static string GeneratePositionClassForHorizontalAlignment(HorizontalAlignment value)\n\t{\n\t\treturn value switch\n\t\t{\n\t\t\tHorizontalAlignment.Left => \"start-0\",\n\t\t\tHorizontalAlignment.Center => \"start-50 translate-middle-x\",\n\t\t\tHorizontalAlignment.Right => \"end-0\",\n\t\t\t_ => throw new ArgumentOutOfRangeException(nameof(value), value, \"the value is not a valid enum item.\")\n\t\t};\n\t}\n\n\tprivate static string GeneratePositionClassForVerticalAlignment(VerticalAlignment value)\n\t{\n\t\treturn value switch\n\t\t{\n\t\t\tVerticalAlignment.Top => \"top-0\",\n\t\t\tVerticalAlignment.Middle => \"top-50 translate-middle-y\",\n\t\t\tVerticalAlignment.Bottom => \"bottom-0\",\n\t\t\t_ => throw new ArgumentOutOfRangeException(nameof(value), value, \"the value is not a valid enum item.\")\n\t\t};\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/Bootstrap5ToastMessageHtmlGeneratorOptions.cs",
    "content": "﻿using System;\n\nnamespace Sakura.AspNetCore.Mvc.Implementations;\n\npublic record Bootstrap5ToastMessageHtmlGeneratorOptions\n{\n    public bool Dismissible { get; set; } = true;\n    public bool ShowIcon { get; set; } = true;\n\n    public string DismissAriaLabel { get; set; } = \"Close\";\n\n    public bool Animation { get; set; } = true;\n    public bool AutoHide { get; set; } = true;\n\n    public TimeSpan Delay { get; set; } = TimeSpan.FromSeconds(5);\n\n    public HorizontalAlignment HorizontalAlignment { get; set; } = HorizontalAlignment.Right;\n    public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Bottom;\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/BootstrapBuilderExtensions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.DependencyInjection.Extensions;\nusing Microsoft.Framework.DependencyInjection;\n\nnamespace Sakura.AspNetCore.Mvc.Implementations;\n\n\n\n/// <summary>\n/// Provide extension methods for configuring Bootstrap-style messages in ASP.NET Core applications.\n/// </summary>\npublic static class BootstrapBuilderExtensions\n{\n    /// <summary>\n    /// Use the Bootstrap 5 alert messages as the operation message UI implementation.\n    /// </summary>\n    /// <param name=\"serviceBuilder\">The <see cref=\"OperationMessageServiceBuilder\"/> instance.</param>\n    /// <param name=\"configAction\">Optional action for configure the <see cref=\"Bootstrap5AlertMessageHtmlGeneratorOptions\"/>.</param>\n    /// <returns>The <see cref=\"Bootstrap5MessagesServiceBuilder\"/> instance which can be used to further configure related services.</returns>\n    public static Bootstrap5MessagesServiceBuilder UseBootstrap5Alerts(this OperationMessageServiceBuilder serviceBuilder, Action<Bootstrap5AlertMessageHtmlGeneratorOptions>? configAction = null)\n    {\n        serviceBuilder.Services.TryAddSingleton<IOperationMessageHtmlGenerator, Bootstrap5AlertMessageHtmlGenerator>();\n        serviceBuilder.Services.TryAddSingleton<IOperationMessageLevelToStyleMapper, DefaultBootstrap5OperationMessageLevelToStyleMapper>();\n\n        if (configAction != null)\n        {\n            serviceBuilder.Services.Configure(configAction);\n        }\n\n        return new (serviceBuilder.Services);\n    }\n\n    /// <summary>\n    /// Use the Bootstrap 5 alert messages as the operation message UI implementation.\n    /// </summary>\n    /// <param name=\"serviceBuilder\">The <see cref=\"OperationMessageServiceBuilder\"/> instance.</param>\n    /// <param name=\"configAction\">Optional action for configure the <see cref=\"Bootstrap5AlertMessageHtmlGeneratorOptions\"/>.</param>\n    /// <returns>The <see cref=\"Bootstrap5MessagesServiceBuilder\"/> instance which can be used to further configure related services.</returns>\n    public static Bootstrap5MessagesServiceBuilder UseBootstrap5Toasts(this OperationMessageServiceBuilder serviceBuilder, Action<Bootstrap5ToastMessageHtmlGeneratorOptions>? configAction = null)\n    {\n        serviceBuilder.Services.TryAddSingleton<IOperationMessageHtmlGenerator, Bootstrap5ToastMessageHtmlGenerator>();\n        serviceBuilder.Services.TryAddSingleton<IOperationMessageLevelToStyleMapper, DefaultBootstrap5OperationMessageLevelToStyleMapper>();\n\n        if (configAction != null)\n        {\n            serviceBuilder.Services.Configure(configAction);\n        }\n\n        return new(serviceBuilder.Services);\n    }\n\n    /// <summary>\n    /// Use the bootstrap icons for the Bootstrap 5 alert messages.\n    /// </summary>\n    /// <param name=\"builder\">The <see cref=\"Bootstrap5MessagesServiceBuilder\"/> instance.</param>\n    /// <param name=\"configAction\">Optional action for configure the <see cref=\"BootstrapIconCssClassMapperOptions\"/>.</param>\n    public static void UseBootstrapIcons(this IBootstrapIconServiceBuilder builder, Action<BootstrapIconCssClassMapperOptions>? configAction = null)\n    {\n        builder.Services.TryAddSingleton<IIconMapper, BootstrapIconMapper>();\n        builder.Services.TryAddSingleton<IIconToCssClassMapper, DefaultBootstrapIconCssClassMapper>();\n\n        if (configAction != null)\n        {\n            builder.Services.Configure(configAction);\n        }\n    }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/BootstrapIconCssClassMapperOptions.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.Implementations;\n\n/// <summary>\n/// Used to configure the options for the <see cref=\"BootstrapIconMapper\"/> class.\n/// </summary>\npublic record BootstrapIconCssClassMapperOptions\n{\n    /// <summary>\n    /// Get or set the style of the Bootstrap icon.\n    /// </summary>\n    public BootstrapIconStyle IconStyle { get; set; } = BootstrapIconStyle.Normal;\n\n    /// <summary>\n    /// Get or set whether to use the circle icon for the exclamation mark.\n    /// </summary>\n    public bool UseCircleForExclamation { get; set; } = false;\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/BootstrapIconMapper.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.Implementations;\n\n/// <summary>\n/// Provide default implementation for <see cref=\"IIconToCssClassMapper\"/> using Bootstrap icons.\n/// </summary>\n/// <param name=\"cssClassMapper\"></param>\npublic class BootstrapIconMapper(IIconToCssClassMapper cssClassMapper)\n    : CssClassBasedIconMapper(cssClassMapper)\n{\n    /// <inheritdoc />\n    protected override string? BaseCssClasses { get; } = \"bi\";\n}\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/BootstrapIconStyle.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.Implementations;\n\n/// <summary>\n/// Get or set the style of the Bootstrap icon.\n/// </summary>\npublic enum BootstrapIconStyle\n{\n    /// <summary>\n    /// Normal icon style, which is the default style.\n    /// </summary>\n    Normal,\n    /// <summary>\n    /// Filled icon style, which is used for filled icons.\n    /// </summary>\n    Filled,\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/DefaultBootstrap5OperationMessageLevelToStyleMapper.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.Implementations;\n\n/// <summary>\n/// Provides the default implementation of <see cref=\"IOperationMessageLevelToStyleMapper\"/>.\n/// </summary>\npublic class DefaultBootstrap5OperationMessageLevelToStyleMapper : IOperationMessageLevelToStyleMapper\n{\n    /// <inheritdoc />\n    public string? GetLevelStyleName(OperationMessageLevel level)\n    {\n        return level switch\n        {\n            OperationMessageLevel.Success => \"success\",\n            OperationMessageLevel.Warning => \"warning\",\n            OperationMessageLevel.Error or OperationMessageLevel.Critical => \"danger\",\n            OperationMessageLevel.Info => \"info\",\n            OperationMessageLevel.Debug or OperationMessageLevel.Verbose => \"secondary\",\n             _ => null,\n        };\n    }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/DefaultBootstrapIconCssClassMapper.cs",
    "content": "﻿using Microsoft.Extensions.Options;\n\nnamespace Sakura.AspNetCore.Mvc.Implementations;\n\n/// <summary>\n/// Provide the default implementation of <see cref=\"IIconToCssClassMapper\"/> for Bootstrap icons.\n/// </summary>\n/// <param name=\"options\">The options for icon mapping.</param>\npublic class DefaultBootstrapIconCssClassMapper(IOptions<BootstrapIconCssClassMapperOptions> options)\n    : IIconToCssClassMapper\n{\n    /// <inheritdoc />\n    public string? GetIconCssClass(OperationMessageLevel level)\n    {\n        var baseClass = level switch\n        {\n            OperationMessageLevel.Success => \"check-circle\",\n            OperationMessageLevel.Warning => options.Value.UseCircleForExclamation\n                ? \"exclamation-circle\"\n                : \"exclamation-triangle\",\n            OperationMessageLevel.Info => \"info-circle\",\n            OperationMessageLevel.Error => \"x-circle\",\n            _ => null\n        };\n\n        // No icon for unclassified or debug/verbose levels\n        if (baseClass is null)\n        {\n            return null;\n        }\n\n        return options.Value.IconStyle switch\n        {\n            BootstrapIconStyle.Normal => $\"bi-{baseClass}\",\n            BootstrapIconStyle.Filled => $\"bi-{baseClass}-fill\",\n            _ => null\n        };\n    }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/HorizontalAlignment.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.Implementations;\n\npublic enum HorizontalAlignment\n{\n    Left,\n    Center,\n    Right\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/IBootstrapIconServiceBuilder.cs",
    "content": "﻿using Microsoft.Extensions.DependencyInjection;\n\nnamespace Sakura.AspNetCore.Mvc.Implementations;\n\n/// <summary>\n/// Represent as a service builder which may apply Bootstrap icons.\n/// </summary>\npublic interface IBootstrapIconServiceBuilder\n{\n    /// <summary>\n    /// Get the service collection.\n    /// </summary>\n    IServiceCollection Services { get; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/Utility.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nusing Microsoft.AspNetCore.Mvc.Rendering;\n\nnamespace Sakura.AspNetCore.Mvc.Implementations;\n\n/// <summary>\n/// Provide utility methods. This class is static.\n/// </summary>\ninternal static class Utility\n{\n\t/// <summary>\n\t/// Execute the specified action for each item in the <see cref=\"IEnumerable{T}\"/> source.\n\t/// </summary>\n\t/// <typeparam name=\"T\">The element type of the <paramref name=\"source\"/>.</typeparam>\n\t/// <param name=\"source\">The source collection.</param>\n\t/// <param name=\"action\">The action to be performed on each item of the <paramref name=\"source\"/>.</param>\n\tpublic static void ForEach<T>(this IEnumerable<T> source, Action<T> action)\n\t{\n\t\tforeach (var item in source)\n\t\t{\n\t\t\taction(item);\n\t\t}\n\t}\n\t\n\t/// <summary>\n\t/// Adds multiple CSS classes to the specified <see cref=\"TagBuilder\"/> instance.\n\t/// </summary>\n\t/// <param name=\"tag\">The <see cref=\"TagBuilder\"/> instance.</param>\n\t/// <param name=\"cssClassList\">The collection of CSS classes to be adding.</param>\n\tpublic static void AddCssClasses(this TagBuilder tag, params IEnumerable<string> cssClassList)\n\t{\n\t\tcssClassList.ForEach(tag.AddCssClass);\n\t}\n\n\t/// <summary>\n\t/// Convert the <see cref=\"bool\"/> value to a JavaScript string representation.\n\t/// </summary>\n\t/// <param name=\"value\">The value to be converted.</param>\n\t/// <returns>The converted JavaScript value representation for <paramref name=\"value\"/>.</returns>\n\tpublic static string ToJavaScriptString(this bool value) =>\n\t\tvalue ? \"true\" : \"false\";\n}\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Implementations/VerticalAlignment.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.Implementations;\n\npublic enum VerticalAlignment\n{\n    Top,\n    Middle,\n    Bottom\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/MessageTagHelper.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Provide tag helper service for messages.\n/// </summary>\n/// <param name=\"messageAccessor\">The <see cref=\"IOperationMessageAccessor\"/> service.</param>\n/// <param name=\"generator\">The <see cref=\"IOperationMessageHtmlGenerator\"/> service.</param>\n[HtmlTargetElement(TagName)]\npublic class MessageTagHelper(IOperationMessageAccessor messageAccessor, IOperationMessageHtmlGenerator generator)\n\t: TagHelper\n{\n\t/// <summary>\n\t/// The tag name of this tag helper. This field is constant.\n\t/// </summary>\n\t[PublicAPI]\n\tpublic const string TagName = \"operation-message-list\";\n\n\t/// <summary>\n\t///     Synchronously executes the <see cref=\"TagHelper\" /> with the given <paramref name=\"context\" /> and\n\t///     <paramref name=\"output\" />.\n\t/// </summary>\n\t/// <param name=\"context\">Contains information associated with the current HTML tag.</param>\n\t/// <param name=\"output\">A stateful HTML element used to generate an HTML tag.</param>\n\tpublic override void Process(TagHelperContext context, TagHelperOutput output)\n\t{\n\t// Generate the output\n\t\tvar tag = generator.GenerateList(messageAccessor.Messages, context);\n\n\t\t// Dismiss current tag\n\t\toutput.TagName = null;\n\t\t// Append content\n\t\toutput.PostContent.AppendHtml(tag);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/OperationMessageServiceBuilder.cs",
    "content": "﻿using Microsoft.Extensions.DependencyInjection;\n\nnamespace Microsoft.Framework.DependencyInjection;\n\n/// <summary>\n/// Used to configure operation message related services. This class is sealed and cannot be inherited.\n/// </summary>\n/// <param name=\"services\">The <see cref=\"IServiceCollection\"/> instance.</param>\npublic sealed class OperationMessageServiceBuilder(IServiceCollection services)\n{\n    /// <summary>\n    /// Get the service collection.\n    /// </summary>\n    public IServiceCollection Services { get; } = services;\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/Sakura.AspNetCore.Mvc.Messages.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n\t<PropertyGroup>\n\t\t<Description>This library provides HTML generator and tag helpers to display bootstrap-styled operation message alerts for ASP.NET Core MVC projects.</Description>\n\t\t<AssemblyTitle>ASP.NET Core MVC Message Extension Library</AssemblyTitle>\n\t\t<VersionPrefix>1.0.2</VersionPrefix>\n\t\t<Authors>Iris Sakura</Authors>\n\t\t<TargetFrameworks>netstandard2.0;net462;net8.0</TargetFrameworks>\n\t\t<AssemblyName>Sakura.AspNetCore.Mvc.Messages</AssemblyName>\n\t\t<PackageId>Sakura.AspNetCore.Mvc.Messages</PackageId>\n\t\t<PackageTags>ASP.NET;ASP.NETCore;MVC;MVCCore;Bootstrap;Message;Messages</PackageTags>\n\t\t<PackageReleaseNotes>Using IHtmlContent to improve the generation compatibility.</PackageReleaseNotes>\n\t\t<PackageProjectUrl>https://github.com/sgjsakura/AspNetCore</PackageProjectUrl>\n\t\t<PackageLicenseUrl></PackageLicenseUrl>\n\t\t<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>\n\t\t<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>\n\t\t<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>\n\t\t<RepositoryUrl>https://github.com/sgjsakura/AspNetCore.git</RepositoryUrl>\n\t\t<GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n\t\t<Version>3.0.0</Version>\n\t\t<RootNamespace>Sakura.AspNetCore.Mvc</RootNamespace>\n\t\t<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n\t</PropertyGroup>\n\n\n\t<PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n\t\t<GenerateDocumentationFile>true</GenerateDocumentationFile>\n\t</PropertyGroup>\n\n\t<ItemGroup>\n\t\t<ProjectReference Include=\"..\\Sakura.AspNetCore.Messages\\Sakura.AspNetCore.Messages.csproj\" />\n\t</ItemGroup>\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'net8.0' \">\n\t\t<FrameworkReference Include=\"Microsoft.AspNetCore.App\" />\n\t</ItemGroup>\n\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'netstandard2.0' \">\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Razor.Runtime\" Version=\"1.0.0\" />\n\t</ItemGroup>\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'net462' \">\n\t\t<Reference Include=\"System\" />\n\t\t<Reference Include=\"Microsoft.CSharp\" />\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Razor.Runtime\" Version=\"1.0.0\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<PackageReference Include=\"System.Collections.Immutable\" Version=\"8.0.0\" />\n\t</ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/ServiceCollectionExtensions.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.DependencyInjection.Extensions;\nusing Sakura.AspNetCore;\nusing Sakura.AspNetCore.Mvc;\nusing Sakura.AspNetCore.Mvc.Implementations;\n\n// ReSharper disable once CheckNamespace\n\nnamespace Microsoft.Framework.DependencyInjection;\n\n/// <summary>\n///     Provide extension methods for service injection. This class is static.\n/// </summary>\n[PublicAPI]\npublic static class ServiceCollectionExtensions\n{\n\t/// <summary>\n\t///     Add operation messages and all related services.\n\t/// </summary>\n\t/// <param name=\"services\">The <see cref=\"IServiceCollection\" /> object.</param>\n\t/// <param name=\"setupAction\">Optional setup actions.</param>\n\tpublic static OperationMessageServiceBuilder AddOperationMessages(this IServiceCollection services,\n\t\tAction<OperationMessageOptions>? setupAction = null)\n\t{\n\t\tservices.AddOperationMessageAccessor(setupAction);\n\t\treturn new (services);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.Messages/StaticIIconToCssClassMapper.cs",
    "content": "using System.Collections.Frozen;\nusing System.Collections.Generic;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n/// Provide static mapping between <see cref=\"OperationMessageLevel\"/> and CSS class names for icons.\n/// </summary>\n/// <param name=\"mappings\">The mapping dictionary.</param>\npublic class StaticIIconToCssClassMapper(IReadOnlyDictionary<OperationMessageLevel, string> mappings) \n    : IIconToCssClassMapper\n{\n    /// <summary>\n    /// Internal frozen dictionary to store the mappings between <see cref=\"OperationMessageLevel\"/> and CSS class names.\n    /// </summary>\n    private FrozenDictionary<OperationMessageLevel, string> Mappings { get; } = mappings.ToFrozenDictionary();\n\n    /// <inheritdoc />\n    public string? GetIconCssClass(OperationMessageLevel level)\n    {\n        return Mappings.GetValueRefOrNullRef(level);\n    }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/FirstAndLastPagerItemActiveMode.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Define how to active the fist and last item in a pager.\n/// </summary>\npublic enum FirstAndLastPagerItemActiveMode\n{\n\t/// <summary>\n\t///     The first or last button is always active regardless what the current page is.\n\t/// </summary>\n\tAlways,\n\n\t/// <summary>\n\t///     The first or last button is inactive if the current page is just the first/last page.\n\t/// </summary>\n\tNotInCurrentPage,\n\n\t/// <summary>\n\t///     The first or last button is inactive if user can see the link item for the first/last page.\n\t/// </summary>\n\tNotInVisiblePageList\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/BaseUriLinkGenerator.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Http.Extensions;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Provide the common feature for base-uri related pager item link generator.\n/// </summary>\npublic abstract class BaseUriLinkGenerator : IPagerItemLinkGenerator\n{\n\t/// <summary>\n\t///     Get or set the base URI used to generate the full URI. If this property is null, the current relative URI is used\n\t///     as the base URI.\n\t/// </summary>\n\tpublic string BaseUri { get; set; }\n\n\t/// <summary>\n\t///     Generate the link url for the specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t[Pure]\n\tpublic string GenerateLink(PagerItemGenerationContext context)\n\t{\n\t\tvar baseUri = GetRealBaseUri(context.ViewContext);\n\n\t\t// Hosted uri for API compatibility\n\t\tvar hostedUri = new Uri(\"http://test.local/\");\n\n\t\t// Generate uri object\n\t\tvar uri = new Uri(baseUri, UriKind.RelativeOrAbsolute);\n\n\t\t// Start with slash detection.\n\t\tvar isStartWithSlash = false;\n\n\t\t// If the uri is not absolute uri, convert it to an absolute one\n\t\tvar isAbsolute = uri.IsAbsoluteUri;\n\t\tif (!isAbsolute)\n\t\t{\n\t\t\tisStartWithSlash = !string.IsNullOrEmpty(baseUri) && baseUri[0] == '/';\n\t\t\turi = new Uri(hostedUri, uri);\n\t\t}\n\n\t\t// Core method to handle URI.\n\t\tvar finalUri = HandleUriCore(uri, context);\n\n\t\t// If the original uri is not absolute, remove the hosted part\n\t\tif (!isAbsolute)\n\t\t\tfinalUri = hostedUri.MakeRelativeUri(finalUri);\n\n\t\tvar result = UriHelper.Encode(finalUri);\n\n\t\t// Add start slash if necessary\n\t\tif (!isAbsolute && isStartWithSlash)\n\t\t\tresult = \"/\" + result;\n\n\t\treturn result;\n\t}\n\n\t/// <summary>\n\t///     Get the real base URI used to calculate the final URI.\n\t/// </summary>\n\t/// <param name=\"viewContext\">The <see cref=\"ViewContext\" /> instance.</param>\n\t/// <returns>The real base URI.</returns>\n\t[Pure]\n\tprotected string GetRealBaseUri(ViewContext viewContext)\n\t{\n\t\treturn BaseUri ?? GetCurrentUriWithQuery(viewContext);\n\t}\n\n\t/// <summary>\n\t///     Get the current uri and query string from the current view context.\n\t/// </summary>\n\t/// <param name=\"viewContext\">The view context object.</param>\n\t/// <returns>Current uri and query string of the view context.</returns>\n\t[Pure]\n\tprotected static string GetCurrentUriWithQuery(ViewContext viewContext)\n\t{\n\t\tvar request = viewContext.HttpContext.Request;\n\t\treturn string.Concat(request.PathBase, request.Path, request.QueryString);\n\t}\n\n\t/// <summary>\n\t///     The core method for handling the base uri.\n\t/// </summary>\n\t/// <param name=\"baseUri\">The URL to handle, this URL is ensured in absolute mode.</param>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns></returns>\n\t/// <remarks>\n\t///     Most URI handling method requires the URI be absolute format, however in the view page relative URI is\n\t///     recommended. The <paramref name=\"baseUri\" /> argument in this method has been handled and is ensured to be\n\t///     absolute. The generator will correctly recover it to the original format after handling.\n\t/// </remarks>\n\tprotected abstract Uri HandleUriCore([NotNull] Uri baseUri, [NotNull] PagerItemGenerationContext context);\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/CustomHtmlContentGenerator.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Html;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Generate <see cref=\"IHtmlContent\" /> from a custom generator.\n/// </summary>\npublic class CustomHtmlContentGenerator : IPagerItemContentGenerator\n{\n\t/// <summary>\n\t/// </summary>\n\t/// <param name=\"htmlContentGenerator\">The string generator callback delegate.</param>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"htmlContentGenerator\" /> is <c>null</c>.</exception>\n\tpublic CustomHtmlContentGenerator([NotNull] Func<PagerItemGenerationContext, IHtmlContent> htmlContentGenerator)\n\t{\n\t\tHtmlContentGenerator = htmlContentGenerator ?? throw new ArgumentNullException(nameof(htmlContentGenerator));\n\t}\n\n\t/// <summary>\n\t///     Get the string generator callback delegate.\n\t/// </summary>\n\t[PublicAPI]\n\t[NotNull]\n\tpublic Func<PagerItemGenerationContext, IHtmlContent> HtmlContentGenerator { get; }\n\n\t/// <summary>\n\t///     Generate the content for a specified pager item.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The generated HTML content for the pager item.</returns>\n\tpublic IHtmlContent GenerateContent(PagerItemGenerationContext context)\n\t{\n\t\treturn HtmlContentGenerator(context);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/CustomLinkGenerator.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Generate a link url string for a custom string generator.\n/// </summary>\npublic class CustomLinkGenerator : IPagerItemLinkGenerator\n{\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"CustomLinkGenerator\" />.\n\t/// </summary>\n\t/// <param name=\"linkGenerator\">The link genreator callback delegate.</param>\n\tpublic CustomLinkGenerator([NotNull] Func<PagerItemGenerationContext, string> linkGenerator)\n\t{\n\t\tLinkGenerator = linkGenerator ?? throw new ArgumentNullException(nameof(linkGenerator));\n\t}\n\n\t/// <summary>\n\t///     Get the link genreator callback delegate.\n\t/// </summary>\n\t[PublicAPI]\n\t[NotNull]\n\tpublic Func<PagerItemGenerationContext, string> LinkGenerator { get; }\n\n\t/// <summary>\n\t///     Generate the link url for the specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\tpublic string GenerateLink(PagerItemGenerationContext context)\n\t{\n\t\treturn LinkGenerator(context);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/CustomQueryStringLinkGenerator.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Represent as a pager item link generator that append a query parameter to the current url with custom parameter\n///     name and value generators.\n/// </summary>\npublic class CustomQueryStringLinkGenerator : QueryStringLinkGenerator\n{\n\t/// <summary>\n\t///     Create a new instance with specified information.\n\t/// </summary>\n\t/// <param name=\"queryNameGenerator\">The query parameter name generating method delegate.</param>\n\t/// <param name=\"queryValueGenerator\">The query parameter value generating method delegate.</param>\n\t/// <exception cref=\"ArgumentNullException\">\n\t///     The <paramref name=\"queryNameGenerator\" /> or\n\t///     <paramref name=\"queryValueGenerator\" /> is <c>null</c>.\n\t/// </exception>\n\tpublic CustomQueryStringLinkGenerator([NotNull] Func<PagerItemGenerationContext, string> queryNameGenerator,\n\t\t[NotNull] Func<PagerItemGenerationContext, string> queryValueGenerator)\n\t{\n\t\tQueryNameGenerator = queryNameGenerator ?? throw new ArgumentNullException(nameof(queryNameGenerator));\n\t\tQueryValueGenerator = queryValueGenerator ?? throw new ArgumentNullException(nameof(queryValueGenerator));\n\t}\n\n\t/// <summary>\n\t///     Get the query parameter name generating method delegate.\n\t/// </summary>\n\t[PublicAPI]\n\tpublic Func<PagerItemGenerationContext, string> QueryNameGenerator { get; }\n\n\t/// <summary>\n\t///     Get the query parameter value generating method delegate.\n\t/// </summary>\n\t[PublicAPI]\n\tpublic Func<PagerItemGenerationContext, string> QueryValueGenerator { get; }\n\n\t/// <summary>\n\t///     Generate the query parameter name for the specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The query parameter name for current pager item.</returns>\n\tpublic override string GenerateQueryParameterName(PagerItemGenerationContext context)\n\t{\n\t\treturn QueryNameGenerator(context);\n\t}\n\n\t/// <summary>\n\t///     Generate the query parameter value for the specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The query parameter name for current pager item.</returns>\n\tpublic override string GenerateQueryParameterValue(PagerItemGenerationContext context)\n\t{\n\t\treturn QueryValueGenerator(context);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/CustomQueryValueLinkGenerator.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Generate the query parameter value using a custom method.\n/// </summary>\npublic class CustomQueryValueLinkGenerator : QueryValueLinkGenerator\n{\n\t/// <summary>\n\t///     Initialize a new instance of the generator.\n\t/// </summary>\n\t/// <param name=\"queryParameterName\">The query parameter name when generating the link URL.</param>\n\t/// <param name=\"queryValueGenerator\">The query parameter value generating method delegate.</param>\n\t/// <exception cref=\"ArgumentNullException\">\n\t///     The <paramref name=\"queryParameterName\" /> or\n\t///     <paramref name=\"queryValueGenerator\" /> is <c>null</c>.\n\t/// </exception>\n\tpublic CustomQueryValueLinkGenerator([NotNull] string queryParameterName,\n\t\t[NotNull] Func<PagerItemGenerationContext, string> queryValueGenerator) : base(queryParameterName)\n\t{\n\t\tQueryValueGenerator = queryValueGenerator ?? throw new ArgumentNullException(nameof(queryValueGenerator));\n\t}\n\n\t/// <summary>\n\t///     Get the query parameter value generating method delegate.\n\t/// </summary>\n\t[NotNull]\n\tpublic Func<PagerItemGenerationContext, string> QueryValueGenerator { get; }\n\n\t/// <summary>\n\t///     When derived, generate the query parameter value for the specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The query parameter name for current pager item.</returns>\n\tpublic override string GenerateQueryParameterValue(PagerItemGenerationContext context)\n\t{\n\t\treturn QueryValueGenerator(context);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/CustomStringContentGenerator.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Html;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Generate <see cref=\"IHtmlContent\" /> from a custom string generator.\n/// </summary>\npublic class CustomStringContentGenerator : StringContentGenerator\n{\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"CustomStringContentGenerator\" />.\n\t/// </summary>\n\t/// <param name=\"stringContentGenerator\">The string generator callback delegate.</param>\n\t/// <param name=\"encodeText\">Whether the format result should be HTML encoded before be written to page.</param>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"stringContentGenerator\" /> is <c>null</c>.</exception>\n\tpublic CustomStringContentGenerator([NotNull] Func<PagerItemGenerationContext, string> stringContentGenerator,\n\t\tbool encodeText) : base(encodeText)\n\t{\n\t\tStringContentGenerator = stringContentGenerator ?? throw new ArgumentNullException(nameof(stringContentGenerator));\n\t}\n\n\t/// <summary>\n\t///     Get the string generator callback delegate.\n\t/// </summary>\n\t[PublicAPI]\n\t[NotNull]\n\tpublic Func<PagerItemGenerationContext, string> StringContentGenerator { get; }\n\n\t/// <summary>\n\t///     Generate the content string.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The generated content string.</returns>\n\tprotected override string GenerateContentString(PagerItemGenerationContext context)\n\t{\n\t\treturn StringContentGenerator(context);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/DisabledLinkGenerator.cs",
    "content": "﻿using Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     This type is used to disable link generation for a pager item.\n/// </summary>\npublic class DisabledLinkGenerator : IPagerItemLinkGenerator\n{\n\t/// <summary>\n\t///     Generate the link url for the specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\tpublic string GenerateLink(PagerItemGenerationContext context)\n\t{\n\t\treturn null;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/FormattedLinkGenerator.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing JetBrains.Annotations;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Generate link url string from a formatted string. The page number will be used as the format argument {0}.\t///\n/// </summary>\npublic class FormattedLinkGenerator : IPagerItemLinkGenerator\n{\n\t/// <summary>\n\t///     Initialize a new generator with specified parameters.\n\t/// </summary>\n\t/// <param name=\"format\">The format string used to be generate the link.</param>\n\t/// <param name=\"formatProvider\">\n\t///     The format provider used to generate the link. If this parameter is <c>null</c>,\n\t///     <see cref=\"CultureInfo.InvariantCulture\" /> will be used.\n\t/// </param>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"format\" /> is <c>null</c>.</exception>\n\tpublic FormattedLinkGenerator([NotNull] string format,\n\t\tIFormatProvider formatProvider = null)\n\t{\n\t\tFormat = format ?? throw new ArgumentNullException(nameof(format));\n\t\tFormatProvider = formatProvider ?? CultureInfo.InvariantCulture;\n\t}\n\n\t/// <summary>\n\t///     Get the format string used to be generate the link.\n\t/// </summary>\n\t[PublicAPI]\n\t[NotNull]\n\tpublic string Format { get; }\n\n\t/// <summary>\n\t///     Get the format provider used to generate the link.\n\t/// </summary>\n\t[PublicAPI]\n\t[NotNull]\n\tpublic IFormatProvider FormatProvider { get; }\n\n\t#region Implementation of IPagerItemLinkGenerator\n\n\t/// <summary>\n\t///     Generate the link url for the specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\tpublic string GenerateLink(PagerItemGenerationContext context)\n\t{\n\t\treturn string.Format(FormatProvider, Format, context.PagerItem.PageNumber);\n\t}\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/FormattedQueryValueLinkGenerator.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing JetBrains.Annotations;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Generate the query parammeter value from a formatted string. The page number will be used as the format argument\n///     {0}.\n/// </summary>\npublic class FormattedQueryValueLinkGenerator : QueryValueLinkGenerator\n{\n\t/// <summary>\n\t///     Initialize a new instance of the generator.\n\t/// </summary>\n\t/// <param name=\"queryParameterName\">The query parameter name when generating the link URL.</param>\n\t/// <param name=\"format\">The format string used to be generate the content string..</param>\n\t/// <param name=\"formatProvider\">\n\t///     The format provider used to generate the content string. If this parameter is <c>null</c>,\n\t///     <see cref=\"CultureInfo.InvariantCulture\" /> will be used.\n\t/// </param>\n\t/// <exception cref=\"ArgumentNullException\">\n\t///     The <paramref name=\"queryParameterName\" /> or <paramref name=\"format\" /> is\n\t///     <c>null</c>.\n\t/// </exception>\n\tpublic FormattedQueryValueLinkGenerator([NotNull] string queryParameterName, [NotNull] string format,\n\t\tIFormatProvider formatProvider = null) : base(queryParameterName)\n\t{\n\t\tFormat = format ?? throw new ArgumentNullException(nameof(format));\n\t\tFormatProvider = formatProvider ?? CultureInfo.InvariantCulture;\n\t}\n\n\t/// <summary>\n\t///     Get the format string used to be generate the query parameter value.\n\t/// </summary>\n\t[PublicAPI]\n\t[NotNull]\n\tpublic string Format { get; }\n\n\t/// <summary>\n\t///     Get the format provider used to generate the query parameter value.\n\t/// </summary>\n\t[PublicAPI]\n\t[NotNull]\n\tpublic IFormatProvider FormatProvider { get; }\n\n\n\t/// <summary>\n\t///     Generate the query parameter value for the specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The query parameter name for current pager item.</returns>\n\tpublic override string GenerateQueryParameterValue(PagerItemGenerationContext context)\n\t{\n\t\treturn string.Format(FormatProvider, Format, context.PagerItem.PageNumber);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/FormattedStringContentGenerator.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Html;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Generate <see cref=\"IHtmlContent\" /> from a formatted string. The page number will be used as the format argument\n///     {0}.\n/// </summary>\npublic class FormattedStringContentGenerator : StringContentGenerator\n{\n\t/// <summary>\n\t///     Initialize a new generator with specified parameters.\n\t/// </summary>\n\t/// <param name=\"format\">The format string used to be generate the content string.</param>\n\t/// <param name=\"encodeText\">Whether the format result should be HTML encoded before be written to page.</param>\n\t/// <param name=\"formatProvider\">\n\t///     The format provider used to generate the content string. If this parameter is <c>null</c>,\n\t///     <see cref=\"CultureInfo.CurrentCulture\" /> will be used.\n\t/// </param>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"format\" /> is <c>null</c>.</exception>\n\tpublic FormattedStringContentGenerator([LocalizationRequired] [NotNull] string format, bool encodeText,\n\t\tIFormatProvider formatProvider = null) : base(encodeText)\n\t{\n\t\tFormat = format ?? throw new ArgumentNullException(nameof(format));\n\t\tFormatProvider = formatProvider ?? CultureInfo.CurrentCulture;\n\t}\n\n\t/// <summary>\n\t///     Get the format string used to be generate the content string.\n\t/// </summary>\n\t[PublicAPI]\n\t[NotNull]\n\tpublic string Format { get; }\n\n\t/// <summary>\n\t///     Get the format provider used to generate the content string.\n\t/// </summary>\n\t[PublicAPI]\n\t[NotNull]\n\tpublic IFormatProvider FormatProvider { get; }\n\n\t/// <summary>\n\t///     When be derived, generate the content string.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The generated content string.</returns>\n\tprotected override string GenerateContentString(PagerItemGenerationContext context)\n\t{\n\t\treturn string.Format(FormatProvider, Format, context.PagerItem.PageNumber);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/FragmentLinkGenerator.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Generate pager link into current URL's hash (fragment) part.\n/// </summary>\npublic abstract class FragmentLinkGenerator : BaseUriLinkGenerator\n{\n\t/// <summary>\n\t///     The core method for handling the current uri.\n\t/// </summary>\n\t/// <param name=\"baseUri\">The URL to handle, this URL is ensured in absolute mode.</param>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns></returns>\n\t/// <remarks>\n\t///     Most URI handling method requirest the URI be absolute format, however in the view page relative URI is\n\t///     recommended. The <paramref name=\"baseUri\" /> argument in this method has been handled and is ensured to be\n\t///     absolute. The generator will correctly recover it to the original format after handling.\n\t/// </remarks>\n\tprotected override Uri HandleUriCore(Uri baseUri, PagerItemGenerationContext context)\n\t{\n\t\t// Create URI and change fragment\n\t\tvar uriBuilder = new UriBuilder(baseUri)\n\t\t{\n\t\t\tFragment = GenerateFragment(context)\n\t\t};\n\n\t\treturn uriBuilder.Uri;\n\t}\n\n\n\t/// <summary>\n\t///     When derived, generate the fragment part for a specified pager item.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns></returns>\n\tprotected abstract string GenerateFragment([NotNull] PagerItemGenerationContext context);\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/QueryStringLinkGenerator.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Http.Extensions;\nusing Microsoft.AspNetCore.WebUtilities;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Represent as base class for all query string based link generator.\n/// </summary>\npublic abstract class QueryStringLinkGenerator : BaseUriLinkGenerator\n{\n\t/// <summary>\n\t///     Change a specified parameter value for an uri.\n\t/// </summary>\n\t/// <param name=\"baseUri\">The uri string which need to be changed.</param>\n\t/// <param name=\"queryParameterName\">The changing query parameter.</param>\n\t/// <param name=\"queryParameterValue\">The new value of the query parameter.</param>\n\t/// <returns>\n\t///     A new uri string which the specified query parameter value is changed; If no matched query parameter is found,\n\t///     append the new value to the end.\n\t/// </returns>\n\t[PublicAPI]\n\t[Pure]\n\tprotected static Uri ChangeQueryParameterValue(Uri baseUri, string queryParameterName,\n\t\tstring queryParameterValue)\n\t{\n\t\t// Extract query string and change parameter\n\t\tvar qs = QueryHelpers.ParseQuery(baseUri.Query);\n\t\tqs[queryParameterName] = new[] {queryParameterValue};\n\n\t\tvar qb = new QueryBuilder();\n\t\tforeach (var item in qs)\n\t\t\tqb.Add(item.Key, (IEnumerable<string>) item.Value);\n\n\t\t// Rebuild uri\n\t\tvar builder = new UriBuilder(baseUri)\n\t\t{\n\t\t\tQuery = qb.ToString().Remove(0, 1) // Remove leading \"?\" mark\n\t\t};\n\n\t\treturn builder.Uri;\n\t}\n\n\t/// <summary>\n\t///     When derived, generate the query parameter name for the specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The query parameter name for current pager item.</returns>\n\tpublic abstract string GenerateQueryParameterName([NotNull] PagerItemGenerationContext context);\n\n\t/// <summary>\n\t///     When derived, generate the query parameter value for the specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The query parameter name for current pager item.</returns>\n\tpublic abstract string GenerateQueryParameterValue([NotNull] PagerItemGenerationContext context);\n\n\t/// <summary>\n\t///     The core method for handling the current uri.\n\t/// </summary>\n\t/// <param name=\"baseUri\">The URL to handle, this URL is ensured in absolute mode.</param>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The generated URL string.</returns>\n\t/// <remarks>\n\t///     Most URI handling method requirest the URI be absolute format, however in the view page relative URI is\n\t///     recommended. The <paramref name=\"baseUri\" /> argument in this method has been handled and is ensured to be\n\t///     absolute. The generator will correctly recover it to the original format after handling.\n\t/// </remarks>\n\tprotected override Uri HandleUriCore(Uri baseUri, PagerItemGenerationContext context)\n\t{\n\t\t// Get name and value\n\t\tvar name = GenerateQueryParameterName(context);\n\t\tvar value = GenerateQueryParameterValue(context);\n\n\t\t// Generate result\n\t\treturn ChangeQueryParameterValue(baseUri, name, value);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/QueryValueLinkGenerator.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Represent as a base class for all <see cref=\"QueryStringLinkGenerator\" /> that use fixed query parameter name.\n/// </summary>\npublic abstract class QueryValueLinkGenerator : QueryStringLinkGenerator\n{\n\t/// <summary>\n\t///     Initialize a new instance of the generator.\n\t/// </summary>\n\t/// <param name=\"queryParameterName\">The query parameter name when generating the link URL.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"queryParameterName\" /> is <c>null</c>.</exception>\n\tprotected QueryValueLinkGenerator([NotNull] string queryParameterName)\n\t{\n\t\tQueryParameterName = queryParameterName ?? throw new ArgumentNullException(nameof(queryParameterName));\n\t}\n\n\t/// <summary>\n\t///     Get or set the query parameter name for the generated URL.\n\t/// </summary>\n\t[PublicAPI]\n\t[NotNull]\n\tpublic string QueryParameterName { get; }\n\n\t/// <summary>\n\t///     When derived, generate the query parameter name for the specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The query parameter name for current pager item.</returns>\n\tpublic override string GenerateQueryParameterName(PagerItemGenerationContext context)\n\t{\n\t\treturn QueryParameterName;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/SimpleLinkGenerator.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Generate a link url from a simple string.\n/// </summary>\npublic class SimpleLinkGenerator : IPagerItemLinkGenerator\n{\n\t/// <summary>\n\t///     Initialize a new generator with specified information.\n\t/// </summary>\n\t/// <param name=\"text\">The text content.</param>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"text\" /> is <c>null</c>.</exception>\n\tpublic SimpleLinkGenerator([NotNull] string text)\n\t{\n\t\tText = text ?? throw new ArgumentNullException(nameof(text));\n\t}\n\n\t/// <summary>\n\t///     Get the text content.\n\t/// </summary>\n\t[PublicAPI]\n\t[NotNull]\n\tpublic string Text { get; }\n\n\t#region Implementation of IPagerItemLinkGenerator\n\n\t/// <summary>\n\t///     Generate the link url for the specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\tpublic string GenerateLink(PagerItemGenerationContext context)\n\t{\n\t\treturn Text;\n\t}\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/SimpleQueryValueLinkGenerator.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Generate the pager link text with simple query parameter name and values.\n/// </summary>\npublic class SimpleQueryValueLinkGenerator : QueryValueLinkGenerator\n{\n\t/// <summary>\n\t///     Initialize a new instance of the generator.\n\t/// </summary>\n\t/// <param name=\"queryParameterName\">The query parameter name when generating the link URL.</param>\n\t/// <param name=\"queryParameterValue\">The query parameter value when generating the link URL.</param>\n\t/// <exception cref=\"ArgumentNullException\">\n\t///     The <paramref name=\"queryParameterName\" /> or\n\t///     <paramref name=\"queryParameterValue\" /> is <c>null</c>.\n\t/// </exception>\n\tpublic SimpleQueryValueLinkGenerator([NotNull] string queryParameterName, [NotNull] string queryParameterValue)\n\t\t: base(queryParameterName)\n\t{\n\t\tQueryParameterValue = queryParameterValue ?? throw new ArgumentNullException(nameof(queryParameterValue));\n\t}\n\n\t/// <summary>\n\t///     Get the generated query parameter value.\n\t/// </summary>\n\t[PublicAPI]\n\t[NotNull]\n\tpublic string QueryParameterValue { get; }\n\n\t/// <summary>\n\t///     Generate the query parameter value for the specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The query parameter name for current pager item.</returns>\n\tpublic override string GenerateQueryParameterValue(PagerItemGenerationContext context)\n\t{\n\t\treturn QueryParameterValue;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/SimpleStringContentGenerator.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Html;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Generate <see cref=\"IHtmlContent\" /> from a simple encoded or not encoded string.\n/// </summary>\npublic class SimpleStringContentGenerator : StringContentGenerator\n{\n\t/// <summary>\n\t///     Initialize a new generator with specified information.\n\t/// </summary>\n\t/// <param name=\"text\">The text content.</param>\n\t/// <param name=\"encodeText\">\n\t///     Indicate that whether the generated string content should be HTML encoded before be written to\n\t///     page.\n\t/// </param>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"text\" /> is <c>null</c>.</exception>\n\tpublic SimpleStringContentGenerator([LocalizationRequired] [NotNull] string text, bool encodeText) : base(encodeText)\n\t{\n\t\tText = text ?? throw new ArgumentNullException(nameof(text));\n\t}\n\n\t/// <summary>\n\t///     Get the text content.\n\t/// </summary>\n\t[PublicAPI]\n\t[NotNull]\n\tpublic string Text { get; }\n\n\t/// <summary>\n\t///     When be derived, generate the content string.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The generated content string.</returns>\n\tprotected override string GenerateContentString(PagerItemGenerationContext context)\n\t{\n\t\treturn Text;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Generators/StringContentGenerator.cs",
    "content": "﻿using JetBrains.Annotations;\nusing Microsoft.AspNetCore.Html;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc.Generators;\n\n/// <summary>\n///     Represent as generic base class for all <see cref=\"IPagerItemContentGenerator\" /> which generate\n///     <see cref=\"IHtmlContent\" /> from simple <see cref=\"string\" /> value.\n/// </summary>\npublic abstract class StringContentGenerator : IPagerItemContentGenerator\n{\n\t/// <summary>\n\t///     Initialize a new generator with specified information.\n\t/// </summary>\n\t/// <param name=\"encodeText\">\n\t///     Indicate that whether the generated string content should be HTML encoded before be written to\n\t///     page.\n\t/// </param>\n\tprotected StringContentGenerator(bool encodeText)\n\t{\n\t\tEncodeText = encodeText;\n\t}\n\n\t/// <summary>\n\t///     Get a value that indicates that whether the generated string content should be HTML encoded before be written to\n\t///     page.\n\t/// </summary>\n\t[PublicAPI]\n\tpublic bool EncodeText { get; }\n\n\t/// <summary>\n\t///     Generate the content for a specified pager item.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The generated HTML content for the pager item.</returns>\n\tpublic IHtmlContent GenerateContent(PagerItemGenerationContext context)\n\t{\n\t\treturn GenerateContentString(context).ToHtmlContent(EncodeText);\n\t}\n\n\t/// <summary>\n\t///     When be derived, generate the content string.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The generated content string.</returns>\n\tprotected abstract string GenerateContentString(PagerItemGenerationContext context);\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/IPagerItemContentGenerator.cs",
    "content": "﻿using System.ComponentModel;\nusing Microsoft.AspNetCore.Html;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Define the necessary feature for a pager item content generator.\n/// </summary>\n[TypeConverter(typeof(PagerItemContentGeneratorConverter))]\npublic interface IPagerItemContentGenerator\n{\n\t/// <summary>\n\t///     Generate the content for a specified pager item.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The generated HTML content for the pager item.</returns>\n\tIHtmlContent GenerateContent(PagerItemGenerationContext context);\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/IPagerItemLinkGenerator.cs",
    "content": "﻿using System.ComponentModel;\nusing JetBrains.Annotations;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Define the necessary feature for a pager item link generator.\n/// </summary>\n[TypeConverter(typeof(PagerItemLinkGeneratorConverter))]\npublic interface IPagerItemLinkGenerator\n{\n\t/// <summary>\n\t///     Generate the link url for the specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The generation context.</param>\n\tstring? GenerateLink([NotNull] PagerItemGenerationContext context);\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/BootstrapPagerHtmlGenerator.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Html;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\n// ReSharper disable MustUseReturnValue\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     An HTML code generator using bootstrap theme for paged list pagers.\n/// </summary>\n[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]\npublic class BootstrapPagerHtmlGenerator : IPagerHtmlGenerator\n{\n\t/// <summary>\n\t///     Generate the entire HTML content for a pager renderling list.\n\t/// </summary>\n\t/// <param name=\"list\">The rendering list to be generating.</param>\n\t/// <param name=\"context\">The pager generation context.</param>\n\t/// <returns>The entire HTML content for the generated pager.</returns>\n\tpublic IHtmlContent GeneratePager(PagerRenderingList list, PagerGenerationContext context)\n\t{\n\t\treturn GeneratePagerCore(list, context.GenerationMode == PagerGenerationMode.Full);\n\t}\n\n\t#region Core Methods for Pager Generation\n\n\t/// <summary>\n\t///     Determine if a <see cref=\"PagerRenderingItem\" /> has a valid link.\n\t/// </summary>\n\t/// <param name=\"item\">The <see cref=\"PagerRenderingItem\" /> object.</param>\n\t/// <returns>\n\t///     If the <paramref name=\"item\" /> has a valid link and is not disabled, returns <c>true</c>; otherwise, return\n\t///     <c>false</c>.\n\t/// </returns>\n\tprivate static bool CanLink(PagerRenderingItem item)\n\t{\n\t\treturn item.State != PagerRenderingItemState.Disabled && !string.IsNullOrEmpty(item.Link);\n\t}\n\n\t/// <summary>\n\t///     Append additional HTML attributes according to the setting.\n\t/// </summary>\n\t/// <param name=\"tag\">The tag to appending attributes.</param>\n\t/// <param name=\"settingDictionary\">The dictionary which store all the settings.</param>\n\t/// <param name=\"attributePrefix\">\n\t///     The perfix for attributes used to search valid attributes in\n\t///     <paramref name=\"settingDictionary\" />.\n\t/// </param>\n\tprivate static void AppendAdditionalAttributes(TagBuilder tag, IDictionary<string, string> settingDictionary,\n\t\tstring attributePrefix)\n\t{\n\t\tforeach (var i in settingDictionary)\n\t\t\tif (i.Key.StartsWith(attributePrefix))\n\t\t\t\ttag.Attributes[i.Key.Substring(attributePrefix.Length)] = i.Value;\n\t}\n\n\t/// <summary>\n\t///     The core method to generate the HTML content.\n\t/// </summary>\n\t/// <param name=\"list\">The pager list to generating the content.</param>\n\t/// <param name=\"generateContainer\">Whether a container element should be generated.</param>\n\t/// <returns>The final <see cref=\"IHtmlContent\" />.</returns>\n\tprotected static IHtmlContent GeneratePagerCore(PagerRenderingList list, bool generateContainer)\n\t{\n\t\tif (list == null)\n\t\t\tthrow new ArgumentNullException(nameof(list));\n\n\t\tvar content = GeneratePagerItems(list.Items);\n\n\t\t// No container\n\t\tif (!generateContainer)\n\t\t\treturn content;\n\n\t\t// With container\n\t\tvar container = GenerateContainer(list);\n\t\tcontainer.InnerHtml.SetHtmlContent(content);\n\n\t\treturn container;\n\t}\n\n\t/// <summary>\n\t///     Generate the container tag.\n\t/// </summary>\n\t/// <returns>The container tag.</returns>\n\tprotected static TagBuilder GenerateContainer(PagerRenderingList list)\n\t{\n\t\tvar tag = new TagBuilder(\"ul\");\n\t\ttag.AddCssClass(\"pagination\"); // core CSS class for pagination\n\n\t\tAppendAdditionalAttributes(tag, list.Settings, ListAttributeSettingKeyPrefix);\n\n\t\treturn tag;\n\t}\n\n\t/// <summary>\n\t///     Generate the html content for a series of pager items.\n\t/// </summary>\n\t/// <param name=\"items\">The collection of <see cref=\"PagerRenderingItem\" /> to generate the content.</param>\n\t/// <returns>The final <see cref=\"IHtmlContent\" />.</returns>\n\tprotected static IHtmlContent GeneratePagerItems(IEnumerable<PagerRenderingItem> items)\n\t{\n\t\tif (items == null)\n\t\t\tthrow new ArgumentNullException(nameof(items));\n\n\t\tvar content = new DefaultTagHelperContent();\n\n\t\tforeach (var i in items)\n\t\t\tcontent.AppendHtml(GeneratePagerItem(i));\n\n\t\treturn content;\n\t}\n\n\t/// <summary>\n\t///     Generate the html content for a pager item.\n\t/// </summary>\n\t/// <param name=\"item\">The pager item for rendering.</param>\n\t/// <returns>The generated <see cref=\"IHtmlContent\" /> for the <paramref name=\"item\" />.</returns>\n\tprotected static IHtmlContent GeneratePagerItem(PagerRenderingItem item)\n\t{\n\t\tif (item == null)\n\t\t\tthrow new ArgumentNullException(nameof(item));\n\n\t\t// Container\n\t\tvar itemTag = new TagBuilder(\"li\");\n\n\t\tTagBuilder linkTag;\n\n\t\t// Generate <a> or <span> according to its state\n\t\tif (item.State == PagerRenderingItemState.Active)\n\t\t{\n\t\t\titemTag.AddCssClass(\"active\"); // active CSS class\n\t\t\tlinkTag = new TagBuilder(\"span\");\n\t\t}\n\t\telse if (CanLink(item))\n\t\t{\n\t\t\tlinkTag = new TagBuilder(\"a\");\n\t\t\tlinkTag.Attributes[\"href\"] = item.Link;\n\t\t}\n\t\telse\n\t\t{\n\t\t\titemTag.AddCssClass(\"disabled\"); // Disabled core CSS class\n\t\t\tlinkTag = new TagBuilder(\"span\");\n\t\t}\n\n\t\t// Add Bootstrap v4 classes, this is not confict with bootstrap v3. However user may choose to diable it manually.\n\t\tif (!string.Equals(item.Settings.GetValueOfDefault(\"disble-bootstrap-v4-class\"), \"true\",\n\t\t\t    StringComparison.OrdinalIgnoreCase))\n\t\t{\n\t\t\titemTag.AddCssClass(\"page-item\");\n\t\t\tlinkTag.AddCssClass(\"page-link\");\n\t\t}\n\n\t\t// Append father settings\n\t\tAppendAdditionalAttributes(itemTag, item.List.Settings, ItemAttributeSettingKeyPrefix);\n\t\tAppendAdditionalAttributes(linkTag, item.List.Settings, LinkAttributeSettingKeyPrefix);\n\n\t\t// Append all additional attributes\n\t\tAppendAdditionalAttributes(itemTag, item.Settings, ItemAttributeSettingKeyPrefix);\n\t\tAppendAdditionalAttributes(linkTag, item.Settings, LinkAttributeSettingKeyPrefix);\n\n\t\t// Set real content and merge elements\n\t\tlinkTag.InnerHtml.SetHtmlContent(item.Content);\n\t\titemTag.InnerHtml.SetHtmlContent(linkTag);\n\t\treturn itemTag;\n\t}\n\n\t#endregion\n\n\t#region Constants\n\n\t/// <summary>\n\t///     The setting prefix used to define additional container (&lt;li&gt;) HTML attributes. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string ItemAttributeSettingKeyPrefix = \"item-attr-\";\n\n\t/// <summary>\n\t///     The setting prefix used to define additional link (&lt;a&gt; or &lt;span&gt;) HTML attributes. This field is\n\t///     constant.\n\t/// </summary>\n\t[PublicAPI] public const string LinkAttributeSettingKeyPrefix = \"link-attr-\";\n\n\t/// <summary>\n\t///     The setting prefix used to define additional list (&lt;ul&gt;) HTML attributes. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string ListAttributeSettingKeyPrefix = \"list-attr-\";\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/DefaultPagerGenerator.cs",
    "content": "﻿using System.Linq;\nusing Microsoft.AspNetCore.Html;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Default implementation for <see cref=\"IPagerGenerator\" />.\n/// </summary>\npublic class DefaultPagerGenerator : IPagerGenerator\n{\n\t/// <summary>\n\t///     Initialize a new instance with all required services.\n\t/// </summary>\n\t/// <param name=\"listGenerator\">\n\t///     A generator used to generate collection of <see cref=\"PagerItem\" /> from the generation\n\t///     context.\n\t/// </param>\n\t/// <param name=\"renderingListGenerator\">\n\t///     A generator used to generate <see cref=\"PagerRenderingList\" /> from a collection\n\t///     of <see cref=\"PagerItem\" />.\n\t/// </param>\n\t/// <param name=\"htmlGenerator\">\n\t///     A generator used to generate final <see cref=\"IHtmlContent\" /> from a\n\t///     <see cref=\"PagerRenderingList\" />.\n\t/// </param>\n\tpublic DefaultPagerGenerator(IPagerListGenerator listGenerator, IPagerRenderingListGenerator renderingListGenerator,\n\t\tIPagerHtmlGenerator htmlGenerator)\n\t{\n\t\tListGenerator = listGenerator;\n\t\tRenderingListGenerator = renderingListGenerator;\n\t\tHtmlGenerator = htmlGenerator;\n\t}\n\n\tprivate IPagerHtmlGenerator HtmlGenerator { get; }\n\n\tprivate IPagerRenderingListGenerator RenderingListGenerator { get; }\n\n\tprivate IPagerListGenerator ListGenerator { get; }\n\n\t/// <summary>\n\t///     Generate the entire HTML content for a pager renderling list.\n\t/// </summary>\n\t/// <returns>The entire HTML content for the generated pager.</returns>\n\tpublic IHtmlContent GeneratePager(PagerGenerationContext context)\n\t{\n\t\t// Hide Handling\n\t\tif (context.TotalPage <= 1 && context.Options.HideOnSinglePage)\n\t\t\treturn new HtmlString(string.Empty);\n\n\t\tvar list = ListGenerator.GeneratePagerItems(context);\n\n\t\t// Reverse handling\n\t\tif (context.Options.IsReversed)\n\t\t\tlist = new PagerList(list.Items.Reverse());\n\n\t\tvar renderingList = RenderingListGenerator.GenerateRenderingList(list, context);\n\t\tvar html = HtmlGenerator.GeneratePager(renderingList, context);\n\n\t\treturn html;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/DefaultPagerListGenerator.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Provide default implementation for <see cref=\"IPagerListGenerator\" />.\n/// </summary>\npublic class DefaultPagerListGenerator : IPagerListGenerator\n{\n\t/// <summary>\n\t///     Generate a <see cref=\"PagerList\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The pager generation context.</param>\n\t/// <returns>A <see cref=\"PagerList\" /> generated for the pager.</returns>\n\tpublic PagerList GeneratePagerItems(PagerGenerationContext context)\n\t{\n\t\tvar items = GeneratePagerItemsCore(context.CurrentPage, context.TotalPage, context.Options);\n\n\t\treturn new PagerList(items);\n\t}\n\n\t/// <summary>\n\t///     Core method for generating normal pager items according to the page information.\n\t/// </summary>\n\t/// <param name=\"currentPage\">The current page number.</param>\n\t/// <param name=\"totalPage\">The count of total pages.</param>\n\t/// <param name=\"expandForCurrentPage\">How many pages should be expanded for current page.</param>\n\t/// <param name=\"expandForEnding\">How many pages should be expanded for ending.</param>\n\t/// <returns></returns>\n\tprivate static IEnumerable<PagerItem> GeneratePagerNormalItems(int currentPage, int totalPage,\n\t\tint expandForCurrentPage,\n\t\tint expandForEnding)\n\t{\n\t\tvar pageNumberList = new SortedSet<int> {currentPage};\n\n\t\t// Expand for current page\n\t\tfor (var i = 1; i <= expandForCurrentPage; i++)\n\t\t{\n\t\t\tpageNumberList.Add(currentPage + i);\n\t\t\tpageNumberList.Add(currentPage - i);\n\t\t}\n\n\t\t// Expand for ending\n\t\tfor (var i = 1; i <= expandForEnding; i++)\n\t\t{\n\t\t\tpageNumberList.Add(i);\n\t\t\tpageNumberList.Add(totalPage + 1 - i);\n\t\t}\n\n\t\t// Remove invalid items\n\t\tpageNumberList.RemoveWhere(i => i < 1 || i > totalPage);\n\n\t\tvar lastPageNumber = 0;\n\n\t\tforeach (var i in pageNumberList)\n\t\t{\n\t\t\t// Skipped some item\n\t\t\tif (i - lastPageNumber > 1)\n\t\t\t\tyield return new PagerItem\n\t\t\t\t{\n\t\t\t\t\tItemType = PagerItemType.Omitted,\n\t\t\t\t\tPageNumber = -1\n\t\t\t\t};\n\n\t\t\tyield return new PagerItem\n\t\t\t{\n\t\t\t\t// is current page\n\t\t\t\tItemType = i == currentPage ? PagerItemType.Current : PagerItemType.Normal,\n\t\t\t\tPageNumber = i\n\t\t\t};\n\n\t\t\t// Set last page\n\t\t\tlastPageNumber = i;\n\t\t}\n\n\t\t// last page omit handling\n\t\tif (lastPageNumber < totalPage)\n\t\t\tyield return new PagerItem\n\t\t\t{\n\t\t\t\tItemType = PagerItemType.Omitted,\n\t\t\t\tPageNumber = -1\n\t\t\t};\n\t}\n\n\t/// <summary>\n\t///     Generate a pager item for a special layout element.\n\t/// </summary>\n\t/// <param name=\"currentPage\">The current page number.</param>\n\t/// <param name=\"totalPage\">The count of total pages.</param>\n\t/// <param name=\"element\">The <see cref=\"PagerLayoutElement\" /> to generate the <see cref=\"PagerItem\" />.</param>\n\t/// <returns>The generated <see cref=\"PagerItem\" /> for the <paramref name=\"element\" />.</returns>\n\t/// <exception cref=\"ArgumentException\">\n\t///     The <paramref name=\"element\" /> is <see cref=\"PagerLayoutElement.Items\" />, or it is not\n\t///     a valid enum item.\n\t/// </exception>\n\tprivate PagerItem GenerateSpecialItems(int currentPage, int totalPage, PagerLayoutElement element)\n\t{\n\t\tPagerItemType type;\n\t\tint pageNumber;\n\n\t\tswitch (element)\n\t\t{\n\t\t\tcase PagerLayoutElement.GoToFirstPageButton:\n\t\t\t\ttype = PagerItemType.First;\n\t\t\t\tpageNumber = 1;\n\t\t\t\tbreak;\n\t\t\tcase PagerLayoutElement.GoToLastPageButton:\n\t\t\t\ttype = PagerItemType.Last;\n\t\t\t\tpageNumber = totalPage;\n\t\t\t\tbreak;\n\t\t\tcase PagerLayoutElement.GoToPreviousPageButton:\n\t\t\t\ttype = PagerItemType.Previous;\n\t\t\t\tpageNumber = currentPage - 1;\n\t\t\t\tbreak;\n\t\t\tcase PagerLayoutElement.GoToNextPageButton:\n\t\t\t\ttype = PagerItemType.Next;\n\t\t\t\tpageNumber = currentPage + 1;\n\t\t\t\tbreak;\n\t\t\tcase PagerLayoutElement.Items:\n\t\t\t\tthrow new ArgumentException(\"This method is invalid for normal item layout.\", nameof(element));\n\t\t\tdefault:\n\t\t\t\tthrow new ArgumentException(\"The argument is not a valid enum item.\", nameof(element));\n\t\t}\n\n\t\treturn new PagerItem\n\t\t{\n\t\t\tItemType = type,\n\t\t\tPageNumber = pageNumber\n\t\t};\n\t}\n\n\t/// <summary>\n\t///     Generate pager items for the specified layout element.\n\t/// </summary>\n\t/// <param name=\"element\">The layout element to generating the pager items.</param>\n\t/// <param name=\"currentPage\">The current page number.</param>\n\t/// <param name=\"totalPage\">The count of total pages.</param>\n\t/// <param name=\"options\">The options of the pager.</param>\n\t/// <returns>The generated all pager items collection for the specified <paramref name=\"element\" />.</returns>\n\t/// <exception cref=\"ArgumentException\">The <paramref name=\"element\" /> is not a valid enum item.</exception>\n\tprivate IEnumerable<PagerItem> GenerateItemsForLayoutElement(PagerLayoutElement element, int currentPage,\n\t\tint totalPage, PagerOptions options)\n\t{\n\t\tswitch (element)\n\t\t{\n\t\t\tcase PagerLayoutElement.Items:\n\t\t\t\treturn GeneratePagerNormalItems(currentPage, totalPage, options.ExpandPageItemsForCurrentPage,\n\t\t\t\t\toptions.PagerItemsForEndings);\n\t\t\tcase PagerLayoutElement.GoToFirstPageButton:\n\t\t\tcase PagerLayoutElement.GoToLastPageButton:\n\t\t\tcase PagerLayoutElement.GoToNextPageButton:\n\t\t\tcase PagerLayoutElement.GoToPreviousPageButton:\n\t\t\t\treturn new[] {GenerateSpecialItems(currentPage, totalPage, element)};\n\t\t\tdefault:\n\t\t\t\tthrow new ArgumentException(\"The value of the argument is not a valid enum item.\", nameof(element));\n\t\t}\n\t}\n\n\t/// <summary>\n\t///     Generate all pager items for a layout sequence.\n\t/// </summary>\n\t/// <param name=\"currentPage\">The current page number.</param>\n\t/// <param name=\"totalPage\">The count of total pages.</param>\n\t/// <param name=\"layout\">A sequence that indicates the layout of the pager.</param>\n\t/// <param name=\"options\">The options of the pager.</param>\n\t/// <returns>The collection for all pager items.</returns>\n\tprivate IEnumerable<PagerItem> GenerateItemsCore(int currentPage, int totalPage,\n\t\tIEnumerable<PagerLayoutElement> layout, PagerOptions options)\n\t{\n\t\t// Dictionary for each layout, used to cache generation results\n\t\tvar layoutResult = new Dictionary<PagerLayoutElement, PagerItem[]>();\n\n\t\tvar result = new List<PagerItem>();\n\n\t\tforeach (var element in layout)\n\t\t{\n\t\t\t// If the dictionary not contains the layout result, generate once and store it to the dictionary\n\t\t\tif (!layoutResult.TryGetValue(element, out var elementResult))\n\t\t\t{\n\t\t\t\telementResult = GenerateItemsForLayoutElement(element, currentPage, totalPage, options).ToArray();\n\t\t\t\tlayoutResult[element] = elementResult;\n\t\t\t}\n\n\t\t\tresult.AddRange(elementResult);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/// <summary>\n\t///     Generate all pager items.\n\t/// </summary>\n\t/// <param name=\"currentPage\">The current page number in the pager.</param>\n\t/// <param name=\"totalPage\">The total page count of the pager.</param>\n\t/// <param name=\"options\">The options of the pager.</param>\n\t/// <returns>A collection of all <see cref=\"PagerItem\" /> generated for the pager.</returns>\n\tpublic IEnumerable<PagerItem> GeneratePagerItemsCore(int currentPage, int totalPage, PagerOptions options)\n\t{\n\t\treturn GenerateItemsCore(currentPage, totalPage, options.Layout.Elements, options);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/DefaultPagerRenderingListGenerator.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Provide default implementation for <see cref=\"IPagerRenderingListGenerator\" />.\n/// </summary>\npublic class DefaultPagerRenderingListGenerator : IPagerRenderingListGenerator\n{\n\t/// <summary>\n\t///     Build a <see cref=\"PagerRenderingList\" /> according to the <see cref=\"PagerGenerationContext\" /> information.\n\t/// </summary>\n\t/// <param name=\"list\">The <see cref=\"PagerList\" /> which contains all pager items.</param>\n\t/// <param name=\"context\">The <see cref=\"PagerGenerationContext\" /> object which includes all the informations needed.</param>\n\t/// <returns>A <see cref=\"PagerRenderingList\" /> object which represent as a list to ge displayed in the page.</returns>\n\tpublic PagerRenderingList GenerateRenderingList(PagerList list, PagerGenerationContext context)\n\t{\n\t\treturn GenerateRenderingListCore(list.Items, context);\n\t}\n\n\t/// <summary>\n\t///     Determine if the page is in visible page number list.\n\t/// </summary>\n\t/// <param name=\"context\">\n\t///     The <see cref=\"PagerItemGenerationContext\" /> which contains all informations for the pager and\n\t///     current page.\n\t/// </param>\n\t/// <returns>If the current pager item is in the visible list, returns <c>true</c>; otherwise, returns <c>false</c>.</returns>\n\tprivate static bool IsPageVisible(PagerItemGenerationContext context)\n\t{\n\t\tvar number = context.PagerItem.PageNumber;\n\t\treturn number <= context.Options.PagerItemsForEndings\n\t\t       || number >= context.TotalPage - context.Options.PagerItemsForEndings + 1\n\t\t       || number >= context.CurrentPage - context.Options.ExpandPageItemsForCurrentPage\n\t\t       || number <= context.CurrentPage + context.Options.ExpandPageItemsForCurrentPage;\n\t}\n\n\n\t/// <summary>\n\t///     Get a value that indicates whether the current pager item should be disabled.\n\t/// </summary>\n\t/// <param name=\"context\">The pager item generation context.</param>\n\t/// <returns>If the current pager item should be disabled, returns <c>true</c>; otherwise returns <c>false</c>.</returns>\n\tprivate bool ItemShouldBeDisabled(PagerItemGenerationContext context)\n\t{\n\t\tswitch (context.PagerItem.ItemType)\n\t\t{\n\t\t\tcase PagerItemType.First:\n\t\t\tcase PagerItemType.Last:\n\t\t\t\tswitch (context.PagerItemOptions.ActiveMode)\n\t\t\t\t{\n\t\t\t\t\tcase FirstAndLastPagerItemActiveMode.Always:\n\t\t\t\t\t\treturn false;\n\t\t\t\t\tcase FirstAndLastPagerItemActiveMode.NotInCurrentPage:\n\t\t\t\t\t\treturn context.CurrentPage == context.PagerItem.PageNumber;\n\t\t\t\t\tcase FirstAndLastPagerItemActiveMode.NotInVisiblePageList:\n\t\t\t\t\t\treturn IsPageVisible(context);\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tthrow new InvalidOperationException(\n\t\t\t\t\t\t\t$\"The value of '{nameof(context.PagerItemOptions.ActiveMode)}' property cannot be a valid enum item for a pager item of type '{context.PagerItem.ItemType}'.\");\n\t\t\t\t}\n\t\t\tcase PagerItemType.Next:\n\t\t\t\treturn context.CurrentPage == context.TotalPage;\n\t\t\tcase PagerItemType.Previous:\n\t\t\t\treturn context.CurrentPage == 1;\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t}\n\t}\n\n\t/// <summary>\n\t///     Get the rendering state of the current pager item.\n\t/// </summary>\n\t/// <param name=\"context\">The pager item generation context.</param>\n\t/// <returns>The rendering item of the current pager item.</returns>\n\tprivate PagerRenderingItemState GetRenderingItemState(PagerItemGenerationContext context)\n\t{\n\t\tswitch (context.PagerItem.ItemType)\n\t\t{\n\t\t\tcase PagerItemType.First:\n\t\t\tcase PagerItemType.Last:\n\t\t\tcase PagerItemType.Previous:\n\t\t\tcase PagerItemType.Next:\n\t\t\t\tvar disabled = ItemShouldBeDisabled(context);\n\t\t\t\tif (!disabled)\n\t\t\t\t\treturn PagerRenderingItemState.Normal;\n\t\t\t\tswitch (context.PagerItemOptions.InactiveBehavior)\n\t\t\t\t{\n\t\t\t\t\tcase SpecialPagerItemInactiveBehavior.Disable:\n\t\t\t\t\t\treturn PagerRenderingItemState.Disabled;\n\t\t\t\t\tcase SpecialPagerItemInactiveBehavior.Hide:\n\t\t\t\t\t\treturn PagerRenderingItemState.Hidden;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tthrow new InvalidOperationException(\n\t\t\t\t\t\t\t$\"The value of '{nameof(context.PagerItemOptions.InactiveBehavior)}' property cannot be a valid enum item for a pager item of type '{context.PagerItem.ItemType}'.\");\n\t\t\t\t}\n\t\t\tcase PagerItemType.Current:\n\t\t\t\treturn PagerRenderingItemState.Active;\n\t\t\tdefault:\n\t\t\t\treturn PagerRenderingItemState.Normal;\n\t\t}\n\t}\n\n\t/// <summary>\n\t///     Generate a <see cref=\"PagerRenderingItem\" /> for the current pager item.\n\t/// </summary>\n\t/// <param name=\"list\">The ownner list of the new item.</param>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The generated <see cref=\"PagerRenderingItem\" /> instance.</returns>\n\tprivate PagerRenderingItem GenerateRenderingItem(PagerRenderingList list, PagerItemGenerationContext context)\n\t{\n\t\treturn new PagerRenderingItem(list)\n\t\t{\n\t\t\tContent = context.PagerItemOptions.Content?.GenerateContent(context),\n\t\t\tLink = context.PagerItemOptions.Link?.GenerateLink(context),\n\t\t\tSettings = new Dictionary<string, string>(context.PagerItemOptions.AdditionalSettings),\n\t\t\tState = GetRenderingItemState(context)\n\t\t};\n\t}\n\n\t/// <summary>\n\t///     Generate a <see cref=\"PagerRenderingList\" /> for a series of <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"items\">The collection of <see cref=\"PagerItem\" /> which are included in the list.</param>\n\t/// <param name=\"context\">The generation context.</param>\n\t/// <returns>The generated <see cref=\"PagerRenderingList\" /> instance.</returns>\n\tprivate PagerRenderingList GenerateRenderingListCore(IEnumerable<PagerItem> items, PagerGenerationContext context)\n\t{\n\t\tvar optionsCache = new Dictionary<PagerItemType, PagerItemOptions>();\n\n\t\tvar renderingItemList = new List<PagerRenderingItem>();\n\n\t\t// Owner\n\t\tvar result = new PagerRenderingList\n\t\t{\n\t\t\tSettings = new Dictionary<string, string>(context.Options.AdditionalSettings)\n\t\t};\n\n\t\tforeach (var item in items)\n\t\t{\n\t\t\t// Try to get cached options, or create a new option instance\n\t\t\tif (!optionsCache.TryGetValue(item.ItemType, out var itemOptions))\n\t\t\t{\n\t\t\t\titemOptions = context.Options.ItemOptions.GetMergedOptionsFor(item);\n\t\t\t\toptionsCache[item.ItemType] = itemOptions;\n\t\t\t}\n\n\t\t\t// Generate item context\n\t\t\tvar itemContext = new PagerItemGenerationContext(context, item, itemOptions);\n\n\t\t\t// Generate item and add to list\n\t\t\tvar renderingItem = GenerateRenderingItem(result, itemContext);\n\t\t\trenderingItemList.Add(renderingItem);\n\t\t}\n\n\t\t// Result\n\t\tresult.Items = new ReadOnlyCollection<PagerRenderingItem>(renderingItemList);\n\t\treturn result;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/IPagerGenerator.cs",
    "content": "﻿using Microsoft.AspNetCore.Html;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Define the necessary feature needed to generate a pager.\n/// </summary>\npublic interface IPagerGenerator\n{\n\t/// <summary>\n\t///     Generate the entire HTML content for a pager tag.\n\t/// </summary>\n\t/// <param name=\"context\">The context information for pager generation.</param>\n\t/// <returns>The entire HTML content for the generated pager.</returns>\n\tIHtmlContent GeneratePager(PagerGenerationContext context);\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/IPagerHtmlGenerator.cs",
    "content": "﻿using Microsoft.AspNetCore.Html;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Define the feature needed to generate a pager's HTML for an <see cref=\"IPagedList\" /> object.\n/// </summary>\npublic interface IPagerHtmlGenerator\n{\n\t/// <summary>\n\t///     Generate the entire HTML content for a pager renderling list.\n\t/// </summary>\n\t/// <param name=\"list\">The rendering list to be generating.</param>\n\t/// <param name=\"context\">The pager generation context.</param>\n\t/// <returns>The entire HTML content for the generated pager.</returns>\n\tIHtmlContent GeneratePager(PagerRenderingList list, PagerGenerationContext context);\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/IPagerListGenerator.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Define the necessary features used to generate a <see cref=\"PagerList\" /> from the\n///     <see cref=\"PagerGenerationContext\" />.\n/// </summary>\npublic interface IPagerListGenerator\n{\n\t/// <summary>\n\t///     Generate a <see cref=\"PagerList\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The pager generation context.</param>\n\t/// <returns>A <see cref=\"PagerList\" /> generated for the pager.</returns>\n\tPagerList GeneratePagerItems(PagerGenerationContext context);\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/IPagerRenderingListGenerator.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Define necessary feature used to genearte a <see cref=\"PagerRenderingList\" /> from a\n///     <see cref=\"PagerGenerationContext\" />.\n/// </summary>\npublic interface IPagerRenderingListGenerator\n{\n\t/// <summary>\n\t///     Build a <see cref=\"PagerRenderingList\" /> according to the <see cref=\"PagerGenerationContext\" /> information.\n\t/// </summary>\n\t/// <param name=\"list\">The <see cref=\"PagerList\" /> which contains all pager items.</param>\n\t/// <param name=\"context\">The <see cref=\"PagerGenerationContext\" /> object which includes all the informations needed.</param>\n\t/// <returns>A <see cref=\"PagerRenderingList\" /> object which represent as a list to ge displayed in the page.</returns>\n\tPagerRenderingList GenerateRenderingList(PagerList list, PagerGenerationContext context);\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/PagerGenerationContext.cs",
    "content": "﻿using Microsoft.AspNetCore.Mvc.Rendering;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Provide necessary information for pager generation.\n/// </summary>\npublic class PagerGenerationContext\n{\n\t/// <summary>\n\t///     Initialize a new instance with empty setting.\n\t/// </summary>\n\tprotected PagerGenerationContext()\n\t{\n\t}\n\n\t/// <summary>\n\t///     Initialize a new instance.\n\t/// </summary>\n\t/// <param name=\"currentPage\">The current page number in the pager.</param>\n\t/// <param name=\"totalPage\">The total page count of the pager.</param>\n\t/// <param name=\"options\">The options of the pager.</param>\n\t/// <param name=\"viewContext\">The current view context.</param>\n\t/// <param name=\"generationMode\">The generation mode.</param>\n\tpublic PagerGenerationContext(\n\t\tint currentPage,\n\t\tint totalPage,\n\t\tPagerOptions options,\n\t\tViewContext viewContext,\n\t\tPagerGenerationMode generationMode)\n\t{\n\t\tOptions = options;\n\t\tViewContext = viewContext;\n\t\tGenerationMode = generationMode;\n\t\tCurrentPage = currentPage;\n\t\tTotalPage = totalPage;\n\t\tGenerationMode = generationMode;\n\t}\n\n\t/// <summary>\n\t///     Get the current pager number of the pager.\n\t/// </summary>\n\tpublic int CurrentPage { get; protected set; }\n\n\t/// <summary>\n\t///     Get the options for pager generation.\n\t/// </summary>\n\tpublic PagerOptions Options { get; protected set; }\n\n\t/// <summary>\n\t///     Get the total page count of the pager.\n\t/// </summary>\n\tpublic int TotalPage { get; protected set; }\n\n\t/// <summary>\n\t///     Get the pager generation mode.\n\t/// </summary>\n\tpublic PagerGenerationMode GenerationMode { get; protected set; }\n\n\t/// <summary>\n\t///     Get the current view context.\n\t/// </summary>\n\tpublic ViewContext ViewContext { get; protected set; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/PagerItem.cs",
    "content": "﻿using System.Collections.Generic;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Represent as a logic item in the pager.\n/// </summary>\n[PublicAPI]\npublic class PagerItem\n{\n\t/// <summary>\n\t///     Get or set the page number associated with this pager item (if any). Negative value means the page number is not\n\t///     meaningful in current situation.\n\t/// </summary>\n\tpublic int PageNumber { get; set; }\n\n\t/// <summary>\n\t///     Get or set a value that indicates if this pager item is disabled according to the options.\n\t/// </summary>\n\tpublic bool IsDisabled { get; set; }\n\n\t/// <summary>\n\t///     Get or set the type for this pager item.\n\t/// </summary>\n\tpublic PagerItemType ItemType { get; set; }\n\n\t/// <summary>\n\t///     Get or set all additional settings for this pager item.\n\t/// </summary>\n\tpublic IDictionary<string, string> Settings { get; set; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/PagerItemContentGeneratorConverter.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Globalization;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Convert from <see cref=\"string\" /> to <see cref=\"IPagerItemContentGenerator\" /> instances.\n/// </summary>\npublic class PagerItemContentGeneratorConverter : TypeConverter\n{\n\t/// <summary>\n\t///     返回该转换器是否可以使用指定的上下文将给定类型的对象转换为此转换器的类型。\n\t/// </summary>\n\t/// <returns>\n\t///     如果该转换器能够执行转换，则为 true；否则为 false。\n\t/// </returns>\n\t/// <param name=\"context\">一个 <see cref=\"T:System.ComponentModel.ITypeDescriptorContext\" />，提供格式上下文。</param>\n\t/// <param name=\"sourceType\">一个 <see cref=\"T:System.Type\" />，表示要转换的类型。</param>\n\tpublic override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)\n\t{\n\t\treturn sourceType == typeof(string);\n\t}\n\n\t/// <summary>\n\t///     使用指定的上下文和区域性信息将给定的对象转换为此转换器的类型。\n\t/// </summary>\n\t/// <returns>\n\t///     表示转换的 value 的 <see cref=\"T:System.Object\" />。\n\t/// </returns>\n\t/// <param name=\"context\">一个 <see cref=\"T:System.ComponentModel.ITypeDescriptorContext\" />，提供格式上下文。</param>\n\t/// <param name=\"culture\">用作当前区域性的 <see cref=\"T:System.Globalization.CultureInfo\" />。</param>\n\t/// <param name=\"value\">要转换的 <see cref=\"T:System.Object\" />。</param>\n\t/// <exception cref=\"T:System.NotSupportedException\">不能执行转换。</exception>\n\tpublic override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)\n\t{\n\t\tvar realValue = value as string;\n\n\t\tif (realValue == null)\n\t\t\tthrow new NotSupportedException();\n\n\t\treturn PagerItemContentGenerators.FromConfiguration(realValue);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/PagerItemLinkGeneratorConverter.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Globalization;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Convert <see cref=\"string\" /> To <see cref=\"IPagerItemLinkGenerator\" /> objects.\n/// </summary>\npublic class PagerItemLinkGeneratorConverter : TypeConverter\n{\n\t/// <summary>返回该转换器是否可以使用指定的上下文将给定类型的对象转换为此转换器的类型。</summary>\n\t/// <returns>如果该转换器能够执行转换，则为 true；否则为 false。</returns>\n\t/// <param name=\"context\">一个 <see cref=\"T:System.ComponentModel.ITypeDescriptorContext\" />，提供格式上下文。</param>\n\t/// <param name=\"sourceType\">一个 <see cref=\"T:System.Type\" />，表示要转换的类型。</param>\n\tpublic override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)\n\t{\n\t\treturn sourceType == typeof(string);\n\t}\n\n\t/// <summary>使用指定的上下文和区域性信息将给定的对象转换为此转换器的类型。</summary>\n\t/// <returns>表示转换的 value 的 <see cref=\"T:System.Object\" />。</returns>\n\t/// <param name=\"context\">一个 <see cref=\"T:System.ComponentModel.ITypeDescriptorContext\" />，提供格式上下文。</param>\n\t/// <param name=\"culture\">用作当前区域性的 <see cref=\"T:System.Globalization.CultureInfo\" />。</param>\n\t/// <param name=\"value\">要转换的 <see cref=\"T:System.Object\" />。</param>\n\t/// <exception cref=\"T:System.NotSupportedException\">不能执行转换。</exception>\n\tpublic override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)\n\t{\n\t\tvar realValue = value as string;\n\n\t\tif (realValue == null)\n\t\t\tthrow new NotSupportedException();\n\n\t\treturn PagerItemLinkGenerators.FromConfiguration(realValue);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/PagerItemType.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Represent as the type of a paged list pager item.\n/// </summary>\npublic enum PagerItemType\n{\n\t/// <summary>\n\t///     This item is linked to a specified page according to its page number.\n\t/// </summary>\n\tNormal = 0,\n\n\t/// <summary>\n\t///     This item is linked to current page.\n\t/// </summary>\n\tCurrent,\n\n\t/// <summary>\n\t///     This item represents as a placeholder for omitted items.\n\t/// </summary>\n\tOmitted,\n\n\t/// <summary>\n\t///     This item is linked to the previous page.\n\t/// </summary>\n\tPrevious,\n\n\t/// <summary>\n\t///     This item is linked to the next page.\n\t/// </summary>\n\tNext,\n\n\t/// <summary>\n\t///     This item is linked to the first page.\n\t/// </summary>\n\tFirst,\n\n\t/// <summary>\n\t///     This item is linked to the last page.\n\t/// </summary>\n\tLast\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/PagerLayoutConverter.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Globalization;\nusing System.Linq;\nusing System.Text.RegularExpressions;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Convert a custom string to <see cref=\"PagerOptions.Layout\" /> property value.\n/// </summary>\npublic class PagerLayoutConverter : TypeConverter\n{\n\t/// <inheritdoc />\n\tpublic override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)\n\t{\n\t\treturn sourceType == typeof(string);\n\t}\n\n\t/// <inheritdoc />\n\tpublic override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)\n\t{\n\t\tvar realValue = value as string;\n\t\tif (realValue == null)\n\t\t\tthrow new NotSupportedException();\n\n\t\tvar match = Regex.Match(realValue, @\"^(?<type>.*?)(\\:(?<exp>.*))?$\",\n\t\t\tRegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.Singleline);\n\n\t\tif (!match.Success)\n\t\t\tthrow new NotSupportedException();\n\n\t\tvar type = match.Groups[\"type\"].Value;\n\t\tvar exp = match.Groups[\"exp\"].Value;\n\n\t\tswitch (type.ToLowerInvariant())\n\t\t{\n\t\t\tcase \"default\":\n\t\t\t\treturn PagerLayouts.Default;\n\t\t\tcase \"custom\":\n\t\t\t\tvar items = exp.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);\n\t\t\t\treturn\n\t\t\t\t\tnew PagerLayout(items.Select(i => (PagerLayoutElement) Enum.Parse(typeof(PagerLayoutElement), i.Trim(), true)));\n\n\t\t\tdefault:\n\t\t\t\tthrow new NotSupportedException();\n\t\t}\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/PagerList.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Represent as a pager.\n/// </summary>\npublic class PagerList\n{\n\t/// <summary>\n\t///     Initialize a new pager.\n\t/// </summary>\n\t/// <param name=\"items\">All items included in the pager.</param>\n\tpublic PagerList([NotNull] [ItemNotNull] IEnumerable<PagerItem> items)\n\t{\n\t\tItems = items ?? throw new ArgumentNullException(nameof(items));\n\t}\n\n\t/// <summary>\n\t///     Get all items in the pager.\n\t/// </summary>\n\t[NotNull]\n\t[ItemNotNull]\n\tpublic IEnumerable<PagerItem> Items { get; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/PagerRenderingItem.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Html;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Represent as a single item in a paged list pager.\n/// </summary>\npublic class PagerRenderingItem\n{\n\t/// <summary>\n\t///     Initialize a new <see cref=\"PagerRenderingItem\" />.\n\t/// </summary>\n\t/// <param name=\"list\">The owner list of the pager item.</param>\n\tpublic PagerRenderingItem([NotNull] PagerRenderingList list)\n\t{\n\t\tList = list ?? throw new ArgumentNullException(nameof(list));\n\t}\n\n\t/// <summary>\n\t///     Get the owner list for the current pager item.\n\t/// </summary>\n\t[NotNull]\n\tpublic PagerRenderingList List { get; }\n\n\t/// <summary>\n\t///     Get or set the content of the pager item.\n\t/// </summary>\n\tpublic IHtmlContent Content { get; set; }\n\n\t/// <summary>\n\t///     Get or set the linked url address for this page (if any).\n\t/// </summary>\n\tpublic string Link { get; set; }\n\n\t/// <summary>\n\t///     Get or set the state of this item.\n\t/// </summary>\n\tpublic PagerRenderingItemState State { get; set; }\n\n\t/// <summary>\n\t///     Get or set the dictionary used to store all additional settings.\n\t/// </summary>\n\tpublic IDictionary<string, string> Settings { get; set; } = new Dictionary<string, string>();\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/PagerRenderingItemState.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Represent as the state for a paged list pager item.\n/// </summary>\npublic enum PagerRenderingItemState\n{\n\t/// <summary>\n\t///     The item is in a normal state, i.e. not activated nor disabled.\n\t/// </summary>\n\tNormal = 0,\n\n\t/// <summary>\n\t///     The item is activated.\n\t/// </summary>\n\tActive,\n\n\t/// <summary>\n\t///     The item is disabled.\n\t/// </summary>\n\tDisabled,\n\n\t/// <summary>\n\t///     The item should not be displayed.\n\t/// </summary>\n\tHidden\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/PagerRenderingList.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Represent as the visual structure of a paged list pager.\n/// </summary>\npublic class PagerRenderingList\n{\n\t/// <summary>\n\t///     Get or set the items in the list.\n\t/// </summary>\n\tpublic IReadOnlyList<PagerRenderingItem> Items { get; set; }\n\n\t/// <summary>\n\t///     Get or set the additional settings for the pager.\n\t/// </summary>\n\tpublic Dictionary<string, string> Settings { get; set; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/StringToHtmlContentConverter.cs",
    "content": "﻿using Microsoft.AspNetCore.Html;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Provide helper methods to convert string value to <see cref=\"IHtmlContent\" /> object. This class is static.\n/// </summary>\npublic static class StringToHtmlContentConverter\n{\n\t/// <summary>\n\t///     Convert a string value to <see cref=\"IHtmlContent\" /> object.\n\t/// </summary>\n\t/// <param name=\"value\">The value of the string.</param>\n\t/// <param name=\"encodeContent\">\n\t///     Control whether the <paramref name=\"value\" /> should be HTML-encoded before be written to a\n\t///     page.\n\t/// </param>\n\t/// <returns>The converted <see cref=\"IHtmlContent\" /> object.</returns>\n\tpublic static IHtmlContent ToHtmlContent(this string value, bool encodeContent)\n\t{\n\t\treturn encodeContent ? (IHtmlContent) new StringHtmlContent(value) : new HtmlString(value);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Internal/Utility.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore.Mvc.Internal;\n\n/// <summary>\n///     Provide utility methods. This class is static.\n/// </summary>\ninternal static class Utility\n{\n\t/// <summary>\n\t///     Try get the value associated with the speicified key in a dictionary. If the key is not presented, return a custom\n\t///     default value.\n\t/// </summary>\n\t/// <typeparam name=\"TKey\">The key type of the <paramref name=\"dictionary\" />.</typeparam>\n\t/// <typeparam name=\"TValue\">The value type of the <paramref name=\"dictionary\" />.</typeparam>\n\t/// <param name=\"dictionary\">The dictionary which contains all key and values.</param>\n\t/// <param name=\"key\">The key associated with the target value in the <paramref name=\"dictionary\" />.</param>\n\t/// <param name=\"defaultValue\">\n\t///     The default value if <paramref name=\"key\" /> is not presented in the\n\t///     <paramref name=\"dictionary\" />.\n\t/// </param>\n\t/// <returns>\n\t///     If the <paramref name=\"key\" /> is presented in the <paramref name=\"dictionary\" />, returns its associated\n\t///     value; otherwise, returns the <paramref name=\"defaultValue\" />.\n\t/// </returns>\n\tpublic static TValue GetValueOfDefault<TKey, TValue>([NotNull] this IDictionary<TKey, TValue> dictionary, TKey key,\n\t\tTValue defaultValue = default(TValue))\n\n\t{\n\t\tif (dictionary == null)\n\t\t\tthrow new ArgumentNullException(nameof(dictionary));\n\n\t\treturn dictionary.TryGetValue(key, out var result) ? result : defaultValue;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/PagerGenerationMode.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Define the HTML generation mode of a pager related tag.\n/// </summary>\npublic enum PagerGenerationMode\n{\n\t/// <summary>\n\t///     Generate full pager, including all pager items and the container.\n\t/// </summary>\n\tFull,\n\n\t/// <summary>\n\t///     Generate pager items only, containers will not be generated.\n\t/// </summary>\n\tListOnly\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/PagerItemContentGenerators.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing System.Text.RegularExpressions;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Html;\nusing Sakura.AspNetCore.Mvc.Generators;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Provide shortcut generator implementation for <see cref=\"PagerItemOptions.Content\" />. This class is static.\n/// </summary>\n[PublicAPI]\npublic static class PagerItemContentGenerators\n{\n\t#region From Configuration\n\n\t/// <summary>\n\t///     Generate a <see cref=\"IPagerItemContentGenerator\" /> according to the specified configuration text.\n\t/// </summary>\n\t/// <param name=\"configurationText\">The configuration text which described the type and arguments of the generator.</param>\n\t/// <returns>\n\t///     The <see cref=\"IPagerItemContentGenerator\" /> object generated accoding to\n\t///     <paramref name=\"configurationText\" />.\n\t/// </returns>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"configurationText\" /> is <c>null</c>.</exception>\n\t/// <exception cref=\"ArgumentException\">The <paramref name=\"configurationText\" /> cannot be parsed into excepted format.</exception>\n\t/// <exception cref=\"NotSupportedException\">\n\t///     The generator type specified in <paramref name=\"configurationText\" /> is not\n\t///     supported.\n\t/// </exception>\n\tpublic static IPagerItemContentGenerator FromConfiguration(string configurationText)\n\t{\n\t\tif (configurationText == null)\n\t\t\tthrow new ArgumentNullException(nameof(configurationText));\n\n\t\tvar pattern = @\"^(?<type>.*?)(:(?<exp>.*)?)$\";\n\n\t\tvar matchResult = Regex.Match(configurationText, pattern,\n\t\t\tRegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase |\n\t\t\tRegexOptions.Singleline);\n\n\t\tif (!matchResult.Success)\n\t\t\tthrow new ArgumentException(\"The configurationText cannot be parsed.\", nameof(configurationText));\n\n\t\t// Type text, normalize first\n\t\tvar type = matchResult.Groups[\"type\"].Value.Trim().ToLowerInvariant();\n\t\tvar exp = matchResult.Groups[\"exp\"].Value;\n\n\t\tswitch (type)\n\t\t{\n\t\t\tcase \"text\":\n\t\t\t\treturn Text(exp);\n\t\t\tcase \"html\":\n\t\t\t\treturn Html(exp);\n\t\t\tcase \"textformat\":\n\t\t\t\treturn TextFormat(exp);\n\t\t\tcase \"htmlformat\":\n\t\t\t\treturn HtmlFormat(exp);\n\t\t\tcase \"default\":\n\t\t\t\treturn Default;\n\t\t\tdefault:\n\t\t\t\tthrow new NotSupportedException(\n\t\t\t\t\t$\"The generator type string '{type}' in the configuration text is not a valid option.\");\n\t\t}\n\t}\n\n\t#endregion\n\n\t#region Shortcuts\n\n\t/// <summary>\n\t///     Get the generator that use page number as the content of the pager item.\n\t/// </summary>\n\tpublic static IPagerItemContentGenerator Default { get; } = TextFormat(\"{0:d}\");\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates a fixed text content.\n\t/// </summary>\n\t/// <param name=\"text\">The generated text content.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator Text(string text)\n\t{\n\t\treturn new SimpleStringContentGenerator(text, true);\n\t}\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates a fixed html content.\n\t/// </summary>\n\t/// <param name=\"html\">The generated html content.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator Html(string html)\n\t{\n\t\treturn new SimpleStringContentGenerator(html, false);\n\t}\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates a <see cref=\"IHtmlContent\" /> object.\n\t/// </summary>\n\t/// <param name=\"content\">The generated html content.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\t/// <returns></returns>\n\tpublic static IPagerItemContentGenerator Content(IHtmlContent content)\n\t{\n\t\treturn new CustomHtmlContentGenerator(context => content);\n\t}\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates text content using a format string.\n\t/// </summary>\n\t/// <param name=\"textFormat\">The format string used to generate the text.</param>\n\t/// <param name=\"formatProvider\">\n\t///     The format provider object. If this parameter is <c>null</c>,\n\t///     <see cref=\"CultureInfo.CurrentCulture\" /> will be used.\n\t/// </param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator TextFormat(string textFormat, IFormatProvider formatProvider = null)\n\t{\n\t\treturn new FormattedStringContentGenerator(textFormat, true, formatProvider);\n\t}\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates html content using a format string.\n\t/// </summary>\n\t/// <param name=\"htmlFormat\">The format string used to generate the html.</param>\n\t/// <param name=\"formatProvider\">\n\t///     The format provider object. If this parameter is <c>null</c>,\n\t///     <see cref=\"CultureInfo.CurrentCulture\" /> will be used.\n\t/// </param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator HtmlFormat(string htmlFormat, IFormatProvider formatProvider = null)\n\t{\n\t\treturn new FormattedStringContentGenerator(htmlFormat, false, formatProvider);\n\t}\n\n\t#endregion\n\n\t#region Custom Text\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates text content using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"textGenerator\">The generation method.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator CustomText(Func<PagerItemGenerationContext, string> textGenerator)\n\t{\n\t\treturn new CustomStringContentGenerator(textGenerator, true);\n\t}\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates text content using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"textGenerator\">The generation method.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator CustomText(Func<PagerItem, string> textGenerator)\n\t{\n\t\treturn CustomText(context => textGenerator(context.PagerItem));\n\t}\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates text content using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"textGenerator\">The generation method.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator CustomText(Func<int, string> textGenerator)\n\t{\n\t\treturn CustomText(context => textGenerator(context.PagerItem.PageNumber));\n\t}\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates text content using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"textGenerator\">The generation method.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator CustomText(Func<string> textGenerator)\n\t{\n\t\treturn CustomText((PagerItemGenerationContext context) => textGenerator());\n\t}\n\n\t#endregion\n\n\t#region Custom Html\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates html content using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"htmlGenerator\">The generation method.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator CustomHtml(Func<PagerItemGenerationContext, string> htmlGenerator)\n\t{\n\t\treturn new CustomStringContentGenerator(htmlGenerator, false);\n\t}\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates html content using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"htmlGenerator\">The generation method.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator CustomHtml(Func<PagerItem, string> htmlGenerator)\n\t{\n\t\treturn CustomHtml(context => htmlGenerator(context.PagerItem));\n\t}\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates html content using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"htmlGenerator\">The generation method.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator CustomHtml(Func<int, string> htmlGenerator)\n\t{\n\t\treturn CustomHtml(context => htmlGenerator(context.PagerItem.PageNumber));\n\t}\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates html content using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"htmlGenerator\">The generation method.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator CustomHtml(Func<string> htmlGenerator)\n\t{\n\t\treturn CustomHtml((PagerItemGenerationContext context) => htmlGenerator());\n\t}\n\n\t#endregion\n\n\t#region Custom IHtmlContent\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates html content using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"contentGenerator\">The generation method.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator CustomContent(\n\t\tFunc<PagerItemGenerationContext, IHtmlContent> contentGenerator)\n\t{\n\t\treturn new CustomHtmlContentGenerator(contentGenerator);\n\t}\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates html content using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"contentGenerator\">The generation method.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator CustomContent(Func<PagerItem, IHtmlContent> contentGenerator)\n\t{\n\t\treturn CustomContent(context => contentGenerator(context.PagerItem));\n\t}\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates html content using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"contentGenerator\">The generation method.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator CustomContent(Func<int, IHtmlContent> contentGenerator)\n\t{\n\t\treturn CustomContent(context => contentGenerator(context.PagerItem.PageNumber));\n\t}\n\n\t/// <summary>\n\t///     Create a pager item content generator that generates html content using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"contentGenerator\">The generation method.</param>\n\t/// <returns>The pager item content generator object.</returns>\n\tpublic static IPagerItemContentGenerator CustomContent(Func<IHtmlContent> contentGenerator)\n\t{\n\t\treturn CustomContent((PagerItemGenerationContext context) => contentGenerator());\n\t}\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/PagerItemGenerationContext.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Provide necessary information for pager item generation.\n/// </summary>\npublic class PagerItemGenerationContext : PagerGenerationContext\n{\n\t/// <summary>\n\t///     Create a new <see cref=\"PagerItemGenerationContext\" /> from a base <see cref=\"PagerGenerationContext\" />.\n\t/// </summary>\n\t/// <param name=\"baseContext\">The base <see cref=\"PagerGenerationContext\" /> instance.</param>\n\t/// <param name=\"pagerItem\">The current page item.</param>\n\t/// <param name=\"pagerItemOptions\">The current page item options.</param>\n\tpublic PagerItemGenerationContext([NotNull] PagerGenerationContext baseContext, PagerItem pagerItem,\n\t\tPagerItemOptions pagerItemOptions)\n\t{\n\t\tif (baseContext == null)\n\t\t\tthrow new ArgumentNullException(nameof(baseContext));\n\n\t\tCurrentPage = baseContext.CurrentPage;\n\t\tTotalPage = baseContext.TotalPage;\n\t\tOptions = baseContext.Options;\n\t\tViewContext = baseContext.ViewContext;\n\t\tGenerationMode = baseContext.GenerationMode;\n\n\t\tPagerItem = pagerItem;\n\t\tPagerItemOptions = pagerItemOptions;\n\t}\n\n\t/// <summary>\n\t///     Get the current pager item to be generating.\n\t/// </summary>\n\tpublic PagerItem PagerItem { get; }\n\n\t/// <summary>\n\t///     Get the options for the current pager item.\n\t/// </summary>\n\tpublic PagerItemOptions PagerItemOptions { get; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/PagerItemLinkGenerators.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing System.Text.RegularExpressions;\nusing JetBrains.Annotations;\nusing Sakura.AspNetCore.Mvc.Generators;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Provide shortcut generator implementation for <see cref=\"PagerItemOptions.Link\" />. This class is static.\n/// </summary>\n[PublicAPI]\npublic static class PagerItemLinkGenerators\n{\n\t#region Congfiguration\n\n\t/// <summary>\n\t///     Generate a <see cref=\"IPagerItemLinkGenerator\" /> from a configuration string.\n\t/// </summary>\n\t/// <param name=\"configurationText\">The configuration string.</param>\n\t/// <returns>The converted <see cref=\"IPagerItemLinkGenerator\" />.</returns>\n\tpublic static IPagerItemLinkGenerator FromConfiguration(string configurationText)\n\t{\n\t\tif (configurationText == null)\n\t\t\tthrow new ArgumentNullException(nameof(configurationText));\n\n\t\tif (configurationText == null)\n\t\t\tthrow new ArgumentNullException(nameof(configurationText));\n\n\t\tconst string pattern = @\"^(?<type>.*?)(:(?<exp>.*))?$\";\n\n\t\tvar matchResult = Regex.Match(configurationText, pattern,\n\t\t\tRegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase |\n\t\t\tRegexOptions.Singleline);\n\n\t\tif (!matchResult.Success)\n\t\t\tthrow new ArgumentException(\"The configurationText cannot be parsed.\", nameof(configurationText));\n\n\t\t// Type text, normalize first\n\t\tvar type = matchResult.Groups[\"type\"].Value.Trim().ToLowerInvariant();\n\t\tvar exp = matchResult.Groups[\"exp\"].Value;\n\n\t\tswitch (type)\n\t\t{\n\t\t\tcase \"query\":\n\t\t\t\tconst string p2 = @\"^(?<name>.*?)=(?<value>.*)$\";\n\t\t\t\tvar m2 = Regex.Match(\n\t\t\t\t\texp,\n\t\t\t\t\tp2,\n\t\t\t\t\tRegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase\n\t\t\t\t\t| RegexOptions.Singleline);\n\t\t\t\tif (!m2.Success)\n\t\t\t\t\tthrow new NotSupportedException(\n\t\t\t\t\t\t$\"The generator expression string '{exp}' in the configuration text cannot be condiser as a query expression.\");\n\t\t\t\treturn QueryNameAndValueFormat(m2.Groups[\"name\"].Value, m2.Groups[\"value\"].Value);\n\t\t\tcase \"queryname\":\n\t\t\t\treturn QueryName(exp);\n\t\t\tcase \"format\":\n\t\t\t\treturn Format(exp);\n\t\t\tcase \"fixed\":\n\t\t\t\treturn Fixed(exp);\n\t\t\tcase \"default\":\n\t\t\t\treturn Default;\n\t\t\tcase \"disabled\":\n\t\t\t\treturn Disabled;\n\t\t\tdefault:\n\t\t\t\tthrow new NotSupportedException(\n\t\t\t\t\t$\"The generator type string '{type}' in the configuration text is not a valid option.\");\n\t\t}\n\t}\n\n\t#endregion\n\n\t#region Shortcut\n\n\t/// <summary>\n\t///     Get the generator that uses a fixed string as a query parameter name and a formatted string as a query parameter\n\t///     value.\n\t/// </summary>\n\t/// <param name=\"queryParameterName\">The name of the query parameter.</param>\n\t/// <param name=\"queryParameterValueFormat\">The format string of the query parameter value.</param>\n\t/// <param name=\"queryParameterValueFormatProvider\">The format provider for format value generation.</param>\n\t/// <returns>The generated <see cref=\"FormattedQueryValueLinkGenerator\" /> instance.</returns>\n\tpublic static FormattedQueryValueLinkGenerator QueryNameAndValueFormat([NotNull] string queryParameterName,\n\t\t[NotNull] string queryParameterValueFormat, IFormatProvider queryParameterValueFormatProvider = null)\n\t{\n\t\treturn new FormattedQueryValueLinkGenerator(queryParameterName, queryParameterValueFormat,\n\t\t\tqueryParameterValueFormatProvider);\n\t}\n\n\t/// <summary>\n\t///     Get the generator that uses a fixed string as a query parameter name and a page number as a query parameter value.\n\t/// </summary>\n\t/// <param name=\"queryParameterName\">The name of the query parameter.</param>\n\t/// <returns>The generated <see cref=\"FormattedQueryValueLinkGenerator\" /> instance.</returns>\n\tpublic static FormattedQueryValueLinkGenerator QueryName([NotNull] string queryParameterName)\n\t{\n\t\treturn QueryNameAndValueFormat(queryParameterName, \"{0:d}\");\n\t}\n\n\t/// <summary>\n\t///     Get the generator that uses a \"page\" as a query parameter name and a page number as a query parameter value.\n\t/// </summary>\n\t/// <returns>The generated <see cref=\"FormattedQueryValueLinkGenerator\" /> instance.</returns>\n\tpublic static FormattedQueryValueLinkGenerator Default { get; } = QueryName(\"page\");\n\n\t/// <summary>\n\t///     Get the generator that disable the link generation feature.\n\t/// </summary>\n\tpublic static DisabledLinkGenerator Disabled { get; } = new DisabledLinkGenerator();\n\n\t/// <summary>\n\t///     Create a pager item link generator that generates link using a format string.\n\t/// </summary>\n\t/// <param name=\"format\">The format string used to generate the link.</param>\n\t/// <param name=\"formatProvider\">\n\t///     The format provider object. If this parameter is <c>null</c>,\n\t///     <see cref=\"CultureInfo.InvariantCulture\" /> will be used.\n\t/// </param>\n\t/// <returns>The pager item link generator object.</returns>\n\tpublic static FormattedLinkGenerator Format([NotNull] string format, IFormatProvider formatProvider = null)\n\t{\n\t\treturn new FormattedLinkGenerator(format, formatProvider);\n\t}\n\n\t/// <summary>\n\t///     Create a pager item link generator that generates link using a fixed string.\n\t/// </summary>\n\t/// <param name=\"text\">The link url string.</param>\n\t/// <returns>The pager item link generator object.</returns>\n\tpublic static SimpleLinkGenerator Fixed([NotNull] string text)\n\t{\n\t\treturn new SimpleLinkGenerator(text);\n\t}\n\n\t/// <summary>\n\t///     Create a pager item link generator that generates link using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"linkGenerator\">The generation method.</param>\n\t/// <returns>The pager item link generator object.</returns>\n\tpublic static CustomLinkGenerator Custom([NotNull] Func<PagerItemGenerationContext, string> linkGenerator)\n\t{\n\t\treturn new CustomLinkGenerator(linkGenerator);\n\t}\n\n\t/// <summary>\n\t///     Create a pager item link generator that generates link using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"linkGenerator\">The generation method.</param>\n\t/// <returns>The pager item link generator object.</returns>\n\tpublic static CustomLinkGenerator Custom([NotNull] Func<PagerItem, string> linkGenerator)\n\t{\n\t\treturn Custom(context => linkGenerator(context.PagerItem));\n\t}\n\n\t/// <summary>\n\t///     Create a pager item link generator that generates link using a custom generating method.\n\t/// </summary>\n\t/// <param name=\"linkGenerator\">The generation method.</param>\n\t/// <returns>The pager item link generator object.</returns>\n\tpublic static CustomLinkGenerator Custom([NotNull] Func<int, string> linkGenerator)\n\t{\n\t\treturn Custom(context => linkGenerator(context.PagerItem.PageNumber));\n\t}\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/PagerItemMode.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Define the working mode for a pager item.\n/// </summary>\npublic enum PagerItemMode\n{\n\t/// <summary>\n\t///     The pager item is used to navigate the browser to a new location. The generated item is usually an \"a\" element.\n\t/// </summary>\n\tNavigation = 0,\n\n\t/// <summary>\n\t///     The pager item is used to submit data to a specified location. The generated item is usually an \"button\" element\n\t///     with the type \"submit\" specified.\n\t/// </summary>\n\tSubmition,\n\n\t/// <summary>\n\t///     The pager item is used to raise click events. The generated item is usually an \"button\" element with the type\n\t///     \"button\" specified.\n\t/// </summary>\n\tClicking\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/PagerItemOptions.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Control the generation process for a single pager item.\n/// </summary>\npublic class PagerItemOptions\n{\n\t/// <summary>\n\t///     Get or set a generator that used to generate the content of the pager item.\n\t/// </summary>\n\tpublic IPagerItemContentGenerator Content { get; set; }\n\n\t/// <summary>\n\t///     Get or set a generator that used to generate the link of the pager item.\n\t/// </summary>\n\tpublic IPagerItemLinkGenerator Link { get; set; }\n\n\t/// <summary>\n\t///     Get or set the behavior for special pager items (non-standard number items) when it is in active. This property is\n\t///     not used for number items.\n\t/// </summary>\n\tpublic SpecialPagerItemInactiveBehavior? InactiveBehavior { get; set; }\n\n\t/// <summary>\n\t///     Get or set the active mode for the first and last button. This property is only used for first and last items.\n\t/// </summary>\n\tpublic FirstAndLastPagerItemActiveMode? ActiveMode { get; set; }\n\n\t/// <summary>\n\t///     Get or set additional information for the pager item.\n\t/// </summary>\n\tpublic IDictionary<string, string> AdditionalSettings { get; private set; } = new Dictionary<string, string>();\n\n\t/// <summary>\n\t///     Create a clone for the current object.\n\t/// </summary>\n\t/// <returns>A clone for current object.</returns>\n\tpublic PagerItemOptions Clone()\n\t{\n\t\t// Base clone\n\t\tvar result = (PagerItemOptions) MemberwiseClone();\n\t\t// Deep clone\n\t\tresult.AdditionalSettings = new Dictionary<string, string>(AdditionalSettings);\n\n\t\treturn result;\n\t}\n\n\t/// <summary>\n\t///     Merge a <see cref=\"PagerItemOptions\" /> into another base instace.\n\t/// </summary>\n\t/// <param name=\"baseOptions\">The base instance to be merged.</param>\n\t/// <param name=\"additionalOptions\">\n\t///     The additional instance, in which the non-null values will merge into the base\n\t///     instance.\n\t/// </param>\n\t/// <returns>A new <see cref=\"PagerItemOptions\" /> to represent as the merging result.</returns>\n\t/// <remarks>This method will not change either <paramref name=\"baseOptions\" /> or <paramref name=\"additionalOptions\" />.</remarks>\n\t[Pure]\n\t[NotNull]\n\tpublic static PagerItemOptions Merge(PagerItemOptions? baseOptions,\n\t\tPagerItemOptions? additionalOptions)\n\t{\n\t\t// If base options is not null, use its clone as base; otherwise, use a new empty item as base.\n\t\tvar result = baseOptions?.Clone() ?? new PagerItemOptions();\n\n\t\t// Return base item if no additional options are \n\t\tif (additionalOptions == null)\n\t\t\treturn result;\n\n\t\t// Merge attributes\n\t\tresult.Content = additionalOptions.Content ?? result.Content;\n\t\tresult.Link = additionalOptions.Link ?? result.Link;\n\t\tresult.ActiveMode = additionalOptions.ActiveMode ?? result.ActiveMode;\n\t\tresult.InactiveBehavior = additionalOptions.InactiveBehavior ?? result.InactiveBehavior;\n\n\t\t// Merge settings\n\t\tforeach (var i in additionalOptions.AdditionalSettings)\n\t\t\tresult.AdditionalSettings[i.Key] = i.Value;\n\n\t\treturn result;\n\t}\n\n\t/// <summary>\n\t///     Merge all <see cref=\"PagerItemOptions\" /> and get a final result.\n\t/// </summary>\n\t/// <param name=\"options\">\n\t///     A collection of all pager item options to be merged.The items at the leader will be merged\n\t///     earlier.\n\t/// </param>\n\t/// <returns>The final merged result.</returns>\n\t[NotNull]\n\tpublic static PagerItemOptions MergeAll(params PagerItemOptions?[] options)\n\t{\n\t\treturn MergeAll((IEnumerable<PagerItemOptions>) options);\n\t}\n\n\t/// <summary>\n\t///     Merge all <see cref=\"PagerItemOptions\" /> and get a final result.\n\t/// </summary>\n\t/// <param name=\"options\">\n\t///     A collection of all pager item options to be merged.The items at the leader will be merged\n\t///     earlier.\n\t/// </param>\n\t/// <returns>The final merged result.</returns>\n\t[NotNull]\n\tpublic static PagerItemOptions MergeAll(IEnumerable<PagerItemOptions?> options)\n\t{\n\t\treturn options.Aggregate(new PagerItemOptions(), Merge);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/PagerItemOptionsSet.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing JetBrains.Annotations;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Include all <see cref=\"PagerItemOptions\" /> for all types of pager items.\n/// </summary>\npublic class PagerItemOptionsSet\n{\n\t/// <summary>\n\t///     Initialize a new set with all empty settings.\n\t/// </summary>\n\tpublic PagerItemOptionsSet()\n\t{\n\t\tDefault = new PagerItemOptions();\n\t\tNormal = new PagerItemOptions();\n\t\tCurrent = new PagerItemOptions();\n\t\tOmitted = new PagerItemOptions();\n\t\tFirstPageButton = new PagerItemOptions();\n\t\tLastPageButton = new PagerItemOptions();\n\t\tPreviousPageButton = new PagerItemOptions();\n\t\tNextPageButton = new PagerItemOptions();\n\t}\n\n\t/// <summary>\n\t///     Get or set the default setting for all items.\n\t/// </summary>\n\t[NotNull]\n\tpublic PagerItemOptions Default { get; set; }\n\n\t/// <summary>\n\t///     Get or set the setting for normal number items. This setting will be merged with <see cref=\"Default\" />.\n\t/// </summary>\n\tpublic PagerItemOptions Normal { get; set; }\n\n\t/// <summary>\n\t///     Get or set the setting for current page. This setting will be merged with <see cref=\"Normal\" />.\n\t/// </summary>\n\tpublic PagerItemOptions Current { get; set; }\n\n\t/// <summary>\n\t///     Get or set the setting for omitted page. This setting will be merged with <see cref=\"Normal\" />.\n\t/// </summary>\n\tpublic PagerItemOptions Omitted { get; set; }\n\n\t/// <summary>\n\t///     Get or set the setting for \"Go to first page\" button. This setting will be merged with <see cref=\"Default\" />.\n\t/// </summary>\n\tpublic PagerItemOptions FirstPageButton { get; set; }\n\n\t/// <summary>\n\t///     Get or set the setting for \"Go to last page\" button. This setting will be merged with <see cref=\"Default\" />.\n\t/// </summary>\n\tpublic PagerItemOptions LastPageButton { get; set; }\n\n\t/// <summary>\n\t///     Get or set the setting for \"Go to next page\" button. This setting will be merged with <see cref=\"Default\" />.\n\t/// </summary>\n\tpublic PagerItemOptions NextPageButton { get; set; }\n\n\t/// <summary>\n\t///     Get or set the setting for \"Go to previous page\" button. This setting will be merged with <see cref=\"Default\" />.\n\t/// </summary>\n\tpublic PagerItemOptions PreviousPageButton { get; set; }\n\n\t/// <summary>\n\t///     Get the actual <see cref=\"PagerItemOptions\" /> for a specified <see cref=\"PagerLayoutElement\" />.\n\t/// </summary>\n\t/// <param name=\"layoutElement\">The <see cref=\"PagerLayoutElement\" /> which indicates the role of the pager item.</param>\n\t/// <returns>A <see cref=\"PagerItemOptions\" /> used for the current <see cref=\"PagerLayoutElement\" />.</returns>\n\t/// <remarks>\n\t///     The <see cref=\"PagerLayoutElement\" /> enum type cannot tell the difference between normal number pages,\n\t///     current page, and omitted pages. In order to get more accurate result, please use\n\t///     <see cref=\"GetOptionsFor(PagerItemType)\" /> or <see cref=\"GetOptionsFor(PagerItem)\" /> overloads.\n\t/// </remarks>\n\t/// <exception cref=\"ArgumentException\">The value of <paramref name=\"layoutElement\" /> is not a valid enum item.</exception>\n\tpublic PagerItemOptions GetOptionsFor(PagerLayoutElement layoutElement)\n\t{\n\t\tswitch (layoutElement)\n\t\t{\n\t\t\tcase PagerLayoutElement.Items:\n\t\t\t\treturn Normal;\n\t\t\tcase PagerLayoutElement.GoToFirstPageButton:\n\t\t\t\treturn FirstPageButton;\n\t\t\tcase PagerLayoutElement.GoToLastPageButton:\n\t\t\t\treturn LastPageButton;\n\t\t\tcase PagerLayoutElement.GoToPreviousPageButton:\n\t\t\t\treturn PreviousPageButton;\n\t\t\tcase PagerLayoutElement.GoToNextPageButton:\n\t\t\t\treturn NextPageButton;\n\t\t\tdefault:\n\t\t\t\tthrow new ArgumentException(\"The value of the argument is not a valid enum item.\", nameof(layoutElement));\n\t\t}\n\t}\n\n\t/// <summary>\n\t///     Get the actual <see cref=\"PagerItemOptions\" /> for a specified <see cref=\"PagerItemType\" />.\n\t/// </summary>\n\t/// <param name=\"itemType\">The <see cref=\"PagerItemType\" /> which indicates the role of the pager item.</param>\n\t/// <returns>A <see cref=\"PagerItemOptions\" /> used for the current <see cref=\"PagerLayoutElement\" />.</returns>\n\t/// <exception cref=\"ArgumentException\">The value of <paramref name=\"itemType\" /> is not a valid enum item.</exception>\n\tpublic PagerItemOptions GetOptionsFor(PagerItemType itemType)\n\t{\n\t\tswitch (itemType)\n\t\t{\n\t\t\tcase PagerItemType.Normal:\n\t\t\t\treturn Normal;\n\t\t\tcase PagerItemType.First:\n\t\t\t\treturn FirstPageButton;\n\t\t\tcase PagerItemType.Last:\n\t\t\t\treturn LastPageButton;\n\t\t\tcase PagerItemType.Previous:\n\t\t\t\treturn PreviousPageButton;\n\t\t\tcase PagerItemType.Next:\n\t\t\t\treturn NextPageButton;\n\t\t\tcase PagerItemType.Current:\n\t\t\t\treturn Current;\n\t\t\tcase PagerItemType.Omitted:\n\t\t\t\treturn Omitted;\n\t\t\tdefault:\n\t\t\t\tthrow new ArgumentException(\"The value of the argument is not a valid enum item.\", nameof(itemType));\n\t\t}\n\t}\n\n\t/// <summary>\n\t///     Get the actual <see cref=\"PagerItemOptions\" /> for a specified <see cref=\"PagerItemType\" />.\n\t/// </summary>\n\t/// <param name=\"item\">The actual <see cref=\"PagerItem\" /> instance.</param>\n\t/// <returns>A <see cref=\"PagerItemOptions\" /> used for the current <see cref=\"PagerLayoutElement\" />.</returns>\n\t/// <exception cref=\"ArgumentNullException\">The value of <paramref name=\"item\" /> is <c>null</c>.</exception>\n\tpublic PagerItemOptions GetOptionsFor([NotNull] PagerItem item)\n\t{\n\t\tif (item == null)\n\t\t\tthrow new ArgumentNullException(nameof(item));\n\n\t\treturn GetOptionsFor(item.ItemType);\n\t}\n\n\t/// <summary>\n\t///     Get the base item type of a <see cref=\"PagerItemType\" />. If no base itemType is appliable. This method will return\n\t///     <c>null</c>.\n\t/// </summary>\n\t/// <param name=\"itemType\">A <see cref=\"PagerItemType\" /> to be getting the base type.</param>\n\t/// <returns>The base type of <paramref name=\"itemType\" />, or <c>null</c> if no base itemType is appliable.</returns>\n\tpublic static PagerItemType? GetBaseItemType(PagerItemType itemType)\n\t{\n\t\tswitch (itemType)\n\t\t{\n\t\t\tcase PagerItemType.Current:\n\t\t\tcase PagerItemType.Omitted:\n\t\t\t\treturn PagerItemType.Normal;\n\t\t\tdefault:\n\t\t\t\treturn null;\n\t\t}\n\t}\n\n\t/// <summary>\n\t///     Get the final merged <see cref=\"PagerItemOptions\" /> for a specified <see cref=\"PagerItemType\" />.\n\t/// </summary>\n\t/// <param name=\"itemType\">The <see cref=\"PagerItemType\" /> which indicates the role of the pager item.</param>\n\t/// <returns>The final merged <see cref=\"PagerItemOptions\" /> instance.</returns>\n\t[NotNull]\n\tpublic PagerItemOptions GetMergedOptionsFor(PagerItemType itemType)\n\t{\n\t\t// Stack used to LIFO operations\n\t\tvar optionsStack = new Stack<PagerItemOptions>();\n\n\t\t// Recursively get base item\n\t\tPagerItemType? current = itemType;\n\t\twhile (current != null)\n\t\t{\n\t\t\toptionsStack.Push(GetOptionsFor(itemType));\n\t\t\tcurrent = GetBaseItemType(current.Value);\n\t\t}\n\n\t\t// Put default item\n\t\toptionsStack.Push(Default);\n\n\t\t// Merge all items\n\t\treturn PagerItemOptions.MergeAll(optionsStack);\n\t}\n\n\t/// <summary>\n\t///     Get the final merged <see cref=\"PagerItemOptions\" /> for a specified <see cref=\"PagerItem\" />.\n\t/// </summary>\n\t/// <param name=\"item\">The actual <see cref=\"PagerItem\" /> instance.</param>\n\t/// <returns>The final merged <see cref=\"PagerItemOptions\" /> instance.</returns>\n\t[NotNull]\n\tpublic PagerItemOptions GetMergedOptionsFor([NotNull] PagerItem item)\n\t{\n\t\tif (item == null)\n\t\t\tthrow new ArgumentNullException(nameof(item));\n\n\t\treturn GetMergedOptionsFor(item.ItemType);\n\t}\n\n\t/// <summary>\n\t///     Create a deep clone for this object.\n\t/// </summary>\n\t/// <returns>A clone of this object.</returns>\n\tpublic PagerItemOptionsSet Clone()\n\t{\n\t\tvar result = (PagerItemOptionsSet) MemberwiseClone();\n\n\t\tresult.Default = Default.Clone();\n\t\tresult.Current = Current.Clone();\n\t\tresult.Normal = Normal.Clone();\n\t\tresult.Omitted = Omitted.Clone();\n\t\tresult.FirstPageButton = FirstPageButton.Clone();\n\t\tresult.LastPageButton = LastPageButton.Clone();\n\t\tresult.PreviousPageButton = PreviousPageButton.Clone();\n\t\tresult.NextPageButton = NextPageButton.Clone();\n\n\t\treturn result;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/PagerLayout.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.ComponentModel;\nusing System.Linq;\nusing JetBrains.Annotations;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Represent as a layout descriptor for a pager.\n/// </summary>\n[TypeConverter(typeof(PagerLayoutConverter))]\npublic class PagerLayout\n{\n\t/// <summary>\n\t///     Initialize a new <see cref=\"PagerLayout\" /> with specified layout elements.\n\t/// </summary>\n\t/// <param name=\"elements\">The sequence of all layout elements.</param>\n\tpublic PagerLayout([NotNull] IEnumerable<PagerLayoutElement> elements)\n\t{\n\t\tif (elements == null)\n\t\t\tthrow new ArgumentNullException(nameof(elements));\n\n\t\tElements = new ReadOnlyCollection<PagerLayoutElement>(elements.ToArray());\n\t}\n\n\t/// <summary>\n\t///     Get the layout element sequence in this layout.\n\t/// </summary>\n\t[PublicAPI]\n\t[NotNull]\n\tpublic IEnumerable<PagerLayoutElement> Elements { get; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/PagerLayoutElement.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Define the layout element for a paged list pager.\n/// </summary>\npublic enum PagerLayoutElement\n{\n\t/// <summary>\n\t///     Main navigation area with page numbers.\n\t/// </summary>\n\tItems,\n\n\t/// <summary>\n\t///     \"Go to first page\" button.\n\t/// </summary>\n\tGoToFirstPageButton,\n\n\t/// <summary>\n\t///     \"Go to last page\" button.\n\t/// </summary>\n\tGoToLastPageButton,\n\n\t/// <summary>\n\t///     \"Go to previous page\" button.\n\t/// </summary>\n\tGoToPreviousPageButton,\n\n\t/// <summary>\n\t///     \"Go to next page\" button.\n\t/// </summary>\n\tGoToNextPageButton\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/PagerLayouts.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Linq;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Provide common layouts for paged list pager. This class is static.\n/// </summary>\n[PublicAPI]\npublic static class PagerLayouts\n{\n\t/// <summary>\n\t///     Generate a <see cref=\"PagerLayout\" /> for specified items.\n\t/// </summary>\n\t/// <param name=\"items\">Items list.</param>\n\t/// <returns>A <see cref=\"PagerLayout\" /> with specified layout.</returns>\n\tprivate static PagerLayout GenerateLayout(IEnumerable<PagerLayoutElement> items)\n\t{\n\t\treturn new PagerLayout(new ReadOnlyCollection<PagerLayoutElement>(items.ToArray()));\n\t}\n\n\t#region Shoutcuts\n\n\t/// <summary>\n\t///     The default layout. The navigation bar is in the center, surrounded by the next and previous button. The first and\n\t///     last button is in the most lateral.\n\t/// </summary>\n\tpublic static PagerLayout Default { get; } =\n\t\tGenerateLayout(new[]\n\t\t{\n\t\t\tPagerLayoutElement.GoToFirstPageButton,\n\t\t\tPagerLayoutElement.GoToPreviousPageButton,\n\t\t\tPagerLayoutElement.Items,\n\t\t\tPagerLayoutElement.GoToNextPageButton,\n\t\t\tPagerLayoutElement.GoToLastPageButton\n\t\t});\n\n\t/// <summary>\n\t///     The navigation bar is in the center, surrounded by the next and previous button. There is no first and last button,\n\t///     you may use <see cref=\"PagerOptions.PagerItemsForEndings\" /> to show the first and last paged directly.\n\t/// </summary>\n\tpublic static PagerLayout NoFirstAndLastButton { get; } =\n\t\tGenerateLayout(new[]\n\t\t{\n\t\t\tPagerLayoutElement.GoToPreviousPageButton,\n\t\t\tPagerLayoutElement.Items,\n\t\t\tPagerLayoutElement.GoToNextPageButton\n\t\t});\n\n\t/// <summary>\n\t///     Only navigation pager items are in the pager. No additional buttons are included.\n\t/// </summary>\n\tpublic static PagerLayout ItemsOnly { get; } = GenerateLayout(new[] {PagerLayoutElement.Items});\n\n\t/// <summary>\n\t///     Generate a custom layout using specified layout element sequence.\n\t/// </summary>\n\t/// <param name=\"items\">The sequence of the layout elements.</param>\n\t/// <returns>The <see cref=\"PagerLayout\" /> object.</returns>\n\tpublic static PagerLayout Custom(params PagerLayoutElement[] items)\n\t{\n\t\treturn GenerateLayout(items);\n\t}\n\n\t/// <summary>\n\t///     Generate a custom layout using specified layout element sequence.\n\t/// </summary>\n\t/// <param name=\"items\">The sequence of the layout elements.</param>\n\t/// <returns>The <see cref=\"PagerLayout\" /> object.</returns>\n\tpublic static PagerLayout Custom(IEnumerable<PagerLayoutElement> items)\n\t{\n\t\treturn GenerateLayout(items);\n\t}\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/PagerOptions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Contain all options which affect the behavior for a paged list pager.\n/// </summary>\npublic class PagerOptions\n{\n\t/// <summary>\n\t///     Get or set the additional settings dictionary for the pager. The key of the setting is case-insensitive.\n\t/// </summary>\n\tpublic Dictionary<string, string> AdditionalSettings { get; private set; } =\n\t\tnew Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);\n\n\t/// <summary>\n\t///     Get a set for all <see cref=\"PagerItemOptions\" /> for different pager items.\n\t/// </summary>\n\tpublic PagerItemOptionsSet ItemOptions { get; private set; } = new PagerItemOptionsSet();\n\n\t/// <summary>\n\t///     Get or set the layout for different pager elements.\n\t/// </summary>\n\t/// <remarks>\n\t///     If this property is <c>null</c>, <see cref=\"PagerLayouts.Default\" /> will be used.\n\t/// </remarks>\n\t/// <seealso cref=\"PagerLayouts\" />\n\tpublic PagerLayout Layout { get; set; }\n\n\t/// <summary>\n\t///     Get or set how many links for previous and next pages will appear near the current page.\n\t/// </summary>\n\t/// <remarks>\n\t///     The value 0 means no expand page links will be shown (only shows the current page). The value 1 means showing the\n\t///     first one next and previous pages near the current page., etc. You may use any negative value to force show all\n\t///     pages (note: show all pages may badly reduce your page performance).\n\t/// </remarks>\n\tpublic int ExpandPageItemsForCurrentPage { get; set; }\n\n\t/// <summary>\n\t///     Get or set how many links for the beginning and ending pages will appear in the pager.\n\t/// </summary>\n\t/// <remarks>\n\t///     The value 0 means no page links will be shown in the beginning and ending position or the pager. The value 1 means\n\t///     showing the first and last one page links, etc.\n\t/// </remarks>\n\tpublic int PagerItemsForEndings { get; set; }\n\n\t/// <summary>\n\t///     Get or set a value that indicates whether the pager should reverse the layout and all items (including the\n\t///     numbers).\n\t/// </summary>\n\tpublic bool IsReversed { get; set; }\n\n\t/// <summary>\n\t///     Get or set a value that indicates whether the pager should generate nothing if there is a single page in the\n\t///     source.\n\t/// </summary>\n\tpublic bool HideOnSinglePage { get; set; }\n\n\t/// <summary>\n\t///     Get a clone of this object.\n\t/// </summary>\n\t/// <returns>A clone of this object.</returns>\n\tpublic virtual PagerOptions Clone()\n\t{\n\t\tvar result = (PagerOptions) MemberwiseClone();\n\n\t\tresult.ItemOptions = ItemOptions.Clone();\n\t\tresult.Layout = new PagerLayout(Layout.Elements);\n\t\tresult.AdditionalSettings = new Dictionary<string, string>(AdditionalSettings);\n\n\t\treturn result;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/PagerOptionsExtensions.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Provide extension methods for pager options. This class is static.\n/// </summary>\npublic static class PagerOptionsExtensions\n{\n\t/// <summary>\n\t///     Configure a <see cref=\"PagerOptions\" /> instance to set all items as default setting.\n\t/// </summary>\n\t/// <param name=\"options\">The <see cref=\"PagerOptions\" /> to be set.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"options\" /> is <c>null</c>.</exception>\n\t[PublicAPI]\n\tpublic static void ConfigureDefault([NotNull] this PagerOptions options)\n\t{\n\t\t// Argument check\n\t\tif (options == null)\n\t\t\tthrow new ArgumentNullException(nameof(options));\n\n\t\toptions.Layout = PagerLayouts.Default;\n\t\toptions.ExpandPageItemsForCurrentPage = 2;\n\t\toptions.PagerItemsForEndings = 3;\n\n\t\toptions.ItemOptions.Default.Content = PagerItemContentGenerators.Default;\n\t\toptions.ItemOptions.Default.Link = PagerItemLinkGenerators.Default;\n\t\toptions.ItemOptions.Default.InactiveBehavior = SpecialPagerItemInactiveBehavior.Disable;\n\t\toptions.ItemOptions.Default.ActiveMode = FirstAndLastPagerItemActiveMode.NotInCurrentPage;\n\n\t\toptions.ItemOptions.PreviousPageButton.Content = PagerItemContentGenerators.Html(\"&lsaquo;\");\n\t\toptions.ItemOptions.NextPageButton.Content = PagerItemContentGenerators.Html(\"&rsaquo;\");\n\t\toptions.ItemOptions.FirstPageButton.Content = PagerItemContentGenerators.Html(\"&laquo;\");\n\t\toptions.ItemOptions.LastPageButton.Content = PagerItemContentGenerators.Html(\"&raquo;\");\n\n\t\toptions.ItemOptions.Omitted.Content = PagerItemContentGenerators.Text(\"...\");\n\t\toptions.ItemOptions.Omitted.Link = PagerItemLinkGenerators.Disabled;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/Sakura.AspNetCore.Mvc.PagedList.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n\t<PropertyGroup>\n\t\t<Description>This library provides data-paging functionality and pager UI for ASP.NET Core MVC projects.</Description>\n\t\t<AssemblyTitle>ASP.NET Core MVC Pager</AssemblyTitle>\n\t\t<VersionPrefix>2.0.11</VersionPrefix>\n\t\t<Authors>Iris Sakura</Authors>\n\t\t<TargetFrameworks>netstandard1.6;net451;netcoreapp3.0</TargetFrameworks>\n\t\t<AssemblyName>Sakura.AspNetCore.Mvc.PagedList</AssemblyName>\n\t\t<PackageId>Sakura.AspNetCore.Mvc.PagedList</PackageId>\n\t\t<PackageTags>ASP.NET;ASP.NETCore;MVC;MVCCore;Paging;Page;Pager;Bootstrap</PackageTags>\n\t\t<PackageReleaseNotes>Fix model source problem and allow 0 for total pages.</PackageReleaseNotes>\n\t\t<PackageProjectUrl>https://github.com/sgjsakura/AspNetCore</PackageProjectUrl>\n\t\t<PackageLicenseUrl></PackageLicenseUrl>\n\t\t<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>\n\t\t<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>\n\t\t<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>\n\t\t<RootNamespace>Sakura.AspNetCore.Mvc</RootNamespace>\n\t\t<GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n\t\t<RepositoryUrl>https://github.com/sgjsakura/AspNetCore.git</RepositoryUrl>\n\t\t<Version>3.0.2</Version>\n\t\t<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n\t</PropertyGroup>\n\n\t<ItemGroup>\n\t\t<ProjectReference Include=\"..\\Sakura.AspNetCore.PagedList.Abstractions\\Sakura.AspNetCore.PagedList.Abstractions.csproj\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t</ItemGroup>\n\n\t<PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n\t\t<GenerateDocumentationFile>true</GenerateDocumentationFile>\n\t</PropertyGroup>\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'netcoreapp3.0' \">\n\t\t<FrameworkReference Include=\"Microsoft.AspNetCore.App\" />\n\t</ItemGroup>\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' != 'netcoreapp3.0' \">\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Mvc.ViewFeatures\" Version=\"1.0.0\" />\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Razor.Runtime\" Version=\"1.0.0\" />\n\t</ItemGroup>\n\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'net451' \">\n\t\t<Reference Include=\"System\" />\n\t\t<Reference Include=\"Microsoft.CSharp\" />\n\t</ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/ServiceCollectionExtensions.cs",
    "content": "﻿using System;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.DependencyInjection.Extensions;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Add support for dependency injections. This class is static.\n/// </summary>\npublic static class ServiceCollectionExtensions\n{\n\t/// <summary>\n\t///     Add necessary services to generate bootstrap styled pager.\n\t/// </summary>\n\t/// <param name=\"services\">Service collections.</param>\n\tpublic static void AddBootstrapPagerGenerator(this IServiceCollection services)\n\t{\n\t\t// Depended services\n\t\tservices.TryAddTransient<IPagerListGenerator, DefaultPagerListGenerator>();\n\t\tservices.TryAddTransient<IPagerRenderingListGenerator, DefaultPagerRenderingListGenerator>();\n\t\tservices.TryAddTransient<IPagerHtmlGenerator, BootstrapPagerHtmlGenerator>();\n\n\t\t// Main service\n\t\tservices.TryAddTransient<IPagerGenerator, DefaultPagerGenerator>();\n\t}\n\n\n\t/// <summary>\n\t///     Add necessary services to generate bootstrap styled pager and configure additional options.\n\t/// </summary>\n\t/// <param name=\"services\">Service collections.</param>\n\t/// <param name=\"setupAction\">Additional setup action method.</param>\n\tpublic static void AddBootstrapPagerGenerator(this IServiceCollection services,\n\t\tAction<PagerOptions> setupAction)\n\t{\n\t\t// Depended services\n\t\tservices.AddBootstrapPagerGenerator();\n\n\n\t\t// Configuration\n\t\tif (setupAction != null)\n\t\t\tservices.Configure(setupAction);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/SpecialPagerItemInactiveBehavior.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Control how to handle inactive special pager items.\n/// </summary>\npublic enum SpecialPagerItemInactiveBehavior\n{\n\t/// <summary>\n\t///     When the item is inactive, disable it as shown in grey.\n\t/// </summary>\n\tDisable,\n\n\t/// <summary>\n\t///     When then item is inactive, remove it from the pager.\n\t/// </summary>\n\tHide\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/TagHelpers/AjaxOptions.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Provide the AJAX options.\n/// </summary>\npublic class AjaxOptions\n{\n\tpublic bool Enabled { get; set; }\n\n\tpublic AjaxUpdateMode UpdateMode { get; set; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/TagHelpers/AjaxUpdateMode.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Define the update mode for AJAX operation results.\n/// </summary>\npublic enum AjaxUpdateMode\n{\n    /// <summary>\n    ///     The fetched content will be placed before the target element.\n    /// </summary>\n    Before,\n\n    /// <summary>\n    ///     The fetched content will be placed after the target element.\n    /// </summary>\n    After,\n\n    /// <summary>\n    ///     The fetched content with replace the target element.\n    /// </summary>\n    ReplaceWith\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/TagHelpers/PagerTagHelper.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Options;\nusing Sakura.AspNetCore.Mvc.Internal;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Generate a pager in HTML page.\n/// </summary>\n[HtmlTargetElement(HtmlTagName, TagStructure = TagStructure.WithoutEndTag)]\n[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]\npublic class PagerTagHelper : TagHelper\n{\n\t#region Constuctors\n\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"PagerTagHelper\" />.\n\t/// </summary>\n\t/// <param name=\"defaultOptions\">Registered default options value.</param>\n\tpublic PagerTagHelper(IOptions<PagerOptions> defaultOptions)\n\t{\n\t\tDefaultOptions = defaultOptions.Value;\n\t}\n\n\t#endregion\n\n\t#region Registered Service\n\n\t/// <summary>\n\t///     Get the default options registered in the application.\n\t/// </summary>\n\tprivate PagerOptions DefaultOptions { get; }\n\n\t#endregion\n\n\t/// <summary>\n\t///     Get or set the current view context.\n\t/// </summary>\n\t[HtmlAttributeNotBound]\n\t[ViewContext]\n\tpublic ViewContext ViewContext { get; set; }\n\n\t/// <summary>\n\t/// Try to get the paging info from the binding source.\n\t/// </summary>\n\t/// <param name=\"source\">The paged list source.</param>\n\t/// <param name=\"currentPage\">The output current page info.</param>\n\t/// <param name=\"totalPage\">The output total page info.</param>\n\tprivate static void GetPagingInfoFromSource([NotNull]IPagedList source, out int currentPage, out int totalPage)\n\t{\n\t\tcurrentPage = source.PageIndex;\n\t\ttotalPage = source.TotalPage;\n\t}\n\n\t/// <summary>\n\t///     Try to get the page information from the current context.\n\t/// </summary>\n\t/// <param name=\"context\">The <see cref=\"TagHelperContext\" /> object.</param>\n\t/// <param name=\"currentPage\">Return the current page number.</param>\n\t/// <param name=\"totalPage\">Return the total page count.</param>\n\tprivate void GetPagingInfo(TagHelperContext context, out int currentPage, out int totalPage)\n\t{\n\t\tvar hasSource = context.AllAttributes.ContainsName(SourceAttributeName);\n\n\t\tvar hasCurrentPage = context.AllAttributes.ContainsName(CurrentPageAttributeName);\n\t\tvar hasTotalPage = context.AllAttributes.ContainsName(TotalPageAttributeName);\n\n\t\t// Check for source\n\t\tif (hasSource)\n\t\t{\n\t\t\tif (hasCurrentPage || hasTotalPage)\n\t\t\t{\n\t\t\t\tthrow new InvalidOperationException($\"The '{SourceAttributeName}' attribute cannot be used together with either the '{CurrentPageAttributeName}' or the '{TotalPageAttributeName}' attribute.\");\n\t\t\t}\n\n\t\t\tif (Source == null)\n\t\t\t{\n\t\t\t\tthrow new InvalidOperationException($\"The specified paging source came from the '{SourceAttributeName}' attribute cannot be null.\");\n\t\t\t}\n\n\t\t\tGetPagingInfoFromSource(Source, out currentPage, out totalPage);\n\t\t\treturn;\n\t\t}\n\n\t\t// No static page is specified, using inferred source.\n\t\tif (!hasCurrentPage && !hasTotalPage)\n\t\t{\n\t\t\tvar model = ViewContext.ViewData.Model;\n\n\t\t\tif (model is IPagedList source)\n\t\t\t{\n\t\t\t\tGetPagingInfoFromSource(source, out currentPage, out totalPage);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthrow new InvalidOperationException($\"The inferred paging source came from the view model is null or cannot be converted into '{typeof(IPagedList).FullName}' type.\");\n\t\t}\n\n\t\t// Not both set\n\t\tif (!hasTotalPage || !hasCurrentPage)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The '{TotalPageAttributeName}' and '{CurrentPageAttributeName}' attribute must both be set on the element.\");\n\n\t\t// Range check\n\t\tif (TotalPage < 0)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The value of '{TotalPageAttributeName}' attribute cannot be negative.\");\n\n\t\tif (TotalPage == 0 && CurrentPage != 1)\n\t\t{\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The value of '{CurrentPageAttributeName}' attribute must 1 (one) when the value of '{TotalPageAttributeName}' attribute is 0 (zero).\");\n\n\t\t}\n\n\t\tif (TotalPage != 0 && (CurrentPage <= 0 || CurrentPage > TotalPage))\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The value of '{CurrentPageAttributeName}' attribute must between 1 and the value of '{TotalPageAttributeName}' attribute.\");\n\n\n\t\t// Result\n\t\tcurrentPage = CurrentPage;\n\t\ttotalPage = TotalPage;\n\t}\n\n\t/// <summary>\n\t///     Get the real <see cref=\"IPagerGenerator\" /> used in this tag helper/\n\t/// </summary>\n\t/// <param name=\"context\">The tag helper context.</param>\n\t/// <returns>The used <see cref=\"IPagerGenerator\" /> instance.</returns>\n\t/// <exception cref=\"InvalidOperationException\">No valid <see cref=\"IPagerGenerator\" /> is specified.</exception>\n\tprivate IPagerGenerator GetRealGenerator(TagHelperContext context)\n\t{\n\t\tif (context.AllAttributes.ContainsName(GeneratorAttributeName))\n\t\t{\n\t\t\tif (Generator == null)\n\t\t\t\tthrow new InvalidOperationException(\n\t\t\t\t\t$\"The HTML generator specified using '{GeneratorAttributeName}' attribute cannot be null.\");\n\n\t\t\treturn Generator;\n\t\t}\n\n\t\t// Get System registered service.\n\t\tvar registeredGenerator = ViewContext.HttpContext.RequestServices.GetService<IPagerGenerator>();\n\n\t\tif (registeredGenerator == null)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"You must provide a pager HTML generator service object to generate the pager either through the '{GenerationModeAttributeName}' attribute or register it at application startup time.\");\n\n\t\treturn registeredGenerator;\n\t}\n\n\t/// <summary>\n\t///     Merge options set by the tag into the original options.\n\t/// </summary>\n\t/// <param name=\"original\">The original options.</param>\n\t/// <param name=\"context\">The tag helper context</param>\n\t/// <returns>\n\t///     A new <see cref=\"PagerOptions\" /> instance with copies the original settings and merge any shortcut properties\n\t///     on the tag.\n\t/// </returns>\n\tprivate PagerOptions MergeShortcutProperties(PagerOptions original, TagHelperContext context)\n\t{\n\t\tvar result = original.Clone();\n\n\t\t// Merge generators\n\t\tif (context.AllAttributes.ContainsName(ItemDefaultContentGeneratorAttributeName))\n\t\t\tresult.ItemOptions.Default.Content = ItemDefaultContentGenerator;\n\n\t\tif (context.AllAttributes.ContainsName(ItemDefaultLinkGeneratorAttributeName))\n\t\t\tresult.ItemOptions.Default.Link = ItemDefaultLinkGenerator;\n\n\t\t// Merge settings\n\t\tforeach (var i in Settings)\n\t\t\tresult.AdditionalSettings[i.Key] = i.Value;\n\n\t\treturn result;\n\t}\n\n\t/// <summary>\n\t///     Get the real <see cref=\"PagerOptions\" /> used in this tag helper/\n\t/// </summary>\n\t/// <param name=\"context\">The tag helper context.</param>\n\t/// <returns>The used <see cref=\"PagerOptions\" /> instance.</returns>\n\t/// <exception cref=\"InvalidOperationException\">No valid <see cref=\"PagerOptions\" /> is specified.</exception>\n\tprivate PagerOptions GetRealOptions(TagHelperContext context)\n\t{\n\t\tPagerOptions result;\n\n\t\tif (context.AllAttributes.ContainsName(OptionsAttributeName))\n\t\t{\n\t\t\tif (Options == null)\n\t\t\t\tthrow new InvalidOperationException(\n\t\t\t\t\t$\"The pager options specified using '{OptionsAttributeName}' attribute cannot be null.\");\n\n\t\t\tresult = Options;\n\t\t}\n\t\telse if (DefaultOptions == null)\n\t\t{\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"You must provide a pager options either through the '{OptionsAttributeName}' attribute or register it at application startup time.\");\n\t\t}\n\t\telse\n\t\t{\n\t\t\tresult = DefaultOptions;\n\t\t}\n\n\t\t// Merge result and return\n\t\treturn MergeShortcutProperties(result, context);\n\t}\n\n\n\t/// <summary>\n\t///     Handle the options to avoid it is in an invalid state.\n\t/// </summary>\n\t/// <param name=\"options\">The options to be checked.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"options\" /> is <c>null</c>.</exception>\n\t/// <exception cref=\"InvalidOperationException\">The <paramref name=\"options\" />is in an invalid state.</exception>\n\tprivate void CheckOptions([NotNull] PagerOptions options)\n\t{\n\t\tif (options == null)\n\t\t\tthrow new ArgumentNullException(nameof(options));\n\n\t\tif (options.Layout == null)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The value of the '{nameof(PagerOptions.Layout)}' property in the pager options cannot be null.\");\n\n\t\tif (options.PagerItemsForEndings < 0)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The value of the '{nameof(PagerOptions.PagerItemsForEndings)}' property in the pager options cannot be negative.\");\n\n\t\tif (options.ExpandPageItemsForCurrentPage < 0)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The value of the '{nameof(PagerOptions.ExpandPageItemsForCurrentPage)}' property in the pager options cannot be negative.\");\n\n\t\tif (options.Layout == null)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The value of the '{nameof(PagerOptions.Layout)}' property cannot be null.\");\n\t}\n\n\t/// <summary>\n\t///     Synchronously executes the <see cref=\"TagHelper\" /> with the given <paramref name=\"context\" /> and\n\t///     <paramref name=\"output\" />.\n\t/// </summary>\n\t/// <param name=\"context\">Contains information associated with the current HTML tag.</param>\n\t/// <param name=\"output\">A stateful HTML element used to generate an HTML tag.</param>\n\tpublic override void Process(TagHelperContext context, TagHelperOutput output)\n\t{\n\t\t// State check\n\t\tif (output.TagMode != TagMode.SelfClosing)\n\t\t\tthrow new InvalidOperationException($\"The '{HtmlTagName}' tag must use self closing mode.\");\n\n\t\t// Get information and build up context\n\t\tvar generator = GetRealGenerator(context);\n\t\tvar options = GetRealOptions(context);\n\t\tCheckOptions(options);\n\t\tGetPagingInfo(context, out var currentPage, out var totalPage);\n\n\t\tvar pagerContext = new PagerGenerationContext(currentPage, totalPage, options, ViewContext, GenerationMode);\n\n\t\t// Generate result\n\t\tvar result = generator.GeneratePager(pagerContext);\n\n\t\t// Disable default element output\n\t\toutput.SuppressOutput();\n\n\t\t// Append pager content\n\t\toutput.PostElement.AppendHtml(result);\n\t}\n\n\t#region Order\n\n\t/// <summary>\n\t///     Get the default <see cref=\"Order\" /> value for this tag helper. This field is constant.\n\t/// </summary>\n\tpublic const int DefaultOrder = 0;\n\n\t/// <inheritdoc />\n\tpublic override int Order { get; } = DefaultOrder;\n\n\t#endregion\n\n\t#region Html Constants\n\n\t/// <summary>\n\t///     Get the HTML tag name associated with this tag helper. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string HtmlTagName = \"pager\";\n\n\t/// <summary>\n\t///     Get the HTML attribute name for <see cref=\"Options\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string OptionsAttributeName = \"options\";\n\n\t/// <summary>\n\t///     Get the HTML attribute name for <see cref=\"CurrentPage\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string CurrentPageAttributeName = \"current-page\";\n\n\t/// <summary>\n\t///     Get the HTML attribute name for <see cref=\"TotalPage\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string TotalPageAttributeName = \"total-page\";\n\n\t/// <summary>\n\t///     Get the HTML attribute name for <see cref=\"Generator\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string GeneratorAttributeName = \"generator\";\n\n\t/// <summary>\n\t///     Get the HTML attribute name for <see cref=\"GenerationMode\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string GenerationModeAttributeName = \"generation-mode\";\n\n\t/// <summary>\n\t///     Get the HTML attribute perfix for <see cref=\"Settings\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string SettingsAttributePerfix = \"setting-\";\n\n\t/// <summary>\n\t///     Get the HTML attribute name for <see cref=\"Settings\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string SettingsAttributeName = \"settings\";\n\n\t/// <summary>\n\t///     Get the HTML attribute name for <see cref=\"ItemDefaultContentGenerator\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string ItemDefaultContentGeneratorAttributeName = \"item-default-content\";\n\n\t/// <summary>\n\t///     Get the HTML attribute name for <see cref=\"ItemDefaultLinkGenerator\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string ItemDefaultLinkGeneratorAttributeName = \"item-default-link\";\n\n\t/// <summary>\n\t///     Get the HTML attribute name for <see cref=\"Source\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI]\n\tpublic const string SourceAttributeName = \"source\";\n\n\t#endregion\n\n\t#region Html Bounded Properties\n\n\t#region Core Properties\n\n\t/// <summary>\n\t///     Get or set the options for the pager.\n\t/// </summary>\n\t[HtmlAttributeName(OptionsAttributeName)]\n\tpublic PagerOptions Options { get; set; }\n\n\t/// <summary>\n\t///     Get or set the current page number or the pager. This property must use together with <see cref=\"TotalPage\" />, and\n\t///     <see cref=\"Source\" /> property must keep unset.\n\t/// </summary>\n\t[HtmlAttributeName(CurrentPageAttributeName)]\n\tpublic int CurrentPage { get; set; }\n\n\t/// <summary>\n\t///     Get or set the total page count or the pager. This property must use together with <see cref=\"CurrentPage\" />, and\n\t///     <see cref=\"Source\" /> property must keep unset.\n\t/// </summary>\n\t[HtmlAttributeName(TotalPageAttributeName)]\n\tpublic int TotalPage { get; set; }\n\n\t/// <summary>\n\t///     Get or set the <see cref=\"IPagerGenerator\" /> used to generate the pager.\n\t/// </summary>\n\t[HtmlAttributeName(GeneratorAttributeName)]\n\tpublic IPagerGenerator Generator { get; set; }\n\n\n\t/// <summary>\n\t///     Get or set the generation mode for the pager.\n\t/// </summary>\n\t[HtmlAttributeName(GenerationModeAttributeName)]\n\tpublic PagerGenerationMode GenerationMode { get; set; }\n\n\t/// <summary>\n\t/// Get or set the source for the pager.\n\t/// </summary>\n\t[HtmlAttributeName(SourceAttributeName)]\n\tpublic IPagedList Source { get; set; }\n\n\t#endregion\n\n\t#region Shortcut Properties\n\n\t/// <summary>\n\t///     Get or set the additional settings for the current pager. This property is a shortcut property for setting the\n\t///     <see cref=\"PagerOptions.AdditionalSettings\" /> for the <see cref=\"Options\" />.\n\t/// </summary>\n\t[HtmlAttributeName(SettingsAttributeName, DictionaryAttributePrefix = SettingsAttributePerfix)]\n\tpublic Dictionary<string, string> Settings { get; set; } = new Dictionary<string, string>();\n\n\t/// <summary>\n\t///     Get or set the default item content generator for the current pager. This property is a shortcut property for the\n\t///     <see cref=\"Options\" />.\n\t/// </summary>\n\t[HtmlAttributeName(ItemDefaultContentGeneratorAttributeName)]\n\tpublic IPagerItemContentGenerator ItemDefaultContentGenerator { get; set; }\n\n\t/// <summary>\n\t///     Get or set the item link generator for the current pager. This property is a shortcut property for the\n\t///     <see cref=\"Options\" />.\n\t/// </summary>\n\t[HtmlAttributeName(ItemDefaultLinkGeneratorAttributeName)]\n\tpublic IPagerItemLinkGenerator ItemDefaultLinkGenerator { get; set; }\n\n\t#endregion\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.PagedList/TagHelpers/TagHelperUtility.cs",
    "content": "﻿using System;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n/// Provide Utility methods for tag helpers. This class is static.\n/// </summary>\ninternal static class TagHelperUtility\n{\n\t/// <summary>\n\t/// Check the <see cref=\"TagHelperContext\"/> to ensure the specified attribute is not set before.\n\t/// </summary>\n\t/// <param name=\"context\">The <see cref=\"TagHelperContext\"/> instance.</param>\n\t/// <param name=\"attributeName\">The attribute name be checking.</param>\n\tpublic static void CheckAttributeConflicting(this TagHelperContext context, string attributeName)\n\t{\n\t\tif (context.AllAttributes.ContainsName(attributeName))\n\t\t{\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The '{attributeName}' attribute has already set from code explicitly or from another tag helper.\");\n\t\t}\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/AuthorizeAttributeTagHelper.cs",
    "content": "﻿using System.Security.Claims;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Provide authorization-based HTML generation control on existing HTML elements.\n/// </summary>\n[HtmlTargetElement(\"*\", Attributes = PolicyAttributeName)]\npublic class AuthorizeAttributeTagHelper : AuthorizeTagHelperBase\n{\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"AuthorizeTagHelper\" />.\n\t/// </summary>\n\t/// <param name=\"authorizationService\">The <see cref=\"IAuthorizationService\" /> instance.</param>\n\t[UsedImplicitly]\n\tpublic AuthorizeAttributeTagHelper(IAuthorizationService authorizationService)\n\t\t: base(authorizationService)\n\t{\n\t}\n\n\t/// <summary>\n\t///     Get or set the policy name which should be melted.\n\t/// </summary>\n\t[HtmlAttributeName(PolicyAttributeName)]\n\t[UsedImplicitly(ImplicitUseKindFlags.Assign)]\n\tpublic override string Policy { get; set; }\n\n\t/// <summary>\n\t///     Get or set the additional resource used to authorization check if necessary.\n\t/// </summary>\n\t/// <seealso cref=\"IAuthorizationService.AuthorizeAsync(ClaimsPrincipal, object, string)\" />\n\t[HtmlAttributeName(ResourceAttributeName)]\n\t[UsedImplicitly(ImplicitUseKindFlags.Assign)]\n\tpublic override object Resource { get; set; }\n\n\t#region Tag Helper Constants\n\n\t/// <summary>\n\t///     Get the HTML attribute name associated with <see cref=\"Policy\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string PolicyAttributeName = \"asp-authorize-policy\";\n\n\t/// <summary>\n\t///     Get the HTML attribute name associated with <see cref=\"Resource\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string ResourceAttributeName = \"asp-authorize-resource\";\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/AuthorizeTagHelper.cs",
    "content": "﻿using System.Security.Claims;\nusing System.Threading.Tasks;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Provide authorization-based HTML generation control.\n/// </summary>\n[HtmlTargetElement(ElementName)]\npublic class AuthorizeTagHelper : AuthorizeTagHelperBase\n{\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"AuthorizeTagHelper\" />.\n\t/// </summary>\n\t/// <param name=\"authorizationService\">The <see cref=\"IAuthorizationService\" /> instance.</param>\n\t[UsedImplicitly]\n\tpublic AuthorizeTagHelper(IAuthorizationService authorizationService)\n\t\t: base(authorizationService)\n\t{\n\t}\n\n\t/// <summary>\n\t///     Get or set the policy name which should be melt.\n\t/// </summary>\n\t[HtmlAttributeName(PolicyAttributeName)]\n\t[UsedImplicitly(ImplicitUseKindFlags.Assign)]\n\tpublic override string Policy { get; set; }\n\n\t/// <summary>\n\t///     Get or set the additional resource used to authoirzation check if necessary.\n\t/// </summary>\n\t/// <seealso cref=\"IAuthorizationService.AuthorizeAsync(ClaimsPrincipal, object, string)\" />\n\t[HtmlAttributeName(ResourceAttributeName)]\n\t[UsedImplicitly(ImplicitUseKindFlags.Assign)]\n\tpublic override object Resource { get; set; }\n\n\t#region Overrides of TagHelper\n\n\t#region Overrides of TagHelper\n\n\t/// <inheritdoc />\n\tpublic override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)\n\t{\n\t\t// Do not output this element itself.\n\t\toutput.TagName = null;\n\n\t\treturn base.ProcessAsync(context, output);\n\t}\n\n\t#endregion\n\n\t#endregion\n\n\t#region Tag Helper Constants\n\n\t/// <summary>\n\t///     Get the target HTML element name for this tag helper. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string ElementName = \"authorize\";\n\n\t/// <summary>\n\t///     Get the HTML attribute name associated with <see cref=\"Policy\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string PolicyAttributeName = \"policy\";\n\n\n\t/// <summary>\n\t///     Get the HTML attribute name associated with <see cref=\"Resource\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string ResourceAttributeName = \"resource\";\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/AuthorizeTagHelperBase.cs",
    "content": "﻿using System.Security.Claims;\nusing System.Threading.Tasks;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <inheritdoc />\n/// <summary>\n///     Provide common implementations for both <see cref=\"T:Sakura.AspNetCore.Mvc.TagHelpers.AuthorizeTagHelper\" /> and\n///     <see cref=\"T:Sakura.AspNetCore.Mvc.TagHelpers.AuthorizeAttributeTagHelper\" />.\n/// </summary>\npublic abstract class AuthorizeTagHelperBase : TagHelper\n{\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"AuthorizeTagHelperBase\" />.\n\t/// </summary>\n\t/// <param name=\"authorizationService\">The <see cref=\"IAuthorizationService\" /> instance.</param>\n\tprotected AuthorizeTagHelperBase(IAuthorizationService authorizationService)\n\t{\n\t\tAuthorizationService = authorizationService;\n\t}\n\n\tprivate IAuthorizationService AuthorizationService { get; }\n\n\t/// <summary>\n\t///     Get the authorization policy name which current user must melt in order to see the content.\n\t/// </summary>\n\tpublic abstract string Policy { get; set; }\n\n\t/// <summary>\n\t///     Get or set the additional resource used to authorization check if necessary.\n\t/// </summary>\n\t/// <seealso cref=\"IAuthorizationService.AuthorizeAsync(ClaimsPrincipal, object, string)\" />\n\tpublic abstract object Resource { get; set; }\n\n\t/// <summary>\n\t///     Get or set the view context for this tag helper.\n\t/// </summary>\n\t[HtmlAttributeNotBound]\n\t[ViewContext]\n\t[UsedImplicitly(ImplicitUseKindFlags.Assign)]\n\tpublic ViewContext ViewContext { get; set; }\n\n#if NETSTANDARD2_0 || NETCOREAPP2_1 || NETCOREAPP3_0\n\t\t/// <summary>\n\t\t///     Get a value that indicates if the current user is authorized.\n\t\t/// </summary>\n\t\t/// <returns>If the current user is authorized, returns <c>true</c>; otherwise, returns <c>false</c>.</returns>\n\t\tprotected async Task<bool> IsAuthorizedAsync()\n\t\t{\n\t\t\tvar result = await AuthorizationService.AuthorizeAsync(ViewContext.HttpContext.User, Resource, Policy);\n\t\t\treturn result.Succeeded;\n\t\t}\n#else\n\t/// <summary>\n\t///     Get a value that indicates if the current user is authorized.\n\t/// </summary>\n\t/// <returns>If the current user is authorized, returns <c>true</c>; otherwise, returns <c>false</c>.</returns>\n\tprotected Task<bool> IsAuthorizedAsync()\n\t{\n\t\treturn AuthorizationService.AuthorizeAsync(ViewContext.HttpContext.User, Resource, Policy);\n\t}\n\n#endif\n\n\t#region Overrides of TagHelper\n\n\t/// <inheritdoc />\n\tpublic override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)\n\t{\n\t\t// If the authroization rule is not melt, do not output anything.\n\t\tif (!await IsAuthorizedAsync())\n\t\t\toutput.SuppressOutput();\n\t}\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/ConditionalClassTagHelper.cs",
    "content": "﻿using JetBrains.Annotations;\n\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\nusing System.Collections.Generic;\nusing System.Linq;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Add CSS class name to the element conditionally.\n/// </summary>\n[HtmlTargetElement(Attributes = ConditionalClassPrefix + \"*\")]\n[HtmlTargetElement(Attributes = AllConditionalClassAttributeName)]\n[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]\npublic class ConditionalClassTagHelper : TagHelper\n{\n\t/// <summary>\n\t///     The prefix for all attributes will be stored in <see cref=\"ConditionalClasses\" /> dictionary. This field is\n\t///     constant.\n\t/// </summary>\n\t[PublicAPI] public const string ConditionalClassPrefix = \"asp-conditional-class-\";\n\n\n\t/// <summary>\n\t///     The attribute name for setting the entire <see cref=\"ConditionalClasses\" /> dictionary. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string AllConditionalClassAttributeName = \"asp-conditional-classes\";\n\n\t/// <summary>\n\t///     Get or set the dictionary that stores all the conditional class definitions.\n\t/// </summary>\n\t[UsedImplicitly(ImplicitUseKindFlags.Assign | ImplicitUseKindFlags.Access)]\n\t[HtmlAttributeName(AllConditionalClassAttributeName, DictionaryAttributePrefix = ConditionalClassPrefix)]\n\tpublic IDictionary<string, bool> ConditionalClasses { get; set; } = new Dictionary<string, bool>();\n\n\n\t/// <summary>\n\t///     Synchronously executes the <see cref=\"TagHelper\" /> with the given <paramref name=\"context\" /> and\n\t///     <paramref name=\"output\" />.\n\t/// </summary>\n\t/// <param name=\"context\">Contains information associated with the current HTML tag.</param>\n\t/// <param name=\"output\">A stateful HTML element used to generate an HTML tag.</param>\n\tpublic override void Process(TagHelperContext context, TagHelperOutput output)\n\t{\n\t\t// Get all items\n\t\tvar allItems = (from i in ConditionalClasses\n\t\t\twhere i.Value\n\t\t\tselect i.Key).ToList();\n\n\t\t// No actions if no items\n\t\tif (allItems.Count == 0)\n\t\t\treturn;\n\n\t\t// The original class attribute\n\t\tvar classAttr = output.Attributes[\"class\"];\n\n\t\t// If class attribute exists, merge it\n\t\t// Original value of the class attribute\n\t\tvar originalClass = classAttr?.Value?.ToString();\n\n\t\t// append the original class value if not null\n\t\tif (!string.IsNullOrWhiteSpace(originalClass))\n\t\t\tallItems.Add(originalClass);\n\n\t\t// merge to the final class value\n\t\tvar finalClass = string.Join(\" \", allItems);\n\n\n\t\t// Replace original value\n\t\toutput.Attributes.SetAttribute(\"class\", finalClass);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/DisplayTextTagHelper.cs",
    "content": "﻿using System;\nusing System.Reflection;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Localization;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Provide general ability for extract data annotation text from property definition for a model expression.\n/// </summary>\n/// <inheritdoc />\n[HtmlTargetElement(Attributes = TargetElementName)]\npublic class DisplayTextTagHelper : TagHelper\n{\n\tpublic DisplayTextTagHelper(IServiceProvider serviceProvider)\n\t{\n\t\tStringLocalizerFactory = serviceProvider.GetService<IStringLocalizerFactory>();\n\t}\n\n\t#region Services\n\n\t/// <summary>\n\t///     Get the service used to create <see cref=\"IStringLocalizer\" /> services.\n\t/// </summary>\n\tprivate IStringLocalizerFactory StringLocalizerFactory { get; }\n\n\t#endregion\n\n\t/// <summary>\n\t///     Get or set the related model expression for the text element.\n\t/// </summary>\n\t[HtmlAttributeName(ForHtmlAttributeName)]\n\t[UsedImplicitly(ImplicitUseKindFlags.Assign)]\n\tpublic ModelExpression For { get; set; }\n\n\t/// <summary>\n\t///     Get or set the text source used to generated the inner text.\n\t/// </summary>\n\t[HtmlAttributeName(TextSourceHtmlAttributeName)]\n\t[UsedImplicitly(ImplicitUseKindFlags.Assign)]\n\tpublic TextSource TextSource { get; set; } = TextSource.MemberNameOnly;\n\n\tpublic override void Process(TagHelperContext context, TagHelperOutput output)\n\t{\n\t\tif (output.TagMode != TagMode.SelfClosing)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The '{TargetElementName}' element can only use the self closing mode.\");\n\n\n\t\t// get property definition\n\t\tvar member = For.Metadata.ContainerType.GetProperty(For.Metadata.PropertyName,\n\t\t\tBindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance);\n\n\t\t// get the localizer for the container type.\n\t\tvar localizer = StringLocalizerFactory?.Create(For.Metadata.ContainerType);\n\n\t\tvar memberText = member.GetTextForMember(TextSource);\n\t\tvar realText = localizer.TryGetLocalizedText(memberText);\n\n\t\toutput.TagName = null;\n\t\toutput.Content.SetContent(realText);\n\t}\n\n\t#region TagHelper Constants\n\n\t/// <summary>\n\t///     The HTML target element name for this tag helper. This field is constant.\n\t/// </summary>\n\tpublic const string TargetElementName = \"display-text\";\n\n\t/// <summary>\n\t///     The HTML attribute related with the <see cref=\"For\" /> property. This field is constant.\n\t/// </summary>\n\tpublic const string ForHtmlAttributeName = \"for\";\n\n\t/// <summary>\n\t///     The HTML attribute related with the <see cref=\"TextSource\" /> property. This field is constant.\n\t/// </summary>\n\tpublic const string TextSourceHtmlAttributeName = \"text-source\";\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/EnumForSelectTagHelper.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.AspNetCore.Mvc.TagHelpers;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\nusing Microsoft.Extensions.Localization;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Provide select options geneartions for a model expression with enum type.\n/// </summary>\n[HtmlTargetElement(\"select\", Attributes = EnumForAttributeName)]\npublic class EnumForSelectTagHelper : EnumSelectTagHelper\n{\n\t/// <summary>\n\t///     Initialize an new instance with required services.\n\t/// </summary>\n\t/// <param name=\"generator\">The HTML generator service.</param>\n\t/// <param name=\"serviceProvider\">The service instance of <see cref=\"IServiceProvider\" />.</param>\n\tpublic EnumForSelectTagHelper(IHtmlGenerator generator, IServiceProvider serviceProvider)\n\t\t: base(serviceProvider)\n\t{\n\t\tGenerator = generator;\n\t}\n\n\t/// <summary>\n\t///     Get or set the view context object.\n\t/// </summary>\n\t[HtmlAttributeNotBound]\n\t[ViewContext] public ViewContext ViewContext { get; set; }\n\n\t/// <summary>\n\t///     Get the Html Generator service object.\n\t/// </summary>\n\t[PublicAPI]\n\tprotected IHtmlGenerator Generator { get; }\n\n\t/// <summary>\n\t///     Return the actual enum type for generating the list.\n\t/// </summary>\n\t/// <returns></returns>\n\tprotected override Type GetEnumType()\n\t{\n\t\tif (EnumFor == null)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The expression for `{EnumForAttributeName}` attribute cannot be null.\");\n\n\t\tif (!EnumFor.Metadata.IsEnum)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The expression type for `{EnumForAttributeName}` attribute is not a valid enum type nor a nullable enum type.\");\n\n\t\treturn EnumFor.Metadata.UnderlyingOrModelType;\n\t}\n\n\t/// <summary>\n\t///     Synchronously executes the <see cref=\"TagHelper\" /> with the given <paramref name=\"context\" /> and\n\t///     <paramref name=\"output\" />.\n\t/// </summary>\n\t/// <param name=\"context\">Contains information associated with the current HTML tag.</param>\n\t/// <param name=\"output\">A stateful HTML element used to generate an HTML tag.</param>\n\tpublic override void Process(TagHelperContext context, TagHelperOutput output)\n\t{\n\t\t// Generate the list.\n\t\tvar selectListItems = GenerateListForEnumType();\n\n\t\t// Get current value for model expression.\n\t\tvar currentValues = Generator.GetCurrentValues(ViewContext, EnumFor.ModelExplorer, EnumFor.Name, false);\n\n\n\t\tvar tag = Generator.GenerateSelect(ViewContext, EnumFor?.ModelExplorer, null, EnumFor?.Name,\n\t\t\tselectListItems,\n\t\t\tcurrentValues, false,\n\t\t\tnull);\n\n\t\tif (tag != null)\n\t\t{\n\t\t\toutput.MergeAttributes(tag);\n\t\t\toutput.PostContent.AppendHtml(tag.InnerHtml);\n\t\t}\n\t}\n\n\t#region Constants for attributes\n\n\t/// <summary>\n\t///     Get the HTML name associated with the <see cref=\"EnumFor\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string EnumForAttributeName = \"asp-enum-for\";\n\n\t/// <summary>\n\t///     Get or set the model expression used to determine the enum type used to generate the items.\n\t/// </summary>\n\t[HtmlAttributeName(EnumForAttributeName)]\n\tpublic ModelExpression EnumFor { get; set; }\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/EnumOptionValueSource.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Define the value source for <see cref=\"EnumSelectTagHelper\" />.\n/// </summary>\n/// <seealso cref=\"EnumSelectTagHelper\" />\npublic enum EnumOptionValueSource\n{\n\t/// <summary>\n\t///     Use the name of enum item as the value source.\n\t/// </summary>\n\tName = 0,\n\n\t/// <summary>\n\t///     Use the integer value of the enum item as the value source.\n\t/// </summary>\n\tValue\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/EnumSelectTagHelper.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Localization;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <inheritdoc />\n/// <summary>\n///     Generate the item list for a \"select\" element with all items defined in an enum type.\n/// </summary>\npublic abstract class EnumSelectTagHelper : TagHelper\n{\n\t#region Constructor\n\n\t/// <summary>\n\t///     Create a new instance of <see cref=\"EnumSelectTagHelper\" />.\n\t/// </summary>\n\t/// <param name=\"serviceProvider\">The <see cref=\"IServiceProvider\"/> instance used to find dependent services.</param>\n\tprotected EnumSelectTagHelper(IServiceProvider serviceProvider)\n\t{\n\t\tStringLocalizerFactory = serviceProvider.GetService<IStringLocalizerFactory>();\n\t}\n\n\t#endregion\n\n\t/// <summary>\n\t/// The serivce used to provide text localization.\n\t/// </summary>\n\tprivate IStringLocalizerFactory StringLocalizerFactory { get; }\n\n\t#region Services\n\n\t/// <summary>\n\t///     Get the <see cref=\"IStringLocalizer\" /> object used for localization.\n\t/// </summary>\n\tprotected IStringLocalizer EnumTypeStringLocalizer => StringLocalizerFactory?.Create(GetEnumType());\n\n\t#endregion\n\n\n\t#region Abstract Methods\n\n\t/// <summary>\n\t///     When derived, return the actual enum type for generating the list.\n\t/// </summary>\n\t/// <returns></returns>\n\tprotected abstract Type GetEnumType();\n\n\t#endregion\n\n\t/// <summary>\n\t///     Generate a list of <see cref=\"SelectListItem\" /> for a specified enum type.\n\t/// </summary>\n\t/// <returns>The generated list.</returns>\n\tprotected virtual IEnumerable<SelectListItem> GenerateListForEnumType()\n\t{\n\t\treturn GetEnumType()\n\t\t\t.GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static)\n\t\t\t.Select(GetItemForMember);\n\t}\n\n\t#region Constants for attributes\n\n\t/// <summary>\n\t///     Get the HTML name associated with the <see cref=\"TextSource\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string TextSourceAttributeName = \"asp-text-source\";\n\n\t/// <summary>\n\t///     Get the HTML name associated with the <see cref=\"ValueSource\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string ValueSourceAttributeName = \"asp-value-source\";\n\n\t/// <summary>\n\t///     Get or set the text source for the generated options. The default value of this property is\n\t///     <see cref=\"TagHelpers.TextSource.MemberNameOnly\" />.\n\t/// </summary>\n\t[HtmlAttributeName(TextSourceAttributeName)]\n\tpublic TextSource TextSource { get; set; } = TextSource.MemberNameOnly;\n\n\t/// <summary>\n\t///     Get or set the value source for the generated options. The default value of this property is\n\t///     <see cref=\"EnumOptionValueSource.Name\" />.\n\t/// </summary>\n\t[HtmlAttributeName(ValueSourceAttributeName)]\n\tpublic EnumOptionValueSource ValueSource { get; set; } = EnumOptionValueSource.Name;\n\n\t#endregion\n\n\t#region Helper Method\n\n\t/// <summary>\n\t///     Get the text of the option associated with the specified enum item.\n\t/// </summary>\n\t/// <param name=\"memberInfo\">The <see cref=\"MemberInfo\" /> object represents as the enum item.</param>\n\t/// <returns><paramref name=\"memberInfo\" />The option text associated with <paramref name=\"memberInfo\" />.</returns>\n\tprotected virtual string GetTextForMember(MemberInfo memberInfo)\n\t{\n\t\tvar memberText = memberInfo.GetTextForMember(TextSource);\n\t\treturn EnumTypeStringLocalizer.TryGetLocalizedText(memberText);\n\t}\n\n\t/// <summary>\n\t///     Get the value of the option associated with the specified enum item.\n\t/// </summary>\n\t/// <param name=\"memberInfo\">The <see cref=\"MemberInfo\" /> object represents as the enum item.</param>\n\t/// <returns><paramref name=\"memberInfo\" />The option value associated with <paramref name=\"memberInfo\" />.</returns>\n\tprotected virtual string GetValueForMember(MemberInfo memberInfo)\n\t{\n\t\treturn memberInfo.GetValueForEnumMember(ValueSource);\n\t}\n\n\t/// <summary>\n\t///     Generate a <see cref=\"SelectListItem\" /> for a specified <see cref=\"MemberInfo\" />.\n\t/// </summary>\n\t/// <param name=\"memberInfo\">The <see cref=\"MemberInfo\" /> object represents as the enum item.</param>\n\t/// <returns></returns>\n\tprotected virtual SelectListItem GetItemForMember(MemberInfo memberInfo)\n\t{\n\t\treturn new SelectListItem\n\t\t{\n\t\t\tText = GetTextForMember(memberInfo),\n\t\t\tValue = GetValueForMember(memberInfo)\n\t\t};\n\t}\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/EnumTypeSelectTagHelper.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Reflection;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\nusing Microsoft.Extensions.Localization;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Provide select options geneartions for enum items.\n/// </summary>\n[HtmlTargetElement(\"select\", Attributes = EnumTypeAttributeName)]\n[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]\npublic class EnumTypeSelectTagHelper : EnumSelectTagHelper\n{\n\t#region Constrcutor\n\n\t/// <summary>\n\t///     Create a new instance of <see cref=\"EnumTypeSelectTagHelper\" />.\n\t/// </summary>\n\t/// <param name=\"serviceProvider\">The service instance of <see cref=\"IServiceProvider\" />.</param>\n\tpublic EnumTypeSelectTagHelper(IServiceProvider serviceProvider)\n\t\t: base(serviceProvider)\n\t{\n\t}\n\n\t#endregion\n\n\t/// <summary>\n\t///     Return the actual enum type for generating the list.\n\t/// </summary>\n\t/// <returns></returns>\n\tprotected override Type GetEnumType()\n\t{\n\t\tif (EnumType == null)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The expression for '{EnumTypeAttributeName}' attribute cannot be null.\");\n\n\t\t// Remove nullable if necessary\n\t\tvar type = Nullable.GetUnderlyingType(EnumType) ?? EnumType;\n\n\t\tif (!type.GetTypeInfo().IsEnum)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The specified type '{EnumType.AssemblyQualifiedName}' for '{EnumTypeAttributeName}' attribute is not a valid enum type nor a nullable enum type.\");\n\n\t\treturn type;\n\t}\n\n\t/// <summary>\n\t///     Synchronously executes the <see cref=\"T:Microsoft.AspNet.Razor.TagHelpers.TagHelper\" /> with the given\n\t///     <paramref name=\"context\" /> and\n\t///     <paramref name=\"output\" />.\n\t/// </summary>\n\t/// <param name=\"context\">Contains information associated with the current HTML tag.</param>\n\t/// <param name=\"output\">A stateful HTML element used to generate an HTML tag.</param>\n\tpublic override void Process(TagHelperContext context, TagHelperOutput output)\n\t{\n\t\t// Get list and convert to array (avoiding duplicate enumeration)\n\t\tvar items = GenerateListForEnumType().ToArray();\n\n\t\t// Get value\n\t\tvar selectedValue = EnumValue == null ? string.Empty : GetValueForMember(EnumValue.GetMember());\n\n\t\t// Set selected Item\n\t\tvar selectedItem = items.FirstOrDefault(i => i.Value == selectedValue);\n\n\t\tif (selectedItem != null)\n\t\t{\n\t\t\tselectedItem.Selected = true;\n\t\t}\n\n\t\t// Append list items\n\t\toutput.PostContent.AppendHtml(HtmlGeneratorHelper.GenerateGroupsAndOptions(null, items));\n\t}\n\n\t#region Constants for attributes\n\n\t/// <summary>\n\t///     Get the HTML name associated with the <see cref=\"EnumType\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string EnumTypeAttributeName = \"asp-enum-type\";\n\n\t/// <summary>\n\t///     Get the HTML name associated with the <see cref=\"EnumValueAttributeName\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string EnumValueAttributeName = \"asp-enum-value\";\n\n\t/// <summary>\n\t///     Get or set the enum type used to generate the items.\n\t/// </summary>\n\t[HtmlAttributeName(EnumTypeAttributeName)]\n\tpublic Type EnumType { get; set; }\n\n\t/// <summary>\n\t///     Get or set the the selected value for the select tag.\n\t/// </summary>\n\t[HtmlAttributeName(EnumValueAttributeName)]\n\tpublic Enum EnumValue { get; set; }\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/EnumValueTextHelper.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Localization;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Provide general ability for extra data annotation text from its enum item definition for a enum value.\n/// </summary>\n/// <inheritdoc />\n[HtmlTargetElement(TargetElementName)]\npublic class EnumValueTextHelper : TagHelper\n{\n\t#region Constructor\n\n\tpublic EnumValueTextHelper(IServiceProvider serviceProvider)\n\t{\n\t\tStringLocalizerFactory = serviceProvider.GetService<IStringLocalizerFactory>();\n\t}\n\n\t#endregion\n\n\t#region Services\n\n\t/// <summary>\n\t///     Get the service used to create <see cref=\"IStringLocalizer\" /> services.\n\t/// </summary>\n\tprivate IStringLocalizerFactory StringLocalizerFactory { get; }\n\n\t#endregion\n\n\t/// <summary>\n\t///     Get or set the related model expression for the text element.\n\t/// </summary>\n\t[HtmlAttributeName(ValueHtmlAttributeName)]\n\t[UsedImplicitly(ImplicitUseKindFlags.Assign)]\n\tpublic Enum Value { get; set; }\n\n\t[HtmlAttributeName(ForHtmlAttributeName)]\n\t[UsedImplicitly(ImplicitUseKindFlags.Assign)]\n\tpublic ModelExpression For { get; set; }\n\n\t/// <summary>\n\t///     Get or set the text source used to generated the inner text.\n\t/// </summary>\n\t[HtmlAttributeName(TextSourceHtmlAttributeName)]\n\t[UsedImplicitly(ImplicitUseKindFlags.Assign)]\n\tpublic TextSource TextSource { get; set; } = TextSource.MemberNameOnly;\n\n\tpublic override void Process(TagHelperContext context, TagHelperOutput output)\n\t{\n\t\tif (output.TagMode != TagMode.SelfClosing)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The '{TargetElementName}' element can only use the self closing mode.\");\n\n\t\tif (Value == null && For == null)\n\t\t{\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"Either the '{ValueHtmlAttributeName}' attribute or the '{ForHtmlAttributeName}' attribute should be specified.\");\n\t\t}\n\n\t\tif (Value != null && For != null)\n\t\t{\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"Only one of the '{ValueHtmlAttributeName}' attribute and the '{ForHtmlAttributeName}' attribute can be specified.\");\n\t\t}\n\n\t\tvar realValue = Value ?? For.Model;\n\n\t\tif (!(realValue is Enum enumValue))\n\t\t{\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"Your specified value from the '{ValueHtmlAttributeName} attribute or the '{ForHtmlAttributeName}' attribute is not a valid enum value.\");\n\t\t}\n\n\t\t// get property definition\n\t\tvar member = enumValue.GetMember();\n\n\t\t// get the localizer for the container type.\n\t\tvar localizer = StringLocalizerFactory?.Create(member.DeclaringType);\n\n\t\tvar memberText = member.GetTextForMember(TextSource);\n\t\tvar realText = localizer.TryGetLocalizedText(memberText);\n\n\t\toutput.TagName = null;\n\t\toutput.Content.SetContent(realText);\n\t}\n\n\t#region TagHelper Constants\n\n\t/// <summary>\n\t///     The Target HTML element name. This field is constant.\n\t/// </summary>\n\tpublic const string TargetElementName = \"enum-item-display-text\";\n\n\t/// <summary>\n\t///     The HTML attribute related with the <see cref=\"Value\" /> property. This field is constant.\n\t/// </summary>\n\tpublic const string ValueHtmlAttributeName = \"value\";\n\n\t/// <summary>\n\t///     The HTML attribute related with the <see cref=\"For\" /> property. This field is constant.\n\t/// </summary>\n\tpublic const string ForHtmlAttributeName = \"for\";\n\n\t/// <summary>\n\t///     The HTML attribute related with the <see cref=\"TextSource\" /> property. This field is constant.\n\t/// </summary>\n\tpublic const string TextSourceHtmlAttributeName = \"text-source\";\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/FlagsEnumInputTagHelper.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Mvc.TagHelpers;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Support binding checkbox with enum flags.\n/// </summary>\n[HtmlTargetElement(\"input\", Attributes = EnumFlagForAttributeName + \",\" + EnumFlagValueAttributeName)]\npublic class FlagsEnumInputTagHelper : TagHelper\n{\n\t/// <summary>\n\t///     Define the attribute name for <see cref=\"EnumFlagValue\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string EnumFlagValueAttributeName = \"asp-enum-flag-value\";\n\n\t/// <summary>\n\t///     Define the attribute name for <see cref=\"EnumFlagFor\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string EnumFlagForAttributeName = \"asp-enum-flag-for\";\n\n\t/// <summary>\n\t///     Creates a new <see cref=\"InputTagHelper\" />.\n\t/// </summary>\n\t[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]\n\tpublic FlagsEnumInputTagHelper(IHtmlGenerator htmlGenerator)\n\t{\n\t\tHtmlGenerator = htmlGenerator;\n\t}\n\n\t/// <inheritdoc />\n\t/// <remarks>\n\t///     Default order is <c>0</c>.\n\t/// </remarks>\n\tpublic override int Order => new InputTagHelper(HtmlGenerator).Order - 10;\n\n\t/// <summary>\n\t///     The <see cref=\"IHtmlGenerator\" /> Service object.\n\t/// </summary>\n\t[PublicAPI]\n\tprotected IHtmlGenerator HtmlGenerator { get; }\n\n\t/// <summary>\n\t///     Get or set the model expression to retrieve the enum flag value from model.\n\t/// </summary>\n\t[HtmlAttributeName(EnumFlagForAttributeName)]\n\tpublic ModelExpression EnumFlagFor { get; set; }\n\n\t/// <summary>\n\t///     Get or set the actual flag value to compare.\n\t/// </summary>\n\t[HtmlAttributeName(EnumFlagValueAttributeName)]\n\tpublic Enum EnumFlagValue { get; set; }\n\n\t/// <inheritdoc />\n\t/// <remarks>\n\t///     Does nothing if <see cref=\"InputTagHelper.For\" /> is <c>null</c>.\n\t/// </remarks>\n\t/// <exception cref=\"InvalidOperationException\">\n\t///     Thrown if <see cref=\"InputTagHelper.Format\" /> is non-<c>null</c> but <see cref=\"InputTagHelper.For\" /> is\n\t///     <c>null</c>.\n\t/// </exception>\n\tpublic override void Process(TagHelperContext context, TagHelperOutput output)\n\t{\n\t\t// Argument check\n\t\tif (EnumFlagValue == null)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The value of the {EnumFlagValueAttributeName} attribute cannot be empty.\");\n\n\t\tif (!EnumFlagFor.ModelExplorer.Metadata.IsFlagsEnum)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The model expression type must be enum flag when {EnumFlagValueAttributeName} is specified\");\n\n\n\t\t// Get base type and remove nullable\n\t\tvar type = EnumFlagFor.ModelExplorer.ModelType;\n\t\ttype = Nullable.GetUnderlyingType(type) ?? type;\n\n\t\t// Get the defined enum name\n\t\tvar enumName = Enum.GetName(type, EnumFlagValue);\n\t\tif (enumName == null)\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The value of the {EnumFlagValueAttributeName} attribute is not a valid enum flag item.\");\n\n\t\t// Set name and value\n\t\toutput.Attributes.SetAttribute(\"name\", EnumFlagFor.Name);\n\t\toutput.Attributes.SetAttribute(\"value\", enumName);\n\n\t\t// Set checked attribute\n\t\tif (EnumFlagFor.Model is Enum value && value.HasFlag(EnumFlagValue))\n\t\t{\n\t\t\toutput.Attributes.SetAttribute(\"checked\", \"checked\");\n\t\t}\n\t\t// Remove checked attribute\n\t\telse\n\t\t{\n\t\t\tvar attr = output.Attributes[\"checked\"];\n\t\t\tif (attr != null)\n\t\t\t\toutput.Attributes.Remove(attr);\n\t\t}\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/FlagsEnumModelBinder.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc.ModelBinding;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <inheritdoc />\n/// <summary>\n///     Support binding a flags enum value with multiple flag inputs.\n/// </summary>\npublic class FlagsEnumModelBinder : IModelBinder\n{\n#if NETSTANDARD2_0 || NETCOREAPP2_1 || NETCOREAPP3_0\n\tprivate static Task CompletedTask => Task.CompletedTask;\n#else\n\t\tprivate static Task CompletedTask => Microsoft.AspNetCore.Mvc.Internal.TaskCache.CompletedTask;\n#endif\n\n\t/// <inheritdoc />\n\t/// <summary>Attempts to bind a model.</summary>\n\t/// <param name=\"bindingContext\">The <see cref=\"T:Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext\" />.</param>\n\t/// <returns>\n\t///     <para>\n\t///         A <see cref=\"T:System.Threading.Tasks.Task\" /> which will complete when the model binding process completes.\n\t///     </para>\n\t///     <para>\n\t///         If model binding was successful, the\n\t///         <see cref=\"P:Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext.Result\" /> should have\n\t///         <see cref=\"P:Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult.IsModelSet\" /> set to <c>true</c>.\n\t///     </para>\n\t///     <para>\n\t///         A model binder that completes successfully should set\n\t///         <see cref=\"P:Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext.Result\" /> to\n\t///         a value returned from\n\t///         <see cref=\"M:Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult.Success(System.Object)\" />.\n\t///     </para>\n\t/// </returns>\n\tpublic Task BindModelAsync(ModelBindingContext bindingContext)\n\t{\n\t\t// Only accept enum values\n\t\tif (!bindingContext.ModelMetadata.IsFlagsEnum)\n\t\t\treturn CompletedTask;\n\n\t\tvar provideValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);\n\n\t\t// Do nothing if there is no actual values\n\t\tif (provideValue == ValueProviderResult.None)\n\t\t\treturn CompletedTask;\n\n\t\t// Get the real enum type\n\t\tvar enumType = bindingContext.ModelType;\n\t\tenumType = Nullable.GetUnderlyingType(enumType) ?? enumType;\n\n\t\t// Each value self may contains a series of actual values, split it with comma\n\t\tvar strs = provideValue.Values.SelectMany(s => s.Split(','));\n\n\t\t// Convert all items into enum items.\n\t\tvar actualValues = strs.Select(valueString => Enum.Parse(enumType, valueString));\n\n\t\t// Merge to final result\n\t\tvar result = actualValues.Aggregate(0, (current, value) => current | (int)value);\n\n\t\t// Convert to Enum object\n\t\tvar realResult = Enum.ToObject(enumType, result);\n\n\t\t// Result\n\t\tbindingContext.Result = ModelBindingResult.Success(realResult);\n\n\t\treturn CompletedTask;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/FlagsEnumModelBinderProvider.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Mvc.ModelBinding;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <inheritdoc />\n/// <summary>\n///     An <see cref=\"IModelBinderProvider\" /> used to provider <see cref=\"FlagsEnumModelBinder\" /> instances.\n/// </summary>\npublic class FlagsEnumModelBinderProvider : IModelBinderProvider\n{\n\t/// <inheritdoc />\n\t/// <summary>\n\t///     Creates a <see cref=\"IModelBinder\" /> based on <see cref=\"ModelBinderProviderContext\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The <see cref=\"ModelBinderProviderContext\" />.</param>\n\t/// <returns>An <see cref=\"IModelBinder\" />.</returns>\n\tpublic IModelBinder GetBinder([NotNull] ModelBinderProviderContext context)\n\t{\n\t\tif (context == null)\n\t\t\tthrow new ArgumentNullException(nameof(context));\n\n\t\treturn context.Metadata.IsFlagsEnum ? new FlagsEnumModelBinder() : null;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/FlagsEnumModelBinderServiceCollectionExtensions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.ModelBinding;\nusing Microsoft.AspNetCore.Mvc.ModelBinding.Binders;\nusing Microsoft.Extensions.DependencyInjection;\nusing Sakura.AspNetCore.Mvc.TagHelpers;\n\n// ReSharper disable once CheckNamespace\n\nnamespace Microsoft.Framework.DependencyInjection;\n\n/// <summary>\n///     Provide extension methods to add <see cref=\"FlagsEnumModelBinder\" /> to MVC application.\n/// </summary>\n[PublicAPI]\npublic static class FlagsEnumModelBinderServiceCollectionExtensions\n{\n\t/// <summary>\n\t///     Find the index of the first item which satisify the condition defined in <paramref name=\"predicate\" />. If no item\n\t///     is found, this method will return -1.\n\t/// </summary>\n\t/// <typeparam name=\"T\">The element type of the source list.</typeparam>\n\t/// <param name=\"source\">The source element list.</param>\n\t/// <param name=\"predicate\">The delegate method to determine if the element satisify the condition.</param>\n\t/// Find the index of the first item which satisify the condition defined in\n\t/// <paramref name=\"predicate\" />\n\t/// . If no item is found, this method will return -1.\n\t/// <returns>The index of the first item which satisify the condition defined in <paramref name=\"predicate\" />. </returns>\n\tprivate static int FirstIndexOfOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate)\n\t{\n\t\tvar result = 0;\n\n\t\tforeach (var item in source)\n\t\t{\n\t\t\tif (predicate(item))\n\t\t\t\treturn result;\n\n\t\t\tresult++;\n\t\t}\n\n\t\treturn -1;\n\t}\n\n\t/// <summary>\n\t/// Find the insert location for <see cref=\"FlagsEnumModelBinderProvider\"/> instance.\n\t/// </summary>\n\t/// <param name=\"modelBinderProviders\">The MVC <see cref=\"IModelBinderProvider\"/> list.</param>\n\t/// <returns></returns>\n\tprivate static int FindModelBinderProviderInsertLocation(this IList<IModelBinderProvider> modelBinderProviders)\n\t{\n#if NETSTANDARD1_6 || NET451 || NETCOREAPP2_0\n\t\treturn modelBinderProviders.FirstIndexOfOrDefault(i => i is SimpleTypeModelBinderProvider);\n#else\n\t\t\tvar index = modelBinderProviders.FirstIndexOfOrDefault(i => i is FloatingPointTypeModelBinderProvider);\n\t\t\treturn index < 0 ? index : index + 1;\n#endif\n\t}\n\n\t/// <summary>\n\t///     Insert the <see cref=\"FlagsEnumModelBinder\" /> in the correct location of a list of model binders.\n\t/// </summary>\n\t/// <param name=\"modelBinderProviders\">The list of model binders.</param>\n\t/// <remarks>\n\t///     This method will insert <see cref=\"FlagsEnumModelBinder\" /> in MVC model binder chain in order to\n\t///     replace the default behavior for simple enum values. If no insert location is found, this\n\t///     method will add the <see cref=\"FlagsEnumModelBinder\" /> to the end of the <paramref name=\"modelBinderProviders\" />.\n\t/// </remarks>\n\tpublic static void InsertFlagsEnumModelBinderProvider(this IList<IModelBinderProvider> modelBinderProviders)\n\t{\n\t\t// Argument Check\n\t\tif (modelBinderProviders == null)\n\t\t\tthrow new ArgumentNullException(nameof(modelBinderProviders));\n\n\t\tvar providerToInsert = new FlagsEnumModelBinderProvider();\n\n\t\t// Find the location of SimpleTypeModelBinder, the FlagsEnumModelBinder must be inserted before it.\n\t\tvar index = modelBinderProviders.FindModelBinderProviderInsertLocation();\n\n\t\tif (index != -1)\n\t\t\tmodelBinderProviders.Insert(index, providerToInsert);\n\t\telse\n\t\t\tmodelBinderProviders.Add(providerToInsert);\n\t}\n\n\t/// <summary>\n\t///     Configue the <see cref=\"MvcOptions\" /> to insert the <see cref=\"FlagsEnumModelBinder\" />.\n\t/// </summary>\n\t/// <param name=\"options\">The <see cref=\"MvcOptions\" /> object to be configuring.</param>\n\t/// <returns>The <paramref name=\"options\" /> object.</returns>\n\tpublic static MvcOptions AddFlagsEnumModelBinderProvider(this MvcOptions options)\n\t{\n\t\tif (options == null)\n\t\t\tthrow new ArgumentNullException(nameof(options));\n\n\t\toptions.ModelBinderProviders.InsertFlagsEnumModelBinderProvider();\n\t\treturn options;\n\t}\n\n\t/// <summary>\n\t///     Configue the <see cref=\"IMvcBuilder\" /> to insert the <see cref=\"FlagsEnumModelBinder\" />.\n\t/// </summary>\n\t/// <param name=\"builder\">The <see cref=\"IMvcBuilder\" /> object to be configuring.</param>\n\t/// <returns>The <paramref name=\"builder\" /> object.</returns>\n\tpublic static IMvcBuilder AddFlagsEnumModelBinderProvider(this IMvcBuilder builder)\n\t{\n\t\tbuilder.AddMvcOptions(options => AddFlagsEnumModelBinderProvider(options));\n\t\treturn builder;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/HtmlGeneratorHelper.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.AspNetCore.Html;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\n// ReSharper disable MustUseReturnValue\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Helper class for HTML generation. The code in this class are copy from ASP.NET official implementation for\n///     <see cref=\"DefaultHtmlGenerator\" />.\n/// </summary>\ninternal static class HtmlGeneratorHelper\n{\n\t/// <summary>\n\t/// Generate the HTML for a <see cref=\"SelectListItem\" /> with specified display text.\n\t/// </summary>\n\t/// <param name=\"item\">The item data.</param>\n\t/// <param name=\"text\">The display text.</param>\n\t/// <returns>The generated HTML content.</returns>\n\t/// <remarks>\n\t///     Not used directly in HtmlHelper. Exposed for use in DefaultDisplayTemplates.\n\t/// </remarks>\n\tprivate static TagBuilder GenerateOption(SelectListItem item, string text)\n\t{\n\t\tvar tagBuilder = new TagBuilder(\"option\");\n\t\ttagBuilder.InnerHtml.SetContent(text);\n\n\t\tif (item.Value != null)\n\t\t\ttagBuilder.Attributes[\"value\"] = item.Value;\n\n\t\tif (item.Selected)\n\t\t\ttagBuilder.Attributes[\"selected\"] = \"selected\";\n\n\t\tif (item.Disabled)\n\t\t\ttagBuilder.Attributes[\"disabled\"] = \"disabled\";\n\n\t\treturn tagBuilder;\n\t}\n\n\t/// <summary>\n\t///    Generate the HTML for a set of <see cref=\"SelectListItem\" />s.\n\t/// </summary>\n\t/// <param name=\"optionLabel\">The optional label text.</param>\n\t/// <param name=\"selectList\">The select list data.</param>\n\t/// <returns>Generated HTML Content.</returns>\n\tpublic static IHtmlContent GenerateGroupsAndOptions(string optionLabel, IEnumerable<SelectListItem> selectList)\n\t{\n\t\tvar listItemBuilder = new DefaultTagHelperContent();\n\n\t\t// Make optionLabel the first item that gets rendered.\n\t\tif (optionLabel != null)\n\t\t\tlistItemBuilder.AppendLine(GenerateOption(new SelectListItem\n\t\t\t{\n\t\t\t\tText = optionLabel,\n\t\t\t\tValue = string.Empty,\n\t\t\t\tSelected = false\n\t\t\t}));\n\n\t\t// Group items in the SelectList if requested.\n\t\t// Treat each item with Group == null as a member of a unique group\n\t\t// so they are added according to the original order.\n\t\tvar groupedSelectList = selectList.GroupBy(item => item.Group?.GetHashCode() ?? item.GetHashCode());\n\n\t\tforeach (var group in groupedSelectList)\n\t\t{\n\t\t\tvar optGroup = group.First().Group;\n\t\t\tif (optGroup != null)\n\t\t\t{\n\t\t\t\tvar groupBuilder = new TagBuilder(\"optgroup\");\n\t\t\t\tif (optGroup.Name != null)\n\t\t\t\t\tgroupBuilder.MergeAttribute(\"label\", optGroup.Name);\n\n\t\t\t\tif (optGroup.Disabled)\n\t\t\t\t\tgroupBuilder.MergeAttribute(\"disabled\", \"disabled\");\n\n\t\t\t\tgroupBuilder.InnerHtml.AppendLine();\n\t\t\t\tforeach (var item in group)\n\t\t\t\t\tgroupBuilder.InnerHtml.AppendLine(GenerateOption(item));\n\n\t\t\t\tlistItemBuilder.AppendLine(groupBuilder);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tforeach (var item in group)\n\t\t\t\t\tlistItemBuilder.AppendLine(GenerateOption(item));\n\t\t\t}\n\t\t}\n\n\t\treturn listItemBuilder;\n\t}\n\n\t/// <summary>\n\t/// Generate the HTML for a <see cref=\"SelectListItem\" />.\n\t/// </summary>\n\t/// <param name=\"item\">The item data.</param>\n\t/// <returns>The generated HTML content.</returns>\n\t/// <remarks>\n\t///     Not used directly in HtmlHelper. Exposed for use in DefaultDisplayTemplates.\n\t/// </remarks>\n\tprivate static IHtmlContent GenerateOption(SelectListItem item)\n\t{\n\t\tvar tagBuilder = GenerateOption(item, item.Text);\n\t\treturn tagBuilder;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/IdFormatTagHelper.cs",
    "content": "﻿using System;\nusing System.Collections.Concurrent;\nusing System.Globalization;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     A tag helper used to generate\n/// </summary>\n[HtmlTargetElement(\"*\", Attributes = IdFormatAttributeName)]\npublic class IdFormatTagHelper : TagHelper\n{\n\t/// <summary>\n\t///     Get the attribute name used for <see cref=\"IdFormat\" /> property. This field is constant.\n\t/// </summary>\n\tpublic const string IdFormatAttributeName = \"asp-id-format\";\n\n\t/// <summary>\n\t///     Get or set the format string for the id.\n\t/// </summary>\n\t[HtmlAttributeName(IdFormatAttributeName)]\n\tpublic string IdFormat { get; set; }\n\n\t/// <summary>\n\t///     Get or set the start value for each format string.\n\t/// </summary>\n\tpublic static int IdStart { get; set; } = 1;\n\n\t/// <summary>\n\t///     Get or set the key used to store the id-format counting information.\n\t/// </summary>\n\tpublic static string ViewDataKey { get; set; } = \"ASP_IdFormatCountDictionary\";\n\n\t/// <summary>\n\t///     Get or set the <see cref=\"ViewContext\" /> related to this <see cref=\"TagHelper\" />.\n\t/// </summary>\n\t[ViewContext]\n\t[HtmlAttributeNotBound] public ViewContext ViewContext { get; set; }\n\n\t/// <summary>\n\t///     Get the dictionary stored the id format counting inforamtion.\n\t/// </summary>\n\tprotected ConcurrentDictionary<string, int> IdFormatCountDictionary\n\t{\n\t\tget\n\t\t{\n\t\t\tif (ViewDataKey == null)\n\t\t\t\tthrow new InvalidOperationException($\"The property of '{nameof(ViewDataKey)}' cannot be null.\");\n\n\t\t\tif (ViewContext.ViewData.TryGetValue(ViewDataKey, out var result) &&\n\t\t\t    result is ConcurrentDictionary<string, int> dic)\n\t\t\t\treturn dic;\n\n\t\t\tvar newDic = new ConcurrentDictionary<string, int>(StringComparer.Ordinal);\n\t\t\tViewContext.ViewData[ViewDataKey] = newDic;\n\t\t\treturn newDic;\n\t\t}\n\t}\n\n\t/// <summary>\n\t///     Get next count for current id format.\n\t/// </summary>\n\t/// <returns>The next count for current id format.</returns>\n\tprotected int GetNextCount()\n\t{\n\t\tif (string.IsNullOrEmpty(IdFormat))\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The value of html attribute '{IdFormatAttributeName}' cannot be null.\");\n\n\t\treturn IdFormatCountDictionary.AddOrUpdate(IdFormat, IdStart, (key, value) => value + 1);\n\t}\n\n\t/// <summary>\n\t///     Synchronously executes the <see cref=\"TagHelper\" /> with the given <paramref name=\"context\" /> and\n\t///     <paramref name=\"output\" />.\n\t/// </summary>\n\t/// <param name=\"context\">Contains information associated with the current HTML tag.</param>\n\t/// <param name=\"output\">A stateful HTML element used to generate an HTML tag.</param>\n\tpublic override void Process(TagHelperContext context, TagHelperOutput output)\n\t{\n\t\tif (string.IsNullOrEmpty(IdFormat))\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"The value of html attribute '{IdFormatAttributeName}' cannot be null.\");\n\n\t\tif (context.AllAttributes.ContainsName(\"id\"))\n\t\t\tthrow new InvalidOperationException(\n\t\t\t\t$\"You cannot specify both 'id' and '{IdFormatAttributeName}' attribute on one element.\");\n\n\t\toutput.Attributes.SetAttribute(\"id\", string.Format(CultureInfo.InvariantCulture, IdFormat, GetNextCount()));\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/OptionLabelPosition.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Define the appearing location of the optional label of the \"select\" element.\n/// </summary>\npublic enum OptionLabelPosition\n{\n\t/// <summary>\n\t///     The optional label appears in the first.\n\t/// </summary>\n\tFirst = 0,\n\n\t/// <summary>\n\t///     The optional label appears in the last.\n\t/// </summary>\n\tLast\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/PartialViewItemTagHelper.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Mvc.Rendering;\n\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n[HtmlTargetElement(\"partial\", Attributes = $\"{ViewDataItemAttributePrefix}*\")]\n[HtmlTargetElement(\"partial\", Attributes = ViewDataAttributeName)]\n[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]\npublic class PartialViewItemTagHelper : TagHelper\n{\n    /// <summary>\n    ///  The HTML attribute prefix for single view data entry. This field is constant.\n    /// </summary>\n    [PublicAPI]\n    public const string ViewDataItemAttributePrefix = \"view-item-\";\n\n\n    /// <summary>\n    /// The HTML attribute name for the entire view data dictionary. This field is constant.\n    /// </summary>\n    [PublicAPI]\n    public const string ViewDataAttributeName = \"all-view-items\";\n\n    /// <inheritdoc />\n    public override int Order => base.Order - 1;\n\n    /// <summary>\n    ///  The <see cref=\"ViewContext\"/> instance.\n    /// </summary>\n    [ViewContext]\n    [HtmlAttributeNotBound]\n    [UsedImplicitly(ImplicitUseKindFlags.Assign)]\n    public ViewContext ViewContext { get; set; } = null!;\n\n    /// <summary>\n    /// The dictionary containing all the view data items.\n    /// </summary>\n    [HtmlAttributeName(ViewDataAttributeName, DictionaryAttributePrefix = ViewDataItemAttributePrefix)]\n    [UsedImplicitly(ImplicitUseKindFlags.Assign)]\n    public Dictionary<string, object?> ViewDataItems { get; set; } = [];\n\n\n    /// <inheritdoc />\n    public override void Process(TagHelperContext context, TagHelperOutput output)\n    {\n        foreach (var item in ViewDataItems)\n        {\n            ViewContext.ViewData[item.Key] = item.Value;\n        }\n    }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/ReflectionHelper.cs",
    "content": "﻿using System;\nusing System.ComponentModel.DataAnnotations;\nusing System.Globalization;\nusing System.Reflection;\nusing JetBrains.Annotations;\nusing Microsoft.Extensions.Localization;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Provide supporting method to reflection operations. This class is static.\n/// </summary>\n[PublicAPI]\npublic static class ReflectionHelper\n{\n\t/// <summary>\n\t///     Get the <see cref=\"FieldInfo\" /> definition for an enum value object.\n\t/// </summary>\n\t/// <param name=\"obj\">The enum value object.</param>\n\t/// <returns>The corresponding <see cref=\"FieldInfo\" /> definition.</returns>\n\tpublic static FieldInfo GetMember(this Enum obj)\n\t{\n\t\tvar enumType = obj.GetType();\n\t\tvar name = Enum.GetName(enumType, obj);\n\t\treturn enumType.GetField(name);\n\t}\n\n\t/// <summary>\n\t///     Get the display text of an <see cref=\"MemberInfo\" /> from the specified text source.\n\t/// </summary>\n\t/// <param name=\"memberInfo\">The <see cref=\"MemberInfo\" /> object.</param>\n\t/// <param name=\"textSource\">The text source for the enum item.</param>\n\t/// <returns>\n\t///     The text retrieved from the <paramref name=\"memberInfo\" /> definition. If there is no text in the location\n\t///     <paramref name=\"textSource\" /> specified, this method will return <see cref=\"MemberInfo.Name\" />.\n\t/// </returns>\n\t/// <exception cref=\"ArgumentException\">The value of <paramref name=\"textSource\" /> is not a valid enum item.</exception>\n\tpublic static string GetTextForMember(this MemberInfo memberInfo, TextSource textSource)\n\t{\n\t\t// Get DisplayAttribute instance\n\t\tvar attr = memberInfo.GetCustomAttribute<DisplayAttribute>();\n\n\t\t// No attribute is defined, return name immediately\n\t\tif (attr == null)\n\t\t\treturn memberInfo.Name;\n\n\t\t// Variable to store the result\n\t\tstring result;\n\n\t\t// Get data according to the source\n\t\tswitch (textSource)\n\t\t{\n\t\t\tcase TextSource.MemberNameOnly:\n\t\t\t\tresult = memberInfo.Name;\n\t\t\t\tbreak;\n\t\t\tcase TextSource.Name:\n\t\t\t\tresult = attr.GetName();\n\t\t\t\tbreak;\n\t\t\tcase TextSource.ShortName:\n\t\t\t\tresult = attr.GetShortName();\n\t\t\t\tbreak;\n\t\t\tcase TextSource.Description:\n\t\t\t\tresult = attr.GetDescription();\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new ArgumentException(\"The argument value is not a valid enum item.\", nameof(textSource));\n\t\t}\n\n\t\t// No result it found from the source, fallback to name\n\t\tif (string.IsNullOrEmpty(result))\n\t\t\tresult = memberInfo.Name;\n\n\t\t// Return\n\t\treturn result;\n\t}\n\n\t/// <summary>\n\t///     Get the value text of an enum item from the specified value source.\n\t/// </summary>\n\t/// <param name=\"memberInfo\">The <see cref=\"MemberInfo\" /> object represented as an enum item.</param>\n\t/// <param name=\"valueSource\">The value source for the enum item.</param>\n\t/// <returns>The text retrieved from the <paramref name=\"memberInfo\" /> definition.</returns>\n\t/// <exception cref=\"ArgumentException\">The value of <paramref name=\"valueSource\" /> is not a valid enum item.</exception>\n\tpublic static string GetValueForEnumMember(this MemberInfo memberInfo, EnumOptionValueSource valueSource)\n\t{\n\t\tswitch (valueSource)\n\t\t{\n\t\t\tcase EnumOptionValueSource.Value:\n\t\t\t\treturn ((int) Enum.Parse(\n\t\t\t\t\tmemberInfo.DeclaringType ?? throw new InvalidOperationException(\"The enum member has no declaring type.\"),\n\t\t\t\t\tmemberInfo.Name)).ToString(\"D\", CultureInfo.InvariantCulture);\n\t\t\tcase EnumOptionValueSource.Name:\n\t\t\t\treturn memberInfo.Name;\n\t\t\tdefault:\n\t\t\t\tthrow new ArgumentException(\"The argument value is not a valid enum item.\", nameof(valueSource));\n\t\t}\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/ResourceHelper.cs",
    "content": "﻿using Microsoft.Extensions.Localization;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Provide helper methods for resource operations. This class is static.\n/// </summary>\npublic static class ResourceHelper\n{\n\t/// <summary>\n\t///     Try to get the localized text from a given <see cref=\"IStringLocalizer\" />.\n\t/// </summary>\n\t/// <param name=\"localizer\">The <see cref=\"IStringLocalizer\" /> service used to pick up the localized text.</param>\n\t/// <param name=\"text\">The original text object.</param>\n\t/// <returns>\n\t///     If the <paramref name=\"localizer\" /> is not <c>null</c>, this method will pick the localized text from it;\n\t///     otherwise, this method will return the <paramref name=\"text\" /> unmodified.\n\t/// </returns>\n\tpublic static string TryGetLocalizedText(this IStringLocalizer localizer, string text)\n\t{\n\t\treturn localizer != null ? localizer[text] : text;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/Sakura.AspNetCore.Mvc.TagHelpers.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <Description>ASP.NET Core MVC Extensions Library provides addtional tag helpers and utility for simplify ASP.NET Core web application development.</Description>\n        <AssemblyTitle>ASP.NET Core MVC Extensions Library</AssemblyTitle>\n        <Authors>Iris Sakura</Authors>\n        <TargetFrameworks>netstandard1.6;netstandard2.0;netcoreapp2.0;netcoreapp2.1;netcoreapp3.0</TargetFrameworks>\n        <AssemblyName>Sakura.AspNetCore.Mvc.TagHelpers</AssemblyName>\n        <PackageId>Sakura.AspNetCore.Mvc.TagHelpers</PackageId>\n        <PackageTags>ASP.NET;ASP.NETCore;MVC;MVCCore;TagHelper;Enum;Flags;OptionLabel;Select;Option</PackageTags>\n        <PackageReleaseNotes>Add PartialViewItemTagHelper.</PackageReleaseNotes>\n        <PackageProjectUrl>https://github.com/sgjsakura/AspNetCore</PackageProjectUrl>\n        <PackageLicenseUrl></PackageLicenseUrl>\n        <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>\n        <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>\n        <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>\n        <GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n        <RepositoryUrl>https://github.com/sgjsakura/AspNetCore.git</RepositoryUrl>\n        <Version>1.5.0</Version>\n        <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n        <Nullable>enable</Nullable>\n        <WarningLevel>9999</WarningLevel>\n\n    </PropertyGroup>\n\n    <ItemGroup Condition=\" '$(TargetFramework)' == 'netstandard1.6' \">\n        <PackageReference Include=\"Microsoft.AspNetCore.Mvc.TagHelpers\" Version=\"1.0.0\" />\n    </ItemGroup>\n\n    <ItemGroup Condition=\" '$(TargetFramework)' == 'netstandard2.0' \">\n        <PackageReference Include=\"Microsoft.AspNetCore.Mvc.TagHelpers\" Version=\"2.0.0\" />\n    </ItemGroup>\n\n    <ItemGroup Condition=\" '$(TargetFramework)' == 'netcoreapp2.0' \">\n        <PackageReference Include=\"Microsoft.AspNetCore.Mvc.TagHelpers\" Version=\"1.0.0\" />\n    </ItemGroup>\n\n    <ItemGroup Condition=\" '$(TargetFramework)' == 'netcoreapp2.1' \">\n        <PackageReference Include=\"Microsoft.AspNetCore.Mvc.TagHelpers\" Version=\"2.1.0\" />\n    </ItemGroup>\n\n    <ItemGroup Condition=\" '$(TargetFramework)' == 'netcoreapp3.0' \">\n        <FrameworkReference Include=\"Microsoft.AspNetCore.App\" />\n    </ItemGroup>\n\n    <ItemGroup>\n      <PackageReference Include=\"JetBrains.Annotations\" Version=\"2024.3.0\" />\n    </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/SelectOptionLabelTagHelper.cs",
    "content": "﻿using System;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Give the \"select\" element an optional label item.\n/// </summary>\n[HtmlTargetElement(\"select\", Attributes = OptionLabelAttributeName)]\npublic class SelectOptionLabelTagHelper : TagHelper\n{\n\t/// <summary>\n\t///     Synchronously executes the <see cref=\"T:Microsoft.AspNet.Razor.Runtime.TagHelpers.TagHelper\" /> with the given\n\t///     <paramref name=\"context\" /> and\n\t///     <paramref name=\"output\" />.\n\t/// </summary>\n\t/// <param name=\"context\">Contains information associated with the current HTML tag.</param>\n\t/// <param name=\"output\">A stateful HTML element used to generate an HTML tag.</param>\n\tpublic override void Process(TagHelperContext context, TagHelperOutput output)\n\t{\n\t\tvar optionTag = new TagBuilder(\"option\");\n\n\t\t// 默认值\n\t\toptionTag.Attributes[\"value\"] = \"\";\n\n\t\t// 文字\n\t\toptionTag.InnerHtml.Append(OptionLabel);\n\n\t\tswitch (OptionLabelPosition)\n\t\t{\n\t\t\tcase OptionLabelPosition.First:\n\t\t\t\toutput.PreContent.AppendHtml(optionTag);\n\t\t\t\tbreak;\n\t\t\tcase OptionLabelPosition.Last:\n\t\t\t\toutput.PostContent.AppendHtml(optionTag);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new InvalidOperationException(\n\t\t\t\t\t\"The value of OptionLabelPosition property is not a valid enum item.\");\n\t\t}\n\t}\n\n\t#region Constant field\n\n\t/// <summary>\n\t///     Get the html attribute name associated with <see cref=\"OptionLabel\" /> property. This field is constant.\n\t/// </summary>\n\tpublic const string OptionLabelAttributeName = \"asp-option-label\";\n\n\t/// <summary>\n\t///     Get the html attribute name associated with <see cref=\"OptionLabelPosition\" /> property. This field is constant.\n\t/// </summary>\n\tpublic const string OptionLabelPositionAttributeName = \"asp-option-label-position\";\n\n\t#endregion\n\n\t#region Binding Properties\n\n\t/// <summary>\n\t///     Get or set the content string for option label.\n\t/// </summary>\n\t[HtmlAttributeName(OptionLabelAttributeName)]\n\tpublic string OptionLabel { get; set; }\n\n\n\t/// <summary>\n\t///     Get or set the existing location for the option label. The default value of this property is\n\t///     <see cref=\"OptionLabelPosition.First\" />.\n\t/// </summary>\n\t[HtmlAttributeName(OptionLabelPositionAttributeName)]\n\tpublic OptionLabelPosition OptionLabelPosition { get; set; } = OptionLabelPosition.First;\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/SelectValueOptionTagHelper.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Dynamically set the option as selected if the value of the option matches the target value provided by the parent\n///     select element.\n/// </summary>\n[HtmlTargetElement(\"option\")]\n[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]\npublic class SelectValueOptionTagHelper : TagHelper\n{\n\t/// <summary>\n\t///     Get the default mode when comparing values. This field is constant.\n\t/// </summary>\n\tpublic const StringComparison DefaultSelectValueCompareMode = StringComparison.OrdinalIgnoreCase;\n\n\t/// <summary>\n\t///     Get the key object to retrieve selected value from the executing context.\n\t/// </summary>\n\tpublic static object SelectedValueItemKey { get; } = new object();\n\n\t/// <summary>\n\t///     Get the key object to retrieve selected value compare mode from the executing context.\n\t/// </summary>\n\tpublic static object SelectedValueCompareModeKey { get; } = new object();\n\n\t/// <summary>\n\t///     Provide a standard manner to add the selected value to the context.\n\t/// </summary>\n\t/// <param name=\"context\">The context object.</param>\n\t/// <param name=\"value\">The value to be added.</param>\n\t/// <param name=\"comparison\">\n\t///     The comparison mode for the <paramref name=\"value\" />. The default value of this parameter is\n\t///     defined in <see cref=\"DefaultSelectValueCompareMode\" />.\n\t/// </param>\n\t[PublicAPI]\n\tpublic static void SetSelectedValue(TagHelperContext context, string value,\n\t\tStringComparison comparison = DefaultSelectValueCompareMode)\n\t{\n\t\tcontext.Items[SelectedValueItemKey] = value;\n\t\tcontext.Items[SelectedValueCompareModeKey] = comparison;\n\t}\n\n\t/// <summary>\n\t///     Try to get the selected value string from the context. If no selected value is set, this method will returns\n\t///     <c>null</c>.\n\t/// </summary>\n\t/// <param name=\"context\">The tag helper context.</param>\n\t/// <returns>The selected value string from the context. If no selected value is set, this method will returns <c>null</c>.</returns>\n\tprotected string GetSelectedValueFromContext(TagHelperContext context)\n\t{\n\t\treturn context.Items.TryGetValue(SelectedValueItemKey, out var result) ? result as string : null;\n\t}\n\n\t/// <summary>\n\t///     Try to get the selected value comparison mode from the context. If no mode is set, this method will returns\n\t///     <see cref=\"DefaultSelectValueCompareMode\" />.\n\t/// </summary>\n\t/// <param name=\"context\">The tag helper context.</param>\n\t/// <returns>\n\t///     The selected value comparison mode from the context. If no mode is set, this method will returns\n\t///     <see cref=\"DefaultSelectValueCompareMode\" />.\n\t/// </returns>\n\tprotected StringComparison GetSelectedValueCompareModeFromContext(TagHelperContext context)\n\t{\n\t\treturn DefaultSelectValueCompareMode;\n\t}\n\n\t/// <summary>\n\t///     Make the current option as selected.\n\t/// </summary>\n\t/// <param name=\"output\">Tag output context.</param>\n\tprotected virtual void MarkAsSelected(TagHelperOutput output)\n\t{\n\t\toutput.Attributes.SetAttribute(\"selected\", \"selected\");\n\t}\n\n\t/// <summary>\n\t///     Synchronously executes the <see cref=\"T:Microsoft.AspNet.Razor.TagHelpers.TagHelper\" /> with the given\n\t///     <paramref name=\"context\" /> and\n\t///     <paramref name=\"output\" />.\n\t/// </summary>\n\t/// <param name=\"context\">Contains information associated with the current HTML tag.</param>\n\t/// <param name=\"output\">A stateful HTML element used to generate an HTML tag.</param>\n\tpublic override void Process(TagHelperContext context, TagHelperOutput output)\n\t{\n\t\tvar selectedValue = GetSelectedValueFromContext(context);\n\n\t\t// Do nothing if no selected value is set.\n\t\tif (selectedValue == null)\n\t\t\treturn;\n\n\t\t// Get the actual value\n\t\tvar value = context.AllAttributes[\"value\"]?.Value as string;\n\n\t\t// Compare value\n\t\tif (string.Equals(value, selectedValue, GetSelectedValueCompareModeFromContext(context)))\n\t\t\tMarkAsSelected(output);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/SelectValueTagHelper.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Razor.TagHelpers;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Add support for set automatically mark selected state for options.\n/// </summary>\n[HtmlTargetElement(\"select\", Attributes = ValueAttributeName)]\n[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]\npublic class SelectValueTagHelper : TagHelper\n{\n\t/// <inheritdoc />\n\tpublic override void Init(TagHelperContext context)\n\t{\n\t\tbase.Init(context);\n\t\tSelectValueOptionTagHelper.SetSelectedValue(context, Value, ValueCompareMode);\n\t}\n\n\t#region Html bound Properties\n\n\t/// <summary>\n\t///     Get the HTML name associated with the <see cref=\"Value\" /> property. This field is constant.\n\t/// </summary>\n\t[PublicAPI] public const string ValueAttributeName = \"asp-value\";\n\n\t/// <summary>\n\t///     Get or set the selected item's value of the current element.\n\t/// </summary>\n\t[HtmlAttributeName(ValueAttributeName)]\n\tpublic string Value { get; set; }\n\n\t/// <summary>\n\t///     Get the HTML name associated with the <see cref=\"ValueCompareMode\" /> property. This field is constant.\n\t/// </summary>\n\tpublic const string ValueCompareModeAttributeName = \"asp-value-compare-mode\";\n\n\t/// <summary>\n\t///     Get or set the value comparison mode for the selected value. The default value of this property is\n\t///     <see cref=\" SelectValueOptionTagHelper.DefaultSelectValueCompareMode\" />.\n\t/// </summary>\n\t[HtmlAttributeName(ValueCompareModeAttributeName)]\n\tpublic StringComparison ValueCompareMode { get; set; } =\n\t\tSelectValueOptionTagHelper.DefaultSelectValueCompareMode;\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/TextSource.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n///     Define the text source for text generators for a relection items.\n/// </summary>\npublic enum TextSource\n{\n\t/// <summary>\n\t///     Use the member name as the text.\n\t/// </summary>\n\tMemberNameOnly = 0,\n\n\t/// <summary>\n\t///     If <see cref=\"DisplayAttribute\" /> is defined and <see cref=\"DisplayAttribute.Name\" /> is provided, use its value;\n\t///     Otherwise, fallback to the <see cref=\"MemberNameOnly\" /> option.\n\t/// </summary>\n\tName,\n\n\t/// <summary>\n\t///     If <see cref=\"DisplayAttribute\" /> is defined and <see cref=\"DisplayAttribute.ShortName\" /> is provided, use its\n\t///     value; Otherwise, fallback to the <see cref=\"MemberNameOnly\" /> option.\n\t/// </summary>\n\tShortName,\n\n\t/// <summary>\n\t///     If <see cref=\"DisplayAttribute\" /> is defined and <see cref=\"DisplayAttribute.Description\" /> is provided, use its\n\t///     value; Otherwise, fallback to the <see cref=\"MemberNameOnly\" /> option.\n\t/// </summary>\n\tDescription\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TagHelpers/ViewDataExtensions.cs",
    "content": "﻿using JetBrains.Annotations;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\n\nnamespace Sakura.AspNetCore.Mvc.TagHelpers;\n\n/// <summary>\n/// Provide extension methods for <see cref=\"ViewDataDictionary\"/> data extracting. This class is static.\n/// </summary>\n[PublicAPI]\npublic static class ViewDataExtensions\n{\n    /// <summary>\n    /// Try getting the value of type <typeparamref name=\"T\"/> stored in the <paramref name=\"viewData\"/> with the specified <paramref name=\"key\"/>. If Value is not found or cannot be converted to the target type, return the default value of <typeparamref name=\"T\"/>.\n    /// </summary>\n    /// <typeparam name=\"T\">The type of the value.</typeparam>\n    /// <param name=\"viewData\">The view data dictionary.</param>\n    /// <param name=\"key\">The key of the value.</param>\n    /// <returns>If the item with the specified <paramref name=\"key\"/> is found in the <paramref name=\"viewData\"/> and its type is <typeparamref name=\"T\"/>, return its value. Otherwise, return the default value of <typeparamref name=\"T\"/></returns>.\n    public static T? GetItemOrDefault<T>(this ViewDataDictionary viewData, string key)\n    {\n        return viewData.TryGetValue(key, out var value) && value is T result\n            ? result \n            : default;\n    }\n\n    /// <summary>\n    /// Try getting the value of type <typeparamref name=\"T\"/> stored in the <paramref name=\"viewData\"/> with the specified <paramref name=\"key\"/>. If Value is not found or cannot be converted to the target type, return the <paramref name=\"defaultValue\"/>.\n    /// </summary>\n    /// <typeparam name=\"T\">The type of the value.</typeparam>\n    /// <param name=\"viewData\">The view data dictionary.</param>\n    /// <param name=\"key\">The key of the value.</param>\n    /// <param name=\"defaultValue\">The default value to be returned if the value cannot be found.</param>\n    /// <returns>If the item with the specified <paramref name=\"key\"/> is found in the <paramref name=\"viewData\"/> and its type is <typeparamref name=\"T\"/>, return its value. Otherwise, return the <paramref name=\"defaultValue\"/>.</returns>\n    public static T GetItemOrDefault<T>(this ViewDataDictionary viewData, string key, T defaultValue)\n    {\n        return viewData.TryGetValue(key, out var value) && value is T result\n            ? result\n            : defaultValue;\n    }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TempDataExtensions/EnhancedSessionStateTempDataProvider.cs",
    "content": "﻿#if !NETCOREAPP3_0\nusing System.Collections.Generic;\nusing System.Linq;\nusing JetBrains.Annotations;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\n\nnamespace Sakura.AspNetCore.Mvc\n{\n\t/// <summary>\n\t///     Enhanced session-state based temp data provider which can be used to save and load complex data objects.\n\t/// </summary>\n\tpublic class EnhancedSessionStateTempDataProvider : SessionStateTempDataProvider\n\t{\n\t\t/// <summary>\n\t\t///     Initialize an new instance with required services.\n\t\t/// </summary>\n\t\t/// <param name=\"objectSerializer\">The required object serializer service.</param>\n\t\tpublic EnhancedSessionStateTempDataProvider(IObjectSerializer objectSerializer)\n\t\t{\n\t\t\tObjectSerializer = objectSerializer;\n\t\t}\n\n\t\t/// <summary>\n\t\t///     The object serializer service object used by the provider.\n\t\t/// </summary>\n\t\t[PublicAPI]\n\t\tprotected IObjectSerializer ObjectSerializer { get; }\n\n\t\t/// <summary>\n\t\t///     Load temp data dictionary from the specified <see cref=\"HttpContext\" /> object.\n\t\t/// </summary>\n\t\t/// <param name=\"context\">The <see cref=\"HttpContext\" /> object.</param>\n\t\t/// <returns>The loaded temp data dictionary. If no temp data is found, this method will return <c>null</c>.</returns>\n\t\tpublic override IDictionary<string, object> LoadTempData(HttpContext context)\n\t\t{\n\t\t\tvar baseResult = base.LoadTempData(context);\n\n\t\t\treturn baseResult?.ToDictionary(item => item.Key,\n\t\t\t\titem => ObjectSerializer.Deserialize(item.Value as string));\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Save temp data dictionary to the specified <see cref=\"HttpContext\" /> object.\n\t\t/// </summary>\n\t\t/// <param name=\"context\">The <see cref=\"HttpContext\" /> object.</param>\n\t\t/// <param name=\"values\">The temp data dictionary to be saving.</param>\n\t\tpublic override void SaveTempData(HttpContext context, IDictionary<string, object> values)\n\t\t{\n\t\t\tif (values == null)\n\t\t\t{\n\t\t\t\tbase.SaveTempData(context, null);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar newDic = values.ToDictionary(item => item.Key, item => (object) ObjectSerializer.Serialize(item.Value));\n\t\t\tbase.SaveTempData(context, newDic);\n\t\t}\n\t}\n}\n\n#endif"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TempDataExtensions/HtmlContentConverter.NetCoreApp3.cs",
    "content": "﻿#if NETCOREAPP3_0\nusing System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Text.Encodings.Web;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing Microsoft.AspNetCore.Html;\n\nnamespace Sakura.AspNetCore.Mvc\n{\n\t/// <summary>\n\t///     Provide conversion ability between <see cref=\"IHtmlContent\" /> and raw HTML strings.\n\t/// </summary>\n\tpublic class HtmlContentConverter : JsonConverter<IHtmlContent>\n\t{\n\t\t/// <inheritdoc />\n\t\tpublic override IHtmlContent Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\n\t\t{\n\t\t\tvar str = JsonSerializer.Deserialize<string>(ref reader);\n\t\t\treturn new HtmlString(str);\n\t\t}\n\n\t\t/// <inheritdoc />\n\t\tpublic override void Write(Utf8JsonWriter writer, IHtmlContent value, JsonSerializerOptions options)\n\t\t{\n\t\t\tusing var sw = new StringWriter();\n\t\t\tvalue.WriteTo(sw, HtmlEncoder.Default);\n\n\t\t\tJsonSerializer.Serialize(writer, sw.ToString());\n\t\t}\n\n\t\t/// <inheritdoc />\n\t\tpublic override bool CanConvert(Type typeToConvert)\n\t\t{\n\t\t\tvar result = typeof(IHtmlContent).IsAssignableFrom(typeToConvert);\n\t\t\tDebug.WriteLine(\"result = {0}, Type = {1}\", result, typeToConvert);\n\n\t\t\treturn result;\n\t\t}\n\t}\n\n\tpublic class HtmlContentConverterFactory : JsonConverterFactory\n\t{\n\t\tprivate static HtmlContentConverter Converter { get; } = new();\n\n\t\t/// <inheritdoc />\n\t\tpublic override bool CanConvert(Type typeToConvert)\n\t\t{\n\t\t\treturn typeof(IHtmlContent).IsAssignableFrom(typeToConvert);\n\t\t}\n\n\t\t/// <inheritdoc />\n\t\tpublic override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)\n\t\t{\n\t\t\treturn Converter;\n\t\t}\n\t}\n}\n\n#endif"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TempDataExtensions/HtmlContentConverter.cs",
    "content": "﻿#if !NETCOREAPP3_0\nusing System;\nusing System.IO;\nusing System.Reflection;\nusing System.Text.Encodings.Web;\nusing Microsoft.AspNetCore.Html;\nusing Newtonsoft.Json;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Provide conversion ability between <see cref=\"IHtmlContent\" /> and raw HTML strings.\n/// </summary>\npublic class HtmlContentConverter : JsonConverter\n{\n\t/// <inheritdoc />\n\tpublic override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)\n\t{\n\t\tvar realValue = value as IHtmlContent;\n\n\t\tif (realValue == null)\n\t\t{\n\t\t\twriter.WriteNull();\n\t\t\treturn;\n\t\t}\n\n\t\tusing var sw = new StringWriter();\n\t\trealValue.WriteTo(sw, HtmlEncoder.Default);\n\t\twriter.WriteValue(sw.ToString());\n\t\t;\n\t}\n\n\t/// <inheritdoc />\n\tpublic override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)\n\t{\n\t\tvar str = existingValue as string;\n\n\t\tif (str == null) return null;\n\n\t\tvar value = new HtmlString(str);\n\t\treturn value;\n\t}\n\n\t/// <inheritdoc />\n\tpublic override bool CanConvert(Type objectType)\n\t{\n\t\treturn typeof(IHtmlContent).IsAssignableFrom(objectType);\n\t}\n}\n#endif"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TempDataExtensions/IObjectSerializer.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Define the serialization service for arbitrary object.\n/// </summary>\npublic interface IObjectSerializer\n{\n\t/// <summary>\n\t///     Serialize an object to another object.\n\t/// </summary>\n\t/// <param name=\"obj\">The object to be serializing.</param>\n\t/// <returns>The serialized object.</returns>\n\tstring Serialize(object obj);\n\n\t/// <summary>\n\t///     Deserialize object to the original object.\n\t/// </summary>\n\t/// <param name=\"obj\">The string to be deserializing.</param>\n\t/// <returns>The deserialized object.</returns>\n\tobject Deserialize(string obj);\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TempDataExtensions/JsonObjectSerializer.cs",
    "content": "﻿using System;\n\nusing JetBrains.Annotations;\n\nusing Microsoft.Extensions.Options;\n\n#if NETCOREAPP3_0\nusing System.Text.Json;\n#else\nusing Newtonsoft.Json;\n#endif\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Default <see cref=\"IObjectSerializer\" /> using ASP.NET Core in-built JSON serialization Service.\n/// </summary>\n[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]\npublic class JsonObjectSerializer : IObjectSerializer\n{\n\t/// <summary>\n\t///     Initialize a new instance of <see cref=\"JsonObjectSerializer\" /> service.\n\t/// </summary>\n\t/// <param name=\"options\">The configuration options value.</param>\n\tpublic JsonObjectSerializer(IOptions<TempDataSerializationOptions> options)\n\t{\n\t\tOptions = options.Value;\n\n#if NETCOREAPP3_0\n\t\tJsonSerializerOptions = GenerateOptions();\n#else\n\t\tJsonSerializerSettings = GenerateSettings();\n#endif\n\t}\n\n\t/// <summary>\n\t///     The configuration options instance.\n\t/// </summary>\n\tprivate TempDataSerializationOptions Options { get; }\n\n\t/// <summary>\n\t///     Deserialize object to the original object.\n\t/// </summary>\n\t/// <param name=\"obj\">The string to be deserializing.</param>\n\t/// <returns>The deserialized object.</returns>\n\tpublic object Deserialize(string obj)\n\t{\n\t\tif (string.IsNullOrEmpty(obj)) return null;\n\n\n#if NETCOREAPP3_0\n\t\tvar objInfo = JsonSerializer.Deserialize<SerializedObjectInfo>(obj);\n#else\n\t\tvar objInfo = JsonConvert.DeserializeObject<SerializedObjectInfo>(obj);\n#endif\n\n\t\tvar type = Type.GetType(objInfo.TypeName);\n\n#if NETCOREAPP3_0\n\t\treturn JsonSerializer.Deserialize(objInfo.Value, type, JsonSerializerOptions);\n#else\n\t\treturn JsonConvert.DeserializeObject(objInfo.Value, type, JsonSerializerSettings);\n#endif\n\t}\n\n\t/// <summary>\n\t///     Serialize an object to another object.\n\t/// </summary>\n\t/// <param name=\"obj\">The object to be serializing.</param>\n\t/// <returns>The serialized object.</returns>\n\tpublic string Serialize(object obj)\n\t{\n\t\tif (obj == null) return string.Empty;\n\n\t\tvar typeName = obj.GetType().AssemblyQualifiedName;\n\n\n#if NETCOREAPP3_0\n\t\treturn JsonSerializer.Serialize(new SerializedObjectInfo\n\t\t{\n\t\t\tTypeName = typeName,\n\t\t\tValue = JsonSerializer.Serialize(obj, JsonSerializerOptions)\n\t\t});\n#else\n\t\treturn JsonConvert.SerializeObject(new SerializedObjectInfo\n\t\t{\n\t\t\tTypeName = typeName,\n\t\t\tValue = JsonConvert.SerializeObject(obj, JsonSerializerSettings)\n\t\t});\n#endif\n\t}\n\n\n#if NETCOREAPP3_0\n\t/// <summary>\n\t///     The <see cref=\"System.Text.Json.JsonSerializerOptions\" /> instance used to controlling the JSON serialization\n\t///     process.\n\t/// </summary>\n\tprivate JsonSerializerOptions JsonSerializerOptions { get; }\n\n\t/// <summary>\n\t///     Generate the <see cref=\"JsonSerializerOptions\" /> value.\n\t/// </summary>\n\t/// <returns>The generated <see cref=\"System.Text.Json.JsonSerializerOptions\" /> instance.</returns>\n\tprivate JsonSerializerOptions GenerateOptions()\n\t{\n\t\tvar result = new JsonSerializerOptions();\n\n\t\t// Add additional converters.\n\t\tif (Options.Converters != null)\n\t\t\tforeach (var item in Options.Converters)\n\t\t\t\tresult.Converters.Add(item);\n\n\t\treturn result;\n\t}\n#else\n\n\t/// <summary>\n\t///     The <see cref=\"Newtonsoft.Json.JsonSerializerSettings\" /> instance used to controlling the JSON serialization\n\t///     process.\n\t/// </summary>\n\tprivate JsonSerializerSettings JsonSerializerSettings { get; }\n\n\t/// <summary>\n\t///     Generate the <see cref=\"Newtonsoft.Json.JsonSerializerSettings\" /> value.\n\t/// </summary>\n\t/// <returns>The generated <see cref=\"System.Text.Json.JsonSerializerOptions\" /> instance.</returns>\n\tprivate JsonSerializerSettings GenerateSettings()\n\t{\n\t\tvar result = new JsonSerializerSettings();\n\n\t\tif (Options.Converters != null)\n\t\t{\n\t\t\tforeach (var item in Options.Converters)\n\t\t\t{\n\t\t\t\tresult.Converters.Add(item);\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n#endif\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TempDataExtensions/Sakura.AspNetCore.Mvc.TempDataExtensions.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n\t<PropertyGroup>\n\t\t<LangVersion>latest</LangVersion>\n\t\t<Description>This package add the data type compatibility for ASP.NET Core TempData feature.</Description>\n\t\t<AssemblyTitle>ASP.NET Core TempData Extension Library</AssemblyTitle>\n\t\t<VersionPrefix>1.1.0</VersionPrefix>\n\t\t<Authors>Iris Sakura</Authors>\n\t\t<TargetFrameworks>netstandard1.6;net451;netcoreapp3.0</TargetFrameworks>\n\t\t<AssemblyName>Sakura.AspNetCore.Mvc.TempDataExtensions</AssemblyName>\n\t\t<PackageId>Sakura.AspNetCore.Mvc.TempDataExtensions</PackageId>\n\t\t<PackageTags>ASP.NET;ASP.NETCore;MVC;MVCCore;TempData;ITempDataProvider</PackageTags>\n\t\t<PackageReleaseNotes>Update the TempData to add IHtmlContent Support.</PackageReleaseNotes>\n\t\t<PackageProjectUrl>https://github.com/sgjsakura/AspNetCore</PackageProjectUrl>\n\t\t<PackageLicenseUrl></PackageLicenseUrl>\n\t\t<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>\n\t\t<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>\n\t\t<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>\n\t\t<GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n\t\t<RepositoryUrl>https://github.com/sgjsakura/AspNetCore.git</RepositoryUrl>\n\t\t<RootNamespace>Sakura.AspNetCore.Mvc</RootNamespace>\n\t\t<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n\t\t<Version>2.0.0</Version>\n\t</PropertyGroup>\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'netstandard1.6' \">\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Mvc.ViewFeatures\" Version=\"1.0.0\" />\n\t</ItemGroup>\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'netcoreapp3.0' \">\n\t\t<FrameworkReference Include=\"Microsoft.AspNetCore.App\" />\n\t</ItemGroup>\n\n\t<PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n\t\t<GenerateDocumentationFile>true</GenerateDocumentationFile>\n\t</PropertyGroup>\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'net451' \">\n\t\t<Reference Include=\"System\" />\n\t\t<Reference Include=\"Microsoft.CSharp\" />\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Mvc.ViewFeatures\" Version=\"1.0.0\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<PackageReference Include=\"JetBrains.Annotations\" Version=\"2024.3.0\" />\n\t\t<PackageReference Include=\"Microsoft.Extensions.Options\" Version=\"1.0.0\" />\n\t</ItemGroup>\n\n</Project>"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TempDataExtensions/SerializedObjectInfo.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Represents as the information for a serialized object.\n/// </summary>\npublic class SerializedObjectInfo\n{\n\t/// <summary>\n\t///     The type name of the object.\n\t/// </summary>\n\tpublic string TypeName { get; set; }\n\n\t/// <summary>\n\t///     The value text of the object.\n\t/// </summary>\n\tpublic string Value { get; set; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TempDataExtensions/ServiceCollectionExtensions.cs",
    "content": "﻿using JetBrains.Annotations;\nusing Microsoft.AspNetCore.Html;\nusing Microsoft.Extensions.DependencyInjection.Extensions;\nusing Sakura.AspNetCore.Mvc;\nusing System;\n\n#if NETCOREAPP3_0\nusing Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure;\n#else\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\n#endif\n\n// ReSharper disable once CheckNamespace\n\nnamespace Microsoft.Extensions.DependencyInjection;\n\n/// <summary>\n///     Provide extension methods for service configurations. This class is static.\n/// </summary>\npublic static class ServiceCollectionExtensions\n{\n\t/// <summary>\n\t///     Replace the default temp data implementation in order to support complex data storage.\n\t/// </summary>\n\t/// <param name=\"services\">The service container to adding the service.</param>\n\t/// <param name=\"configureOptions\">Additional steps to configure the <see cref=\"TempDataSerializationOptions\" />.</param>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"services\" /> is <c>null</c>.</exception>\n\t[PublicAPI]\n\tpublic static IServiceCollection AddEnhancedTempData([NotNull] this IServiceCollection services,\n\t\tAction<TempDataSerializationOptions> configureOptions = null)\n\t{\n\t\t// Argument check\n\t\tif (services == null)\n\t\t\tthrow new ArgumentNullException(nameof(services));\n\n\t\t// Add the IObjectSerializer implementation\n\t\tservices.TryAddSingleton<IObjectSerializer, JsonObjectSerializer>();\n\t\tservices.AddOptions();\n\n#if NETCOREAPP3_0\n\t\tservices.Replace(new(typeof(TempDataSerializer),\n\t\t\ttypeof(TypedJsonTempDataSerializer), ServiceLifetime.Singleton));\n#else\n\t\t// Replace default ITempDataProvider\n\t\tservices.Replace(new(typeof(ITempDataProvider), typeof(EnhancedSessionStateTempDataProvider),\n\t\t\tServiceLifetime.Singleton));\n#endif\n\n\t\t// Config options\n\t\tif (configureOptions != null) services.Configure(configureOptions);\n\n\t\treturn services;\n\t}\n\n\t/// <summary>\n\t///     Enable serialization for <see cref=\"IHtmlContent\" /> objects.\n\t/// </summary>\n\t/// <param name=\"options\">The <see cref=\"TempDataSerializationOptions\" /> to be configuring.</param>\n\t/// <returns>The <paramref name=\"options\" /> argument.</returns>\n\t/// <remarks>\n\t///     This method use the <see cref=\"HtmlContentConverter\" /> to convert between HTML content and raw HTML strings. This\n\t///     manner only guarantees an equivalent page rendering result, however, the actual <see cref=\"IHtmlContent\" />\n\t///     instance maybe changed. Under such circumstance, please do not use this manner if you are trying to modify the\n\t///     inner structure for any <see cref=\"IHtmlContent\" /> instance between actions.\n\t/// </remarks>\n\t[PublicAPI]\n\tpublic static TempDataSerializationOptions EnableHtmlContentSerialization(this TempDataSerializationOptions options)\n\t{\n\t\t// Add converter for IHtmlContent.\n\t\toptions.Converters.Add(new HtmlContentConverter());\n\t\treturn options;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TempDataExtensions/TempDataSerializationOptions.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Collections.ObjectModel;\n\n#if NETCOREAPP3_0\nusing JsonConverter = System.Text.Json.Serialization.JsonConverter;\n#else\nusing JsonConverter = Newtonsoft.Json.JsonConverter;\n#endif\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n///     Provide optional configuration data for temp data serialization process.\n/// </summary>\npublic class TempDataSerializationOptions\n{\n\t/// <summary>\n\t///     A collection of <see cref=\"JsonConverter\" /> should be used during the JSON serialization.\n\t/// </summary>\n\tpublic IList<JsonConverter> Converters { get; } = new Collection<JsonConverter>();\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.TempDataExtensions/TypedJsonTempDataSerializer.cs",
    "content": "﻿#if NETCOREAPP3_0\nusing System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure;\n\nnamespace Sakura.AspNetCore.Mvc\n{\n\t/// <summary>\n\t///     Enhanced temp data serializer used in ASP.NET Core 3.0 Apps.\n\t/// </summary>\n\t/// <inheritdoc />\n\tpublic class TypedJsonTempDataSerializer : TempDataSerializer\n\t{\n\t\t/// <summary>\n\t\t///     Initialize a new instance of <see cref=\"TypedJsonTempDataSerializer\" />.\n\t\t/// </summary>\n\t\t/// <param name=\"objectSerializer\">The internal <see cref=\"IObjectSerializer\" /> service instance.</param>\n\t\tpublic TypedJsonTempDataSerializer(IObjectSerializer objectSerializer)\n\t\t{\n\t\t\tObjectSerializer = objectSerializer;\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Internal helper used to serialize between object and string.\n\t\t/// </summary>\n\t\tprivate IObjectSerializer ObjectSerializer { get; }\n\n\t\t/// <inheritdoc />\n\t\tpublic override IDictionary<string, object> Deserialize(byte[] unprotectedData)\n\t\t{\n\t\t\tvar dic = JsonSerializer.Deserialize<Dictionary<string, string>>(unprotectedData);\n\n\t\t\tvar result = new Dictionary<string, object>();\n\n\t\t\tforeach (var (key, value) in dic) result.Add(key, ObjectSerializer.Deserialize(value));\n\n\t\t\treturn result;\n\t\t}\n\n\t\t/// <inheritdoc />\n\t\tpublic override byte[] Serialize(IDictionary<string, object> values)\n\t\t{\n\t\t\tif (values == null || values.Count == 0) return Array.Empty<byte>();\n\n\t\t\tvar realDictionary = new Dictionary<string, string>();\n\n\t\t\tforeach (var (key, value) in values) realDictionary.Add(key, ObjectSerializer.Serialize(value));\n\n\t\t\treturn JsonSerializer.SerializeToUtf8Bytes(realDictionary);\n\t\t}\n\t}\n}\n\n#endif"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.VisualStyles/Sakura.AspNetCore.Mvc.VisualStyles.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>netstandard2.0</TargetFramework>\n    <RootNamespace>Sakura.AspNetCore.Mvc</RootNamespace>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.Extensions.Options\" Version=\"2.1.1\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.VisualStyles/VisualStyle.cs",
    "content": "﻿using System;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n/// Define the common visual style names. This class is static.\n/// </summary>\npublic class VisualStyle : IEquatable<VisualStyle>\n{\n\t/// <summary>\n\t/// The name of the UI framework. \n\t/// </summary>\n\tpublic string Framework { get; }\n\n\t/// <summary>\n\t/// The version fo the UI Framework.\n\t/// </summary>\n\tpublic string Version { get; }\n\n\t/// <summary>\n\t/// Initialize a new instance of <see cref=\"VisualStyle\"/>.\n\t/// </summary>\n\t/// <param name=\"framework\">The name of the framework UI.</param>\n\t/// <param name=\"version\">The version of the framework UI.</param>\n\t/// <exception cref=\"ArgumentNullException\"></exception>\n\tpublic VisualStyle(string framework, string version)\n\t{\n\n\t\tFramework = framework ?? throw new ArgumentNullException(nameof(framework));\n\t\tVersion = version ?? throw new ArgumentNullException(nameof(version));\n\t}\n\n\t/// <summary>\n\t/// Provide command UI framework names. This class is static.\n\t/// </summary>\n\tpublic static class CommonFrameworks\n\t{\n\t\t/// <summary>\n\t\t/// The Bootstrap framework UI.\n\t\t/// </summary>\n\t\tpublic const string Bootstrap = \"Bootstrap\";\n\t}\n\n\t/// <summary>\n\t/// Bootstrap 4.\n\t/// </summary>\n\tpublic static VisualStyle Bootstrap4 { get; } = new VisualStyle(CommonFrameworks.Bootstrap, \"4\");\n\n\t/// <summary>\n\t/// Bootstrap 5.\n\t/// </summary>\n\tpublic static VisualStyle Bootstrap5 { get; } = new VisualStyle(CommonFrameworks.Bootstrap, \"5\");\n\n\t/// <summary>\n\t/// Try match 2 visual styles.\n\t/// </summary>\n\t/// <param name=\"first\">The first visual style to be matching.</param>\n\t/// <param name=\"second\">The second visual style to be matching.</param>\n\t/// <returns></returns>\n\t/// <exception cref=\"ArgumentNullException\"></exception>\n\tpublic static VisualStyleMatchType TryMatch(VisualStyle first, VisualStyle second)\n\t{\n\t\tif (first == null) throw new ArgumentNullException(nameof(first));\n\t\tif (second == null) throw new ArgumentNullException(nameof(second));\n\n\t\tif (string.Equals(first.Framework, second.Framework))\n\t\t{\n\t\t\treturn string.Equals(first.Version, second.Version)\n\t\t\t\t? VisualStyleMatchType.Exact\n\t\t\t\t: VisualStyleMatchType.Framework;\n\t\t}\n\n\t\treturn VisualStyleMatchType.None;\n\t}\n\n\t#region Equality\n\n\t/// <inheritdoc />\n\tpublic bool Equals(VisualStyle other)\n\t{\n\t\tif (ReferenceEquals(null, other)) return false;\n\t\tif (ReferenceEquals(this, other)) return true;\n\t\treturn string.Equals(Framework, other.Framework, StringComparison.OrdinalIgnoreCase) && string.Equals(Version, other.Version, StringComparison.OrdinalIgnoreCase);\n\t}\n\n\t/// <inheritdoc />\n\tpublic override bool Equals(object obj)\n\t{\n\t\tif (ReferenceEquals(null, obj)) return false;\n\t\tif (ReferenceEquals(this, obj)) return true;\n\t\tif (obj.GetType() != this.GetType()) return false;\n\t\treturn Equals((VisualStyle)obj);\n\t}\n\n\t/// <inheritdoc />\n\tpublic override int GetHashCode()\n\t{\n\t\tunchecked\n\t\t{\n\t\t\treturn (StringComparer.OrdinalIgnoreCase.GetHashCode(Framework) * 397) ^ StringComparer.OrdinalIgnoreCase.GetHashCode(Version);\n\t\t}\n\t}\n\n\tpublic static bool operator ==(VisualStyle left, VisualStyle right)\n\t{\n\t\treturn Equals(left, right);\n\t}\n\n\tpublic static bool operator !=(VisualStyle left, VisualStyle right)\n\t{\n\t\treturn !Equals(left, right);\n\t}\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.VisualStyles/VisualStyleMatchResult.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc;\n\npublic class VisualStyleMatchResult(\n    VisualStyle requiredStyle,\n    VisualStyle selectedStyle,\n    VisualStyleMatchType matchType)\n{\n    public VisualStyle RequiredStyle { get; } = requiredStyle;\n\n    public VisualStyle SelectedStyle { get; } = selectedStyle;\n\n    public VisualStyleMatchType MatchType { get; } = matchType;\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.VisualStyles/VisualStyleMatchType.cs",
    "content": "﻿namespace Sakura.AspNetCore.Mvc;\n\npublic enum VisualStyleMatchType\n{\n    None,\n    Framework,\n    Exact,\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.VisualStyles/VisualStyleOptions.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Collections.ObjectModel;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n/// Provide options for visual style settings.\n/// </summary>\npublic class VisualStyleOptions\n{\n    /// <summary>\n    /// The preferred visual styles. Item orders indicate the preference priority.\n    /// </summary>\n    public IList<VisualStyle> PreferredStyles { get; } = new Collection<VisualStyle>();\n\n\n    /// <summary>\n    /// The minimal match level for the style selection.\n    /// </summary>\n    public VisualStyleMatchType MinimalMatchLevel { get; set; } = VisualStyleMatchType.None;\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.Mvc.VisualStyles/VisualStyleService.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.Extensions.Options;\n\nnamespace Sakura.AspNetCore.Mvc;\n\n/// <summary>\n/// Provide service for visual style detection and selection.\n/// </summary>\npublic class VisualStyleService(IOptions<VisualStyleOptions> options)\n{\n    private VisualStyleOptions Options { get; } = options.Value;\n\n    public VisualStyleMatchResult Match(IEnumerable<VisualStyle> allowedStyles)\n    {\n        var result =\n            from i in Options.PreferredStyles\n            from j in allowedStyles\n            let matchType = VisualStyle.TryMatch(i, j)\n            where matchType >= Options.MinimalMatchLevel\n            orderby matchType descending\n            select new VisualStyleMatchResult(i, j, matchType);\n\n        return result.FirstOrDefault();\n    }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList/CacheMode.cs",
    "content": "﻿using System;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Define the execution policy for caching features.\n/// </summary>\n[Flags]\npublic enum CacheMode\n{\n\t/// <summary>\n\t///     Always using real-time calculated data until user caches data manually.\n\t/// </summary>\n\tManual = 0,\n\n\t/// <summary>\n\t///     Automatically cache and fresh data when access it in the first time.\n\t/// </summary>\n\tAuto,\n\n\t/// <summary>\n\t///     Immediately cache and fresh data whenever caching is built up and data is dismissed.\n\t/// </summary>\n\tAutoWithPrefetch\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList/Cacheable.cs",
    "content": "﻿using System;\nusing System.Threading;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Represent as a cachable data.\n/// </summary>\n/// <typeparam name=\"T\">The type of the data to be caching。</typeparam>\n/// <seealso cref=\"ICacheControl\" />\npublic class Cacheable<T> : ICacheControl\n{\n\t/// <summary>\n\t///     Auto refreshing counter used by refreshing control feature.\n\t/// </summary>\n\tprivate int _AutoRefreshControlCount;\n\n\t/// <summary>\n\t///     Initialize a new instance using specified information.\n\t/// </summary>\n\t/// <param name=\"getDataCallback\">The callback delegate to obtain the data to be caching.</param>\n\t/// <param name=\"cacheCallback\">\n\t///     A optional callback delegate to generate a cached copy for original data. If this parameter\n\t///     is <c>null</c>, <see cref=\"DefaultCacheCallback\" /> will be used.\n\t/// </param>\n\t/// <param name=\"cacheMode\">\n\t///     The default cache mode of the cache. The default value of this parameter is\n\t///     <see cref=\"CacheMode.Manual\" />.\n\t/// </param>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"getDataCallback\" /> is <c>null</c>.</exception>\n\tpublic Cacheable(Func<T> getDataCallback, Func<T, T> cacheCallback = null, CacheMode cacheMode = CacheMode.Manual)\n\t{\n\t\tGetDataCallback = getDataCallback ?? throw new ArgumentNullException(nameof(getDataCallback));\n\n\t\t// Set the caching callback.\n\t\tCacheCallback = cacheCallback ?? DefaultCacheCallback;\n\n\t\t// Set the caching mode.\n\t\tCacheMode = cacheMode;\n\n\t\t// Trigger the first dismiss notification.\n\t\tNotifyDismiss();\n\t}\n\n\t/// <summary>\n\t///     Get the callback delegate for generating a cached copy.\n\t/// </summary>\n\tprivate Func<T, T> CacheCallback { get; }\n\n\t/// <summary>\n\t///     Get the callback delegate for get the data to be caching.\n\t/// </summary>\n\tprivate Func<T> GetDataCallback { get; }\n\n\t/// <summary>\n\t///     Get the cached data. If no cache is available, the default value of <typeparamref name=\"T\" /> will be returned.\n\t/// </summary>\n\tpublic T CachedData { get; private set; }\n\n\t/// <summary>\n\t///     Get a value that indicates whether the cache is available now.\n\t/// </summary>\n\tpublic bool IsCached { get; private set; }\n\n\t/// <summary>\n\t///     Get the cached data. If cache is not available, the load result from source will be returned, and caching may or\n\t///     may not be executed according to the behavior indicated by <see cref=\"CacheMode\" /> property.\n\t/// </summary>\n\tpublic T Data\n\t{\n\t\tget\n\t\t{\n\t\t\t// Alaways return the cache if available.\n\t\t\tif (IsCached)\n\t\t\t\treturn CachedData;\n\n\t\t\t// No caching if caching mode is set to \"manual\".\n\t\t\tif (CacheMode == CacheMode.Manual)\n\t\t\t\treturn GetDataDirectly();\n\n\t\t\t// Otherwise, reload the cache and return the result.\n\t\t\tReload();\n\t\t\treturn CachedData;\n\t\t}\n\t}\n\n\t/// <summary>\n\t///     Forcely remove the current cache and reload it.\n\t/// </summary>\n\tpublic void Reload()\n\t{\n\t\tCachedData = CacheCallback(GetDataCallback());\n\t\tIsCached = true;\n\t}\n\n\t/// <summary>\n\t///     Generate a cache from the data source. If the data is already cached, this method will do nothing.\n\t/// </summary>\n\tpublic void Cache()\n\t{\n\t\tif (!IsCached)\n\t\t\tReload();\n\t}\n\n\t/// <summary>\n\t///     Uncache the current value. This method will make cache dismissed, but will not automatically remove or refresh the\n\t///     cached data\n\t/// </summary>\n\tpublic void Uncache()\n\t{\n\t\tIsCached = false;\n\t}\n\n\t/// <summary>\n\t///     Get or set the cache mode of the current object.\n\t/// </summary>\n\tpublic CacheMode CacheMode { get; set; }\n\n\n\t/// <summary>\n\t///     Temporary disable auto refreshing.\n\t/// </summary>\n\t/// <returns>An <see cref=\"IDisposable\" /> object which will re-enabled the auto refreshing when it is disposed.</returns>\n\tpublic IDisposable DisableAutoRefresh()\n\t{\n\t\treturn new DisableAutoRefreshController<T>(this);\n\t}\n\n\t/// <summary>\n\t///     The default value for <see cref=\"CacheCallback\" />\n\t/// </summary>\n\t/// <param name=\"item\">The data to be cached.</param>\n\t/// <returns>The cached data.</returns>\n\t/// <remarks>This method will do nothing.</remarks>\n\tprivate static T DefaultCacheCallback(T item)\n\t{\n\t\treturn item;\n\t}\n\n\t/// <summary>\n\t///     Get data directly from the source and ignore any cache.\n\t/// </summary>\n\t/// <returns>The data got directly from the source.</returns>\n\tpublic T GetDataDirectly()\n\t{\n\t\treturn GetDataCallback();\n\t}\n\n\t/// <summary>\n\t///     Notify the cached data is dismissed, and reload it if necessary.\n\t/// </summary>\n\tpublic void NotifyDismiss()\n\t{\n\t\t// Make the cache unavailable.\n\t\tIsCached = false;\n\n\t\t// Automatically reload the cache if cache mode is set to \"prefetch\" and currently caching refresh is enabled.\n\t\tif (CacheMode == CacheMode.AutoWithPrefetch && _AutoRefreshControlCount == 0)\n\t\t\tReload();\n\t}\n\n\n\t/// <summary>\n\t///     Increase the auto refreshing control value to disable auto refreshing temporary.\n\t/// </summary>\n\tinternal void AddAutoRefreshControl()\n\t{\n\t\tInterlocked.Increment(ref _AutoRefreshControlCount);\n\t}\n\n\t/// <summary>\n\t///     Decrease the auto refreshing control value, and reload data immediately if auto refreshing is re-enabled.\n\t/// </summary>\n\tinternal void RemoveAutoRefreshControl()\n\t{\n\t\t// If the counter is zero then reload data.\n\t\tif (Interlocked.Decrement(ref _AutoRefreshControlCount) == 0)\n\t\t\tReload();\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList/DisableAutoRefreshController.cs",
    "content": "﻿using System;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Controller object used to disable auto refreshing. This class cannot be inherited.\n/// </summary>\ninternal sealed class DisableAutoRefreshController<T> : IDisposable\n{\n\t/// <summary>\n\t///     Initialize a new instance.\n\t/// </summary>\n\t/// <param name=\"cacheObject\">The master <see cref=\"Cacheable{T}\" /> object.</param>\n\tpublic DisableAutoRefreshController(Cacheable<T> cacheObject)\n\t{\n\t\tCacheObject = cacheObject;\n\t\tCacheObject.AddAutoRefreshControl();\n\t}\n\n\t/// <summary>\n\t///     Get the master <see cref=\"Cacheable{T}\" /> object for this object.\n\t/// </summary>\n\tprivate Cacheable<T> CacheObject { get; }\n\n\t#region IDisposable Support\n\n\t/// <summary>\n\t///     Get or set a value that indicates whether this object is disposed.\n\t/// </summary>\n\tprivate bool IsDisposed { get; set; }\n\n\t/// <summary>\n\t///     Dispose the current object and free all resources.\n\t/// </summary>\n\t/// <param name=\"disposing\">Whether unmanaged resource should be disposed.</param>\n\tprivate void Dispose(bool disposing)\n\t{\n\t\tif (!IsDisposed)\n\t\t{\n\t\t\t// Remark\n\t\t\tIsDisposed = true;\n\n\t\t\tif (disposing)\n\t\t\t\tCacheObject.RemoveAutoRefreshControl();\n\t\t}\n\t}\n\n\t/// <summary>\n\t///     Dispose the current object and free all resources.\n\t/// </summary>\n\t~DisableAutoRefreshController()\n\t{\n\t\tDispose(false);\n\t}\n\n\t/// <summary>\n\t///     Dispose the current object and free all resources.\n\t/// </summary>\n\tpublic void Dispose()\n\t{\n\t\tDispose(true);\n\t\tGC.SuppressFinalize(this);\n\t}\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList/DynamicPagedList.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Represent as a paged list with page-changing support.\n/// </summary>\n/// <typeparam name=\"T\">The element type in the data source.</typeparam>\npublic class DynamicPagedList<T> : DynamicPagedListBase<IEnumerable<T>, T>\n{\n\t/// <summary>\n\t///     Initialize a new instance with specified information.\n\t/// </summary>\n\t/// <param name=\"source\">The data source to be paging.</param>\n\t/// <param name=\"pageSize\">The size of each page.</param>\n\t/// <param name=\"pageIndex\">The index of the current page. Page index starts from 1.</param>\n\t/// <param name=\"cacheOptions\">Additional options for the paged list.</param>\n\tpublic DynamicPagedList(IEnumerable<T> source, int pageSize, int pageIndex = 1,\n\t\tDynamicPagedListCacheOptions cacheOptions = null) : base(source, pageSize, pageIndex, cacheOptions)\n\t{\n\t}\n\n\t/// <summary>\n\t///     Get the total count of the data source.\n\t/// </summary>\n\t/// <returns>The total count of the data source.</returns>\n\tprotected override int GetTotalCount()\n\t{\n\t\treturn Source.Count();\n\t}\n\n\t/// <summary>\n\t///     Get the data in the current page.\n\t/// </summary>\n\t/// <returns>The data in the current page.</returns>\n\tprotected override IEnumerable<T> GetCurrentPage()\n\t{\n\t\treturn Source.Skip((PageIndex - 1) * PageSize).Take(PageSize);\n\t}\n\n\t/// <summary>\n\t///     Make a cached copy for the current page.\n\t/// </summary>\n\t/// <param name=\"source\">The data source to be caching.</param>\n\t/// <returns>The cached data.</returns>\n\tprotected override IEnumerable<T> CacheData(IEnumerable<T> source)\n\t{\n\t\treturn source.ToArray();\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList/DynamicPagedListBase.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Provide base common features for <see cref=\"DynamicPagedList{T}\" /> and <see cref=\"DynamicQueryablePagedList{T}\" />\n///     .\n/// </summary>\n/// <typeparam name=\"TCollection\">The collection type of the list.</typeparam>\n/// <typeparam name=\"TElement\">The element type of the list.</typeparam>\npublic abstract class DynamicPagedListBase<TCollection, TElement> : IPagedList<TElement>\n\twhere TCollection : IEnumerable<TElement>\n{\n\t#region Constructors\n\n\t/// <summary>\n\t///     Initialize a new instance with specified information.\n\t/// </summary>\n\t/// <param name=\"source\">The data source to be paging.</param>\n\t/// <param name=\"pageSize\">The size of each page.</param>\n\t/// <param name=\"pageIndex\">The index of the current page, start from 1.</param>\n\t/// <param name=\"cacheOptions\">Additional cacheOptions for the paged list.</param>\n\tprotected DynamicPagedListBase(TCollection source, int pageSize, int pageIndex = 1,\n\t\tDynamicPagedListCacheOptions cacheOptions = null)\n\t{\n\t\t// Default parameters\n\t\tvar defaultOptions = new DynamicPagedListCacheOptions\n\t\t{\n\t\t\tCurrentPageCacheMode = CacheMode.Manual,\n\t\t\tTotalCountCacheMode = CacheMode.Manual\n\t\t};\n\n\t\t// Merge cacheOptions\n\t\tcacheOptions = cacheOptions ?? defaultOptions;\n\n\t\t// Initialize the data source\n\t\tSource = source != null ? source : throw new ArgumentNullException(nameof(source));\n\n\n\t\t// Set Paging information\n\t\tPageSize = pageSize;\n\t\tPageIndex = pageIndex;\n\n\n\t\t// Caching total count for first time\n\t\tTotalCountCacheInternal = new Cacheable<int>(GetTotalCount, cacheMode: cacheOptions.TotalCountCacheMode);\n\t\t// Caching current page for first time\n\t\tCurrentPageCacheInternal = new Cacheable<TCollection>(GetCurrentPage, CacheData, cacheOptions.CurrentPageCacheMode);\n\n\t\t// Set initialized flag\n\t\tIsInitialized = true;\n\t}\n\n\t#endregion\n\n\t#region Data Source\n\n\t/// <summary>\n\t///     Get the original data source.\n\t/// </summary>\n\tpublic TCollection Source { get; }\n\n\t#endregion\n\n\t#region Enumerator\n\n\t/// <summary>\n\t///     Implemetation enumeration for <see cref=\"DynamicPagedListBase{TCollection,TElement}\" /> object.\n\t/// </summary>\n\tpublic struct Enumerator : IEnumerator<TElement>\n\t{\n\t\t/// <summary>\n\t\t///     Get the source list of the enumerator.\n\t\t/// </summary>\n\t\t[NotNull]\n\t\tprivate DynamicPagedListBase<TCollection, TElement> List { get; }\n\n\t\t/// <summary>\n\t\t///     Get the version of the enumerator.\n\t\t/// </summary>\n\t\tprivate int Version { get; }\n\n\t\t/// <summary>\n\t\t///     Get the inner enumerator used for this enumerator.\n\t\t/// </summary>\n\t\t[NotNull]\n\t\tprivate IEnumerator<TElement> InnerEnumerator { get; }\n\n\t\tinternal Enumerator([NotNull] DynamicPagedListBase<TCollection, TElement> source)\n\t\t{\n\t\t\tList = source;\n\t\t\tInnerEnumerator = source.CurrentPage.GetEnumerator();\n\t\t\tVersion = source._Version;\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Check the version of the enumrator. If the version not matches, throw an exception.\n\t\t/// </summary>\n\t\tprivate void CheckVersion()\n\t\t{\n\t\t\tif (List._Version != Version)\n\t\t\t\tthrow new InvalidOperationException(\n\t\t\t\t\t\"You cannot change the paging information or reload page while enumeration is proceed on this paged list.\");\n\t\t}\n\n\t\t/// <inheritdoc />\n\t\tpublic bool MoveNext()\n\t\t{\n\t\t\tCheckVersion();\n\t\t\treturn InnerEnumerator.MoveNext();\n\t\t}\n\n\t\t/// <inheritdoc />\n\t\tpublic void Reset()\n\t\t{\n\t\t\tCheckVersion();\n\t\t\tInnerEnumerator.Reset();\n\t\t}\n\n\t\t/// <inheritdoc />\n\t\tpublic TElement Current\n\t\t{\n\t\t\tget\n\t\t\t{\n\t\t\t\tCheckVersion();\n\t\t\t\treturn InnerEnumerator.Current;\n\t\t\t}\n\t\t}\n\n\t\t/// <inheritdoc />\n\t\tobject IEnumerator.Current => ((IEnumerator) InnerEnumerator).Current;\n\n\t\t/// <inheritdoc />\n\t\tpublic void Dispose()\n\t\t{\n\t\t\tInnerEnumerator.Dispose();\n\t\t}\n\t}\n\n\t#endregion\n\n\t#region Core Features must be implemented in Derived Classes\n\n\t/// <summary>\n\t///     When be derived, returns the total count of the data source.\n\t/// </summary>\n\t/// <returns>The total count of the data source.</returns>\n\tprotected abstract int GetTotalCount();\n\n\t/// <summary>\n\t///     When be derived, get the elements in the current page.\n\t/// </summary>\n\t/// <returns>The collection of elements in the current page.</returns>\n\tprotected abstract TCollection GetCurrentPage();\n\n\t/// <summary>\n\t///     When be derived, Cache the data page.\n\t/// </summary>\n\t/// <param name=\"source\">The data page to be cached.</param>\n\t/// <returns>The cached copy of <paramref name=\"source\" />.</returns>\n\tprotected abstract TCollection CacheData(TCollection source);\n\n\t#endregion\n\n\t#region Data Caching and Controllers\n\n\t/// <summary>\n\t///     Get the internal caching object for total count.\n\t/// </summary>\n\tprivate Cacheable<int> TotalCountCacheInternal { get; }\n\n\t/// <summary>\n\t///     Get the internal caching object for current page.\n\t/// </summary>\n\tprivate Cacheable<TCollection> CurrentPageCacheInternal { get; }\n\n\t/// <summary>\n\t///     Get the cache controller for the total count cache.\n\t/// </summary>\n\tpublic ICacheControl TotalCountCache => TotalCountCacheInternal;\n\n\t/// <summary>\n\t///     Get the cache controller for the current page cache.\n\t/// </summary>\n\tpublic ICacheControl CurrentPageCache => CurrentPageCacheInternal;\n\n\t#endregion\n\n\t#region Cached Core Data Properties\n\n\t/// <summary>\n\t///     Get the data in current page.\n\t/// </summary>\n\tpublic TCollection CurrentPage => CurrentPageCacheInternal.Data;\n\n\t/// <summary>\n\t///     Get the total count of the data source.\n\t/// </summary>\n\tpublic int TotalCount => TotalCountCacheInternal.Data;\n\n\t#endregion\n\n\t#region Paging Core Features\n\n\t/// <summary>\n\t///     The current page index.\n\t/// </summary>\n\tprivate int _PageIndex;\n\n\t/// <summary>\n\t///     The size of each page.\n\t/// </summary>\n\tprivate int _PageSize;\n\n\t/// <summary>\n\t///     Get or set the current page index. The page index is started from 1.\n\t/// </summary>\n\tpublic int PageIndex\n\t{\n\t\tget => _PageIndex;\n\t\tset\n\t\t{\n\t\t\t// If initialized, make detailed value check\n\t\t\tif (IsInitialized)\n\t\t\t{\n\t\t\t\t// Range check\n\t\t\t\tif (value < 1 || value > TotalPage)\n\t\t\t\t\tthrow new ArgumentOutOfRangeException(nameof(value), value, \"The page index is out of valid range.\");\n\n\t\t\t\tif (_PageIndex != value)\n\t\t\t\t{\n\t\t\t\t\t_PageIndex = value;\n\n\t\t\t\t\t// Notify data dismiss\n\t\t\t\t\tCurrentPageCacheInternal.NotifyDismiss();\n\t\t\t\t}\n\t\t\t}\n\t\t\t// When initializing, skip the detailed check\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Basic range check\n\t\t\t\tif (value < 1)\n\t\t\t\t\tthrow new ArgumentOutOfRangeException(nameof(value), value, \"The page index is out of valid range.\");\n\n\t\t\t\t_PageIndex = value;\n\t\t\t}\n\t\t}\n\t}\n\n\t/// <summary>\n\t///     Get or set the size of each page.\n\t/// </summary>\n\t/// <remarks>\n\t///     Modify this property will reset the <see cref=\"PageIndex\" /> to <c>1</c>. This may raise data refreshing. If you\n\t///     will change <see cref=\"PageIndex\" /> later, please condiser call <see cref=\"ICacheControl.DisableAutoRefresh\" /> on\n\t///     <see cref=\"CurrentPageCache\" /> property in order to improve the performance.\n\t/// </remarks>\n\tpublic int PageSize\n\t{\n\t\tget => _PageSize;\n\t\tset\n\t\t{\n\t\t\tif (value <= 0)\n\t\t\t\tthrow new ArgumentOutOfRangeException(nameof(value), value, \"Page size must be positive integer\");\n\n\t\t\t_PageSize = value;\n\t\t\tPageIndex = 1;\n\t\t}\n\t}\n\n\t#endregion\n\n\t#region Additional propreties\n\n\t/// <summary>\n\t///     Get a value that indicates if the object has been initialized.\n\t/// </summary>\n\tpublic bool IsInitialized { get; }\n\n\t/// <summary>\n\t///     Get the total page count of the data source.\n\t/// </summary>\n\tpublic int TotalPage => (TotalCount - 1) / PageSize + 1;\n\n\t/// <summary>\n\t///     Get the count of current page.\n\t/// </summary>\n\tpublic int Count\n\t{\n\t\tget\n\t\t{\n\t\t\t// Cache property value.\n\t\t\tvar totalCount = TotalCount;\n\n\t\t\t// Total page\n\t\t\t// NOTE: Cannot use propreties since it may cause duplicated computation\n\t\t\tvar totalPage = (totalCount - 1) / PageSize + 1;\n\n\t\t\t// Only the last page should be calculated.\n\t\t\treturn PageIndex != totalPage ? PageSize : totalCount - PageSize * (totalPage - 1);\n\t\t}\n\t}\n\n\t#endregion\n\n\t#region Version Control and Enumeration\n\n\t/// <summary>\n\t///     Version field used to check enumeration.\n\t/// </summary>\n\tprivate int _Version;\n\n\t/// <summary>\n\t///     Increase the data version.\n\t/// </summary>\n\tprotected void IncreaseVersion()\n\t{\n\t\tInterlocked.Increment(ref _Version);\n\t}\n\n\t#endregion\n\n\t#region Interface Implementations\n\n\t/// <inheritdoc />\n\tpublic IEnumerator<TElement> GetEnumerator()\n\t{\n\t\treturn new Enumerator(this);\n\t}\n\n\n\t/// <inheritdoc />\n\tIEnumerator IEnumerable.GetEnumerator()\n\t{\n\t\treturn GetEnumerator();\n\t}\n\n\t/// <inheritdoc />\n\tpublic TElement this[int index] => CurrentPage.ElementAt(index);\n\n\t#endregion\n\n\t#region Not Supported Features\n\n\tint IList.Add(object value)\n\t{\n\t\tthrow new NotSupportedException();\n\t}\n\n\tvoid IList.Clear()\n\t{\n\t\tthrow new NotSupportedException();\n\t}\n\n\tbool IList.Contains(object value)\n\t{\n\t\tthrow new NotImplementedException();\n\t}\n\n\tint IList.IndexOf(object value)\n\t{\n\t\tthrow new NotImplementedException();\n\t}\n\n\tvoid IList.Insert(int index, object value)\n\t{\n\t\tthrow new NotSupportedException();\n\t}\n\n\tvoid IList.Remove(object value)\n\t{\n\t\tthrow new NotSupportedException();\n\t}\n\n\tvoid IList.RemoveAt(int index)\n\t{\n\t\tthrow new NotSupportedException();\n\t}\n\n\t#endregion\n\n\t#region Supported But Not Recommended Featrures\n\n\tvoid ICollection.CopyTo(Array array, int index)\n\t{\n\t\tArray.Copy(CurrentPage.ToArray(), 0, array, index, Count);\n\t}\n\n\n\tbool IList.IsFixedSize => true;\n\n\tbool IList.IsReadOnly => true;\n\n\tobject IList.this[int index]\n\t{\n\t\tget => this[index];\n\t\tset => throw new NotSupportedException();\n\t}\n\n\n\tbool ICollection.IsSynchronized => false;\n\n\tobject ICollection.SyncRoot { get; } = new object();\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList/DynamicPagedListCacheOptions.cs",
    "content": "﻿namespace Sakura.AspNetCore;\n\n/// <summary>\n///     Define additional creation options when create a paged list.\n/// </summary>\npublic class DynamicPagedListCacheOptions\n{\n\t/// <summary>\n\t///     The default cache mode for total count.\n\t/// </summary>\n\tpublic CacheMode TotalCountCacheMode { get; set; }\n\n\t/// <summary>\n\t///     The default cache mode for current page data.\n\t/// </summary>\n\tpublic CacheMode CurrentPageCacheMode { get; set; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList/DynamicQueryablePagedList.cs",
    "content": "﻿using System.Linq;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Represent as a paged list created from <see cref=\"IQueryable\" /> data source with page-changing support.\n/// </summary>\n/// <typeparam name=\"T\">The element type in the data source.</typeparam>\npublic class DynamicQueryablePagedList<T> : DynamicPagedListBase<IQueryable<T>, T>\n{\n\t/// <summary>\n\t///     Initialize a new instance with specified information.\n\t/// </summary>\n\t/// <param name=\"source\">The data source to be paging.</param>\n\t/// <param name=\"pageSize\">The size of each page.</param>\n\t/// <param name=\"pageIndex\">The index of the current page. Page index starts from 1.</param>\n\t/// <param name=\"cacheOptions\">Additional options for the paged list.</param>\n\tpublic DynamicQueryablePagedList(IQueryable<T> source, int pageSize, int pageIndex = 1,\n\t\tDynamicPagedListCacheOptions cacheOptions = null) : base(source, pageSize, pageIndex, cacheOptions)\n\t{\n\t}\n\n\t/// <summary>\n\t///     Get the total count of the data source.\n\t/// </summary>\n\t/// <returns>The total count of the data source.</returns>\n\tprotected override int GetTotalCount()\n\t{\n\t\treturn Source.Count();\n\t}\n\n\t/// <summary>\n\t///     Get the data in the current page.\n\t/// </summary>\n\t/// <returns>The data in the current page.</returns>\n\tprotected override IQueryable<T> GetCurrentPage()\n\t{\n\t\treturn Source.Skip(PageSize * (PageIndex - 1)).Take(PageSize);\n\t}\n\n\t/// <summary>\n\t///     Make a cached copy for the current page.\n\t/// </summary>\n\t/// <param name=\"source\">The data source to be caching.</param>\n\t/// <returns>The cached data.</returns>\n\tprotected override IQueryable<T> CacheData(IQueryable<T> source)\n\t{\n\t\treturn source.ToArray().AsQueryable();\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList/ICacheControl.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Define caching management related features.\n/// </summary>\n[PublicAPI]\npublic interface ICacheControl\n{\n\t/// <summary>\n\t///     Get or set the cache mode of the current object.\n\t/// </summary>\n\tCacheMode CacheMode { get; set; }\n\n\t/// <summary>\n\t///     Cache the current data.\n\t/// </summary>\n\tvoid Cache();\n\n\t/// <summary>\n\t///     Uncache the current data.\n\t/// </summary>\n\tvoid Uncache();\n\n\t/// <summary>\n\t///     Forcedly reload data even caching is available.\n\t/// </summary>\n\tvoid Reload();\n\n\t/// <summary>\n\t///     Temporally disable auto refreshing.\n\t/// </summary>\n\t/// <returns>An <see cref=\"IDisposable\" /> object that will re-enable auto refreshing when it is disposed.</returns>\n\tIDisposable DisableAutoRefresh();\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList/PagedList.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Provide fast access for snapshotted one page data from a data source.\n/// </summary>\n/// <typeparam name=\"TSource\">The type of the data source.</typeparam>\n/// <typeparam name=\"TElement\">The element type in the page.</typeparam>\npublic class PagedList<TSource, TElement> : IPagedList<TElement>\n{\n\t/// <summary>\n\t///     Initializes a new instance of the <see cref=\"PagedList{TSource,TElement}\" /> class.\n\t/// </summary>\n\t/// <param name=\"currentPage\">\n\t///     The list of current page data.\n\t/// </param>\n\t/// <param name=\"source\">\n\t///     The source of the page data.\n\t/// </param>\n\t/// <param name=\"pageSize\">\n\t///     The page size.\n\t/// </param>\n\t/// <param name=\"pageIndex\">\n\t///     The page index of current page.\n\t/// </param>\n\t/// <param name=\"totalCount\">\n\t///     The total data count in the <paramref name=\"source\" />.\n\t/// </param>\n\t/// <param name=\"totalPage\">\n\t///     The total page of the <paramref name=\"source\" />.\n\t/// </param>\n\t/// <exception cref=\"ArgumentOutOfRangeException\">\n\t///     The <paramref name=\"pageSize\" /> or <paramref name=\"pageIndex\" /> is not positive, or the\n\t///     <paramref name=\"totalCount\" /> or <paramref name=\"totalPage\" /> is negative.\n\t/// </exception>\n\t/// <exception cref=\"ArgumentNullException\">\n\t///     The <paramref name=\"currentPage\" /> or <paramref name=\"source\" /> is <c>null</c>.\n\t/// </exception>\n\tpublic PagedList([NotNull] IList<TElement> currentPage, [NotNull] TSource source, int pageSize, int pageIndex,\n\t\tint totalCount, int totalPage)\n\t{\n\t\tif (currentPage == null)\n\t\t\tthrow new ArgumentNullException(nameof(currentPage));\n\n\t\tif (pageSize <= 0)\n\t\t\tthrow new ArgumentOutOfRangeException(nameof(pageSize));\n\n\t\tif (pageIndex <= 0)\n\t\t\tthrow new ArgumentOutOfRangeException(nameof(pageIndex));\n\n\t\tif (totalCount < 0)\n\t\t\tthrow new ArgumentOutOfRangeException(nameof(totalCount));\n\n\t\tif (totalPage < 0)\n\t\t\tthrow new ArgumentOutOfRangeException(nameof(totalPage));\n\n\t\tSource = source != null ? source : throw new ArgumentNullException(nameof(source));\n\t\tPageSize = pageSize;\n\t\tPageIndex = pageIndex;\n\t\tCurrentPage = new ReadOnlyCollection<TElement>(currentPage);\n\t\tTotalCount = totalCount;\n\t\tTotalPage = totalPage;\n\t}\n\n\t/// <summary>\n\t///     Get the actual data contained in the current page.\n\t/// </summary>\n\t[PublicAPI]\n\tprotected ReadOnlyCollection<TElement> CurrentPage { get; }\n\n\t/// <summary>\n\t///     The source of the page data. This property is a reference of the constructor argument, any change of the source\n\t///     will not be watched.\n\t/// </summary>\n\t[PublicAPI]\n\tpublic TSource Source { get; }\n\n\t/// <summary>\n\t///     Get the size of each page.\n\t/// </summary>\n\tpublic int PageSize { get; }\n\n\t/// <summary>\n\t///     Get the index of the current page in the original data source.\n\t/// </summary>\n\t/// <remarks>\n\t///     The index is start from one (not zero).\n\t/// </remarks>\n\tpublic int PageIndex { get; }\n\n\t/// <summary>\n\t///     Get the total item count of data source.\n\t/// </summary>\n\tpublic int TotalCount { get; }\n\n\t/// <summary>\n\t///     Get the total page count.\n\t/// </summary>\n\tpublic int TotalPage { get; }\n\n\n\t/// <summary>\n\t///     Get the item count in the current page.\n\t/// </summary>\n\tpublic int Count => CurrentPage.Count;\n\n\t/// <summary>\n\t///     Get the element at the specified location in the current page.\n\t/// </summary>\n\t/// <param name=\"index\">The index of the location starts at zero.</param>\n\t/// <returns>The element in the specified location.</returns>\n\tpublic TElement this[int index] => CurrentPage[index];\n\n\t#region Implementation of IEnumerable\n\n\t/// <inheritdoc />\n\tIEnumerator<TElement> IEnumerable<TElement>.GetEnumerator()\n\t{\n\t\treturn CurrentPage.GetEnumerator();\n\t}\n\n\t/// <inheritdoc />\n\tIEnumerator IEnumerable.GetEnumerator()\n\t{\n\t\treturn ((IEnumerable) CurrentPage).GetEnumerator();\n\t}\n\n\t#endregion\n\n\t#region Implementation of ICollection\n\n\t/// <inheritdoc />\n\tvoid ICollection.CopyTo(Array array, int index)\n\t{\n\t\t((ICollection) CurrentPage).CopyTo(array, index);\n\t}\n\n\t/// <inheritdoc />\n\tbool ICollection.IsSynchronized => ((ICollection) CurrentPage).IsSynchronized;\n\n\t/// <inheritdoc />\n\tobject ICollection.SyncRoot => ((ICollection) CurrentPage).SyncRoot;\n\n\t#endregion\n\n\t#region Implementation of IList\n\n\t/// <inheritdoc />\n\tint IList.Add(object value)\n\t{\n\t\tthrow new NotSupportedException();\n\t}\n\n\t/// <inheritdoc />\n\tvoid IList.Clear()\n\t{\n\t\tthrow new NotSupportedException();\n\t}\n\n\n\t/// <inheritdoc />\n\tbool IList.Contains(object value)\n\t{\n\t\treturn ((IList) CurrentPage).Contains(value);\n\t}\n\n\n\t/// <inheritdoc />\n\tint IList.IndexOf(object value)\n\t{\n\t\treturn ((IList) CurrentPage).IndexOf(value);\n\t}\n\n\n\t/// <inheritdoc />\n\tvoid IList.Insert(int index, object value)\n\t{\n\t\tthrow new NotSupportedException();\n\t}\n\n\t/// <inheritdoc />\n\tvoid IList.Remove(object value)\n\t{\n\t\tthrow new NotSupportedException();\n\t}\n\n\t/// <inheritdoc />\n\tvoid IList.RemoveAt(int index)\n\t{\n\t\tthrow new NotSupportedException();\n\t}\n\n\t/// <inheritdoc />\n\tbool IList.IsFixedSize => true;\n\n\t/// <inheritdoc />\n\tbool IList.IsReadOnly => true;\n\n\t/// <inheritdoc />\n\tobject IList.this[int index]\n\t{\n\t\tget => ((IList) CurrentPage)[index];\n\t\tset => throw new NotSupportedException();\n\t}\n\n\t#endregion\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList/PagedListCreationHelper.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing JetBrains.Annotations;\n\n// ReSharper disable PossibleMultipleEnumeration\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Provide extension methods for creating <see cref=\"IPagedList{T}\" /> instances. This class is static.\n/// </summary>\n[PublicAPI]\npublic static class PagedListCreationHelper\n{\n\t/// <summary>\n\t///     Create a snapshot for one page of a <see cref=\"IEnumerable{T}\" /> object.\n\t/// </summary>\n\t/// <typeparam name=\"T\">The element type in the data source.</typeparam>\n\t/// <param name=\"source\">The source <see cref=\"IEnumerable{T}\" /> object to be converting.</param>\n\t/// <param name=\"pageSize\">The size of each page.</param>\n\t/// <param name=\"pageIndex\">The index of the current page. Page index starts from 1.</param>\n\t/// <returns>A <see cref=\"PagedList{TSource,TElement}\" /> object created by paging the <paramref name=\"source\" /> object.</returns>\n\tpublic static PagedList<IEnumerable<T>, T> ToPagedList<T>([NotNull] this IEnumerable<T> source, int pageSize,\n\t\tint pageIndex = 1)\n\t{\n\t\treturn CreatePagedListCore(source, pageSize, pageIndex, (data, skip, take) => data.Skip(skip).Take(take).ToArray(),\n\t\t\tdata => data.Count());\n\t}\n\n\t/// <summary>\n\t///     Create a snapshot for one page of a <see cref=\"IQueryable{T}\" /> object.\n\t/// </summary>\n\t/// <typeparam name=\"T\">The element type in the data source.</typeparam>\n\t/// <param name=\"source\">The source <see cref=\"IQueryable{T}\" /> object to be converting.</param>\n\t/// <param name=\"pageSize\">The size of each page.</param>\n\t/// <param name=\"pageIndex\">The index of the current page. Page index starts from 1.</param>\n\t/// <returns>A <see cref=\"PagedList{TSource,TElement}\" /> object created by paging the <paramref name=\"source\" /> object.</returns>\n\tpublic static PagedList<IQueryable<T>, T> ToPagedList<T>([NotNull] this IQueryable<T> source, int pageSize,\n\t\tint pageIndex = 1)\n\t{\n\t\treturn CreatePagedListCore(source, pageSize, pageIndex, (data, skip, take) => data.Skip(skip).Take(take).ToArray(),\n\t\t\tdata => data.Count());\n\t}\n\n\n\t/// <summary>\n\t///     Core method used to generate an instance of <see cref=\"PagedList{TSource,TElement}\" />.\n\t/// </summary>\n\t/// <typeparam name=\"TSource\">The source type.</typeparam>\n\t/// <typeparam name=\"TElement\">The element type in the paged list.</typeparam>\n\t/// <param name=\"source\">The source to be paged.</param>\n\t/// <param name=\"pageSize\">The size of page.</param>\n\t/// <param name=\"pageIndex\">The index of the currnet page.</param>\n\t/// <param name=\"pageFunc\">A paging function that skip some items, and then take some items, finally convert to a list.</param>\n\t/// <param name=\"countFunc\">A function to count the items in the source.</param>\n\t/// <returns>A <see cref=\"PagedList{TSource,TElement}\" /> object created by paging the <paramref name=\"source\" /> object.</returns>\n\tprivate static PagedList<TSource, TElement> CreatePagedListCore<TSource, TElement>([NotNull] this TSource source,\n\t\tint pageSize, int pageIndex, Func<TSource, int, int, IList<TElement>> pageFunc, Func<TSource, int> countFunc)\n\t{\n\t\tif (source == null)\n\t\t\tthrow new ArgumentNullException(nameof(source));\n\n\t\tif (pageSize <= 0)\n\t\t\tthrow new ArgumentOutOfRangeException(nameof(pageSize), pageSize, \"The page size must be positive.\");\n\n\t\tif (pageIndex <= 0)\n\t\t\tthrow new ArgumentOutOfRangeException(nameof(pageIndex), pageIndex, \"The page index must be positive.\");\n\n\t\tvar skipValue = pageSize * (pageIndex - 1);\n\t\tvar takeValue = pageSize;\n\n\t\tvar currentPage = pageFunc(source, skipValue, takeValue);\n\t\tvar totalCount = countFunc(source);\n\t\tvar totalPage = (totalCount - 1) / pageSize + 1;\n\n\t\treturn new PagedList<TSource, TElement>(currentPage, source, pageSize, pageIndex, totalCount, totalPage);\n\t}\n\n\n\t/// <summary>\n\t///     Convert <see cref=\"IEnumerable{T}\" /> object into a <see cref=\"DynamicPagedList{T}\" /> object.\n\t/// </summary>\n\t/// <typeparam name=\"T\">The element type in the data source.</typeparam>\n\t/// <param name=\"source\">The source <see cref=\"IEnumerable{T}\" /> object to be converting.</param>\n\t/// <param name=\"pageSize\">The size of each page.</param>\n\t/// <param name=\"pageIndex\">The index of the current page. Page index starts from 1.</param>\n\t/// <param name=\"cacheOptions\">Options for the paged list.</param>\n\t/// <returns>A <see cref=\"DynamicPagedList{T}\" /> object created by paging the <paramref name=\"source\" /> object.</returns>\n\tpublic static DynamicPagedList<T> ToDynamicPagedList<T>(this IEnumerable<T> source, int pageSize, int pageIndex = 1,\n\t\tDynamicPagedListCacheOptions cacheOptions = null)\n\t{\n\t\treturn new DynamicPagedList<T>(source, pageSize, pageIndex, cacheOptions);\n\t}\n\n\t/// <summary>\n\t///     Convert <see cref=\"IQueryable{T}\" /> object into a <see cref=\"DynamicQueryablePagedList{T}\" /> object.\n\t/// </summary>\n\t/// <typeparam name=\"T\">The element type in the data source.</typeparam>\n\t/// <param name=\"source\">The source <see cref=\"IQueryable{T}\" /> object to be converting.</param>\n\t/// <param name=\"pageSize\">The size of each page.</param>\n\t/// <param name=\"pageIndex\">The index of the current page. Page index starts from 1.</param>\n\t/// <param name=\"cacheOptions\">Options for the paged list.</param>\n\t/// <returns>A <see cref=\"DynamicQueryablePagedList{T}\" /> object created by paging the <paramref name=\"source\" /> object.</returns>\n\tpublic static DynamicQueryablePagedList<T> ToDynamicPagedList<T>(this IQueryable<T> source, int pageSize,\n\t\tint pageIndex = 1,\n\t\tDynamicPagedListCacheOptions cacheOptions = null)\n\t{\n\t\treturn new DynamicQueryablePagedList<T>(source, pageSize, pageIndex, cacheOptions);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList/Sakura.AspNetCore.PagedList.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <Description>ASP.NET Core PagedList core library provides data paging functionality for ASP.NET Core targeted projects.</Description>\n    <AssemblyTitle>ASP.NET Core PagedList Core Library</AssemblyTitle>\n    <VersionPrefix>2.0.1</VersionPrefix>\n    <TargetFramework>netstandard1.0</TargetFramework>\n    <AssemblyName>Sakura.AspNetCore.PagedList</AssemblyName>\n    <PackageId>Sakura.AspNetCore.PagedList</PackageId>\n    <PackageTags>ASP.NET;ASP.NETCore;Page;Paging;Cache;Caching</PackageTags>\n    <PackageReleaseNotes>Update dependency</PackageReleaseNotes>\n    <PackageProjectUrl>https://github.com/sgjsakura/AspNetCore</PackageProjectUrl>\n    <PackageLicenseUrl>https://github.com/sgjsakura/AspNetCore/blob/master/LICENSE.txt</PackageLicenseUrl>\n    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>\n    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>\n    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>\n    <RootNamespace>Sakura.AspNetCore</RootNamespace>\n    <Authors>Iris Sakura</Authors>\n    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n    <RepositoryUrl>https://github.com/sgjsakura/AspNetCore.git</RepositoryUrl>\n    <Version>2.0.2</Version>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"System.Linq.Queryable\" Version=\"4.3.0\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\Sakura.AspNetCore.PagedList.Abstractions\\Sakura.AspNetCore.PagedList.Abstractions.csproj\" />\n  </ItemGroup>\n\n\t<PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n\t\t<GenerateDocumentationFile>true</GenerateDocumentationFile>\n\t</PropertyGroup>\n\n</Project>\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList.Abstractions/DynamicPagedListExtensions.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Provide additional extension method for <see cref=\"IDynamicPagedList\" /> objects. This class is static.\n/// </summary>\n[PublicAPI]\npublic static class DynamicPagedListExtensions\n{\n\t/// <summary>\n\t///     Check the argument.\n\t/// </summary>\n\t/// <param name=\"pagedList\">The paged list to be checking.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"pagedList\" /> is null.</exception>\n\tprivate static void CheckArgument(IDynamicPagedList pagedList)\n\t{\n\t\tif (pagedList == null)\n\t\t\tthrow new ArgumentNullException(nameof(pagedList));\n\t}\n\n\n\t/// <summary>\n\t///     Move the paged list to the first page.\n\t/// </summary>\n\t/// <param name=\"pagedList\">The paged list to be moving.</param>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"pagedList\" /> is null.</exception>\n\tpublic static void GoToFirstPage(this IDynamicPagedList pagedList)\n\t{\n\t\tCheckArgument(pagedList);\n\n\t\tpagedList.PageIndex = 0;\n\t}\n\n\t/// <summary>\n\t///     Move the paged list to the last page.\n\t/// </summary>\n\t/// <param name=\"pagedList\">The paged list to be moving.</param>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"pagedList\" /> is null.</exception>\n\tpublic static void GoToLastPage(this IDynamicPagedList pagedList)\n\t{\n\t\tCheckArgument(pagedList);\n\n\t\tpagedList.PageIndex = pagedList.TotalPage - 1;\n\t}\n\n\t/// <summary>\n\t///     Move the paged list to the previous page.\n\t/// </summary>\n\t/// <param name=\"pagedList\">The paged list to be moving.</param>\n\t/// <returns>\n\t///     If the operation is successful, returns <c>true</c>; If the paged list is already in the first page, return\n\t///     <c>false</c>.\n\t/// </returns>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"pagedList\" /> is null.</exception>\n\tpublic static bool GoToPreviousPage(this IDynamicPagedList pagedList)\n\t{\n\t\tCheckArgument(pagedList);\n\n\t\tif (pagedList.IsFirstPage())\n\t\t\treturn false;\n\n\t\tpagedList.PageIndex--;\n\t\treturn true;\n\t}\n\n\t/// <summary>\n\t///     Move the paged list to the next page.\n\t/// </summary>\n\t/// <param name=\"pagedList\">The paged list to be moving.</param>\n\t/// <returns>\n\t///     If the operation is successful, returns <c>true</c>; If the paged list is already in the last page, return\n\t///     <c>false</c>.\n\t/// </returns>\n\t/// <exception cref=\"ArgumentNullException\"><paramref name=\"pagedList\" /> is null.</exception>\n\tpublic static bool GoToNextPage(this IDynamicPagedList pagedList)\n\t{\n\t\tCheckArgument(pagedList);\n\n\t\tif (pagedList.IsLastPage())\n\t\t\treturn false;\n\n\t\tpagedList.PageIndex++;\n\t\treturn true;\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList.Abstractions/IDynamicPagedList.cs",
    "content": "using JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Define as a <see cref=\"IPagedList\" /> with dynamically page changing support.\n/// </summary>\npublic interface IDynamicPagedList : IPagedList\n{\n\t/// <summary>\n\t///     The current data paged.\n\t/// </summary>\n\t[PublicAPI]\n\tnew int PageIndex { get; set; }\n\n\t/// <summary>\n\t///     The size of each page.\n\t/// </summary>\n\t[PublicAPI]\n\tnew int PageSize { get; set; }\n\n#if NETCOREAPP3_0\n\n\t\t/// <summary>\n\t\t///     Move the paged list to the first page.\n\t\t/// </summary>\n\t\t/// <param name=\"pagedList\">The paged list to be moving.</param>\n\t\tpublic void GoToFirstPage() => PageIndex = 1;\n\n\t\t/// <summary>\n\t\t///     Move the paged list to the last page.\n\t\t/// </summary>\n\t\tpublic void GoToLastPage() => PageIndex = TotalPage;\n\n\t\t/// <summary>\n\t\t///     Move the paged list to the previous page.\n\t\t/// </summary>\n\t\t/// <returns>\n\t\t///     If the operation is successful, returns <c>true</c>; If the paged list is already in the first page, return\n\t\t///     <c>false</c>.\n\t\t/// </returns>\n\t\tpublic bool GoToPreviousPage()\n\t\t{\n\t\t\tif (IsFirstPage)\n\t\t\t\treturn false;\n\n\t\t\tPageIndex--;\n\t\t\treturn true;\n\t\t}\n\n\t\t/// <summary>\n\t\t///     Move the paged list to the next page.\n\t\t/// </summary>\n\t\t/// <returns>\n\t\t///     If the operation is successful, returns <c>true</c>; If the paged list is already in the last page, return\n\t\t///     <c>false</c>.\n\t\t/// </returns>\n\t\tpublic bool GoToNextPage()\n\t\t{\n\t\t\tif (IsLastPage)\n\t\t\t\treturn false;\n\n\t\t\tPageIndex++;\n\t\t\treturn true;\n\t\t}\n#endif\n\n}\n\n/// <summary>\n///     Extend <see cref=\"IPagedList\" /> in order to provide strong-typed data access.\n/// </summary>\n/// <typeparam name=\"T\">The element type in the data page.</typeparam>\n// ReSharper disable once PossibleInterfaceMemberAmbiguity\npublic interface IDynamicPagedList<out T> : IDynamicPagedList, IPagedList<T>\n{\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList.Abstractions/IPagedList.cs",
    "content": "﻿using System.Collections;\nusing System.Collections.Generic;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Define the necessary feature needed for data paging.\n/// </summary>\npublic interface IPagedList : IList\n{\n\t/// <summary>\n\t///     Get the index of the current page in the original data source.\n\t/// </summary>\n\t/// <remarks>\n\t///     The index is start from one (not zero).\n\t/// </remarks>\n\t[PublicAPI]\n\tint PageIndex { get; }\n\n\t/// <summary>\n\t///     Get the size of each page.\n\t/// </summary>\n\t[PublicAPI]\n\tint PageSize { get; }\n\n\t/// <summary>\n\t///     Get the total page count.\n\t/// </summary>\n\t[PublicAPI]\n\tint TotalPage { get; }\n\n\t/// <summary>\n\t///     Get the total item count of data source.\n\t/// </summary>\n\t[PublicAPI]\n\tint TotalCount { get; }\n\n#if NETCOREAPP3_0\n\n\t\t/// <summary>\n\t\t/// Get a value that indicates whether the paged list is currently at the first page.\n\t\t/// </summary>\n\t\tpublic bool IsFirstPage => PageIndex == 1;\n\n\t\t/// <summary>\n\t\t/// Get a value that indicates whether the paged list is currently at the last page.\n\t\t/// </summary>\n\t\tpublic bool IsLastPage => PageIndex == TotalPage;\n\n\n#endif\n\n}\n\n/// <summary>\n///     Extend <see cref=\"IPagedList\" /> in order to provide strong-typed data access.\n/// </summary>\n/// <typeparam name=\"T\">The element type in the data page.</typeparam>\npublic interface IPagedList<out T> : IPagedList, IReadOnlyList<T>\n{\n\t/// <summary>\n\t///     Get the element count in the collection.\n\t/// </summary>\n\tnew int Count { get; }\n\n\t/// <summary>\n\t///     Get the element at the specified location.\n\t/// </summary>\n\t/// <param name=\"index\">The zero-based index of the specified element.</param>\n\t/// <returns>The element at the <paramref name=\"index\" /> location.</returns>\n\tnew T this[int index] { get; }\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList.Abstractions/PagedListExtensions.cs",
    "content": "﻿using System;\nusing JetBrains.Annotations;\n\nnamespace Sakura.AspNetCore;\n\n// Features are provided by interface default implementation for C#8 targetd framework.\n#if !NETCOREAPP3\n/// <summary>\n///     Provide additional extension method for <see cref=\"IPagedList\" /> objects. This class is static.\n/// </summary>\n[PublicAPI]\npublic static class PagedListExtensions\n{\n\t/// <summary>\n\t///     Check the argument.\n\t/// </summary>\n\t/// <param name=\"pagedList\">The paged list to be checking.</param>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"pagedList\" /> is null.</exception>\n\tprivate static void CheckArgument(IPagedList pagedList)\n\t{\n\t\tif (pagedList == null)\n\t\t\tthrow new ArgumentNullException(nameof(pagedList));\n\t}\n\n\n\t/// <summary>\n\t///     Get a value that indicates if the paged list is currently in the first page.\n\t/// </summary>\n\t/// <param name=\"pagedList\">The paged list to be checking.</param>\n\t/// <returns>If the paged list is currently in the first page, returns <c>true</c>; otherwise,returns <c>false</c>.</returns>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"pagedList\" /> is null.</exception>\n\tpublic static bool IsFirstPage(this IPagedList pagedList)\n\t{\n\t\tCheckArgument(pagedList);\n\n\t\treturn pagedList.PageIndex == 1;\n\t}\n\n\t/// <summary>\n\t///     Get a value that indicates if the paged list is currently in the last page.\n\t/// </summary>\n\t/// <param name=\"pagedList\">The paged list to be checking.</param>\n\t/// <returns>If the paged list is currently in the last page, returns <c>true</c>; otherwise,returns <c>false</c>.</returns>\n\t/// <exception cref=\"ArgumentNullException\">The <paramref name=\"pagedList\" /> is null.</exception>\n\tpublic static bool IsLastPage(this IPagedList pagedList)\n\t{\n\t\tCheckArgument(pagedList);\n\n\t\treturn pagedList.PageIndex == pagedList.TotalPage;\n\t}\n}\n#endif"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList.Abstractions/Sakura.AspNetCore.PagedList.Abstractions.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <Description>ASP.NET Core PagedList abstraction layer defines the IPagedList and IDynamicPagedList interface and useful extension methods for ASP.NET Core targeted projects. This project does not contains the implementation for creating paged list from a certain data source.</Description>\n    <AssemblyTitle>ASP.NET Core PagedList Core Abstraction Layer</AssemblyTitle>\n    <VersionPrefix>2.0.0</VersionPrefix>\n    <TargetFrameworks>netstandard1.0;netcoreapp3.0</TargetFrameworks>\n    <AssemblyName>Sakura.AspNetCore.PagedList.Abstractions</AssemblyName>\n    <PackageId>Sakura.AspNetCore.PagedList.Abstractions</PackageId>\n    <PackageTags>ASP.NET;ASP.NETCore;Page;Paging</PackageTags>\n    <PackageReleaseNotes>Update to support C# 8.0 interface default implementation</PackageReleaseNotes>\n    <PackageProjectUrl>https://github.com/sgjsakura/AspNetCore</PackageProjectUrl>\n    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>\n    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>\n    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>\n    <Authors>Iris Sakura</Authors>\n    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n    <RepositoryUrl>https://github.com/sgjsakura/AspNetCore.git</RepositoryUrl>\n    <Version>3.0.0</Version>\n    <RootNamespace>Sakura.AspNetCore</RootNamespace>\n    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n    <AssemblyVersion>3.0.0.0</AssemblyVersion>\n    <FileVersion>3.0.0.0</FileVersion>\n  </PropertyGroup>\n\n  <PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n    <GenerateDocumentationFile>true</GenerateDocumentationFile>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"JetBrains.Annotations\" Version=\"2024.3.0\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList.Async/AsyncPagedListCreationHelper.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing JetBrains.Annotations;\nusing Microsoft.EntityFrameworkCore;\n\n// ReSharper disable PossibleMultipleEnumeration\nnamespace Sakura.AspNetCore;\n\n/// <summary>\n///     Provide extension methods for creating <see cref=\"IPagedList{T}\" /> instances asynchronously. This class is static.\n/// </summary>\n[PublicAPI]\npublic static class AsyncPagedListCreationHelper\n{\n\t/// <summary>\n\t///     Create a snapshot for one page of a <see cref=\"IQueryable{T}\" /> object.\n\t/// </summary>\n\t/// <typeparam name=\"T\">The element type in the data source.</typeparam>\n\t/// <param name=\"source\">The source <see cref=\"IQueryable{T}\" /> object to be converting.</param>\n\t/// <param name=\"pageSize\">The size of each page.</param>\n\t/// <param name=\"pageIndex\">The index of the current page. Page index starts from 1.</param>\n\t/// <param name=\"cancellationToken\">The cancellation token used to cancel the operation.</param>\n\t/// <returns>A <see cref=\"IPagedList{T}\" /> object created by paging the <paramref name=\"source\" /> object.</returns>\n\tpublic static Task<PagedList<IQueryable<T>, T>> ToPagedListAsync<T>([NotNull] this IQueryable<T> source, int pageSize,\n\t\tint pageIndex = 1, CancellationToken cancellationToken = default)\n\t{\n\t\treturn CreatePagedListCoreAsync(source, pageSize, pageIndex,\n\t\t\t(data, skip, take) => data.Skip(skip).Take(take).ToArrayAsync(cancellationToken),\n\t\t\tdata => data.CountAsync(cancellationToken));\n\t}\n\n\n\t/// <summary>\n\t///     Core method used to generate an instance of <see cref=\"PagedList{TSource,TElement}\" />.\n\t/// </summary>\n\t/// <typeparam name=\"TSource\">The source type.</typeparam>\n\t/// <typeparam name=\"TElement\">The element type in the paged list.</typeparam>\n\t/// <param name=\"source\">The source to be paged.</param>\n\t/// <param name=\"pageSize\">The size of page.</param>\n\t/// <param name=\"pageIndex\">The index of the currnet page.</param>\n\t/// <param name=\"pageFunc\">A paging function that skip some items, and then take some items, finally convert to a list.</param>\n\t/// <param name=\"countFunc\">A function to count the items in the source.</param>\n\t/// <returns></returns>\n\tprivate static async Task<PagedList<TSource, TElement>> CreatePagedListCoreAsync<TSource, TElement>(\n\t\t[NotNull] this TSource source,\n\t\tint pageSize, int pageIndex, Func<TSource, int, int, Task<TElement[]>> pageFunc, Func<TSource, Task<int>> countFunc)\n\t{\n\t\tif (source == null)\n\t\t\tthrow new ArgumentNullException(nameof(source));\n\n\t\tif (pageSize <= 0)\n\t\t\tthrow new ArgumentOutOfRangeException(nameof(pageSize), pageSize, \"The page size must be positive.\");\n\n\t\tif (pageIndex <= 0)\n\t\t\tthrow new ArgumentOutOfRangeException(nameof(pageIndex), pageIndex, \"The page index must be positive.\");\n\n\t\tvar skipValue = pageSize * (pageIndex - 1);\n\t\tvar takeValue = pageSize;\n\n\t\tvar currentPage = await pageFunc(source, skipValue, takeValue).ConfigureAwait(true);\n\t\tvar totalCount = await countFunc(source).ConfigureAwait(true);\n\t\tvar totalPage = (totalCount - 1) / pageSize + 1;\n\n\t\treturn new PagedList<TSource, TElement>(currentPage, source, pageSize, pageIndex, totalCount, totalPage);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.AspNetCore.PagedList.Async/Sakura.AspNetCore.PagedList.Async.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <Description>This package provides extension method to create PagedList using EntityFramework async method.</Description>\n    <AssemblyTitle>ASP.NET Core PagedList Async Extension Library</AssemblyTitle>\n    <TargetFrameworks>netstandard1.3;net451</TargetFrameworks>\n    <AssemblyName>Sakura.AspNetCore.PagedList.Async</AssemblyName>\n    <PackageId>Sakura.AspNetCore.PagedList.Async</PackageId>\n    <PackageTags>ASP.NET;ASP.NETCore;Page;Paging;Cache;Caching</PackageTags>\n    <PackageReleaseNotes>Minor update to improvement performance.</PackageReleaseNotes>\n    <PackageProjectUrl>https://github.com/sgjsakura/AspNetCore</PackageProjectUrl>\n    <PackageLicenseUrl></PackageLicenseUrl>\n    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>\n    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>\n    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>\n    <Authors>Iris Sakura</Authors>\n    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n    <RepositoryUrl>https://github.com/sgjsakura/AspNetCore.git</RepositoryUrl>\n    <RootNamespace>Sakura.AspNetCore</RootNamespace>\n    <Version>1.1.1</Version>\n    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\Sakura.AspNetCore.PagedList\\Sakura.AspNetCore.PagedList.csproj\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"1.0.0\" />\n  </ItemGroup>\n\n  <PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n    <GenerateDocumentationFile>true</GenerateDocumentationFile>\n  </PropertyGroup>\n\n  <ItemGroup Condition=\" '$(TargetFramework)' == 'net451' \">\n    <Reference Include=\"System\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.EntityFrameworkCore.FromSqlExtensions/DbContextFromSqlExtensions.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing System.Reflection;\nusing JetBrains.Annotations;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Internal;\nusing Microsoft.EntityFrameworkCore.Metadata.Internal;\nusing Microsoft.EntityFrameworkCore.Query.Internal;\n\nnamespace Sakura.EntityFrameworkCore.FromSqlExtensions;\n\n/// <summary>\n///     Provide extension methods which can be used to execute parameterized SQL statements and retrieve the result data\n///     set directly. This class is static.\n/// </summary>\npublic static class DbContextFromSqlExtensions\n{\n\t/// <summary>\n\t///     The runtime reference of the\n\t///     <see cref=\"RelationalQueryableExtensions.FromSql{T}(IQueryable{T}, RawSqlString, object[])\" /> method.\n\t/// </summary>\n\tprivate static MethodInfo FromSqlMethod { get; } = typeof(RelationalQueryableExtensions).GetTypeInfo()\n\t\t.GetDeclaredMethods(nameof(RelationalQueryableExtensions.FromSql))\n\t\t.Single(i => i.GetParameters().Length == 3);\n\n\t/// <summary>\n\t///     Executing a SQL statement and retrieve the result set directly frm the specified <see cref=\"DbContext\" />.\n\t/// </summary>\n\t/// <typeparam name=\"T\">The type of the item in the result set.</typeparam>\n\t/// <param name=\"dbContext\">The <see cref=\"DbContext\" /> instance.</param>\n\t/// <param name=\"sql\">The SQL statement to be executing.</param>\n\t/// <param name=\"parameters\">Arguments provided along with the parameterized <paramref name=\"sql\" />.</param>\n\t/// <returns>A <see cref=\"IQueryable{T}\" /> instance which can be used for retrieving the result or making a further query.</returns>\n\t[PublicAPI]\n\tpublic static IQueryable<T> FromSql<T>(this DbContext dbContext, RawSqlString sql, params object[] parameters)\n\t{\n\t\t// Argument check\n\t\tif (dbContext == null) throw new ArgumentNullException(nameof(dbContext));\n\n\t\t// Add new query type if not exists\n\t\tdbContext.TryAddQueryType<T>();\n\n\t\t// Get the query provider service instance\n\t\tvar queryProvider = dbContext.GetDependencies().QueryProvider;\n\n\t\t// Build query source. Actually query source is just used to determine the final result item type.\n\t\tvar querySource = new EntityQueryable<T>(queryProvider);\n\n\t\t// Build LINQ expression for Entity Framework Core.\n\t\tvar expression = Expression.Call(null, FromSqlMethod.MakeGenericMethod(typeof(T)), querySource.Expression,\n\t\t\tExpression.Constant(sql), Expression.Constant(parameters));\n\n\t\t// Return result\n\t\treturn queryProvider.CreateQuery<T>(expression);\n\t}\n\n\t/// <summary>\n\t///     Executing a SQL statement and retrieve the result set directly frm the specified <see cref=\"DbContext\" />.\n\t/// </summary>\n\t/// <typeparam name=\"T\">The type of the item in the result set.</typeparam>\n\t/// <param name=\"dbContext\">The <see cref=\"DbContext\" /> instance.</param>\n\t/// <param name=\"sql\">The parameterized SQL statement represented as a <see cref=\"FormattableString\" />.</param>\n\t/// <returns>A <see cref=\"IQueryable{T}\" /> instance which can be used for retrieving the result or making a further query.</returns>\n\t[PublicAPI]\n\tpublic static IQueryable<T> FromSql<T>(this DbContext dbContext, FormattableString sql)\n\t{\n\t\treturn dbContext.FromSql<T>(sql.Format, sql.GetArguments());\n\t}\n\n\t/// <summary>\n\t///     Try to add a new query type into the <see cref=\"DbContext\" /> if it not exists yet.\n\t/// </summary>\n\t/// <typeparam name=\"T\">The query type should be adding.</typeparam>\n\t/// <param name=\"dbContext\">The <see cref=\"DbContext\" /> instance.</param>\n\tprivate static void TryAddQueryType<T>(this DbContext dbContext)\n\t{\n\t\tvar model = dbContext.Model.AsModel();\n\t\tvar type = typeof(T);\n\t\tif (model.FindEntityType(type) == null) model.AddQueryType(type);\n\t}\n}"
  },
  {
    "path": "Sakura.AspNetCore.Extensions/Sakura.EntityFrameworkCore.FromSqlExtensions/Sakura.EntityFrameworkCore.FromSqlExtensions.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFrameworks>netstandard2.0</TargetFrameworks>\n    <Authors>Iris Sakura</Authors>\n    <Product>Sakura.AspNetCore.Extensions</Product>\n    <Description>Provides extension methods used to executing raw SQL statements and retriving the result set directly from DbContext.</Description>\n    <Copyright></Copyright>\n    <PackageProjectUrl>https://github.com/sgjsakura/AspNetCore</PackageProjectUrl>\n    <RepositoryUrl>https://github.com/sgjsakura/AspNetCore.git</RepositoryUrl>\n    <PackageLicenseUrl></PackageLicenseUrl>\n    <RepositoryType />\n    <PackageReleaseNotes>Update the package reference.</PackageReleaseNotes>\n    <PackageTags>EntityFramework;EntityFrameworkCore;FromSql;Extension</PackageTags>\n    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>\n    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n    <Version>1.1.0</Version>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"JetBrains.Annotations\" Version=\"2024.3.0\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Relational\" Version=\"2.1.0\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "TagHelperDemo.md",
    "content": "# ASP.NET Core Tag Helper Extension Library Usage Guideline\n\nASP.NET Core uses a new coding writing design mode named `Tag Helpers` replacing the original `Html Helpers` utlity in order to simplify server-side HTML generation. This library is an supplement for the official `Microsoft.AspNetCore.Mvc.TagHelpers` package and added a series of new tag helpers. This page will demostrate the usages all tag helpers provided in the `Sakura.AspNetCore.Mvc.TagHelpers` library. \n\n## Installation\n\nTo use new tag helpers provided in this library, you must first install and configure the package. In order to install it to your project, please open `project.json` file and add a new depenedency named `Sakura.AspNetCore.Mvc.TagHelpers` in the `dependencies` section.\n\nAnd then, you should enable new tag helpers in your MVC view. The most simple way to enable all tag helpers for all pages is add a new line in `_ViewImports.cs` files as follow:\n```HTML\n@addTagHelper *, Sakura.AspNetCore.Mvc.TagHelpers\n```\n\nIf you need to enable/disable a custom tag helper in a specified page, please see the documentation for `@addTagHelper` directive.\n\n## TagHelper List\n\nThis section will describle the usage of all tag helpers.\n\n### `EnumSelectForTagHelper` and `EnumSelectTypeHelper`\n\nGeneration a list of all enum items and allow user to select one of them is a common task in MVC projects. Currently, you may use `HtmlHelper.GenerateEnumList` to generated a `SelectItemList` object and then set it to `asp-items` attributes on an `<select>` tags. These steps can work, however it is a bit complex as well as you can not control the generated item's name and value, thus it will be UI unfriendly since the enum names are always short terms without spaces between words, and it also cannot be localizable.\n\nUse the `EnumSelectForTagHelper` You can now use `asp-enum-for` attribute to generate a HTML select list with options for an enum type, the type is specified by the given model expression. The following code shows the basic way for this tag helper:\n```C#\n// backend file \npublic enum ProjectAccessType\n{\n  [Display(Description = \"Everyone can access this project\")]\n  Public,\n  [Display(Description = \"Only specified users can access this project\")]\n  Private,\n  [Display(Description = \"Everyone can access and update this project\")]\n  Community\n}\n\npublic class Project\n{\n  public ProjectAccessType AccessType { get; set; }\n}\n\n```\n\n```HTML\n<!-- In MVC view page -->\n@model Project\n<select asp-enum-for=\"AccessType\"></select>\n```\n\nThe actual page will be generated as:\n```HTML\n<select name=\"AccessType\">\n  <option value=\"Public\">Public</option>\n  <option value=\"Private\">Private</option>\n  <option value=\"Community\">Community</option>\n</select>\n```\n\nAn important enhancement provied by this tag helper is that you can use `asp-text-source` and `asp-value-source` to control the generated text and value of options. The tag helper will detect any `DisplayAttribute` applied on each enum item, and get the property value specified by `asp-text-source` attribute as the display text of options. e.g. The following code:\n```HTML\n<!-- In MVC view page -->\n@model Project\n<select asp-enum-for=\"AccessType\" asp-text-source=\"Description\"></select>\n```\nwill generate the following HTML (using the same backend type definition as first sample):\n```HTML\n<select name=\"AccessType\">\n  <option value=\"Public\">Everyone can access this project</option>\n  <option value=\"Private\">Only specified users can access this project</option>\n  <option value=\"Community\">Everyone can access and update this project</option>\n</select>\n```\nIf none of `asp-text-source` is specified, the default value is set to `EnumNameOnly`, which will generate the result as the same as the first sample. If some enum items is lack of specified inforation (e.g. `Description` is null), the tag helper will also fallback to using the enum name as the text.\nThe `asp-value-source` is used to control the value of options. The default value of this attribute is set to `Name`, which means the name of the enum item is used as the option value. If needed, you can change this attribute to `Value`, in such case, the following code:\n```HTML\n<!-- In MVC view page -->\n@model Project\n<select asp-enum-for=\"AccessType\" asp-value-source=\"Value\"></select>\n```\nWill be generated as:\n```HTML\n<select name=\"AccessType\">\n  <option value=\"0\">Public</option>\n  <option value=\"1\">Private</option>\n  <option value=\"2\">Community</option>\n</select>\n```\n*Note: The default MVC model binders can handle enum names correctly, thus you usually do not need to set this attribute.*\n\nThe `EnumSelectTypeTagHelper` is similar ot `EnumSelectForTagHelper`, however you use `asp-enum-type` to specify the enum type explicitly instead of inferring it from a model expression. In such case you may also need to add the `name` attribute for the `<select>` tag manually in order to send its data to server correctly. The following code is a sample usage and will generate the same HTML as the above one:\n\n```HTML\n<select asp-enum-type=\"@typeof(ProjectAccessType)\" name=\"AccessType\"></select>\n```\n\n### `SelectValueTagHelper`\n\nAnother common task related to `<select>` tag is to initially set the default selected value according to the current state of data when a user tries to edit a existing data item. In ASP.NET Core MVC projects, you can use `asp-for` for a `<select>` element to automatically set the initial state according to the model value. However, if you are trying to display a select list without directly model binding, you must using one of the following manner:\n\n1. build-up a `SelectList` instance and provide `selectedValue` argument during construction, and then apply `asp-items` attribute on the `<select>` element.\n2. Define each `<option>` element in HTML and using a Rozar condition expression to the `selected` attribute.\n\nThis first manner is more simple for value calculation, however you will write a lot of C# code for generating a static list. The second manner is more graceful in HTML generation, however you must repeat the `selected` condition on each option.\n\nNow with the new tag helper, you can simplely apply an `asp-value` attribute on any `<select>` tag, and tag helper will help you to automatically make the correct option selected during the page generation. e.g. The following code:\n```HTML\n<select asp-value=\"@myValue\">\n  <option value=\"1\">1</option>\n  <option value=\"2\">2</option>\n</select>\n```\nwill generate the following HTML is the value of `myValue` is equal to `1`:\n```HTML\n<select>\n  <option value=\"1\" selected=\"selected\">1</option>\n  <option value=\"2\">2</option>\n</select>\n```\n*Note: The value of `asp-value` attribute is considered as a string since HTML only accept string as element content. You may need to convert value of other types into string manually.*\n\nAdditionally, you can specify the `asp-value-compare-mode` on the `<select>` tag to control how to determine the specified value is equal to the option value. The default setting is `OrdinalIgnoreCase`, which can be used in mose cases, but you can change it to any enum item in `System.StringComparison` type to change this behavior.\n\n### `SelectOptionLabelTagHelper`\n\nThis is a shortcut tag helper used to add a option label (option with empty value) into a `<select>` tag. You may use `asp-option-label` to set the label text and `asp-option-label-position` to set the location of the label. A sample can be:\n```HTML\n<select asp-option-label=\"Select a option\" asp-option-label-position=\"First\">\n  <option value=\"1\">1</option>\n  <option value=\"1\">1</option>\n</select>\n```\nThe above code will generate the folloing HTML:\n```HTML\n<select>\n  <option value=\"\">Select a option</option>\n  <option value=\"1\">1</option>\n  <option value=\"1\">1</option>\n</select>\n```\n\n### `FlagsEnumInputTagHelper`\n\nFlags enum is a common design pattern used to store multiple flags into a composed value, and filter values using arbitray flag combination. For end users, each flag can be considered as a individual option, and is usually represented as a checkbox. However, the default MVC model binding system does not allow to bind a single flag with a checkbox's check state. \n\nWith the new tag helper, you can now use `asp-flag-enum-for` and `asp-flag-enum-value` attributes on checkbox to correctly bind a certern flag of a model value with the input element. e.g. the following code:\n```C#\n// backend code\n[Flags]\npublic enum Features\n{\n  None = 0x0,\n  CanRead = 0x1,\n  CanWrite = 0x2,\n  CanSeek = 0x4\n}\n\npublic class Stream\n{\n  public Features Features { get; set; }\n}\n\n```\n```HTML\n<!-- In MVC view page -->\n@model Stream\n<label>\n  <input type=\"checkbox\" asp-enum-flag-for=\"Features\" asp-enum-flag-value=\"Features.CanSeek\"> CanSeek\n</label>\n```\nwill generate the following code if the model's `Features` property contains the flag `CanSeek`:\n```HTML\n<label>\n  <input type=\"checkbox\" name=\"Features\" value=\"CanSeek\" checked=\"checked\"> CanSeek\n</label>\n```\n*NOTE: This tag helper only helps you to generate an input element. Unforetunately, the default MVC model binding system cannot handle flags enum items and merge them correctly. In order to receive the final data in you back end callback action, you may also need the `FlagsEnumModelBinder` feature, this binder can be found in the next section.*\n\n### `DisplayTextTagHelper`\n\nIn classical ASP.NET Project, `Html.DisplayText` method is used to display labels with data annotation related dynamic data model. This method is also inherited by the new ASP.NET Core framework. Now for tag helper enabled new Razor page syntax, this method is a bit obesoleted. Another problem for this method is that it lakes the control of text source.\n\nThe new `<display-text>` tag is designed to provide modern and flexible data annotation based text extraction, together with standard ASP.NET Core localization framework. Similar with the `EnumSelectForTagHelper`, it also provides the `text-source` attribute to control the display text source. For example, a backend data model may be defined as:\n```C#\npublic class Account\n{\n  [Display(Name=\"User Name\")]\n  public string UserName { get; set; }\n}\n```\nAnd when a page is designed to show or edit account information, you may want to extract the `Name` property defined in the `DisplayAttribute` as some label text, in this case you may write:\n```HTML\n@model Account \n\n<label><display-text for=\"UserName\" text-source=\"Name\" /></label>\n```\n\nThen the `<display-text>` tag will be converted to a plain HTML content text \"User Name\" in place.\n\n### `EnumItemDisplayTextTagHelper`\n\nConvert a enum item to a user friendly display text is a common task in lots of appliations. In .NET Framework, enum types are auto sealed and thus we cannot add new methods or override existing methods (especially for the `ToString` method). This limitation brings much more work when we trying to convert enum item to a custom string. \n\nA typical solution is defining an extension method such as `GetString` and then call this method in the source code. This manner works, however it is lake of the variation ability in different sneces (i.e. when you want to show long and short display strings in two pages) and localization supports. Using `DisplayAtttribute` combined with resource files is a generic way to add full-level visualization support for them, while it needs reflection operation and thus it is not easy to use it.\n\nNow the `<enum-item-display-text>` tag helper wrapped all necessary works for you and you may just use the `DisplayAttribute` to define display texts for enum items with localization support. For example, an enum type and a typical usage are defined as:\n```C#\npublic enum UserType\n{\n   [Display(Name = \"Generic User\")]\n   User,\n   [Display(Name = \"System Adminisrator\")]\n   Admin,\n}\n\npublic class User\n{\n  public UserType UserType { get; set; }\n}\n\n```\nAnd for a page is designed for a `User` model, you may write:\n```HTML\n@model User\n\n<p>Your user type: <enum-item-display-text for=\"UserType\" text-source=\"Name\" /><p>\n\n```\nThen the placeholder tag helper will be replaced with text \"Generic User\" if your user type value is `UserType.User` and \"System Administrator\" if your type is `UserType.Admin`.\n\nBesides the `for` attribute, you may also use the `value` attribute if you want to show the display text for an existing enum value directly which is not directly related with the page model (and is difficult to extract with `for` attribute). The `text-source` attribute can be used as the same as some other tag helpers above.\n\n\n###  `ConditionalClassTagHelper`\n\nCSS class is an important part for HTML element. It can be used to both styling and item filtering. An element can belong to one or more classes sepecifed by `class` attribute. The `class` attribute is a composed value contains a list of class names, each of them a sepereated with spaces. You cannot specify multiple `class` attributes on a single element, and if you wish to apply some class conditionally, you may:\n\n- Generate the entire class list using C# and set the value to `class` attribute, or\n- Generate a string of class name or empty string according to the condition, and insert it as a part or class string\n\nNeither of those methods are friendly to code reading. Now using the new tag helper, you can simplely append any class on a single element with different conditions using `asp-conditional-class-<className>` attribute. Here is a simple usage demo:\n```HTML\n<a class=\"page\" asp-conditional-class-active=\"currentPage == 1\" href=\"?page=1\">Page 1<a>\n```\nIf the C# expresion condition `currentPage == 1` is satisfied, the above code will generate:\n```HTML\n<a class=\"page active\" href=\"?page=1\">Page 1<a>\n```\nWhile if the condition is not satisfied, this attribute will be simplely ignored.\n\n### `AuthorizeTagHelper` and `AuthorizeAttributeTagHelper`\n\nProviding different content according to user's role is a common task in morden web applications. Usually the user authorization checking should be done in the backend (controlleres and actions), writing complex service invocations in CSHTML file may be not a good idea. However, sometimes it is still accepable to do so, especially when backend coding is not available (e.g. for layout files). To make user authorization check in CSHTML files, you need to inject a service with type `IAuthorizationService` and invoke the `AuthorizeAsync` method together with `await` and `if` keywords. This manner is a bit lengthy, and also mixing Razor blocks and HTML is  not friendly for code maintance.\n\nNow with these two tag helpers, you can easy add HTML style code to show/hide contents according to the current user's permission. The easiest usage is as simple as add one new `asp-authorize-policy` tag-attribute in any HTMl element, which is provided by `AuthorizeAttributeTagHelper`, a simple code is just like following:\n```HTML\n<div asp-authorize-policy=\"Edit\">\n  <!-- any content -->\n</div>\n```\nThe above code make a authorization check on the during the page generation, and if the current user meets the requirements for the `Edit` policy defined in this application, this element and its content will be rendered as usual; otherwise, this element and all its content will be removed. The removal is done in backend, thus the end user cannot found any hint in the final HTML. \n\n*Note: This TagHelper only supports policy-based authorization (which are similar as the major usage of the `AuthorizeAttribute`), Roles-based or other type authorization is not supported. To learn how to define policies in your application, please see the ASP.NET Core Official Documentation.*\n\nThe `AuthorizeAttributeTagHelper` also supports an additional attribute named `asp-authorize-resource` to support the `resource` argument in `IAuthorizationService.AuthorizeAsync` method. This argument is not used in the default authorization service provided by ASP.NET Core library, and thus you usually do not need to set it. However, if you are using any 3rd authorization middleware which may take benefit from this extra argument, you can provide the value easily within HTML style code.\n\nThe `AuthorizeAttributeTagHelper` can be used in any existing HTML element, however, only one root element and its content can be affected in one time. If you want to affect multiple neighboring same level elements, a standalone `<authorize>` tag is provided in `AuthorizeTagHelper`. Its usage is similar as the above, the following code shows an example:\n\n```HTML\n<!-- you may also use \"resource\" attribute to provide resource argument if necessary -->\n<authorize policy=\"Edit\">\n  <div></div>\n  <div></div>\n</authorize>\n```\nYou may see the attribute name is simplified and it can also surrounds a series of elements.\n\n### `IdFormatTagHelper`\n\nIn a complex page, parts of layout may be repeated, e.g. A page of product introduction may has several user comments. Usually you control all of them using class filters or DOM tree traveling, however, sometimes you may need to control each of them invidually. Although you can capture them by location, using ID to pick-up one element is usually more efficient and straightforward. The HTML standard requires every ID must be unique in each page, thus for a server-side repeated range (usually generated by `for` or `foreach`), id must be dynamically generated inside the loop range.\n\nThe `IdFormatTagHelper` provide `asp-id-format` attribute on element in order to generate a id according to a specified format. The final id will be generated using `string.Format` method, will the placeholder `{0}` will a unique number start from one. Here is a example:\n```HTML\n@foreach (var i in Items)\n{\n  <div asp-id-format=\"region-{0}\"></div>\n}\n```\nThe above code will generated the following HTML if the `Items` has 3 elements:\n```HTML\n<div asp-id-format=\"region-1\"></div>\n<div asp-id-format=\"region-2\"></div>\n<div asp-id-format=\"region-3\"></div>\n```\n*Note: Currently the tag helper use the format string as the counting key, that means all element uses a same value of `asp-id-format` will share the counting number. The count is registered and saved in the view data dictionary, which means the entire page processing pipeline (including the layout page, main page, any partial page and view components) will be considered as a single document and the counting will be continous in the final page.*\n\n## Binders\n\n### `FlagsEnumModelBinder`\n\nThe `FlagsEnumInputTagHelper` can help you to generate flag checkboxs, however, default MVC model binder cannot automatically merge all the values. To solve this problem, you may add the `FlagsEnumModelBinder` into you MVC middleware, you may add it in the service configurating phase using the following code:\n```C#\n// In startup.cs\n\npublic void ConfigurationServices(IServiceCollection services)\n{\n  // Other configuration code\n  services.AddMvc(options => \n  {\n    // Using the followint code to add the new binder\n    options.AddFlagsEnumModelBinderProvider();\n  });\n}\n```\n"
  }
]