Merge branch 'dev' into auto-merge/rel-7-2/1909

pull/16429/head
maliming 2 years ago committed by GitHub
commit fb28e9705d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,52 @@
import os
import json
from github import Github
def update_latest_versions():
version = os.environ["GITHUB_REF"].split("/")[-1]
if "rc" not in version:
return False
with open("latest-versions.json", "r") as f:
latest_versions = json.load(f)
latest_versions[0]["version"] = version
with open("latest-versions.json", "w") as f:
json.dump(latest_versions, f, indent=2)
return True
def create_pr():
g = Github(os.environ["GITHUB_TOKEN"])
repo = g.get_repo("abpframework/abp")
branch_name = f"update-latest-versions-{os.environ['GITHUB_REF'].split('/')[-1]}"
base = repo.get_branch("dev")
repo.create_git_ref(ref=f"refs/heads/{branch_name}", sha=base.commit.sha)
# Get the current latest-versions.json file and its sha
contents = repo.get_contents("latest-versions.json", ref="dev")
file_sha = contents.sha
# Update the file in the repo
repo.update_file(
path="latest-versions.json",
message=f"Update latest-versions.json to version {os.environ['GITHUB_REF'].split('/')[-1]}",
content=open("latest-versions.json", "r").read().encode("utf-8"),
sha=file_sha,
branch=branch_name,
)
pr = repo.create_pull(title="Update latest-versions.json",
body="Automated PR to update the latest-versions.json file.",
head=branch_name, base="dev")
pr.create_review_request(reviewers=["ebicoglu", "gizemmutukurt", "skoc10"])
if __name__ == "__dev__":
should_create_pr = update_latest_versions()
if should_create_pr:
create_pr()

@ -0,0 +1,33 @@
name: Update Latest Versions
on:
release:
types:
- published
permissions:
contents: write
pull-requests: write
jobs:
update-versions:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.x
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install PyGithub
- name: Update latest-versions.json and create PR
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python .github/scripts/update_versions.py

@ -455,8 +455,11 @@
"InterestedLicenseType": "Interested License Type",
"MoveWaitList": "Move to wait list",
"CommunityLinkTitle": "Open on the community website",
"CommunityLink": "Community Link",
"CommunityLink": "Community link",
"ReloadFromSource": "Reload From the Source",
"ReloadFromSourceConfirmationMessage": "This post will be refreshed from \"{0}\". Do you want to continue?"
"ReloadFromSourceConfirmationMessage": "This post will be refreshed from \"{0}\". Do you want to continue?",
"UnitPrice": "Unit Price",
"OverallDiscountAmount": "Overall Discount Amount",
"DiscountAmount": "Discount Amount"
}
}

@ -26,7 +26,7 @@
"ContributionGuide": "Contribution Guide",
"Blog": "Blog",
"Commercial": "Commercial",
"MyAccount": "My account",
"MyAccount": "My Account",
"Permission:License": "License",
"Permission:UserInfo": "User info",
"SeeDocuments": "See Documents",

@ -43,9 +43,9 @@
"ContributionGuide": "Contribution Guide",
"Blog": "Blog",
"Commercial": "Commercial",
"MyAccount": "My account",
"MyAccount": "My Account",
"Permission:License": "License",
"Permission:UserInfo": "Usere info",
"Permission:UserInfo": "User info",
"SeeDocuments": "See Documents",
"Samples": "Samples",
"Framework": "Framework",
@ -210,6 +210,7 @@
"Icon": "Icon",
"RecentActivities": "Recent Activities",
"SpringCampaign": "Welcome <br>Spring Sale!",
"SpringCampaign2": "<span>Limited <br> Time Offer!</span>"
"SpringCampaign2": "<span>Limited <br> Time Offer!</span>",
"AboutUs": "About Us"
}
}

@ -208,6 +208,7 @@
"Icon": "图标",
"RecentActivities": "最近的活动",
"SpringCampaign": "欢迎 <br>春季促销!",
"SpringCampaign2": "<span>限时优惠!<br></span>"
"SpringCampaign2": "<span>限时优惠!<br></span>",
"AboutUs": "关于我们"
}
}

@ -41,6 +41,7 @@
"TrialLicensePeriodExpireToday": "您的試用許可期將於今天到期。",
"PurchaseNow": "現在買!",
"WelcomeFallCampaign": "欢迎秋季活动!",
"GiveAwayForNewPurchases": "新购买将赠送应用程序开发课堂培训!"
"GiveAwayForNewPurchases": "新购买将赠送应用程序开发课堂培训!",
"AboutUs": "關於我們"
}
}

@ -247,7 +247,7 @@
"FastEasy": "Fast & Easy",
"AbpSuiteExplanation3": "ABP Suite allows you to easily create CRUD pages. You just need to define your entity and its properties, let the rest to ABP Suite for you! ABP Suite generates all the necessary code for your CRUD page in a few seconds. It supports Angular, MVC and Blazor user interfaces.",
"RichOptions": "Rich Options",
"AbpSuiteExplanation4": "ABP Suite supports multiple UI options like <a href=\"https://docs.microsoft.com/en-us/aspnet/core/razor-pages\">Razor Pages</a> and <a href=\"https://angular.io\">Angular</a>.It also supports multiple databases like <a href=\"https://www.mongodb.com\">MongoDB</a> and all databases supported by <strong>EntityFramework Core</strong> (MS SQL Server, Oracle, MySql, PostgreSQL and <a href=\"https://docs.microsoft.com/en-us/ef/core/providers/?tabs=dotnet-core-cli\">more</a>).",
"AbpSuiteExplanation4": "ABP Suite supports multiple UI options like <a href=\"https://docs.microsoft.com/en-us/aspnet/core/razor-pages\">Razor Pages</a> and <a href=\"https://angular.io\">Angular</a>.It also supports multiple databases like <a href=\"https://www.mongodb.com\">MongoDB</a> and all databases supported by <strong>EntityFramework Core</strong> (MS SQL Server, Oracle, MySql, PostgreSQL, and <a href=\"https://docs.microsoft.com/en-us/ef/core/providers/?tabs=dotnet-core-cli\">other providers...</a>).",
"AbpSuiteExplanation5": "Good thing is that, you don't have to worry about those options. ABP Suite understands your project type and generates the code for your project and places the generated code in correct place in your project.",
"SourceCode": "Source Code",
"AbpSuiteExplanation6": "ABP Suite generates the source code for you! It doesn't generate magic files to generate the web page. ABP Suite generates the source code for <strong>Entity, Repository, Application Service, Code First Migration, JavaScript/TypeScript and CSHTML/HTML</strong> and necessary Interfaces as well. ABP Suite also generates the code according to the <strong>Best Practices</strong> of software development, so you don't have to worry about the generated code's quality.",

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>7.2.1</Version>
<Version>7.3.0</Version>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl>

@ -58,3 +58,22 @@ Here you can see them in table with the GitHub stars, GitHub release counts, rec
In conclusion, these 10 .NET Core libraries are essential tools for any .NET Core developer. They offer a wide range of functionality, from handling errors to mocking for unit testing and simplifying object mapping. Whether you're working on a large-scale enterprise application or a small project, these libraries can help you build more reliable, efficient, and effective applications.
---
### What is ABP Framework?
ABP Framework offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET and the ASP.NET Core platforms. It provides the fundamental infrastructure, production-ready startup templates, modules, themes, tooling, guides and documentation to implement that architecture properly and automate the details and repetitive work as much as possible.
If you are starting a new ASP.NET Core project, try [abp.io](https://abp.io) now...
**IT IS FREE AND OPEN-SOURCE!**
---
> Alper Ebicoglu 🧑🏽‍💻 ABP Framework Core Team Member\
> Follow me for the latest news about .NET and software development:\
> 📌 [twitter.com/alperebicoglu](https://twitter.com/alperebicoglu)\
> 📌 [github.com/ebicoglu](https://github.com/ebicoglu)\
> 📌 [linkedin.com/in/ebicoglu](https://www.linkedin.com/in/ebicoglu)\
> 📌 [medium.com/@alperonline](https://medium.com/@alperonline)

@ -0,0 +1,211 @@
# ABP.IO Platform 7.2 RC Has Been Released
Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **7.2 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version.
Try this version and provide feedback for a more stable version of ABP v7.2! Thanks to all of you.
## Get Started with the 7.2 RC
Follow the steps below to try version 7.2.0 RC today:
1) **Upgrade** the ABP CLI to version `7.2.0-rc.1` using a command line terminal:
````bash
dotnet tool update Volo.Abp.Cli -g --version 7.2.0-rc.1
````
**or install** it if you haven't before:
````bash
dotnet tool install Volo.Abp.Cli -g --version 7.2.0-rc.1
````
2) Create a **new application** with the `--preview` option:
````bash
abp new BookStore --preview
````
See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options.
> You can also use the [Get Started](https://abp.io/get-started) page to generate a CLI command to create a new application.
You can use any IDE that supports .NET 7.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/).
## Migration Guides
There are breaking changes in this version that may affect your application.
Please see the following migration documents, if you are upgrading from v7.1:
* [ABP Framework 7.1 to 7.2 Migration Guide](https://docs.abp.io/en/abp/7.2/Migration-Guides/Abp-7_2)
* [ABP Commercial 7.1 to 7.2 Migration Guide](https://docs.abp.io/en/commercial/7.2/migration-guides/v7_2)
## What's New with ABP Framework 7.2?
In this section, I will introduce some major features released in this version. Here is a brief list of the titles that will be explained in the next sections:
* Grouping of Navigation Menu Items
* Introducing the `BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService`
* CMS Kit Comments: Don't Allow External URLs
* Angular UI New Components
* Others
### Grouping of Navigation Menu Items
Some applications may need to group their main menus to tidy up their menu structure. For example, you may want to group ABP's menu items, which came from modules in a group named *Admin*.
In this version, you can allow to define groups and associate menu items with a group. Then your theme can render your menu items within the specified groups.
**Example:**
```csharp
private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
{
//Creating a new group
context.Menu.AddGroup("Dashboards", l["Dashboards"]);
//Setting the group name for menu items
context.Menu
.AddItem(new ApplicationMenuItem("Home", l["Menu:Home"], groupName: "Dashboards")
.AddItem(new ApplicationMenuItem("Home", l["Menu:Dashboard"], groupName: "Dashboards");
}
```
> **Note**: Currently, only the [LeptonX Theme](https://leptontheme.com/) renders groups for menu items. See the "LeptonX - Render Groups for Menu Items" section below for a demonstration.
### Introducing the `BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService`
In this version, we have introduced the `BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService` service to re-initialize application configurations. This service can be helpful, if you want to reset the application configurations after changing some configurations through your code. For example, you might have changed the values of some settings and might want to be able to get the new settings without the need to refresh the page. For this purpose, the `BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService.ResetAsync()` method can be used to re-initialize the application configurations and cache the updated configurations for further usages.
> For more information, please see [https://github.com/abpframework/abp/issues/15887](https://github.com/abpframework/abp/issues/15887).
### CMS Kit Comments: Disallowing External URLs
CMS Kit provides a [comment system](https://docs.abp.io/en/abp/7.2/Modules/Cms-Kit/Comments) to add the comment feature to any kind of resource, like blog posts for an example. The CMS Kit comment section is good for visitor comments and can improve your interaction with your application users.
Sometimes, malicious users (or bots) can submit advertisement links into the comment sections. With this version, you can specify *allowed external URLs* for a specific comment section and disallow any other external URLs. You just need to configure the `CmsKitCommentOptions` as follows:
```csharp
Configure<CmsKitCommentOptions>(options =>
{
options.AllowedExternalUrls = new Dictionary<string, List<string>>
{
{
"Product",
new List<string>
{
"https://abp.io/"
}
}
};
});
```
If you don't specify any allowed external URLs for a specific comment section, all external URLs are allowed to be used in comments. For more information, please refer to the [CMS Kit: Comments documentation](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Comments).
### New Components for Angular UI
In this version, we have created some useful UI components for Angular UI, which are `abp-checkbox`, `abp-form-input`, and `abp-card`. Instead of using the related HTML elements and specifying bootstrap classes, from this version on, you can use these components.
You can see the following examples for the usage of the `abp-card` component:
```html
<abp-card cardClass="mt-4 mb-5">
<abp-card-body>
<div>...</div>
</abp-card-body>
</abp-card>
```
> See the [Card Component documentation](https://docs.abp.io/en/abp/7.2/UI/Angular/Card-Component) for more information.
### Others
* OpenIddict registered custom scopes have been added to the openid-configuration endpoint (`/.well-known/openid-configuration`) automatically. See [#16141](https://github.com/abpframework/abp/issues/16141) for more information.
* Two new tag-helpers have been added to MVC UI, which are `abp-date-picker` and `abp-date-range-picker`. See [#15806](https://github.com/abpframework/abp/pull/15806) for more information.
* Filtering/searching has been improved in the Docs Module and unified under a single *Search* section. See [#15787](https://github.com/abpframework/abp/issues/15787) for more information.
## What's New with ABP Commercial 7.2?
We've also worked on [ABP Commercial](https://commercial.abp.io/) to align the features and changes made in the ABP Framework. The following sections introduce a few new features coming with ABP Commercial 7.2.
### Authority Delegation
Authority Delegation is a way of delegating the responsibility of the current user to a different user(s) for a limited time. Thus, a user can be switched to the delegated users' account and perform actions on their behalf.
This version introduces support for the **Authority Delegation** in the [Account Module](https://docs.abp.io/en/commercial/latest/modules/account). You can check the following gif for a demonstration:
![authority delegation](authority-delegation.gif)
### Force Password Change at Next Logon
It's a typical need to force users to change their password after their first successful login. Especially, if you as admin create a new user (*from the Users page of the Identity Pro module*, for example) with an easy initial password or a randomly generated password. The user should change his/her password with a more secure password that only they know.
In this version, the "Forcing Password Change at Next Logon" feature has been added for this kind of purpose. Now, it's possible to force a user to change their password on the next login.
The admin only needs to check the *Should change password on next login* option, while creating a new user:
![force password change](force-password-change.png)
After the first successful login, a password change page will open and force the user to change their password:
![password change form](password-change-form.png)
Then, the user starts using their account with a secure password that only they know.
### Periodic Password Changes (Password Aging)
**Password aging** is a mechanism to force users to periodically change their passwords. It allows you to specify a max number of days that a password can be used before it has to be changed.
![password aging](password-aging.png)
You can force this behavior in the "Password renewing settings" section of the Settings page as can be seen in the image above. Then, after the specified time has passed, users will have to renew their passwords.
### LeptonX - Render Groups for Menu Items
As mentioned in the *Grouping of Navigation Menu Items* section above, the [LeptonX Theme](https://leptontheme.com/) renders groups for menu items:
![leptonx-group-menu-render](leptonx-group-render.png)
### Suite: Show Properties on Create/Update/List Pages
In this version, ABP Suite allows you to choose whether a property is visible/invisible on the create/update modals and list page. It also allows you to set specific properties to *readonly* on the update modals.
![suite property](suite-property.png)
## Community News
### ABP - DOTNET CONF'23
![abp-conf](abp-conf.png)
As the ABP team, we've organized 10+ [online events](https://community.abp.io/events) and gained a good experience with software talks. We are organizing ABP Dotnet Conference 2023, a full-featured software conference, in May. You can visit [https://abp.io/conference](https://abp.io/conference) to see speakers, talks, schedules, and other details.
**Less than a month left until the event**! Don't forget to take your seat and buy an early bird ticket from [https://kommunity.com/volosoft/events/1st-abp-conference-96db1a54](https://kommunity.com/volosoft/events/1st-abp-conference-96db1a54)!
### New ABP Community Posts
There are exciting articles contributed by the ABP community as always. I will highlight some of them here:
* [Whats New in .NET 8 🧐 ? Discover ALL .NET 8 Features](https://community.abp.io/posts/whats-new-in-.net-8-discover-all-.net-8-features-llcmrdre) by [Alper Ebicoglu](https://twitter.com/alperebicoglu).
* [Converting Create/Edit Modal to Page - Blazor](https://community.abp.io/posts/converting-createedit-modal-to-page-blazor-eexdex8y) by [Enis Necipoglu](https://twitter.com/EnisNecipoglu).
* [Using Dapper with the ABP Framework](https://community.abp.io/posts/using-dapper-with-the-abp-framework-shp74p2l) by [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan).
* [ABP React Template](https://community.abp.io/posts/abp-react-template-33pjmran) by [Anto Subash](https://twitter.com/antosubash).
* [How to Export Data to Excel Files with ASP.NET Core Minimal API](https://community.abp.io/posts/how-to-export-data-to-excel-files-with-asp.net-core-minimal-api-79o45u3s) by [Berkan Sasmaz](https://twitter.com/berkansasmazz).
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community.
### New ABP Blog Posts
There are also some exciting blog posts written by the ABP team. You can see the following list for some of those articles:
* [ABP Framework: Open Source Web Application Development Framework](https://blog.abp.io/abp/open-source-web-application-development-framework) by [Alper Ebicoglu](https://twitter.com/alperebicoglu).
* [ABP Framework: The Ultimate .NET Web Framework for Rapid Application Development](https://blog.abp.io/abp/ultimate-net-web-framework-for-rapid-application-development) by [Alper Ebicoglu](https://twitter.com/alperebicoglu).
* [Top 10 .NET Core Libraries Every Developer Should Know 🔥](https://blog.abp.io/abp/Top-10-.NET-Core-Libraries-Every-Developer-Should-Know) by [Alper Ebicoglu](https://twitter.com/alperebicoglu)
## Conclusion
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/7.2/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v7.2 RC and provide feedback to help us release a more stable version.
Thanks for being a part of this community!

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

@ -0,0 +1,118 @@
# 💻 How to Optimize Your ASP.NET Application for Improved Performance 🚀
If you want your ASP.NET application to perform well, you need to optimize it for speed, responsiveness, and user experience. Performance optimization is critical for factors like fast page load times, improved response efficiency, and happy users. In this article, I'll provide several tips and tricks to help you optimize performance in ASP.NET Core.
## 🚀 Use Response Compression in Your ASP.NET Application
You can use ASP.NET Core's built-in response compression middleware to compress the response data and reduce the amount of data that needs to be transferred over the network. To use response compression, add the following code to your application's Startup.cs file:
```csharp
services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
});
app.UseResponseCompression();
```
## 🖼️ Optimize Images in Your ASP.NET Application:
Images can be a major contributor to page bloat and slow load times. Here are some tips to optimize images:
🖌️ Use a tool like ImageOptim or Kraken.io to compress and optimize images.
🖼️ Specify the width and height of images in HTML so the browser can allocate space for them before they load.
📝 Use alt attributes to provide descriptive text for images, which can improve accessibility and also help with SEO.
📜 Use lazy loading for images that are below the fold, meaning they're not visible on the initial screen view. You can use libraries like Vanilla LazyLoad to implement lazy loading.
📱 Use responsive images to serve different image sizes to different devices. This can improve page load times by reducing the size of images that are displayed on smaller devices.
💻 Example:
```html
<picture>
<source media="(min-width: 650px)" data-srcset="image.webp">
<source media="(min-width: 465px)" data-srcset="image_small.webp">
<img src="placeholder.png" data-src="image.webp" alt="Image" width="100" height="100" class="lazy" />
</picture>
```
```javascript
var lazyLoadInstance = new LazyLoad();
```
## 🧱 Optimize HTML in Your ASP.NET Application:
The structure and organization of HTML can affect the page speed. Here are some tips to optimize HTML:
📝 Use the heading tags (h1, h2, h3, etc.) in a logical and sequential order.
🔩 Use the "defer" attribute for script tags that don't need to be executed immediately. This can improve the page load times by delaying the execution of scripts until after the page has rendered.
🔩 Use the "async" attribute for script tags that can be executed asynchronously. This can further improve the page load times by allowing scripts to be downloaded and executed simultaneously.
🧱 Use semantic HTML elements (like nav, section, and article) to provide additional structure and meaning to the page.
## 🎨 Optimize CSS and JavaScript in Your ASP.NET Application:
CSS and JavaScript files can be a major contributor to the page load times. Here are some tips to optimize CSS and JavaScript in your ASP.NET application:
🔨 Minify and concatenate CSS and JavaScript files to reduce their size.
🔩 Use the "defer" or "async" attributes for script tags to delay or asynchronously load scripts.
## 🔡 Use system fonts in Your ASP.NET Application:
Loading custom fonts can be slow and increase page load times. Using system fonts can improve page speed by allowing the browser to use fonts that are already installed on the user's device.
## 🖼️ Use Placeholders and Progress Indicators in Your ASP.NET Application:
To improve the perceived performance of your website, you can use placeholders and progress indicators for slow-loading sections of your page. You can use JavaScript to load these sections after the initial page load.
💻 Example:
```html
<div id="placeholder" data-url="/slow-loading-content">
<p>Loading...</p>
</div>
```
```javascript
const placeholder = document.querySelector('#placeholder');
fetch(placeholder.dataset.url)
.then(response => response.text())
.then(html => placeholder.innerHTML = html);
```
## 🔗 Use the Appropriate Link Text and ARIA Labels:
When using links, use appropriate link texts that accurately describe the content of the linked page. This can improve the accessibility and also help with SEO.
ARIA labels should also be used to provide additional context for links. This can also improve the accessibility and help with SEO.
💻 Example:
```html
<a href="https://example.com/" aria-label="Go to Example">Example</a>
<a href="https://example.com/" aria-label="Go to Another Example">Another Example</a>
```
## 🌐 Optimize the Third-party Resources in Your ASP.NET Application:
Third-party resources like social media widgets and advertising scripts can slow down the page load times. Here are some tips to optimize third-party resources:
🔩 Use asynchronous scripts when possible.
🔍 Only load third-party resources that are necessary for the page.
By following these optimization techniques, you can significantly improve the page speed of your ASP.NET Core web application.
## What is ABP Framework?
ABP Framework offers an opinionated architecture to build enterprise software solutions with ASP.NET Core best practices on top of the .NET and the ASP.NET Core platforms. It provides the fundamental web application infrastructure, production-ready dotnet startup templates, modules, asp.net core ui themes, tooling, guides and documentation to implement that ASP.NET core architecture properly and automate the details and repetitive work as much as possible.
If you are starting a new ASP.NET Core project, try [abp.io](https://abp.io/) now...
**IT IS FREE AND OPEN-SOURCE!**

@ -41,6 +41,7 @@ Here, is the list of all available commands before explaining their details:
* **`switch-to-preview`**: Switches to the latest preview version of the ABP Framework.
* **`switch-to-nightly`**: Switches to the latest [nightly builds](Nightly-Builds.md) of the ABP related packages on a solution.
* **`switch-to-stable`**: Switches to the latest stable versions of the ABP related packages on a solution.
* **`switch-to-local`**: Changes NuGet package references on a solution to local project references.
* **`translate`**: Simplifies to translate localization files when you have multiple JSON [localization](Localization.md) files in a source control repository.
* **`login`**: Authenticates on your computer with your [abp.io](https://abp.io/) username and password.
* **`login-info`**: Shows the current user's login information.
@ -453,6 +454,20 @@ abp switch-to-stable [options]
* `--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
### switch-to-local
Changes all NuGet package references to local project references for all the .csproj files in the specified folder (and all its subfolders with any deep). It is not limited to ABP Framework or Module packages.
Usage:
````bash
abp switch-to-local [options]
````
#### Options
* `--solution` or `-s`: Specifies the solution directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
* `--paths` or `-p`: Specifies the local paths that the projects are inside.
### translate
Simplifies to translate [localization](Localization.md) files when you have multiple JSON [localization](Localization.md) files in a source control repository.

@ -241,3 +241,24 @@ That's all! We created a passwordless login with 7 steps.
## Source Code
The completed sample is available on [GitHub repository](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication).
〰️〰️〰️
Happy Coding 🤗
---
I'm Alper Ebicoglu 🧑🏽‍💻 ABP Framework Core Team Member
Follow me for the latest news about .NET and software development:
📌 [twitter.com/alperebicoglu](https://twitter.com/alperebicoglu)
📌 [github.com/ebicoglu](https://github.com/ebicoglu)
📌 [linkedin.com/in/ebicoglu](https://www.linkedin.com/in/ebicoglu)
📌 [medium.com/@alperonline](https://medium.com/@alperonline)
---

@ -258,8 +258,19 @@ Not all the changes are here, but you can check out the following PR of the .NET
* [github.com/abpframework/abp/pull/13626/files](https://github.com/abpframework/abp/pull/13626/files)
...
Happy coding with .NET 7 🤗
〰️〰️〰️
...
Happy Coding 🤗
---
> I'm Alper Ebicoglu 🧑🏽‍💻 ABP Framework Core Team Member
> Follow me for the latest news about .NET and software development:
> 📌 [twitter.com/alperebicoglu](https://twitter.com/alperebicoglu)
>
> 📌 [github.com/ebicoglu](https://github.com/ebicoglu)
>
> 📌 [linkedin.com/in/ebicoglu](https://www.linkedin.com/in/ebicoglu)
>
> 📌 [medium.com/@alperonline](https://medium.com/@alperonline)https://medium.com/@alperonline)\

@ -220,7 +220,17 @@ Become a pioneer and try the new features of .NET 8 now.
Adapt it to your project or start a new .NET 8 project.
[Claim your copy of .NET 8](https://dotnet.microsoft.com/next) today 🏎️ !
〰️〰️〰️
Happy Coding ⌨️
---
> I'm Alper Ebicoglu 🧑🏽‍💻 ABP Framework Core Team Member
> Follow me for the latest news about .NET and software development:
> 📌 [twitter.com/alperebicoglu](https://twitter.com/alperebicoglu)
> 📌 [github.com/ebicoglu](https://github.com/ebicoglu)
> 📌 [linkedin.com/in/ebicoglu](https://www.linkedin.com/in/ebicoglu)
> 📌 [medium.com/@alperonline](https://medium.com/@alperonline)

@ -0,0 +1,325 @@
# Convert Create/Edit Modals to Page
In this document we will explain how to convert the BookStore's Books create & edit modals to regular Angular component pages.
## Before
![bookstore-crud-before](images/old.gif)
## After
![bookstore-crud-after](images/new.gif)
# BooksComponent
This is the main component of the books management. The create & update operations are done in this page. So we'll remove the create & update operations from this page and move a separate angular component for each operation. Each component will be a page.
- Remove the Create & Update modal on template
![remove-modal](images/books-remove-modals.png)
- Modify the **NewBook** button with a link to the **CreateBookComponent** page.
```html
<button
*abpPermission="'BookStore.Books.Create'"
id="create"
class="btn btn-primary"
type="button"
[routerLink]="['create']"
>
<i class="fa fa-plus me-1"></i>
<span>{{ '::NewBook' | abpLocalization }}</span>
</button>
```
- Modify the **Edit** button with a link to the **EditBookComponent** page.
```html
<button
*abpPermission="'BookStore.Books.Edit'"
ngbDropdownItem
[routerLink]="['edit', row.id]"
>
{{ '::Edit' | abpLocalization }}
</button>
```
- Remove all unused methods and variables in the `BookComponent` except the **book** variable, the **ngOnInit** and **delete** methods.
![bookstore-remove-unsued](images/books-remove-unused.png)
- Also we can clear unncessary imports
![bookstore-remove-unsued-imports](images/books-remove-unused-imports.png)
# CreateBookComponent Page
Create a new component by the name `create-book` in your project.
```bash
yarn ng g c book/create-book --skip-tests --style=none
```
- `create-book.component.html`
```html
<div class="card">
<div class="card-body">
<form [formGroup]="form" (ngSubmit)="save()">
<div class="form-group">
<label for="author-id">Author</label><span> * </span>
<select class="form-control" id="author-id" formControlName="authorId">
<option [ngValue]="null">Select author</option>
<option [ngValue]="author.id" *ngFor="let author of authors$ | async">
{{ author.name }}
</option>
</select>
</div>
<div class="mt-2">
<label for="book-name">Name</label><span> * </span>
<input type="text" id="book-name" class="form-control" formControlName="name" autofocus />
</div>
<div class="mt-2">
<label for="book-price">Price</label><span> * </span>
<input type="number" id="book-price" class="form-control" formControlName="price" />
</div>
<div class="mt-2">
<label for="book-type">Type</label><span> * </span>
<select class="form-control" id="book-type" formControlName="type">
<option [ngValue]="null">Select a book type</option>
<option [ngValue]="type.value" *ngFor="let type of bookTypes">
{{ '::Enum:BookType.' + type.value | abpLocalization }}
</option>
</select>
</div>
<div class="mt-2">
<label>Publish date</label><span> * </span>
<input
#datepicker="ngbDatepicker"
class="form-control"
name="datepicker"
formControlName="publishDate"
ngbDatepicker
(click)="datepicker.toggle()"
/>
</div>
<div class="pt-2">
<button type="button" class="btn btn-secondary m-1" [routerLink]="['/books']">
{{ '::Cancel' | abpLocalization }}
</button>
<button class="btn btn-primary">
<i class="fa fa-check mr-1"></i>
{{ '::Save' | abpLocalization }}
</button>
</div>
</form>
</div>
</div>
```
- `create-book.component.ts`
```ts
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { map } from 'rxjs';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
import { BookService, bookTypeOptions } from '@proxy/books';
const { required } = Validators;
@Component({
selector: 'app-create-book',
templateUrl: './create-book.component.html',
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class CreateBookComponent {
//inject() function came with Angular v14, detail: https://angular.io/api/core/inject
private readonly router = inject(Router);
private readonly fb = inject(FormBuilder);
private readonly bookService = inject(BookService);
form: FormGroup;
authors$ = this.bookService.getAuthorLookup().pipe(map(({ items }) => items));
bookTypes = bookTypeOptions;
private buildForm() {
this.form = this.fb.group({
authorId: [null, required],
name: [null, required],
type: [null, required],
publishDate: [undefined, required],
price: [null, required],
});
}
constructor() {
this.buildForm();
}
save() {
if (this.form.invalid) return;
this.bookService.create(this.form.value).subscribe(() => {
this.router.navigate(['/books']);
});
}
}
```
# EditBookComponent Page
Create a new component by the name **edit-book** in your project.
```bash
yarn ng g c book/edit-book --skip-tests --style=none
```
- `edit-book.component.html`
```html
<div class="card">
<div class="card-body">
<form *ngIf="form" [formGroup]="form" (ngSubmit)="save()">
<div class="form-group">
<label for="author-id">Author</label><span> * </span>
<select class="form-control" id="author-id" formControlName="authorId">
<option [ngValue]="null">Select author</option>
<option [ngValue]="author.id" *ngFor="let author of authors$ | async">
{{ author.name }}
</option>
</select>
</div>
<div class="mt-2">
<label for="book-name">Name</label><span> * </span>
<input type="text" id="book-name" class="form-control" formControlName="name" autofocus />
</div>
<div class="mt-2">
<label for="book-price">Price</label><span> * </span>
<input type="number" id="book-price" class="form-control" formControlName="price" />
</div>
<div class="mt-2">
<label for="book-type">Type</label><span> * </span>
<select class="form-control" id="book-type" formControlName="type">
<option [ngValue]="null">Select a book type</option>
<option [ngValue]="type.value" *ngFor="let type of bookTypes">
{{ '::Enum:BookType.' + type.value | abpLocalization }}
</option>
</select>
</div>
<div class="mt-2">
<label>Publish date</label><span> * </span>
<input
#datepicker="ngbDatepicker"
class="form-control"
name="datepicker"
formControlName="publishDate"
ngbDatepicker
(click)="datepicker.toggle()"
/>
</div>
<div class="pt-2">
<button type="button" class="btn btn-secondary m-1" [routerLink]="['/books']">
{{ '::Cancel' | abpLocalization }}
</button>
<button class="btn btn-primary">
<i class="fa fa-check mr-1"></i>
{{ '::Save' | abpLocalization }}
</button>
</div>
</form>
</div>
</div>
```
- `edit-book.component.ts`
```ts
import { Component, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { filter, map, switchMap, tap } from 'rxjs';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
import { BookDto, BookService, bookTypeOptions } from '@proxy/books';
const { required } = Validators;
@Component({
selector: 'app-edit-book',
templateUrl: './edit-book.component.html',
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class EditBookComponent {
//inject() function came with Angular v14, detail: https://angular.io/api/core/inject
private readonly router = inject(Router);
private readonly activatedRoute = inject(ActivatedRoute);
private readonly fb = inject(FormBuilder);
private readonly bookService = inject(BookService);
id: string;
form: FormGroup;
authors$ = this.bookService.getAuthorLookup().pipe(map(({ items }) => items));
bookTypes = bookTypeOptions;
private buildForm(book: BookDto): void {
this.form = this.fb.group({
authorId: [book.authorId, required],
name: [book.name, required],
type: [book.type, required],
publishDate: [new Date(book.publishDate), required],
price: [book.price, required],
});
}
constructor() {
this.activatedRoute.params
.pipe(
filter(params => params.id),
tap(({ id }) => (this.id = id)),
switchMap(({ id }) => this.bookService.get(id)),
tap(book => this.buildForm(book))
)
.subscribe();
}
save(): void {
if (this.form.invalid) return;
this.bookService.update(this.id, this.form.value).subscribe(() => {
this.router.navigate(['/books']);
});
}
}
```
# Update BookRoutingModule
Finally add 2 items to the routes array in the `book-routing.module.ts`
```ts
import { CreateBookComponent } from './create-book/create-book.component';
import { EditBookComponent } from './edit-book/edit-book.component';
const routes: Routes = [
{ path: '', component: BookComponent, canActivate: [AuthGuard, PermissionGuard] },
{ path: 'create', component: CreateBookComponent },
{ path: 'edit/:id', component: EditBookComponent },
];
```
You can check the following commit for more details: https://github.com/abpframework/abp-samples/commit/351ad5e093036702edbb15169968935496afea0e

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 KiB

@ -120,6 +120,8 @@ Build the project, open the build folder, find the `MyPlugIn.dll`:
Copy `MyPlugIn.dll` into the plug-in folder (`D:\Temp\MyPlugIns` for this example).
> Please delete the `MyPlugIn.deps.json` file if you use `build folder` folder as `PlugInSources`.
If you have configured the main application like described above (see Basic Usage section), you should see the `MyService has been initialized` log in the application startup.
## Example: Creating a Plug-In With Razor Pages
@ -227,4 +229,4 @@ If your module uses a relational database and [Entity Framework Core](Entity-Fra
1. The Plugin may check if the database tables does exists and create the tables on the application startup or migrate them if the plug-in has been updated and requires some schema changes. You can use EF Core's migration API to do that.
2. You can improve the `DbMigrator` application to find migrations of the plug-ins and execute them.
There may be other solutions. For example, if your DB admin doesn't allow you to change the database schema in the application code, you may need to manually send a SQL file to the database admin to apply it to the database.
There may be other solutions. For example, if your DB admin doesn't allow you to change the database schema in the application code, you may need to manually send a SQL file to the database admin to apply it to the database.

@ -238,7 +238,7 @@ context.Menu.AddItem(
"BooksStore.Authors",
l["Menu:Authors"],
url: "/Authors"
).RequirePermissions(BookStorePermissions.Books.Default)
).RequirePermissions(BookStorePermissions.Authors.Default)
)
);
````

@ -0,0 +1,45 @@
# Security Headers
ABP Framework allows you to add frequently used security headers into your application. The following security headers will be added as response headers to your application if you use the `UseAbpSecurityHeaders` middleware:
* `X-Content-Type-Options`: Tells the browser to not try and guess what a mime-type of a resource might be, and to just take what mime-type the server has returned.
* `X-XSS-Protection`: This is a feature of Internet Explorer, Chrome, and Safari that stops pages from loading when they detect reflected cross-site scripting (XSS) attacks.
* `X-Frame-Options`: This header can be used to indicate whether or not a browser should be allowed to render a page in a `<iframe>` tag. By specifying this header value as *SAMEORIGIN*, you can make it displayed in a frame on the same origin as the page itself.
* `Content-Security-Policy`: This response header allows you to restrict which resources (such as JavaScript, CSS, images, manifests, etc.) can be loaded, and the URLs that they can be loaded from. This security header will only be added if you configure the `AbpSecurityHeadersOptions` class and enable it.
## Configuration
### AbpSecurityHeadersOptions
`AbpSecurityHeadersOptions` is the main class to enable the `Content-Security-Policy` header, define its value and set other security headers that you want to add to your application.
**Example:**
```csharp
Configure<AbpSecurityHeadersOptions>(options =>
{
options.UseContentSecurityPolicyHeader = true; //false by default
options.ContentSecurityPolicyValue = "object-src 'none'; form-action 'self'; frame-ancestors 'none'";
//adding additional security headers
options.Headers["Referrer-Policy"] = "no-referrer";
});
```
> If the header is the same, the additional security headers you defined take precedence over the default security headers. In other words, it overrides the default security headers' values.
## Security Headers Middleware
Security Headers middleware is an ASP.NET Core request pipeline [middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware) that adds pre-defined security headers to your application, including `X-Content-Type-Options`, `X-XSS-Protection`, and `X-Frame-Options`. Additionally, this middleware also includes those unique security headers in your application if you configure the `AbpSecurityHeadersOptions` as mentioned above.
**Example:**
```csharp
app.UseAbpSecurityHeaders();
```
> You can add this middleware into the `OnApplicationInitialization` method of your module class to register it to the request pipeline. This middleware is already configured in the [ABP Commercial Startup Templates](https://docs.abp.io/en/commercial/latest/startup-templates/index), so you don't need to manually add it if you are using one of these startup templates.
After that, you have registered the `UseAbpSecurityHeaders` middleware into the request pipeline, the defined security headers will be shown in the response headers as in the figure below:
![](../../images/security-response-headers.png)

@ -845,6 +845,15 @@
"path": "UI/AspNetCore/Page-Toolbar-Extensions.md"
}
]
},
{
"text": "Security",
"items": [
{
"text": "Security Headers",
"path": "UI/AspNetCore/Security-Headers.md"
}
]
}
]
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@ -46,6 +46,10 @@ public class AbpInputTagHelper : AbpTagHelper<AbpInputTagHelper, AbpInputTagHelp
public bool SuppressLabel { get; set; }
[HtmlAttributeName("floating-label")]
public bool FloatingLabel { get; set; }
public CheckBoxHiddenInputRenderMode? CheckBoxHiddenInputRenderMode { get; set; }
public AbpInputTagHelper(AbpInputTagHelperService tagHelperService)

@ -54,6 +54,10 @@ public class AbpInputTagHelperService : AbpTagHelperService<AbpInputTagHelper>
output.TagMode = TagMode.StartTagAndEndTag;
output.TagName = "div";
LeaveOnlyGroupAttributes(context, output);
if (TagHelper.FloatingLabel && !isCheckBox)
{
output.Attributes.AddClass("form-floating");
}
output.Attributes.AddClass(isCheckBox ? "mb-2" : "mb-3");
if (isCheckBox)
{

@ -44,6 +44,10 @@ public class AbpSelectTagHelper : AbpTagHelper<AbpSelectTagHelper, AbpSelectTagH
public string Placeholder { get; set; }
[HtmlAttributeName("floating-label")]
public bool FloatingLabel { get; set; }
public AbpSelectTagHelper(AbpSelectTagHelperService tagHelperService)
: base(tagHelperService)
{

@ -56,6 +56,10 @@ public class AbpSelectTagHelperService : AbpTagHelperService<AbpSelectTagHelper>
{
output.TagName = "div";
LeaveOnlyGroupAttributes(context, output);
if (TagHelper.FloatingLabel)
{
output.Attributes.AddClass("form-floating");
}
output.Attributes.AddClass("mb-3");
output.TagMode = TagMode.StartTagAndEndTag;
output.Content.SetHtmlContent(innerHtml);

@ -100,6 +100,14 @@ public class FormElementsDemoModel
<abp-input asp-for="@Model.MyDetailsModel.Description" label="Description" />
</abp-component-demo-section>
<abp-component-demo-section title="Floating Labels" view-path="@FormElementsDemoViewComponent.ViewPath">
<abp-input asp-for="@Model.MyDetailsModel.EmailAddress" label="Email Address" placeholder="name@example.com" floating-label="true" />
<abp-select asp-for="@Model.MyDetailsModel.City" asp-items="@Model.CityList" label="City" floating-label="true" />
<abp-select asp-for="@Model.MyDetailsModel.Cities" asp-items="@Model.CityList" label="Cities" floating-label="true" />
<abp-input asp-for="@Model.MyDetailsModel.Description" label="Description" floating-label="true" />
</abp-component-demo-section>
<abp-component-demo-section title="Sizing" view-path="@FormElementsDemoViewComponent.ViewPath">
<abp-input asp-for="@Model.MyInformMeModel.Name" size="Large" />
<abp-input asp-for="@Model.MyInformMeModel.Name" size="Small" />

@ -20,27 +20,38 @@ public class AbpSecurityHeadersMiddleware : IMiddleware, ITransientDependency
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
/*X-Content-Type-Options header tells the browser to not try and “guess” what a mimetype of a resource might be, and to just take what mimetype the server has returned as fact.*/
AddHeaderIfNotExists(context, "X-Content-Type-Options", "nosniff");
AddHeader(context, "X-Content-Type-Options", "nosniff");
/*X-XSS-Protection is a feature of Internet Explorer, Chrome and Safari that stops pages from loading when they detect reflected cross-site scripting (XSS) attacks*/
AddHeaderIfNotExists(context, "X-XSS-Protection", "1; mode=block");
AddHeader(context, "X-XSS-Protection", "1; mode=block");
/*The X-Frame-Options HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a <frame>, <iframe> or <object>. SAMEORIGIN makes it being displayed in a frame on the same origin as the page itself. The spec leaves it up to browser vendors to decide whether this option applies to the top level, the parent, or the whole chain*/
AddHeaderIfNotExists(context, "X-Frame-Options", "SAMEORIGIN");
AddHeader(context, "X-Frame-Options", "SAMEORIGIN");
if (Options.Value.UseContentSecurityPolicyHeader)
{
AddHeaderIfNotExists(context, "Content-Security-Policy",
AddHeader(context, "Content-Security-Policy",
Options.Value.ContentSecurityPolicyValue.IsNullOrEmpty()
? "object-src 'none'; form-action 'self'; frame-ancestors 'none'"
: Options.Value.ContentSecurityPolicyValue);
}
foreach (var (key, value) in Options.Value.Headers)
{
AddHeader(context, key, value, true);
}
await next.Invoke(context);
}
protected virtual void AddHeaderIfNotExists(HttpContext context, string key, string value)
protected virtual void AddHeader(HttpContext context, string key, string value, bool overrideIfExists = false)
{
if (overrideIfExists && context.Response.Headers.TryGetValue(key, out _))
{
context.Response.Headers[key] = value;
return;
}
context.Response.Headers.AddIfNotContains(new KeyValuePair<string, StringValues>(key, value));
}
}

@ -1,3 +1,5 @@
using System.Collections.Generic;
namespace Volo.Abp.AspNetCore.Security;
public class AbpSecurityHeadersOptions
@ -5,4 +7,11 @@ public class AbpSecurityHeadersOptions
public bool UseContentSecurityPolicyHeader { get; set; }
public string ContentSecurityPolicyValue { get; set; }
public Dictionary<string, string> Headers { get; }
public AbpSecurityHeadersOptions()
{
Headers = new Dictionary<string, string>();
}
}

@ -56,6 +56,7 @@ public class AbpCliCoreModule : AbpModule
options.Commands[SwitchToPreviewCommand.Name] = typeof(SwitchToPreviewCommand);
options.Commands[SwitchToStableCommand.Name] = typeof(SwitchToStableCommand);
options.Commands[SwitchToNightlyCommand.Name] = typeof(SwitchToNightlyCommand);
options.Commands[SwitchToLocal.Name] = typeof(SwitchToLocal);
options.Commands[TranslateCommand.Name] = typeof(TranslateCommand);
options.Commands[BuildCommand.Name] = typeof(BuildCommand);
options.Commands[BundleCommand.Name] = typeof(BundleCommand);

@ -0,0 +1,125 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Cli.Args;
using Volo.Abp.Cli.ProjectModification;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.Commands;
public class SwitchToLocal : IConsoleCommand, ITransientDependency
{
private readonly LocalReferenceConverter _localReferenceConverter;
public const string Name = "switch-to-local";
public ILogger<SwitchToLocal> Logger { get; set; }
public SwitchToLocal(LocalReferenceConverter localReferenceConverter)
{
_localReferenceConverter = localReferenceConverter;
}
public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
{
var workingDirectory = GetWorkingDirectory(commandLineArgs) ?? Directory.GetCurrentDirectory();
if (!Directory.Exists(workingDirectory))
{
throw new CliUsageException(
"Specified directory does not exist." +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
await _localReferenceConverter.ConvertAsync(workingDirectory, GetPaths(commandLineArgs));
}
private List<string> GetPaths(CommandLineArgs commandLineArgs)
{
var paths = commandLineArgs.Options.GetOrNull(
Options.LocalPaths.Short,
Options.LocalPaths.Long
);
if (paths == null)
{
throw new CliUsageException(
"Local paths are not specified!" +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
return paths.Split("|").Select(x=> x.Trim()).ToList();
}
private string GetWorkingDirectory(CommandLineArgs commandLineArgs)
{
var path = commandLineArgs.Options.GetOrNull(
Options.SolutionPath.Short,
Options.SolutionPath.Long
);
if (path == null)
{
return null;
}
if (path.EndsWith(".sln") || path.EndsWith(".csproj"))
{
return Path.GetDirectoryName(path);
}
return path;
}
public string GetShortDescription()
{
return "Changes all NuGet package references to local project references for all the .csproj files in the specified folder" +
" (and all its subfolders with any deep)";
}
public string GetUsageInfo()
{
var sb = new StringBuilder();
sb.AppendLine("");
sb.AppendLine("Usage:");
sb.AppendLine("");
sb.AppendLine(" abp switch-to-local [options]");
sb.AppendLine("");
sb.AppendLine("Options:");
sb.AppendLine("");
sb.AppendLine("-s |--solution <directory-path> (default: current directory)");
sb.AppendLine("-p | --paths <local-paths> (Required)");
sb.AppendLine("");
sb.AppendLine("Examples:");
sb.AppendLine("");
sb.AppendLine(" abp switch-to-local --paths D:\\Github\\abp");
sb.AppendLine(" abp switch-to-local --paths D:\\Github\\abp --solution D:\\test\\MyProject");
sb.AppendLine(" abp switch-to-local --paths \"D:\\Github\\abp|D:\\Github\\volo\"");
sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI");
return sb.ToString();
}
public static class Options
{
public static class SolutionPath
{
public const string Short = "s";
public const string Long = "solution";
}
public static class LocalPaths
{
public const string Short = "p";
public const string Long = "paths";
}
}
}

@ -69,19 +69,19 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Host"));
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Server",
"MyCompanyName.MyProjectName.Host"));
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Client",
"MyCompanyName.MyProjectName.Blazor"));
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Shared",
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Shared",
"MyCompanyName.MyProjectName.Contracts"));
steps.Add(new AppNoLayersMoveProjectsStep());
steps.Add(new AppNoLayersMigrateDatabaseChangeStep());
steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly"));
break;
case UiFramework.BlazorServer:
steps.Add(new RemoveFolderStep("/angular"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc"));
@ -98,7 +98,7 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Mvc", "MyCompanyName.MyProjectName"));
RemoveBlazorWasmProjects(steps);
break;
default:
throw new AbpException("Unkown UI framework: " + context.BuildArgs.UiFramework);
}
@ -107,6 +107,7 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations"));
RandomizeSslPorts(context, steps);
RandomizeStringEncryption(context, steps);
RandomizeAuthServerPassPhrase(context, steps);
UpdateNuGetConfig(context, steps);
ChangeConnectionString(context, steps);
ConfigureDockerFiles(context, steps);

@ -41,6 +41,7 @@ public abstract class AppTemplateBase : TemplateInfo
RemoveUnnecessaryPorts(context, steps);
RandomizeSslPorts(context, steps);
RandomizeStringEncryption(context, steps);
RandomizeAuthServerPassPhrase(context, steps);
UpdateNuGetConfig(context, steps);
ConfigureDockerFiles(context, steps);
ChangeConnectionString(context, steps);
@ -240,7 +241,7 @@ public abstract class AppTemplateBase : TemplateInfo
private static bool IsDefaultThemeForTemplate(ProjectBuildArgs args)
{
var templateThemes = new Dictionary<string, Theme>
var templateThemes = new Dictionary<string, Theme>
{
{ AppTemplate.TemplateName, AppTemplate.DefaultTheme },
{ AppProTemplate.TemplateName, AppProTemplate.DefaultTheme },
@ -610,6 +611,11 @@ public abstract class AppTemplateBase : TemplateInfo
steps.Add(new RandomizeStringEncryptionStep());
}
protected static void RandomizeAuthServerPassPhrase(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
steps.Add(new RandomizeAuthServerPassPhraseStep());
}
protected void UpdateNuGetConfig(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
steps.Add(new UpdateNuGetConfigStep("/aspnet-core/NuGet.Config"));

@ -38,6 +38,7 @@ public abstract class MicroserviceServiceTemplateBase : TemplateInfo
DeleteUnrelatedUiProject(context, steps);
SetRandomPortForHostProject(context, steps);
RandomizeStringEncryption(context, steps);
RandomizeAuthServerPassPhrase(context, steps);
return steps;
}
@ -69,4 +70,9 @@ public abstract class MicroserviceServiceTemplateBase : TemplateInfo
{
steps.Add(new MicroserviceServiceStringEncryptionStep());
}
private static void RandomizeAuthServerPassPhrase(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
steps.Add(new RandomizeAuthServerPassPhraseStep());
}
}

@ -23,19 +23,20 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
DeleteUnrelatedProjects(context, steps);
RandomizeStringEncryption(context, steps);
RandomizeAuthServerPassPhrase(context, steps);
UpdateNuGetConfig(context, steps);
ConfigureTheme(context, steps);
return steps;
}
protected void ConfigureTheme(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
if (!context.BuildArgs.Theme.HasValue)
{
return;
}
if (context.BuildArgs.Theme != Theme.NotSpecified)
{
context.Symbols.Add(context.BuildArgs.Theme.Value.ToString().ToUpper());
@ -54,7 +55,7 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
private static void RemoveLeptonXThemePackagesFromPackageJsonFiles(List<ProjectBuildPipelineStep> steps, UiFramework uiFramework)
{
var mvcUiPackageName = "@volo/abp.aspnetcore.mvc.ui.theme.leptonx";
var packageJsonFilePaths = new List<string>
var packageJsonFilePaths = new List<string>
{
"/MyCompanyName.MyProjectName.AuthServer/package.json",
"/MyCompanyName.MyProjectName.Web/package.json"
@ -68,11 +69,11 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
if (uiFramework == UiFramework.BlazorServer)
{
var blazorServerUiPackageName = "@volo/aspnetcore.components.server.leptonxtheme";
var blazorServerPackageJsonFilePaths = new List<string>
var blazorServerPackageJsonFilePaths = new List<string>
{
"/MyCompanyName.MyProjectName.Blazor/package.json"
};
foreach (var blazorServerPackageJsonFilePath in blazorServerPackageJsonFilePaths)
{
steps.Add(new RemoveDependencyFromPackageJsonFileStep(blazorServerPackageJsonFilePath, mvcUiPackageName));
@ -82,11 +83,11 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
else if (uiFramework == UiFramework.Angular)
{
var ngUiPackageName = "@volosoft/abp.ng.theme.lepton-x";
var angularPackageJsonFilePaths = new List<string>
var angularPackageJsonFilePaths = new List<string>
{
"/angular/package.json"
};
foreach (var angularPackageJsonFilePath in angularPackageJsonFilePaths)
{
steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, ngUiPackageName));
@ -138,7 +139,7 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
steps.Add(new RemoveFolderStep("/apps/blazor"));
steps.Add(new RemoveProjectFromTyeStep("blazor"));
steps.Add(new RemoveProjectFromTyeStep("blazor-server"));
context.Symbols.Add("ui:angular");
break;
@ -157,7 +158,7 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
null,
"/apps/blazor/src/MyCompanyName.MyProjectName.Blazor.Server"));
steps.Add(new RemoveProjectFromTyeStep("blazor-server"));
context.Symbols.Add("ui:blazor");
break;
@ -180,7 +181,7 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
steps.Add(new TemplateProjectRenameStep("MyCompanyName.MyProjectName.Blazor.Server",
"MyCompanyName.MyProjectName.Blazor"));
steps.Add(new RenameProjectInTyeStep("blazor-server", "blazor"));
context.Symbols.Add("ui:blazor-server");
break;
@ -198,7 +199,7 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
steps.Add(new RemoveProjectFromTyeStep("blazor-server"));
steps.Add(new RemoveFolderStep("/apps/angular"));
context.Symbols.Add("ui:mvc");
break;
}
@ -215,4 +216,9 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
{
steps.Add(new UpdateNuGetConfigStep("/NuGet.Config"));
}
private static void RandomizeAuthServerPassPhrase(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
steps.Add(new RandomizeAuthServerPassPhraseStep());
}
}

@ -0,0 +1,35 @@
using System;
using System.Linq;
using Volo.Abp.Cli.ProjectBuilding.Building;
namespace Volo.Abp.Cli.ProjectBuilding.Templates;
public class RandomizeAuthServerPassPhraseStep : ProjectBuildPipelineStep
{
protected const string DefaultPassPhrase = "00000000-0000-0000-0000-000000000000";
public override void Execute(ProjectBuildContext context)
{
var files = context.Files
.Where(x => !x.IsDirectory)
.Where(x => x.Content.IndexOf(DefaultPassPhrase, StringComparison.InvariantCultureIgnoreCase) >= 0)
.ToList();
var randomPassPhrase = Guid.NewGuid().ToString("D");
foreach (var file in files)
{
file.NormalizeLineEndings();
var lines = file.GetLines();
for (var i = 0; i < lines.Length; i++)
{
if (lines[i].Contains(DefaultPassPhrase))
{
lines[i] = lines[i].Replace(DefaultPassPhrase, randomPassPhrase);
}
}
file.SetLines(lines);
}
}
}

@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.ProjectModification;
public class LocalReferenceConverter : ITransientDependency
{
public ILogger<LocalReferenceConverter> Logger { get; set; }
public async Task ConvertAsync(
[NotNull] string directory,
[NotNull] List<string> localPaths)
{
Check.NotNull(directory, nameof(directory));
Check.NotNull(localPaths, nameof(localPaths));
var localProjects = GetLocalProjects(localPaths);
var targetProjects = Directory.GetFiles(directory, "*.csproj", SearchOption.AllDirectories);
Logger.LogInformation($"Converting projects to local reference.");
foreach (var targetProject in targetProjects)
{
Logger.LogInformation($"Converting to local reference: {targetProject}");
await ConvertProjectToLocalReferences(targetProject, localProjects);
}
Logger.LogInformation($"Converted {targetProjects.Length} projects to local references.");
}
private async Task ConvertProjectToLocalReferences(string targetProject, List<string> localProjects)
{
var xmlDocument = new XmlDocument() { PreserveWhitespace = true };
xmlDocument.Load(GenerateStreamFromString(File.ReadAllText(targetProject)));
var matchedNodes = xmlDocument.SelectNodes($"/Project/ItemGroup/PackageReference[@Include]");
if (matchedNodes == null || matchedNodes.Count == 0)
{
return;
}
foreach (XmlNode matchedNode in matchedNodes)
{
var packageName = matchedNode!.Attributes!["Include"].Value;
var localProject = localProjects.Find(x =>
x.EndsWith($"\\{packageName}.csproj") ||
x.EndsWith($"/{packageName}.csproj")
);
if (localProject == null)
{
continue;
}
var parentNode = matchedNode.ParentNode;
parentNode!.RemoveChild(matchedNode);
var newNode = xmlDocument.CreateElement("ProjectReference");
var includeAttr = xmlDocument.CreateAttribute("Include");
includeAttr.Value = CalculateRelativePath(targetProject, localProject);
newNode.Attributes.Append(includeAttr);
parentNode.AppendChild(newNode);
}
File.WriteAllText(targetProject, XDocument.Parse(xmlDocument.OuterXml).ToString());
}
private string CalculateRelativePath(string targetProject, string localProject)
{
return new Uri(targetProject).MakeRelativeUri(new Uri(localProject)).ToString();
}
private List<string> GetLocalProjects(List<string> localPaths)
{
var list = new List<string>();
foreach (var localPath in localPaths)
{
if (!Directory.Exists(localPath))
{
continue;
}
list.AddRange(Directory.GetFiles(localPath, "*.csproj", SearchOption.AllDirectories));
}
return list;
}
private MemoryStream GenerateStreamFromString(string s)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
}

@ -32,7 +32,7 @@ public abstract class ServiceProxyGeneratorBase<T> : IServiceProxyGenerator wher
{
Check.NotNull(args.Url, nameof(args.Url));
var client = CliHttpClientFactory.CreateClient();
var client = CliHttpClientFactory.CreateClient(needsAuthentication: false);
var apiDefinitionResult = await client.GetStringAsync(CliUrls.GetApiDefinitionUrl(args.Url, requestDto));
var apiDefinition = JsonSerializer.Deserialize<ApplicationApiDescriptionModel>(apiDefinitionResult);

@ -5,6 +5,7 @@ namespace Volo.Abp.UI.Navigation;
public class ApplicationMenuGroup
{
private string _displayName;
private string _elementId;
/// <summary>
/// Default <see cref="Order"/> value of a group item.
@ -32,7 +33,12 @@ public class ApplicationMenuGroup
/// <summary>
/// Can be used to render the element with a specific Id for DOM selections.
/// </summary>
public string ElementId { get; set; }
public string ElementId {
get { return _elementId; }
set {
_elementId = NormalizeElementId(value);
}
}
/// <summary>
/// The Display order of the group.
@ -60,6 +66,11 @@ public class ApplicationMenuGroup
return "MenuGroup_" + Name;
}
private string NormalizeElementId(string elementId)
{
return elementId?.Replace(".", "");
}
public override string ToString()
{
return $"[ApplicationMenuGroup] Name = {Name}";

@ -8,6 +8,7 @@ namespace Volo.Abp.UI.Navigation;
public class ApplicationMenuItem : IHasMenuItems, IHasSimpleStateCheckers<ApplicationMenuItem>
{
private string _displayName;
private string _elementId;
/// <summary>
/// Default <see cref="Order"/> value of a menu item.
@ -85,7 +86,12 @@ public class ApplicationMenuItem : IHasMenuItems, IHasSimpleStateCheckers<Applic
/// <summary>
/// Can be used to render the element with a specific Id for DOM selections.
/// </summary>
public string ElementId { get; set; }
public string ElementId {
get { return _elementId; }
set {
_elementId = NormalizeElementId(value);
}
}
/// <summary>
/// Can be used to render the element with extra CSS classes.
@ -151,6 +157,11 @@ public class ApplicationMenuItem : IHasMenuItems, IHasSimpleStateCheckers<Applic
{
return "MenuItem_" + Name;
}
private string NormalizeElementId(string elementId)
{
return elementId?.Replace(".", "");
}
public override string ToString()
{

@ -15,6 +15,7 @@ public class SecurityHeadersTestController_Tests : AspNetCoreMvcTestBase
services.Configure<AbpSecurityHeadersOptions>(options =>
{
options.UseContentSecurityPolicyHeader = true;
options.Headers["Referrer-Policy"] = "no-referrer";
});
base.ConfigureServices(context, services);
@ -30,4 +31,12 @@ public class SecurityHeadersTestController_Tests : AspNetCoreMvcTestBase
responseMessage.Headers.ShouldContain(x => x.Key == "X-Content-Type-Options" & x.Value.First().ToString() == "nosniff");
responseMessage.Headers.ShouldContain(x => x.Key == "Content-Security-Policy" & x.Value.First().ToString() == "object-src 'none'; form-action 'self'; frame-ancestors 'none'");
}
[Fact]
public async Task SecurityHeaders_Custom_Headers_Should_Be_Added()
{
var responseMessage = await GetResponseAsync("/SecurityHeadersTest/Get");
responseMessage.Headers.ShouldNotBeEmpty();
responseMessage.Headers.ShouldContain(x => x.Key == "Referrer-Policy" && x.Value.First().ToString() == "no-referrer");
}
}

@ -1,6 +1,6 @@
[
{
"version": "7.1.0",
"version": "7.1.1",
"releaseDate": "",
"type": "stable",
"message": ""

@ -0,0 +1,790 @@
@page
@model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.DatePickerModel
@section styles {
<abp-style-bundle>
<abp-style src="/css/demo.css"/>
</abp-style-bundle>
}
<link rel="stylesheet"
href="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/default.min.css">
<script src="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<h2>Date Picker & Date Range Picker</h2>
<div class="demo-with-code">
<div class="demo-area">
<abp-date-picker placeholder="New" single-open-and-clear-button="false" week-numbers="Iso" time-picker="true" required today-button-classes="btn-primary" picker-id="testPicker" asp-for="DateTime"></abp-date-picker>
</div>
<div class="code-area" id="test-picker">
<abp-tabs>
<abp-tab title="Modal Class">
<pre><code>
public class DatePickerModel : PageModel
{
[BindProperty]
public DateTime DateTime { get; set; }
public void OnGet()
{
DateTime = DateTime == default ? DateTime.Now : DateTime;
}
}
</code></pre>
</abp-tab>
<abp-tab title="Tag Helper" active="true">
<pre><code>
&lt;abp-date-picker placeholder="New" single-open-and-clear-button="false" week-numbers="Iso" time-picker="true" required today-button-classes="btn-primary" picker-id="testPicker" asp-for="DateTime"/&gt;
</code></pre>
</abp-tab>
<abp-tab title="Rendered">
<pre><code>
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="DateTime"&gt;DateTime&lt;/label&gt;&lt;span&gt; * &lt;/span&gt;
&lt;abp-date-picker placeholder="New" required="" data-show-i-s-o-week-numbers="true" data-time-picker="true" data-today-button-classes="btn-primary" id="testPicker" data-single-open-and-clear-button="false" data-date="2023-04-12T17:56:33.1115260+03:00"&gt;
&lt;div class="input-group"&gt;
&lt;input placeholder="New" required="" type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="btn btn-outline-secondary" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DateTime" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-date="true" type="hidden" data-val="true" data-val-required="The DateTime field is required." id="DateTime" name="DateTime" value="Wed Apr 12 2023 17:56:33 GMT+0300"&gt;
&lt;/abp-date-picker&gt;
&lt;/div&gt;
</code></pre>
</abp-tab>
</abp-tabs>
</div>
</div>
<div class="demo-with-code">
<div class="demo-area">
@section scripts {
<script>
var newPicker = abp.libs.bootstrapDateRangePicker.createDateRangePicker(
{
label: "New JavaScript Picker",
startDate: "2020-01-01",
endDate: "2020-01-02",
singleOpenAndClearButton: false,
placeholder: "New Picker",
});
newPicker.insertAfter($('#test-picker').parent());
</script>
}
</div>
<div class="code-area">
<abp-tabs>
<abp-tab title="Javascript" active="true">
<pre><code>
var newPicker = abp.libs.bootstrapDateRangePicker.createDateRangePicker(
{
label: "New JavaScript Picker",
startDate: "2020-01-01",
endDate: "2020-01-02",
singleOpenAndClearButton: false,
placeholder: "New Picker",
});
</code></pre>
</abp-tab>
<abp-tab title="Rendered">
<pre><code>
&lt;div class="mb-3"&gt;
&lt;label class="form-label"&gt;New JavaScript Picker&lt;/label&gt;
&lt;abp-date-range-picker&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control" placeholder="New Picker"&gt;
&lt;button type="button" class="btn btn-outline-secondary" tabindex="-1" data-type="open"&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" class="btn btn-outline-secondary" tabindex="-1" data-type="clear"&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/abp-date-range-picker&gt;
&lt;/div&gt;
</code></pre>
</abp-tab>
</abp-tabs>
</div>
</div>
<div class="demo-with-code">
<div class="demo-area">
<abp-date-range-picker time-picker="true" asp-for-end="EndDate" asp-for-start="StartDate"></abp-date-range-picker>
</div>
<div class="code-area">
<abp-tabs>
<abp-tab title="Modal Class">
<pre><code>
public class DatePickerModel : PageModel
{
[BindProperty]
public DateTime StartDate { get; set; }
[BindProperty]
public DateTime EndDate { get; set; }
public void OnGet()
{
EndDate = EndDate == default ? DateTime.Now : EndDate;
DateTime = DateTime == default ? DateTime.Now : DateTime;
}
}
</code></pre>
</abp-tab>
<abp-tab title="Tag Helper" active="true">
<pre><code>
&lt;abp-date-range-picker time-picker="true" asp-for-end="EndDate" asp-for-start="StartDate"/&gt;
</code></pre>
</abp-tab>
<abp-tab title="Rendered">
<pre><code>
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="StartDate"&gt;StartDate&lt;/label&gt;
&lt;abp-date-range-picker data-time-picker="true" data-start-date="0001-01-01T00:00:00.0000000" data-end-date="2023-04-12T18:02:13.2033440+03:00"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="d-none btn btn-outline-secondary" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="StartDate" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="EndDate" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-start-date="true" type="hidden" data-val="true" data-val-required="The StartDate field is required." id="StartDate" name="StartDate" value="Mon Jan 01 0001 00:00:00 GMT+0155"&gt;&lt;input data-end-date="true" type="hidden" data-val="true" data-val-required="The EndDate field is required." id="EndDate" name="EndDate" value="Wed Apr 12 2023 18:02:13 GMT+0300"&gt;
&lt;/abp-date-range-picker&gt;
&lt;/div&gt;
</code></pre>
</abp-tab>
</abp-tabs>
</div>
</div>
<div class="demo-with-code">
<div class="demo-area">
<abp-date-picker auto-update-input="true" today-button-classes="btn-primary" asp-for="NullableDateTime"></abp-date-picker>
</div>
<div class="code-area">
<abp-tabs>
<abp-tab title="Modal Class">
<pre><code>
public class DatePickerModel : PageModel
{
[BindProperty]
public DateTime? NullableDateTime { get; set; }
}
</code></pre>
</abp-tab>
<abp-tab title="Tag Helper" active="true">
<pre><code>
&lt;abp-date-picker auto-update-input="true" today-button-classes="btn-primary" asp-for="NullableDateTime"/&gt;
</code></pre>
</abp-tab>
<abp-tab title="Rendered">
<pre><code>
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="NullableDateTime"&gt;NullableDateTime&lt;/label&gt;
&lt;abp-date-picker data-today-button-classes="btn-primary" data-auto-update-input="true"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary d-none" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="btn btn-outline-secondary" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="NullableDateTime" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-date="true" type="hidden" id="NullableDateTime" name="NullableDateTime" value="2023-04-12"&gt;
&lt;/abp-date-picker&gt;
&lt;/div&gt;
</code></pre>
</abp-tab>
</abp-tabs>
</div>
</div>
<div class="demo-with-code">
<div class="demo-area">
<abp-date-range-picker clear-button="false" asp-for-end="NullableEndDate" asp-for-start="NullableStartDate"></abp-date-range-picker>
</div>
<div class="code-area">
<abp-tabs>
<abp-tab title="Modal Class">
<pre><code>
public class DatePickerModel : PageModel
{
[BindProperty]
public DateTime? NullableStartDate { get; set; }
[BindProperty]
public DateTime? NullableEndDate { get; set; }
}
</code></pre>
</abp-tab>
<abp-tab title="Tag Helper" active="true">
<pre><code>
&lt;abp-date-picker auto-update-input="true" today-button-classes="btn-primary" asp-for="NullableDateTime"/&gt;
</code></pre>
</abp-tab>
<abp-tab title="Rendered">
<pre><code>
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="NullableStartDate"&gt;NullableStartDate&lt;/label&gt;
&lt;abp-date-range-picker&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="NullableStartDate" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="NullableEndDate" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-start-date="true" type="hidden" id="NullableStartDate" name="NullableStartDate" value=""&gt;&lt;input data-end-date="true" type="hidden" id="NullableEndDate" name="NullableEndDate" value=""&gt;
&lt;/abp-date-range-picker&gt;
&lt;/div&gt;
</code></pre>
</abp-tab>
</abp-tabs>
</div>
</div>
<div class="demo-with-code">
<div class="demo-area">
<abp-date-picker auto-update-input="true" today-button-classes="btn-primary" asp-for="DateTimeDateTimeOffset"></abp-date-picker>
</div>
<div class="code-area">
<abp-tabs>
<abp-tab title="Modal Class">
<pre><code>
public class DatePickerModel : PageModel
{
[BindProperty]
public DateTimeOffset DateTimeDateTimeOffset { get; set; }
public void OnGet()
{
DateTimeDateTimeOffset = DateTimeDateTimeOffset == default ? DateTimeOffset.Now : DateTimeDateTimeOffset;
}
}
</code></pre>
</abp-tab>
<abp-tab title="Tag Helper" active="true">
<pre><code>
&lt;abp-date-picker auto-update-input="true" today-button-classes="btn-primary" asp-for="DateTimeDateTimeOffset"/&gt;
</code></pre>
</abp-tab>
<abp-tab title="Rendered">
<pre><code>
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="DateTimeDateTimeOffset"&gt;DateTimeDateTimeOffset&lt;/label&gt;
&lt;abp-date-picker data-today-button-classes="btn-primary" data-auto-update-input="true" data-date="2023-04-12T18:10:05.6171150+03:00"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary d-none" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="btn btn-outline-secondary" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DateTimeDateTimeOffset" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-date="true" type="hidden" data-val="true" data-val-required="The DateTimeDateTimeOffset field is required." id="DateTimeDateTimeOffset" name="DateTimeDateTimeOffset" value="2023-04-12"&gt;
&lt;/abp-date-picker&gt;
&lt;/div&gt;
</code></pre>
</abp-tab>
</abp-tabs>
</div>
</div>
<div class="demo-with-code">
<div class="demo-area">
<abp-date-range-picker clear-button="false" asp-for-end="EndDateTimeOffset" asp-for-start="StartDateTimeOffset"></abp-date-range-picker>
</div>
<div class="code-area">
<abp-tabs>
<abp-tab title="Modal Class">
<pre><code>
public class DatePickerModel : PageModel
{
[BindProperty]
public DateTimeOffset StartDateTimeOffset { get; set; }
[BindProperty]
public DateTimeOffset EndDateTimeOffset { get; set; }
public void OnGet()
{
StartDateTimeOffset = StartDateTimeOffset == default ? DateTimeOffset.Now : StartDateTimeOffset;
EndDateTimeOffset = EndDateTimeOffset == default ? DateTimeOffset.Now : EndDateTimeOffset;
}
}
</code></pre>
</abp-tab>
<abp-tab title="Tag Helper" active="true">
<pre><code>
&lt;abp-date-range-picker clear-button="false" asp-for-end="EndDateTimeOffset" asp-for-start="StartDateTimeOffset"/&gt;
</code></pre>
</abp-tab>
<abp-tab title="Rendered">
<pre><code>
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="StartDateTimeOffset"&gt;StartDateTimeOffset&lt;/label&gt;
&lt;abp-date-range-picker data-start-date="2023-04-12T18:11:50.9560980+03:00" data-end-date="2023-04-12T18:11:50.9560980+03:00"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="StartDateTimeOffset" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="EndDateTimeOffset" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-start-date="true" type="hidden" data-val="true" data-val-required="The StartDateTimeOffset field is required." id="StartDateTimeOffset" name="StartDateTimeOffset" value="Wed Apr 12 2023 18:11:50 GMT+0300"&gt;&lt;input data-end-date="true" type="hidden" data-val="true" data-val-required="The EndDateTimeOffset field is required." id="EndDateTimeOffset" name="EndDateTimeOffset" value="Wed Apr 12 2023 18:11:50 GMT+0300"&gt;
&lt;/abp-date-range-picker&gt;
&lt;/div&gt;
</code></pre>
</abp-tab>
</abp-tabs>
</div>
</div>
<div class="demo-with-code">
<div class="demo-area">
<abp-date-picker auto-update-input="true" today-button-classes="btn-primary" asp-for="NullableDateTimeDateTimeOffset"></abp-date-picker>
</div>
<div class="code-area">
<abp-tabs>
<abp-tab title="Modal Class">
<pre><code>
public class DatePickerModel : PageModel
{
[BindProperty]
public DateTimeOffset DateTimeDateTimeOffset { get; set; }
}
</code></pre>
</abp-tab>
<abp-tab title="Tag Helper" active="true">
<pre><code>
&lt;abp-date-picker auto-update-input="true" today-button-classes="btn-primary" asp-for="NullableDateTimeDateTimeOffset"/&gt;
</code></pre>
</abp-tab>
<abp-tab title="Rendered">
<pre><code>
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="NullableDateTimeDateTimeOffset"&gt;NullableDateTimeDateTimeOffset&lt;/label&gt;
&lt;abp-date-picker data-today-button-classes="btn-primary" data-auto-update-input="true"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary d-none" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="btn btn-outline-secondary" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="NullableDateTimeDateTimeOffset" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-date="true" type="hidden" id="NullableDateTimeDateTimeOffset" name="NullableDateTimeDateTimeOffset" value="2023-04-12"&gt;
&lt;/abp-date-picker&gt;
&lt;/div&gt;
</code></pre>
</abp-tab>
</abp-tabs>
</div>
</div>
<div class="demo-with-code">
<div class="demo-area">
<abp-date-range-picker clear-button="false" asp-for-end="NullableEndDateTimeOffset" asp-for-start="NullableStartDateTimeOffset"></abp-date-range-picker>
</div>
<div class="code-area">
<abp-tabs>
<abp-tab title="Modal Class">
<pre><code>
public class DatePickerModel : PageModel
{
[BindProperty]
public DateTimeOffset? NullableStartDateTimeOffset { get; set; }
[BindProperty]
public DateTimeOffset? NullableEndDateTimeOffset { get; set; }
}
</code></pre>
</abp-tab>
<abp-tab title="Tag Helper" active="true">
<pre><code>
&lt;abp-date-range-picker clear-button="false" asp-for-end="NullableEndDateTimeOffset" asp-for-start="NullableStartDateTimeOffset"/&gt;
</code></pre>
</abp-tab>
<abp-tab title="Rendered">
<pre><code>
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="NullableStartDateTimeOffset"&gt;NullableStartDateTimeOffset&lt;/label&gt;
&lt;abp-date-range-picker&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="NullableStartDateTimeOffset" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="NullableEndDateTimeOffset" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-start-date="true" type="hidden" id="NullableStartDateTimeOffset" name="NullableStartDateTimeOffset" value=""&gt;&lt;input data-end-date="true" type="hidden" id="NullableEndDateTimeOffset" name="NullableEndDateTimeOffset" value=""&gt;
&lt;/abp-date-range-picker&gt;
&lt;/div&gt;
</code></pre>
</abp-tab>
</abp-tabs>
</div>
</div>
<div class="demo-with-code">
<div class="demo-area">
<abp-date-picker auto-update-input="true" today-button-classes="btn-primary" asp-for="StringDate"></abp-date-picker>
</div>
<div class="code-area">
<abp-tabs>
<abp-tab title="Modal Class">
<pre><code>
public class DatePickerModel : PageModel
{
[BindProperty]
public string StringDate { get; set; }
}
</code></pre>
</abp-tab>
<abp-tab title="Tag Helper" active="true">
<pre><code>
&lt;abp-date-picker auto-update-input="true" today-button-classes="btn-primary" asp-for="StringDate"/&gt;
</code></pre>
</abp-tab>
<abp-tab title="Rendered">
<pre><code>
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="StringDate"&gt;StringDate&lt;/label&gt;
&lt;abp-date-picker data-today-button-classes="btn-primary" data-auto-update-input="true"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary d-none" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="btn btn-outline-secondary" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="StringDate" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-date="true" type="hidden" id="StringDate" name="StringDate" value="2023-04-12"&gt;
&lt;/abp-date-picker&gt;
&lt;/div&gt;
</code></pre>
</abp-tab>
</abp-tabs>
</div>
</div>
<div class="demo-with-code">
<div class="demo-area">
<abp-date-range-picker clear-button="false" asp-for-end="StringEndDate" asp-for-start="StringStartDate"></abp-date-range-picker>
</div>
<div class="code-area">
<abp-tabs>
<abp-tab title="Modal Class">
<pre><code>
public class DatePickerModel : PageModel
{
[BindProperty]
public string StringStartDate { get; set; }
[BindProperty]
public string StringEndDate { get; set; }
}
</code></pre>
</abp-tab>
<abp-tab title="Tag Helper" active="true">
<pre><code>
&lt;abp-date-range-picker clear-button="false" asp-for-end="StringEndDate" asp-for-start="StringStartDate"&gt;&lt;/abp-date-range-picker&gt;
</code></pre>
</abp-tab>
<abp-tab title="Rendered">
<pre><code>
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="StringStartDate"&gt;StringStartDate&lt;/label&gt;
&lt;abp-date-range-picker&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="Processing..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="StringStartDate" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="StringEndDate" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-start-date="true" type="hidden" id="StringStartDate" name="StringStartDate" value=""&gt;&lt;input data-end-date="true" type="hidden" id="StringEndDate" name="StringEndDate" value=""&gt;
&lt;/abp-date-range-picker&gt;
&lt;/div&gt;
</code></pre>
</abp-tab>
</abp-tabs>
</div>
</div>
<h2>Dynamic Form</h2>
<div class="demo-with-code">
<div class="demo-area">
<abp-dynamic-form abp-model="DynamicFormExample">
</abp-dynamic-form>
</div>
<div class="code-area">
<abp-tabs>
<abp-tab title="Modal Class">
<pre><code>
public class DatePickerModel : PageModel
{
public class DynamicForm
{
[BindProperty]
[DateRangePicker("MyPicker",true)]
public DateTime StartDate { get; set; }
[BindProperty]
[DateRangePicker("MyPicker",false)]
[DatePickerOptions(nameof(DatePickerOptions))]
public DateTime EndDate { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
public DateTime DateTime { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DateRangePicker("MyPicker2",true)]
public DateTime? NullableStartDate { get; set; }
[BindProperty]
[DateRangePicker("MyPicker2")]
public DateTime? NullableEndDate { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
public DateTime? NullableDateTime { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DateRangePicker("MyPicker3",true)]
public DateTimeOffset StartDateTimeOffset { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DateRangePicker("MyPicker3")]
public DateTimeOffset EndDateTimeOffset { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
public DateTimeOffset DateTimeDateTimeOffset { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DateRangePicker("MyPicker4",true)]
public DateTimeOffset? NullableStartDateTimeOffset { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DateRangePicker("MyPicker4")]
public DateTimeOffset? NullableEndDateTimeOffset { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
public DateTimeOffset? NullableDateTimeDateTimeOffset { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DateRangePicker("MyPicker5",true)]
public string StringStartDate { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DateRangePicker("MyPicker5")]
public string StringEndDate { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DatePicker]
public string StringDate { get; set; }
}
public AbpDatePickerOptions DatePickerOptions { get; set; }
[BindProperty]
public DynamicForm DynamicFormExample { get; set; }
public void OnGet()
{
DynamicFormExample ??= new DynamicForm
{
StartDate = DateTime.Now,
EndDate = DateTime.Now,
DateTime = DateTime.Now,
StartDateTimeOffset = DateTimeOffset.Now,
EndDateTimeOffset = DateTimeOffset.Now,
DateTimeDateTimeOffset = DateTimeOffset.Now,
};
DatePickerOptions = new AbpDatePickerOptions();
DatePickerOptions.LinkedCalendars = false;
DatePickerOptions.Ranges = new List&lt;AbpDatePickerRange&gt;();
DatePickerOptions.Ranges.Add(new AbpDatePickerRange("Today", DateTime.Now, DateTime.Now));
}
public void OnPost()
{
return;
}
}
</code></pre>
</abp-tab>
<abp-tab title="Tag Helper" active="true">
<pre><code>
&lt;abp-dynamic-form abp-model="DynamicFormExample"&gt;
&lt;/abp-dynamic-form&gt;
</code></pre>
</abp-tab>
<abp-tab title="Rendered">
<pre><code>
&lt;form method="post" novalidate="novalidate"&gt;
&lt;div class="row"&gt;
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="DynamicFormExample_StartDate"&gt;Start Date - End Date&lt;/label&gt;
&lt;abp-date-range-picker id="MyPicker" data-linked-calendars="false" data-ranges="{&quot;Today&quot;:[&quot;2023-04-17T15:21:46.6586240+03:00&quot;,&quot;2023-04-17T15:21:46.6586240+03:00&quot;]}" data-start-date="2023-04-17T15:21:46.6569460+03:00" data-end-date="2023-04-17T15:21:46.6570750+03:00"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="d-none btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;ti class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.StartDate" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.EndDate" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-start-date="true" type="hidden" data-val="true" data-val-required="The Start Date - End Date field is required." id="DynamicFormExample_StartDate" name="DynamicFormExample.StartDate" value="Mon Apr 17 2023 15:21:46 GMT+0300"&gt;&lt;input data-end-date="true" type="hidden" data-val="true" data-val-required="The EndDate field is required." id="DynamicFormExample_EndDate" name="DynamicFormExample.EndDate" value="Mon Apr 17 2023 15:21:46 GMT+0300"&gt;
&lt;/abp-date-range-picker&gt;
&lt;/div&gt;
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="DynamicFormExample_NullableStartDate"&gt;Nullable Start Date - Nullable End Date&lt;/label&gt;
&lt;abp-date-range-picker id="MyPicker2" data-linked-calendars="false" data-ranges="{&quot;Today&quot;:[&quot;2023-04-17T15:21:46.6586240+03:00&quot;,&quot;2023-04-17T15:21:46.6586240+03:00&quot;]}"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="d-none btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.NullableStartDate" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.NullableEndDate" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-start-date="true" type="hidden" id="DynamicFormExample_NullableStartDate" name="DynamicFormExample.NullableStartDate" value=""&gt;&lt;input data-end-date="true" type="hidden" id="DynamicFormExample_NullableEndDate" name="DynamicFormExample.NullableEndDate" value=""&gt;
&lt;/abp-date-range-picker&gt;
&lt;/div&gt;
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="DynamicFormExample_StartDateTimeOffset"&gt;Start DateTime Offset - End DateTime Offset&lt;/label&gt;
&lt;abp-date-range-picker id="MyPicker3" data-linked-calendars="false" data-ranges="{&quot;Today&quot;:[&quot;2023-04-17T15:21:46.6586240+03:00&quot;,&quot;2023-04-17T15:21:46.6586240+03:00&quot;]}" data-start-date="2023-04-17T15:21:46.6573400+03:00" data-end-date="2023-04-17T15:21:46.6574650+03:00"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="d-none btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.StartDateTimeOffset" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.EndDateTimeOffset" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-start-date="true" type="hidden" data-val="true" data-val-required="The Start DateTime Offset - End DateTime Offset field is required." id="DynamicFormExample_StartDateTimeOffset" name="DynamicFormExample.StartDateTimeOffset" value="Mon Apr 17 2023 15:21:46 GMT+0300"&gt;&lt;input data-end-date="true" type="hidden" data-val="true" data-val-required="The EndDateTimeOffset field is required." id="DynamicFormExample_EndDateTimeOffset" name="DynamicFormExample.EndDateTimeOffset" value="Mon Apr 17 2023 15:21:46 GMT+0300"&gt;
&lt;/abp-date-range-picker&gt;
&lt;/div&gt;
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="DynamicFormExample_NullableStartDateTimeOffset"&gt;Nullable Start DateTime Offset - Nullable End DateTime Offset&lt;/label&gt;
&lt;abp-date-range-picker id="MyPicker4" data-linked-calendars="false" data-ranges="{&quot;Today&quot;:[&quot;2023-04-17T15:21:46.6586240+03:00&quot;,&quot;2023-04-17T15:21:46.6586240+03:00&quot;]}"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="d-none btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.NullableStartDateTimeOffset" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.NullableEndDateTimeOffset" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-start-date="true" type="hidden" id="DynamicFormExample_NullableStartDateTimeOffset" name="DynamicFormExample.NullableStartDateTimeOffset" value=""&gt;&lt;input data-end-date="true" type="hidden" id="DynamicFormExample_NullableEndDateTimeOffset" name="DynamicFormExample.NullableEndDateTimeOffset" value=""&gt;
&lt;/abp-date-range-picker&gt;
&lt;/div&gt;
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="DynamicFormExample_StringStartDate"&gt;String Start Date - String End Date&lt;/label&gt;
&lt;abp-date-range-picker id="MyPicker5" data-linked-calendars="false" data-ranges="{&quot;Today&quot;:[&quot;2023-04-17T15:21:46.6586240+03:00&quot;,&quot;2023-04-17T15:21:46.6586240+03:00&quot;]}"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="d-none btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.StringStartDate" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.StringEndDate" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-start-date="true" type="hidden" id="DynamicFormExample_StringStartDate" name="DynamicFormExample.StringStartDate" value=""&gt;&lt;input data-end-date="true" type="hidden" id="DynamicFormExample_StringEndDate" name="DynamicFormExample.StringEndDate" value=""&gt;
&lt;/abp-date-range-picker&gt;
&lt;/div&gt;
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="DynamicFormExample_DateTime"&gt;DateTime&lt;/label&gt;
&lt;abp-date-picker data-linked-calendars="false" data-ranges="{&quot;Today&quot;:[&quot;2023-04-17T15:21:46.6586240+03:00&quot;,&quot;2023-04-17T15:21:46.6586240+03:00&quot;]}" data-date="2023-04-17T15:21:46.6572040+03:00"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="d-none btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.DateTime" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-date="true" type="hidden" data-val="true" data-val-required="The DateTime field is required." id="DynamicFormExample_DateTime" name="DynamicFormExample.DateTime" value="Mon Apr 17 2023 15:21:46 GMT+0300"&gt;
&lt;/abp-date-picker&gt;
&lt;/div&gt;
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="DynamicFormExample_NullableDateTime"&gt;Nullable DateTime&lt;/label&gt;
&lt;abp-date-picker data-linked-calendars="false" data-ranges="{&quot;Today&quot;:[&quot;2023-04-17T15:21:46.6586240+03:00&quot;,&quot;2023-04-17T15:21:46.6586240+03:00&quot;]}"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="d-none btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.NullableDateTime" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-date="true" type="hidden" id="DynamicFormExample_NullableDateTime" name="DynamicFormExample.NullableDateTime" value=""&gt;
&lt;/abp-date-picker&gt;
&lt;/div&gt;
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="DynamicFormExample_DateTimeDateTimeOffset"&gt;DateTime DateTime Offset&lt;/label&gt;
&lt;abp-date-picker data-linked-calendars="false" data-ranges="{&quot;Today&quot;:[&quot;2023-04-17T15:21:46.6586240+03:00&quot;,&quot;2023-04-17T15:21:46.6586240+03:00&quot;]}" data-date="2023-04-17T15:21:46.6575990+03:00"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="d-none btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.DateTimeDateTimeOffset" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-date="true" type="hidden" data-val="true" data-val-required="The DateTime DateTime Offset field is required." id="DynamicFormExample_DateTimeDateTimeOffset" name="DynamicFormExample.DateTimeDateTimeOffset" value="Mon Apr 17 2023 15:21:46 GMT+0300"&gt;
&lt;/abp-date-picker&gt;
&lt;/div&gt;
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="DynamicFormExample_NullableDateTimeDateTimeOffset"&gt;Nullable DateTime DateTime Offset&lt;/label&gt;
&lt;abp-date-picker data-linked-calendars="false" data-ranges="{&quot;Today&quot;:[&quot;2023-04-17T15:21:46.6586240+03:00&quot;,&quot;2023-04-17T15:21:46.6586240+03:00&quot;]}"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="d-none btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.NullableDateTimeDateTimeOffset" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-date="true" type="hidden" id="DynamicFormExample_NullableDateTimeDateTimeOffset" name="DynamicFormExample.NullableDateTimeDateTimeOffset" value=""&gt;
&lt;/abp-date-picker&gt;
&lt;/div&gt;
&lt;div class="mb-3"&gt;
&lt;label class="form-label" for="DynamicFormExample_StringDate"&gt;String Date&lt;/label&gt;
&lt;abp-date-picker data-linked-calendars="false" data-ranges="{&quot;Today&quot;:[&quot;2023-04-17T15:21:46.6586240+03:00&quot;,&quot;2023-04-17T15:21:46.6586240+03:00&quot;]}"&gt;
&lt;div class="input-group"&gt;
&lt;input type="text" autocomplete="off" class="form-control"&gt;
&lt;button type="button" tabindex="-1" data-type="clear" class="d-none btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-times"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;button type="button" tabindex="-1" data-type="open" class="btn btn-outline-secondary" data-busy-text="İşleniyor..."&gt;
&lt;i class="fa fa-calendar"&gt;&lt;/i&gt;
&lt;/button&gt;
&lt;/div&gt;&lt;span class="text-danger col-auto field-validation-valid" data-valmsg-for="DynamicFormExample.StringDate" data-valmsg-replace="true"&gt;&lt;/span&gt;&lt;input data-date="true" type="hidden" id="DynamicFormExample_StringDate" name="DynamicFormExample.StringDate" value=""&gt;
&lt;/abp-date-picker&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/form&gt;
</code></pre>
</abp-tab>
</abp-tabs>
</div>
</div>

@ -0,0 +1,182 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components;
public class DatePickerModel : PageModel
{
[BindProperty]
[DisplayName("Start Date - End Date")]
public DateTime StartDate { get; set; }
[BindProperty]
public DateTime EndDate { get; set; }
[BindProperty]
public DateTime DateTime { get; set; }
[BindProperty]
[DisplayName("Nullable Start Date - Nullable End Date")]
public DateTime? NullableStartDate { get; set; }
[BindProperty]
public DateTime? NullableEndDate { get; set; }
[BindProperty]
[DisplayName("Nullable DateTime")]
public DateTime? NullableDateTime { get; set; }
[BindProperty]
[DisplayName("Start DateTime Offset - End DateTime Offset")]
public DateTimeOffset StartDateTimeOffset { get; set; }
[BindProperty]
public DateTimeOffset EndDateTimeOffset { get; set; }
[BindProperty]
[DisplayName("DateTime DateTime Offset")]
public DateTimeOffset DateTimeDateTimeOffset { get; set; }
[BindProperty]
[DisplayName("Nullable Start DateTime Offset - Nullable End DateTime Offset")]
public DateTimeOffset? NullableStartDateTimeOffset { get; set; }
[BindProperty]
public DateTimeOffset? NullableEndDateTimeOffset { get; set; }
[BindProperty]
[DisplayName("Nullable DateTime DateTime Offset")]
public DateTimeOffset? NullableDateTimeDateTimeOffset { get; set; }
[BindProperty]
[DisplayName("String Start Date - String End Date")]
public string StringStartDate { get; set; }
[BindProperty]
public string StringEndDate { get; set; }
[BindProperty]
[DisplayName("String Date")]
public string StringDate { get; set; }
public class DynamicForm
{
[BindProperty]
[DateRangePicker("MyPicker",true)]
[DisplayName("Start Date - End Date")]
public DateTime StartDate { get; set; }
[BindProperty]
[DateRangePicker("MyPicker",false)]
[DatePickerOptions(nameof(DatePickerOptions))]
public DateTime EndDate { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
public DateTime DateTime { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DateRangePicker("MyPicker2",true)]
[DisplayName("Nullable Start Date - Nullable End Date")]
public DateTime? NullableStartDate { get; set; }
[BindProperty]
[DateRangePicker("MyPicker2")]
public DateTime? NullableEndDate { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DisplayName("Nullable DateTime")]
public DateTime? NullableDateTime { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DateRangePicker("MyPicker3",true)]
[DisplayName("Start DateTime Offset - End DateTime Offset")]
public DateTimeOffset StartDateTimeOffset { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DateRangePicker("MyPicker3")]
public DateTimeOffset EndDateTimeOffset { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DisplayName("DateTime DateTime Offset")]
public DateTimeOffset DateTimeDateTimeOffset { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DateRangePicker("MyPicker4",true)]
[DisplayName("Nullable Start DateTime Offset - Nullable End DateTime Offset")]
public DateTimeOffset? NullableStartDateTimeOffset { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DateRangePicker("MyPicker4")]
public DateTimeOffset? NullableEndDateTimeOffset { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DisplayName("Nullable DateTime DateTime Offset")]
public DateTimeOffset? NullableDateTimeDateTimeOffset { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DateRangePicker("MyPicker5",true)]
[DisplayName("String Start Date - String End Date")]
public string StringStartDate { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DateRangePicker("MyPicker5")]
public string StringEndDate { get; set; }
[BindProperty]
[DatePickerOptions(nameof(DatePickerOptions))]
[DatePicker]
[DisplayName("String Date")]
public string StringDate { get; set; }
}
public AbpDatePickerOptions DatePickerOptions { get; set; }
[BindProperty]
public DynamicForm DynamicFormExample { get; set; }
public void OnGet()
{
StartDate = StartDate == default ? DateTime.Now : StartDate;
EndDate = EndDate == default ? DateTime.Now : EndDate;
DateTime = DateTime == default ? DateTime.Now : DateTime;
StartDateTimeOffset = StartDateTimeOffset == default ? DateTimeOffset.Now : StartDateTimeOffset;
EndDateTimeOffset = EndDateTimeOffset == default ? DateTimeOffset.Now : EndDateTimeOffset;
DateTimeDateTimeOffset = DateTimeDateTimeOffset == default ? DateTimeOffset.Now : DateTimeDateTimeOffset;
DynamicFormExample ??= new DynamicForm {
StartDate = DateTime.Now,
EndDate = DateTime.Now,
DateTime = DateTime.Now,
StartDateTimeOffset = DateTimeOffset.Now,
EndDateTimeOffset = DateTimeOffset.Now,
DateTimeDateTimeOffset = DateTimeOffset.Now,
};
DatePickerOptions = new AbpDatePickerOptions();
DatePickerOptions.LinkedCalendars = false;
DatePickerOptions.Ranges = new List<AbpDatePickerRange>();
DatePickerOptions.Ranges.Add(new AbpDatePickerRange("Today", DateTime.Now, DateTime.Now));
}
public void OnPost()
{
return;
}
}

@ -1,34 +1,221 @@
@page
@model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.IndexModel
@{
ViewData["Title"] = "Index";
ViewData["Title"] = "Bootstrap Tag Helpers";
}
<h2>Components</h2>
<ul>
<li><a asp-page="Components/alerts">Alerts</a></li>
<li><a asp-page="Components/badges">Badges</a></li>
@*<li><a asp-page="Components/blockquotes"> > Blockquotes</a></li>*@
<li><a asp-page="Components/Borders">Borders</a></li>
<li><a asp-page="Components/breadcrumbs">Breadcrumbs</a></li>
<li><a asp-page="Components/Buttons">Buttons</a></li>
<li><a asp-page="Components/ButtonGroups">Button Groups</a></li>
<li><a asp-page="Components/Cards">Cards</a></li>
<li><a asp-page="Components/Carousel">Carousel</a></li>
<li><a asp-page="Components/collapse">Collapse</a></li>
<li><a asp-page="Components/Dropdowns">Dropdowns</a></li>
<li><a asp-page="Components/DynamicForms">Dynamic Forms</a></li>
<li><a asp-page="Components/FormElements">Form Elements</a></li>
<li><a asp-page="Components/Grids">Grids</a></li>
<li><a asp-page="Components/ListGroup">List Groups</a></li>
<li><a asp-page="Components/Modals">Modals</a></li>
<li><a asp-page="Components/Navs">Navs</a></li>
<li><a asp-page="Components/Paginator">Paginator</a></li>
<li><a asp-page="Components/Popovers">Popovers</a></li>
<li><a asp-page="Components/ProgressBars">Progress Bars</a></li>
<li><a asp-page="Components/Tables">Tables</a></li>
<li><a asp-page="Components/Tabs">Tabs</a></li>
<li><a asp-page="Components/tooltips">Tooltips</a></li>
</ul>
@section styles {
<abp-style src="/Pages/Index.css"/>
}
<div class="container">
<div class="row p-3">
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Alerts">Alerts</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Badges">Badges</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Borders">Borders</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Breadcrumbs">Breadcrumbs</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Buttons">Buttons</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/ButtonGroups">Button Groups</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Cards">Cards</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Carousel">Carousel</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Collapse">Collapse</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-3" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/DatePicker">Date Picker & Date Range Picker</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Dropdowns">Dropdowns</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/DynamicForms">Dynamic Forms</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/FormElements">Form Elements</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Grids">Grids</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/ListGroup">List Groups</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Modals">Modals</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Navs">Navs</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Paginator">Paginator</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Popovers">Popovers</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/ProgressBars">Progress Bars</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Tables">Tables</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Tabs">Tabs</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
<div class="col-3">
<abp-card class="text-center p-4" style="width: 15rem; height: 7rem;">
<abp-card-body>
<abp-card-title>
<a abp-card-link asp-page="Components/Tooltips">Tooltips</a>
</abp-card-title>
</abp-card-body>
</abp-card>
</div>
</div>
</div>

@ -32,6 +32,7 @@ public class BootstrapDemoMenuContributor : IMenuContributor
new ApplicationMenuItem(BootstrapDemoMenus.Components.Cards, "Cards", url: "/Components/Cards"),
new ApplicationMenuItem(BootstrapDemoMenus.Components.Carousel, "Carousel", url: "/Components/Carousel"),
new ApplicationMenuItem(BootstrapDemoMenus.Components.Collapse, "Collapse", url: "/Components/Collapse"),
new ApplicationMenuItem(BootstrapDemoMenus.Components.DatePicker, "Date Picker & Date Range Picker", url: "/Components/DatePicker"),
new ApplicationMenuItem(BootstrapDemoMenus.Components.Dropdowns, "Dropdowns", url: "/Components/Dropdowns"),
new ApplicationMenuItem(BootstrapDemoMenus.Components.DynamicForms, "Dynamic Forms", url: "/Components/DynamicForms"),
new ApplicationMenuItem(BootstrapDemoMenus.Components.FormElements, "Form Elements", url: "/Components/FormElements"),

@ -17,6 +17,7 @@ public class BootstrapDemoMenus
public const string Cards = Root + ".Cards";
public const string Carousel = Root + ".Carousel";
public const string Collapse = Root + ".Collapse";
public const string DatePicker = Root + ".DatePicker";
public const string Dropdowns = Root + ".Dropdowns";
public const string DynamicForms = Root + ".DynamicForms";
public const string FormElements = Root + ".FormElements";

@ -18,4 +18,10 @@
<ProjectReference Include="..\..\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Pages\**\*.css">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

@ -0,0 +1,80 @@
trigger:
tags:
include:
- "*.*.*"
resources:
repositories:
- repository: devops
type: github
endpoint: github.com_skoc10
name: volosoft/devops
ref: master
variables:
# Container registry service connection established during pipeline creation
dockerRegistryServiceConnection: 'volosoft-reg'
workDir: '$(Build.SourcesDirectory)'
bootstrapTaghelpersDir: '$(workDir)/abp/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo'
# tag: $[replace(variables['Build.SourceBranch'], 'refs/tags/', '')]
tag: $(Build.BuildNumber)
DOCKER_BUILDKIT: 1
pool:
vmImage: 'ubuntu-latest'
stages:
- stage: Package
displayName: Package
jobs:
- job: Build
displayName: Package Helm Charts and Values
pool:
vmImage: 'ubuntu-latest'
steps:
- checkout: self
- checkout: devops
- script: |
cd $(bootstrapTaghelpersDir) && dotnet publish -c Release -o bin/Release/publish
- task: Docker@2
displayName: Build Image
inputs:
command: build
repository: demo/bootstrap-taghelpers
dockerfile: $(bootstrapTaghelpersDir)/Dockerfile
buildContext: $(bootstrapTaghelpersDir)
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)
- task: Docker@2
displayName: Push Image
inputs:
command: push
repository: demo/bootstrap-taghelpers
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)
- bash: |
mkdir -p $(Build.SourcesDirectory)/devops/aks/versions
cat <<EOF > $(Build.SourcesDirectory)/devops/aks/versions/bootstrap-taghelpers-version.yaml
image:
repository: volosoft.azurecr.io/demo/bootstrap-taghelpers
tag: "$(tag)"
EOF
cat $(Build.SourcesDirectory)/devops/aks/versions/bootstrap-taghelpers-version.yaml >> $(Build.SourcesDirectory)/devops/aks/helm/values/app/demo/bootstrap-taghelpers.abp.io.yaml
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: bootstrap-taghelpers'
inputs:
PathtoPublish: '$(Build.SourcesDirectory)/devops/aks/helm'
ArtifactName: 'bootstrap-taghelpers'

@ -0,0 +1,5 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
@addTagHelper *, Volo.Blogging.Web

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Blogging.Posts;
namespace Volo.Blogging.Members;
public interface IMemberAppService : IApplicationService
{
Task<BlogUserDto> FindAsync(string username);
}

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
@ -20,5 +21,7 @@ namespace Volo.Blogging.Posts
Task<PostWithDetailsDto> CreateAsync(CreatePostDto input);
Task<PostWithDetailsDto> UpdateAsync(Guid id, UpdatePostDto input);
Task<List<PostWithDetailsDto>> GetListByUserIdAsync(Guid userId);
}
}

@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
using Volo.Blogging.Posts;
using Volo.Blogging.Users;
namespace Volo.Blogging.Members;
public class MemberAppService : BloggingAppServiceBase, IMemberAppService
{
private readonly IRepository<BlogUser, Guid> _userRepository;
public MemberAppService(IRepository<BlogUser, Guid> userRepository)
{
_userRepository = userRepository;
}
public async Task<BlogUserDto> FindAsync(string username)
{
var user = await _userRepository.FindAsync(x => x.UserName == username);
if (user == null)
{
return null;
}
return ObjectMapper.Map<BlogUser, BlogUserDto>(user);
}
}

@ -188,6 +188,13 @@ namespace Volo.Blogging.Posts
return ObjectMapper.Map<Post, PostWithDetailsDto>(post);
}
public async Task<List<PostWithDetailsDto>> GetListByUserIdAsync(Guid userId)
{
var posts = await PostRepository.GetListByUserIdAsync(userId);
return ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts);
}
[Authorize(BloggingPermissions.Posts.Create)]
public async Task<PostWithDetailsDto> CreateAsync(CreatePostDto input)
{

@ -59,6 +59,8 @@
"ClearCacheConfirmationMessage": "Are you sure you want to clear the cache?",
"MarkdownSupported": "Markdown is supported",
"FileUploadInfo": "Drag, drop, or paste a copied image.",
"PostDescriptionHint": "* Will be rendered in the article link preview, supports HTML"
"PostDescriptionHint": "* Will be rendered in the article link preview, supports HTML",
"ReadMore": "Continue Reading",
"MemberNotPublishedPostYet": "No posts yet!"
}
}
}

@ -15,5 +15,7 @@ namespace Volo.Blogging.Posts
Task<Post> GetPostByUrl(Guid blogId, string url, CancellationToken cancellationToken = default);
Task<List<Post>> GetOrderedList(Guid blogId,bool descending = false, CancellationToken cancellationToken = default);
Task<List<Post>> GetListByUserIdAsync(Guid userId, CancellationToken cancellationToken = default);
}
}

@ -8,6 +8,7 @@ using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Blogging.EntityFrameworkCore;
using Volo.Blogging.Users;
namespace Volo.Blogging.Posts
{
@ -61,6 +62,14 @@ namespace Volo.Blogging.Posts
}
public async Task<List<Post>> GetListByUserIdAsync(Guid userId, CancellationToken cancellationToken = default)
{
var query = (await GetDbSetAsync()).Where(p => p.CreatorId == userId)
.OrderByDescending(p => p.CreationTime);
return await query.ToListAsync(GetCancellationToken(cancellationToken));
}
public override async Task<IQueryable<Post>> WithDetailsAsync()
{
return (await GetQueryableAsync()).IncludeDetails();

@ -0,0 +1,28 @@
// This file is automatically generated by ABP framework to use MVC Controllers from CSharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Http.Client;
using Volo.Abp.Http.Modeling;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.ClientProxying;
using Volo.Blogging.Blogs;
using Volo.Blogging.Blogs.Dtos;
using Volo.Blogging.Members;
using Volo.Blogging.Posts;
// ReSharper disable once CheckNamespace
namespace Volo.Blogging.ClientProxies;
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IMemberAppService), typeof(MembersClientProxy))]
public partial class MembersClientProxy : ClientProxyBase<IMemberAppService>, IMemberAppService
{
public Task<BlogUserDto> FindAsync(string username)
{
return RequestAsync<BlogUserDto>(nameof(FindAsync), new ClientProxyRequestTypeValue
{
{ typeof(string), username }
});
}
}

@ -0,0 +1,7 @@
// This file is part of MembersClientProxy, you can customize it here
// ReSharper disable once CheckNamespace
namespace Volo.Blogging.ClientProxies;
public partial class MembersClientProxy
{
}

@ -1,5 +1,6 @@
// This file is automatically generated by ABP framework to use MVC Controllers from CSharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Http.Client;
@ -65,6 +66,14 @@ public partial class PostsClientProxy : ClientProxyBase<IPostAppService>, IPostA
});
}
public virtual async Task<List<PostWithDetailsDto>> GetListByUserIdAsync(Guid userId)
{
return await RequestAsync<List<PostWithDetailsDto>>(nameof(GetListByUserIdAsync), new ClientProxyRequestTypeValue
{
{ typeof(Guid), userId }
});
}
public virtual async Task DeleteAsync(Guid id)
{
await RequestAsync(nameof(DeleteAsync), new ClientProxyRequestTypeValue

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp;
@ -60,6 +61,13 @@ namespace Volo.Blogging
{
return _postAppService.UpdateAsync(id, input);
}
[HttpGet]
[Route("user/{userId}")]
public Task<List<PostWithDetailsDto>> GetListByUserIdAsync(Guid userId)
{
return _postAppService.GetListByUserIdAsync(userId);
}
[HttpDelete]
[Route("{id}")]

@ -58,5 +58,13 @@ namespace Volo.Blogging.Posts
return await query.OrderByDescending(x => x.CreationTime).ToListAsync(GetCancellationToken(cancellationToken));
}
public async Task<List<Post>> GetListByUserIdAsync(Guid userId, CancellationToken cancellationToken = default)
{
var query = (await GetMongoQueryableAsync(cancellationToken)).Where(x => x.CreatorId == userId)
.OrderByDescending(x => x.CreationTime);
return await query.ToListAsync(GetCancellationToken(cancellationToken));
}
}
}

@ -95,6 +95,7 @@ namespace Volo.Blogging
options.Conventions.AddPageRoute("/Blogs/Posts/Detail", routePrefix + "{blogShortName:blogNameConstraint}/{postUrl}");
options.Conventions.AddPageRoute("/Blogs/Posts/Edit", routePrefix + "{blogShortName}/posts/{postId}/edit");
options.Conventions.AddPageRoute("/Blogs/Posts/New", routePrefix + "{blogShortName}/posts/new");
options.Conventions.AddPageRoute("/Members/Index", routePrefix + "members/{userName}");
});
Configure<DynamicJavaScriptProxyOptions>(options =>

@ -65,14 +65,19 @@
<div class="col-auto pe-1">
@if (Model.Post.Writer != null)
{
<img gravatar-email="@Model.Post.Writer.Email" default-image="Identicon" class="article-avatar" />
<a href="/Members/@Model.Post.Writer.UserName">
<img gravatar-email="@Model.Post.Writer.Email" default-image="Identicon" class="article-avatar"/>
</a>
}
</div>
<div class="col ps-1">
@if (Model.Post.Writer != null)
{
<h5 class="mt-2 mb-1">@(Model.Post.Writer.UserName) <span>@BloggingPageHelper.ConvertDatetimeToTimeAgo(Model.Post.CreationTime)</span></h5>
<a href="/Members/@Model.Post.Writer.UserName">
<h5 class="mt-2 mb-1">
@(Model.Post.Writer.UserName) <span>@BloggingPageHelper.ConvertDatetimeToTimeAgo(Model.Post.CreationTime)</span>
</h5>
</a>
}
<i class="fa fa-eye"></i> @L["WiewsWithCount", @Model.Post.ReadCount]

@ -1,5 +1,6 @@
@page
@using Microsoft.AspNetCore.Authorization
@using Volo.Blogging.Areas.Blog.Helpers.TagHelpers
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.OwlCarousel
@using Volo.Blogging
@inject IAuthorizationService Authorization
@ -59,11 +60,17 @@
<div class="user-card">
<div class="row">
<div class="col-auto pe-1">
<img gravatar-email="@post.Writer.Email" default-image="Identicon" class="article-avatar" />
<a href="/Members/@post.Writer.UserName">
<img gravatar-email="@post.Writer.Email" default-image="Identicon" class="article-avatar"/>
</a>
</div>
<div class="col ps-1">
<h5 class="mt-2 mb-1">@post.Writer.UserName <span>@BloggingPageHelper.ConvertDatetimeToTimeAgo(post.CreationTime)</span></h5>
<i class="fa fa-eye"></i> @L["WiewsWithCount", post.ReadCount]
<a href="/Members/@post.Writer.UserName">
<h5 class="mt-2 mb-1">
@post.Writer.UserName <span>@BloggingPageHelper.ConvertDatetimeToTimeAgo(post.CreationTime)</span>
</h5>
</a>
<i class="fa fa-eye"></i> @L["WiewsWithCount", post.ReadCount]
@*<span class="vs-seperator">|</span>
<i class="fa fa-comment"></i> @L["CommentWithCount", post.CommentCount]*@
</div>

@ -0,0 +1,96 @@
@page
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers
@using Volo.Abp.Users
@using Volo.Blogging.Localization
@model Volo.Blogging.Pages.Members.IndexModel
@inject IStringLocalizer<BloggingResource> L
@inject ICurrentUser CurrentUser
@{
ViewBag.Title = @Model.User.UserName.ToUpper() + " - " + L["Blogs"].Value;
}
@section styles {
<abp-style src="/Pages/Members/Index.css"/>
}
<main>
<div class="container">
<div class="row gx-lg-5">
<div class="col-md-4 mb-5 mb-md-0">
<div class="card h-auto member-profile-info">
<div class="card-body">
<div class="d-inline-block position-relative">
<img gravatar-email="@Model.User.Email" default-image="Identicon" class="post-member-img rounded-circle d-block"/>
</div>
@if (Model.User.UserName != null)
{
<h2 class="m-0">@Model.User.UserName</h2>
}
<small class="d-block mt-4">@L["UserName"].Value.ToUpper()</small>
<h5>@Model.User.UserName</h5>
</div>
</div>
</div>
@if (Model.Posts is not null && Model.Posts.Any())
{
<div class="col-md-8">
<abp-tabs>
<abp-tab name="all-posts" title="All Blog Posts">
<div class="mt-4 pt-3">
@foreach (var post in Model.Posts)
{
<div class="post-item">
<div class="post-type-cont">
<a href="@Model.GetMemberProfileUrl(Model.User)" class="text-decoration-none">
<img gravatar-email="@Model.User.Email" default-image="Identicon" class="post-member-img rounded-circle d-block"/>
</a>
<span class="post-type">
<i class="fas fa-pen-nib"></i>
@L["Blog"].Value.ToUpper()
</span>
</div>
<div class="post-detail-cont">
<div class="post-info fs-12 mb-2">
<a href="@Model.GetMemberProfileUrl(Model.User)" class="text-decoration-none">
<span class="text-dark dot">@Model.User.UserName</span>
</a>
<span class="text-dark-200 dot">@post.CreationTime.ToString("MMMM yyyy")</span>
<span class="text-dark-200">@post.ReadCount.ToString() @L["Views"]</span>
</div>
<h3 class="post-title mb-3">
<a href="@Model.GetBlogPostUrl(post)">
@post.Title
</a>
</h3>
<p class="post-desc">
<a href="@Model.GetBlogPostUrl(post)">
@post.Description.TruncateWithPostfix(150)
</a>
<a href="@Model.GetBlogPostUrl(post)" class="readMore">@L["ReadMore"]</a>
</p>
</div>
<div class="post-img-cont">
<div class="post-list-span text-center post">
<img src="@post.CoverImage" class="box-articles">
</div>
</div>
</div>
}
</div>
</abp-tab>
</abp-tabs>
</div>
}
else
{
<div class="col-md-8">
<div class="mt-5 pt-6">
<p>@L["MemberNotPublishedPostYet"]</p>
</div>
</div>
}
</div>
</div>
</main>

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
using Volo.Blogging.Blogs;
using Volo.Blogging.Members;
using Volo.Blogging.Posts;
namespace Volo.Blogging.Pages.Members;
public class IndexModel : AbpPageModel
{
private readonly IPostAppService _postAppService;
private readonly IMemberAppService _memberAppService;
private readonly IBlogAppService _blogAppService;
public BlogUserDto User { get; set; }
public List<PostWithDetailsDto> Posts { get; set; }
public Dictionary<Guid, string> BlogShortNameMap { get; set; }
public IndexModel(IPostAppService postAppService, IMemberAppService memberAppService, IBlogAppService blogAppService)
{
_postAppService = postAppService;
_memberAppService = memberAppService;
_blogAppService = blogAppService;
}
public async Task<IActionResult> OnGetAsync(string userName)
{
User = await _memberAppService.FindAsync(userName);
if (User is null)
{
return Redirect("/");
}
Posts = await _postAppService.GetListByUserIdAsync(User.Id);
var blogIds = Posts.Select(x => x.BlogId).Distinct();
BlogShortNameMap = new Dictionary<Guid, string>();
foreach (var blogId in blogIds)
{
BlogShortNameMap[blogId] = (await _blogAppService.GetAsync(blogId)).ShortName;
}
return Page();
}
public string GetBlogPostUrl(PostWithDetailsDto post)
{
var blogShortName = BlogShortNameMap[post.BlogId];
return "/" + blogShortName + "/" + post.Url;
}
public string GetMemberProfileUrl(BlogUserDto user)
{
return "/members/" + user.UserName;
}
}

@ -0,0 +1,3 @@
.post-desc {
overflow-wrap: break-word;
}

@ -160,6 +160,12 @@
}, ajaxParams));
};
volo.blogging.posts.getListByUserId = function (userId, ajaxParams) {
return abp.ajax($.extend(true, {
url: abp.appPath + 'api/blogging/posts/user/' + userId + '',
type: 'GET'
}, ajaxParams));
};
})();
// controller volo.blogging.tags
@ -176,7 +182,20 @@
};
})();
// controller volo.blogging.members
(function() {
abp.utils.createNamespace(window, 'volo.blogging.members');
volo.blogging.members.get = function (username, ajaxParams) {
return abp.ajax($.extend(true, {
url: abp.appPath + 'api/blogging/members/' + username + '',
type: 'GET'
}, ajaxParams));
};
})();
})();

@ -60,7 +60,7 @@ $(document).ready(function () {
$selectIcon.popover({
placement: 'left',
html: true,
trigger: 'focus',
trigger: 'click',
title: l('PickYourReaction'),
content: $popoverContent.html()
}).on('shown.bs.popover', function () {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save