diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/zh-Hans.json index e790ad80af..a773568a31 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/zh-Hans.json @@ -11,6 +11,6 @@ "CommercialSupportWebSite": "商业版支持网站", "CommunityWebSite": "ABP社区网站", "ManageAccount": "我的帐户 | ABP.IO", - "ManageYourAccount": "管理您的帐户" + "ManageYourProfile": "管理您的个人资料" } -} +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json index 382d31dd5d..3a1b574d21 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json @@ -161,6 +161,9 @@ "Error_Page_500_Title": "Looks like something went wrong!", "Error_Page_500_Description_1": "We track these errors automatically, but if the problem persists feel free to
contact us. In the meantime, try refreshing.", "Error_Page_500_Description_2": "Contact with us at info@abp.io.", - "Books": "Books" + "Books": "Books", + "ABPDiscordServer": "ABP Discord Server", + "ABPCommunityTalks": "ABP Community Talks", + "ABPCommunityPosts": "ABP Community Posts" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json index 01335e7921..c7ccd1e1e2 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json @@ -14,6 +14,20 @@ "Volo.AbpIo.Domain:020002": "不能删除该NPM包因为\"{Modules}\"模块正在使用此包.", "Volo.AbpIo.Domain:020003": "不能删除该NPM包因为\"{Modules}\"模块正在使用此包并且\"{NugetPackages}\"Nuget包依赖此包.", "Volo.AbpIo.Domain:020004": "不能删除该Nuget包因为\"{Modules}\"模块正在使用此包.", + "Volo.AbpIo.Domain:030000": "您已经完成了试用期。", + "Volo.AbpIo.Domain:030001": "此组织名称已存在。", + "Volo.AbpIo.Domain:030002": "一旦激活,您将无法将试用许可证切换到 -requested- 状态!", + "Volo.AbpIo.Domain:030003": "没有这种状态!", + "Volo.AbpIo.Domain:030004": "由于意外错误,无法更改状态!", + "Volo.AbpIo.Domain:030005": "试用许可证处于激活状态时可以更新开始和结束日期!", + "Volo.AbpIo.Domain:030006": "结束日期必须大于开始日期!", + "Volo.AbpIo.Domain:030007": "此试用许可证已激活!", + "Volo.AbpIo.Domain:030008": "只有状态为-purchased-时才能设置购买日期!", + "Volo.AbpIo.Domain:030009": "找不到用户!", + "Volo.AbpIo.Domain:030010": "要购买试用许可证,您首先需要激活您的试用许可证!", + "Volo.AbpIo.Domain:030011": "购买后不能删除试用许可证!", + "Volo.AbpIo.Domain:070000": "组织名称只能包含拉丁字母、数字、点和连字符!", + "Volo.AbpIo.Domain:070001": "公司名称只能包含拉丁字母、数字、点、空格和连字符!", "WantToLearn?": "想学习吗?", "ReadyToGetStarted?": "准备开始了吗?", "JoinOurCommunity": "加入我们的社区", @@ -39,6 +53,114 @@ "TrialLicensePeriodHasExpired": "您的试用许可期限已于 {0} 天前到期。", "TrialLicensePeriodWillExpire": "您的试用许可期限将在 {0} 天后到期。", "TrialLicensePeriodExpireToday": "您的试用许可期将于今天到期。", - "PurchaseNow": "现在买!" + "PurchaseNow": "现在买!", + "LatestReleaseLogs": "最新发布日志", + "RoadMap": "路线图", + "FAQ": "常见问题", + "SourceCode": "源代码", + "SeeAllPosts": "查看所有帖子", + "Contribute": "贡献", + "LiveDemo": "在线演示", + "GetLicense": "获得许可证", + "OpenSource": "开源", + "WebApplication": "Web应用程序", + "MeetTheABP": "认识 ABP", + "CompleteWebDevelopment": "一个完整的 Web 开发", + "Platform": "平台", + "ABPDescription": "ABP 框架是一个完整的基础架构,可通过遵循软件开发最佳实践和约定来创建现代 Web 应用程序。", + "StrongInfrastructure": "强大的基础设施", + "CompleteArchitecture": "完整的架构", + "DeveloperFocused": "以开发者为中心", + "ShareYourExperiences": "分享您使用 ABP 框架的经验", + "LatestPosts": "最新的帖子", + "Views": "意见", + "LearnLatestNewsAboutABPFramework": "获取有关 ABP 的最新相关信息,例如新版本、免费资源、帖子等。", + "DeveloperTools": "开发者工具", + "StartupTemplates": "启动模板", + "ApplicationModules": "应用模块", + "UI": "UI", + "Themes": "主题", + "Premium": "高级的", + "PrivacyPolicy": "隐私政策", + "TermsAndConditions": "条款 & 条件", + "WouldLikeToReceiveMarketingMaterials": "我想收到产品交易和特别优惠等市场推广材料。", + "JoinOurMarketingNewsletter": "加入我们市场推广时事通讯", + "CommunityPrivacyPolicyConfirmation": "我同意条款 & 条件和隐私政策 。", + "WouldLikeToReceiveNotification": "我想从 abp.io 网站接收最新消息。", + "CommercialNewsletterConfirmationMessage": "我同意条款 & 条件 和 隐私政策。", + "FreeDDDEBook": "免费 DDD 电子书", + "AdditionalServices": "额外服务", + "Learn": "学习", + "AccountOverview": "账户信息", + "MyOrganizations": "我的组织", + "MySupportQuestions": "我的支持问题", + "MyProfile": "我的简介", + "Logout": "登出", + "Home": "主页", + "Posts": "帖子", + "JoinTheABPCommunity": "加入 ABP 社区", + "SubmitYourPost": "提交您的帖子", + "Modules": "模块", + "Tools": "工具", + "Pricing": "价格", + "ChangeLogs": "更改日志", + "SubscribeToNewsletter": "订阅时事通讯", + "SubscribeToNewsletterDescription": "获取有关 ABP 的最新相关信息,例如新版本、免费资源、帖子等。", + "EmailAddress": "邮箱地址", + "Subscribe": "订阅", + "WelcomeToABP": "欢迎来到 ABP", + "EULA": "最终用户许可协议", + "ABPCommercialIntroductionMessage": "预建应用程序模块、高级启动模板、快速应用程序开发工具、专业 UI 主题和高级支持。", + "MasteringAbpFrameworkEBook": "掌握 ABP 框架", + "MasteringTheABPFrameworkExplanation": "本书由 ABP 框架的创建者撰写,将帮助您全面了解框架和现代 Web 应用程序开发技术。", + "Speakers": "发言者", + "PreviousEvents": "以往活动", + "WatchTheEvent": "观看活动", + "RegisterNow": "现在注册", + "ThereIsNoEvent": "没有活动。", + "Events": "活动", + "Volo.AbpIo.Domain:080000": "已有一个名为 \"{Name}\" 的购买项目", + "MasteringAbpFrameworkBook": "书籍:掌握 ABP 框架", + "ABPIO-CommonPreferenceDefinition": "获取有关 ABP 平台的最新消息,例如新帖子、活动等。", + "BuiltOn": "建立在", + "AbpFramework": "ABP 框架", + "Volo.AbpIo.Domain:080001": "开始时间不能大于结束时间", + "Enum:BookType:0": "掌握 ABP 框架", + "Enum:PurchasePlatform:0": "亚马逊", + "Enum:PurchasePlatform:1": "Packt", + "Copied": "已复制!", + "CouldNotCopy": "无法复制!", + "CopyNotSupportByYourBrowser": "此功能在您使用的浏览器中不起作用。", + "City": "城市", + "ZipCode": "邮政编码", + "Address": "地址", + "Homepage": "主页", + "Year": "年份", + "Copyright": "版权所有 © {1}", + "DomainDrivenDesign": "领域驱动设计", + "CrossCuttingConcerns": "横切关注点", + "AbpCommunity": "ABP 社区", + "Footer_GithubStarCount": "{0} 在 GitHub 上加星", + "Footer_NugetDownloadCount": "{0} 在 NuGet 上下载", + "AbpDescription": "ABP 是一个开源应用程序框架,专注于基于 AspNet Core 的 Web 应用程序开发。 Don't repeat yourself,专注于自己的业务代码。", + "Layout_AbpFramework_MetaTitle": "ABP 框架 - 开源 Web 应用程序框架", + "CommunityTalks_CountdownDays": "天", + "CommunityTalks_CountdownHours": "小时", + "CommunityTalks_CountdownMinutes": "分钟", + "CommunityTalks_CountdownSeconds": "秒", + "SeePreviousEvents": "查看以前的活动", + "CookieConsent_Accept": "接受", + "CookieConsent_Explanation_1": "我们使用 cookie 为您提供在我们网站上的最佳体验。", + "CookieConsent_Explanation_2": "如果您继续浏览,则表示您同意我们的隐私政策和cookie 政策。。", + "Error_Page_400_Title": "提供请求的页面时出现问题。", + "Error_Page_400_Description_1": "通常这意味着在处理您的请求时发生了意外错误。", + "Error_Page_400_Description_2": "如果问题仍然存在,请通过 info@abp.io 联系我们,我们将帮助您开始。", + "GoToHomepage": "去首页", + "Error_Page_404_Title": "页面未找到!", + "Error_Page_404_Description_1": "这不是您要查找的网页。", + "Error_Page_500_Title": "好像出了什么问题!", + "Error_Page_500_Description_1": "我们会自动跟踪这些错误,但如果问题仍然存在,请随时
联系我们。 与此同时,尝试刷新。", + "Error_Page_500_Description_2": "通过 info@abp.io 与我们联系。", + "Books": "书籍" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/zh-Hans.json index df772fa60b..524c3345e3 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Blog/Localization/Resources/zh-Hans.json @@ -1,5 +1,7 @@ { "culture": "zh-Hans", "texts": { + "AbpTitle": "ABP 框架 - 开源 Web 应用程序框架", + "AbpDescription": "ABP 是一个开源应用程序框架,专注于基于 AspNet Core 的 Web 应用程序开发。 Don't repeat yourself,专注于自己的业务代码。" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json index 36c00e9015..caf4a9dbbe 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json @@ -536,6 +536,7 @@ "Pricing_Page_Testimonial_2": "We are seeing the value of using ABP Commercial to reduce the overhead of custom development projects. And the team is able to unify the code pattern in different project streams. We see more potential in the framework for us to build new features faster than before. We trust we will be constantly seeing the value of leveraging ABP Commercial.", "Pricing_Page_Testimonial_3": "We love ABP. We don't have to write everything from scratch. We start from out-of-the-box features and just focus on what we really need to write. Also, ABP is well-architected and the code is high quality with fewer bugs. If we would have to write everything we needed on our own, we might have to spend years. Once more things we like is that the new version, or issue fixing, or improvement come out very soon every other week. We don't wait too long.", "Pricing_Page_Testimonial_4": "ABP Commercial is a fantastic product would recommend. Commercial products to market for our customers in a single configurable platform. The jump start that the framework and tooling provide any team is worth every cent. ABP Commercial was the best fit for our needs.", + "Pricing_Page_Testimonial_5": "ABP Framework is not only a framework, but it is also a guide for project development/management, because it provides DDD, GenericRepository, DI, Microservice, and Modularity training. Even if you are not going to use the framework itself, you can develop yourself with docs.abp.io which is well and professionally prepared (OpenIddict, Redis, Quartz etc.). Because many things are pre-built, it shortens project development time significantly (Such as login page, exception handling, data filtering, seeding, audit logging, localization, auto API controller etc.). As an example from our application, I have used Local Event Bus for stock control. So, I am able to manage order movements by writing stock handler. It is wonderful not to lose time for CreationTime, CreatorId. They are being filled automatically.", "AbpBookDownloadArea_ClaimYourEBook": "Claim your Mastering ABP Framework E-Book", "AddMemberModal_Warning_1": "If the username you are trying to add doesn't exist in the system, please ask your team member to register on {0} and share the username of his/her account with you.", "MyOrganizations_Detail_WelcomeMessage": "Welcome to your organization, {0}", @@ -730,7 +731,10 @@ "Welcome_Page_UseSameCredentialForCommercialWebsites": "Use the same credentials for both commercial.abp.io and support.abp.io.", "WatchCrudPagesVideo": "Watch the \"Creating CRUD Pages with ABP Suite\" Video!", "WatchGeneratingFromDatabaseVideo": "Watch the \"ABP Suite: Generating CRUD Pages From Existing Database Tables\" Video!", - "WatchTakeCloserLookVideo": "Watch the \"Take a closer look at the code generation: ABP Suite\" Video!" - + "WatchTakeCloserLookVideo": "Watch the \"Take a closer look at the code generation: ABP Suite\" Video!", + "ConfirmedEmailAddressRequiredToStartTrial": "You should have a confirmed email address in order to start a trial license.", + "EmailVerificationMailNotSent": "Email verification mail couldn't send.", + "GetConfirmationEmail": "Click here to get a confirmation email if you haven't got it before.", + "WhichLicenseTypeYouAreInterestedIn": "Which license type you are interested in?" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json index b5d9d65c82..3513a37dde 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json @@ -29,14 +29,12 @@ "ContributionGuide": "贡献指南", "BugReport": "Bug报告", "SeeAllPosts": "查看所有的文章", - "WelcomeToABPCommunity!": "欢迎来到ABP社区!", - "MyProfile": "我的资料", - "MyOrganizations": "我的组织", + "WelcomeToABP": "欢迎来到ABP", "EmailNotValid": "请输入有效的电子邮箱地址.", "FeatureRequest": "功能请求", "CreatePostTitleInfo": "文章标题显示在文章列表中.", "CreatePostSummaryInfo": "文章的简短摘要将显示在文章列表中.", - "CreatePostCoverInfo": "为了创建有效的文章,请添加封面图. 仅支持16:9的图片!", + "CreatePostCoverInfo": "要创建一个有效的帖子,需要添加封面图片. 上传16:9的图片获取最佳视觉效果. 文件最大支持: 1MB", "ThisExtensionIsNotAllowed": "不允许此扩展名.", "TheFileIsTooLarge": "文件过大.", "GoToThePost": "转到文章", @@ -45,7 +43,7 @@ "Done": "完成", "Open": "打开", "Closed": "关闭", - "LatestQuestionOnThe": "有关的最新问题", + "RecentQuestionFrom": "最近的问题来自", "Stackoverflow": "Stackoverflow", "Votes": "票数", "Answer": "回答", @@ -59,7 +57,7 @@ "QuestionItemErrorMessage": "无法从Stackoverflow获取最新的问题详细信息.", "Oops": "哎呀!", "CreatePostSuccessMessage": "文章提交成功. 网站管理员审核通过后将被发布.", - "ChooseCoverImage": "选项一张封面图片", + "Browse": "浏览", "CoverImage": "封面图片", "ShareYourExperiencesWithTheABPFramework": "分享你的ABP Framework经验!", "Optional": "可选的", @@ -88,6 +86,8 @@ "PostRequestFromGithubIssue": "现在没有任何文章请求.", "LatestPosts": "最新的帖子", "ArticleRequests": "文章请求", + "ArticleRequestsDescription": "想在这里查看具体内容吗? 您可以要求社区创建它!", + "LatestContentRequests": "最新内容请求", "AllPostRequests": "查看所有文章请求", "SubscribeToTheNewsletter": "订阅简讯", "NewsletterEmailDefinition": "获取有关ABP发生的信息,例如新版本,免费资源,文章等.", @@ -115,7 +115,6 @@ "VideoUrl": "视频Url", "GithubPostUrl": "Github文章Url", "ExternalPostUrl": "外部文章Url", - "CreatePostCoverInfo": "要创建一个有效的帖子,需要添加封面图片. 上传16:9的图片获取最佳视觉效果. 文件最大支持: 1MB", "ThankYouForContribution": "感谢你对ABP社区的贡献", "GithubPost": "Github文章", "GithubPostSubmitStepOne": "1. 用Markdown格式在GitHub的任何公共存储库上写一篇文章. 示例", @@ -143,6 +142,48 @@ "Volo.AbpIo.Domain:060001": "源 URL(\"{PostUrl}\") 不是 Github URL", "Volo.AbpIo.Domain:060002": "文章内容无法从 Github(\"{PostUrl}\") 资源中获得。", "Volo.AbpIo.Domain:060003": "没有找到文章内容!", - "SeeMore": "查看更多" + "SeeMore": "查看更多", + "JoinTheABPCommunity": "加入 ABP 社区", + "ABPCommunityTalks": "ABP 社区会谈", + "LiveDemo": "现场演示", + "GetLicense": "获得许可证", + "GetStarted": "开始使用", + "SourceCode": "源代码", + "LeaveComment": "发表评论", + "ReadMore": "阅读更多", + "ShowMore": "展示更多", + "NoPublishedPostsYet": "还没有发布的帖子。", + "Name": "名字", + "Surname": "姓氏", + "WebSite": "网站", + "FullURL": "完整网址", + "JobTitle": "职称", + "Prev": "上一页", + "Previous": "前面的", + "Next": "下一页", + "Share": "分享", + "SortBy": "排序方式", + "NoPublishedEventsYet": "尚未发布活动。", + "SubscribeYoutubeChannel": "订阅 Youtube 频道", + "Enum:EventType:0": "会谈", + "MemberNotPublishedPostYet": "该成员尚未发布任何帖子。", + "TimeAgo": "{0} 前", + "Discord_Page_JoinCommunityMessage": "加入 ABP Discord 社区", + "Discord_Page_Announce": "我们很高兴地宣布 ABP 社区 Discord 服务器!", + "Discord_Page_Description_1": "ABP 社区从第一天起就一直在壮大。 我们希望通过创建一个官方的 ABP Discord 服务器将其带入下一步,这样 ABP 社区就可以使用即时消息的功能彼此进行互动。", + "Discord_Page_Description_2": "ABP Community Discord Server 是您可以使用 ABP 框架来展示您的创作、分享对您有用的技巧、了解有关 ABP 框架的最新消息和公告、与社区成员聊天交流想法并玩得开心的地方!", + "Discord_Page_Description_3": "此 ABP 社区 Discord 服务器是 ABP 核心团队的官方服务器,存在于服务器上进行监控。", + "Discord_Page_JoinToServer": "加入 ABP Discord 服务器", + "Events_Page_MetaTitle": "ABP 社区活动", + "Events_Page_MetaDescription": "现场活动由 ABP 团队主持,是充满社区内容、演示、问答和围绕 ABP 正在发生的事情的讨论的休闲会议。", + "Events_Page_Title": "ABP社区会谈", + "Events_Page_WritingFromUser": "阅读来自 ABP 社区的 {0} 的文章。", + "Post_Create_Page_MetaTitle": "最新帖子", + "Post_Create_Page_MetaDescription": "创建您的帖子以分享您对 ABP 框架的经验并为 ABP 社区做出贡献。", + "Post_Create_Page_CreateNewPost": "创建新帖子", + "Post_Index_Page_MetaDescription": "ABP 社区的目的是为使用 ABP 框架的开发人员创建一个贡献环境。", + "Layout_Title": "{0} | ABP 社区", + "Layout_MetaDescription": "ABP 社区是一个人们可以分享有关 ABP 框架的帖子并关注项目的环境。", + "Index_Page_CommunityIntroduction": "这是 ABP 框架、.NET 和软件开发的中心。 您可以阅读文章,观看视频教程,了解 ABP 的开发进度和 ABP 相关事件,帮助其他开发人员并与 ABP 社区分享您的专业知识。" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json index dd91379201..7de1be2f2b 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json @@ -174,6 +174,7 @@ "CreateProjectWizard": "此向导让你从启动模板创建一个新项目,该启动模板已正确配置为可以快速启动你的项目.", "TieredOption": "创建一个分层解决方案,其中Web和Http API层在物理上是分离的. 如果没有选中则创建一个不那么复杂且适合大多数场景的分层解决方案.", "SeparateIdentityServerOption": "将服务器端分离为两个应用程序:第一个应用程序用于身份服务器,第二个应用程序用于服务器端HTTP API.", + "ProgressiveWebApplicationOption": "将项目指定为渐进式 Web 应用程序", "UseslatestPreVersion": "使用最新的预发布版本", "ReadTheDocumentation": "阅读文档", "Documentation": "文档", @@ -214,7 +215,11 @@ "SeeDocs": "查看文档", "None": "空", "Application": "应用程序", + "ApplicationExplanation": "基于领域驱动设计实践创建一个完全分层的解决方案。 推荐用于需要可维护和可扩展代码库的长期项目。", + "ApplicationNoLayer": "应用程序(单层)", + "ApplicationNoLayerExplanation": "创建单层 Web 应用程序。 推荐用于构建具有更简单且易于理解的架构的应用程序。", "Module": "模块", + "ModuleExplanation": "创建可重用、完全分层的应用程序模块解决方案。 您可以使用此选项为您的模块化应用程序创建模块。", "PackageName": "包名称", "LicenseURL": "许可URL", "License": "许可", @@ -273,6 +278,100 @@ "SubscribeToNewsletter": "订阅时事通讯以获取有关 ABP.IO 平台中发生的事件的信息,例如新版本、文章、优惠等。", "FirstEdition": "第一版", "ThankYou": "谢谢!", - "CheckboxMandatory": "你需要检查这个才能继续!" + "CheckboxMandatory": "你需要检查这个才能继续!", + "UserInterface": "用户界面", + "APIGateway": "API 网关", + "Database": "数据库", + "Saas": "Saas", + "OpenSourceWebApp": "打开 source
Web 应用程序", + "Framework": "框架", + "AuditLoggingExplanation": "自动跟踪系统中的所有操作和数据更改。", + "AbpNewCommandExplanation": "使用 ABP 启动模板创建新的解决方案。", + "AbpAddModuleCommandExplanation": "将预构建的应用程序模块安装到您的解决方案中", + "AbpUpdateCommandExplanation": "自动更新解决方案中所有与 ABP 相关的 NuGet 和 NPM 包。", + "ExploreAllCLICommands": "探索所有 CLI 命令", + "ExploreDocumentationAndGuides": "探索全面的文档和指南。", + "Documentations": "文档", + "Views": "意见", + "ReadMore": "阅读更多", + "EnterYouEmailToGetNews": "输入您的电子邮件以获取有关 ABP 框架的最新消息", + "Tiered": "分层", + "SeparateIdentityServer": "独立的身份服务器", + "ProgressiveWebApplication": "渐进式 Web 应用程序", + "Preview": "预览", + "CreateANewSolution": "创建一个新的解决方案", + "ABPFrameworkFeatures": "ABP 框架 功能", + "Commercial": "商业版", + "ThirdPartyTools": "第三方工具", + "Back": "后退", + "Community": "社区", + "SeeMore": "查看更多", + "DetailsOfTheEBook": "电子书详情", + "JoinOurMarketingNewsletter": "加入我们的市场推广时事通讯", + "FrameworkNewsletterConfirmationMessage": "我同意条款和条件 和 隐私政策。", + "GetYourFreeEBook": "获取您的 免费 DDD 电子书", + "EverythingYouNeedToKnow": "您需要了解的一切。", + "PreOrderNow": "立即预订", + "UITheming": "用户界面主题", + "UIThemingExplanation": "创建可重用的 UI 主题和布局,或使用预构建的 UI 主题之一。", + "DataFilteringExplanation2": "自动过滤来自数据库的查询,以轻松实现软删除和多租户等模式。", + "NeedHelp": "您需要帮助吗?", + "GiveYourProjectAName": "给您的项目命名", + "SelectProjectType": "选择项目类型", + "SelectUIFramework": "选择 UI 框架", + "SelectDatabaseProvider": "选择数据库提供者", + "SelectDatabaseManagementSystem": "选择数据库管理系统", + "InstallingTheABPCLI": "安装 ABP CLI", + "CreateYourProjectNow": "立即创建您的项目", + "OrderOn": "在 {0} 订购", + "DownloadFreeDDDBook": "下载免费的 DDD 书", + "WhatIsABPFramework": "什么是 ABP 框架?", + "TenantDatabase": "租户 {0} 数据库", + "SharedDatabase": "共享数据库", + "ConnectionResolver": "连接解析器", + "TenantBasedDataFilter": "基于租户的数据过滤器", + "ApplicationCode": "申请代码", + "TenantResolution": "租户决议", + "TenantUser": "租户 {0} 用户", + "CardTitle": "卡片标题", + "View": "查看", + "Model": "模型", + "Email": "电子邮箱", + "Password": "密码", + "Address": "地址", + "Gender": "性别", + "Male": "男", + "Female": "女", + "Submit": "提交", + "Unspecified": "未指定", + "StaticFileMiddleware": "静态文件中间件", + "RazorViewEngine": "Razor 视图引擎", + "PhysicalFiles": "物理文件 (wwwroot)", + "EmbeddedFiles": "嵌入式文件(DDL) ", + "DynamicFiles": "动态文件(内存)", + "BuildSolutionsWithAbp": "遵循使用 ABP 的软件开发最佳实践,构建可维护的 .NET 解决方案。", + "BuyOnAmazon": "在亚马逊上购买", + "BuyOnPackt": "在 Packt 上购买", + "Discounted": "打折", + "MasteringAbpFramework_Book_KeyFeatures": "主要特性", + "MasteringAbpFramework_Book_Key_Features_Description_1": "使用 ABP 框架构建强大、可维护、模块化和可扩展的软件解决方案。", + "MasteringAbpFramework_Book_Key_Features_Description_2": "了解如何在您的 Web 应用程序中实施 SOLID 原则和领域驱动设计。", + "MasteringAbpFramework_Book_Key_Features_Description_3": "了解 ABP 框架如何通过自动执行重复性任务来加快您的开发周期。", + "MasteringAbpFramework_Book_Description": "书籍说明", + "MasteringAbpFramework_Book_Description_Details_1": "ABP 框架是一个完整的基础架构,用于通过遵循软件 \n 开发最佳实践和约定来创建现代 Web 应用程序。 借助 ABP 的高级框架和生态系统,您可以 \n 实现 Don't Repeat Yourself (DRY) 原则并专注于您的业务代码。", + "MasteringAbpFramework_Book_Description_Details_2": "本书由 ABP 框架的创建者撰写,将帮助您全面了解该框架和现代 Web 应用程序开发技术。 通过对基本概念和实际示例的逐步解释,您将了解现代 Web 解决方案的要求以及 ABP\n 框架如何使开发您自己的解决方案变得轻松愉快。 您将发现\n 企业 Web 应用程序开发的常见需求,并探索 ABP 提供的基础架构。 在整本书中,您将掌握构建可维护和模块化 Web 解决方案的软件开发最佳实践。", + "MasteringAbpFramework_Book_Description_Details_3": "在本书结束时,您将能够创建一个易于开发、\n 维护和测试的完整 Web 解决方案。", + "MasteringAbpFramework_Book_WhatYouWillLearn": "你将会学到什么", + "MasteringAbpFramework_Book_What_You_Will_Learn_1": "设置开发环境并开始使用 ABP 框架。", + "MasteringAbpFramework_Book_What_You_Will_Learn_2": "使用 Entity Framework Core 和 MongoDB 开发您的数据访问层。", + "MasteringAbpFramework_Book_What_You_Will_Learn_3": "了解横切关注点以及 ABP 如何自动执行重复性任务。", + "MasteringAbpFramework_Book_What_You_Will_Learn_4": "掌握使用 ABP 框架实施领域驱动设计。", + "MasteringAbpFramework_Book_What_You_Will_Learn_5": "使用 ASP.NET Core MVC (Razor Pages) 和 Blazor 构建 UI 页面和组件。", + "MasteringAbpFramework_Book_What_You_Will_Learn_6": "使用多租户创建模块化 Web 应用程序。", + "MasteringAbpFramework_Book_What_You_Will_Learn_7": "了解模块化并创建可重用的应用程序模块。", + "MasteringAbpFramework_Book_What_You_Will_Learn_8": "使用 ABP 框架编写单元、集成和 UI 测试。", + "MasteringAbpFramework_Book_WhoIsThisBookFor": "这本书是给谁看的", + "MasteringAbpFramework_Book_WhoIsThisBookFor_Description": "本书适用于希望学习软件架构和最佳实践的 Web 开发人员,以使用 Microsoft 技术和 ABP 框架构建\n 可维护的基于 Web 的解决方案。 C#\n 和 ASP.NET Core 的基本知识是开始阅读本书所必需的。", + "ComputersAndTechnology": "计算机与技术" } } \ No newline at end of file diff --git a/docs/en/CLI.md b/docs/en/CLI.md index cc85f0e573..ffde52eff0 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -34,6 +34,7 @@ Here, is the list of all available commands before explaining their details: * **`add-package`**: Adds an ABP package to a project. * **`add-module`**: Adds a [multi-package application module](https://docs.abp.io/en/abp/latest/Modules/Index) to a solution. * **`list-modules`**: Lists names of open-source application modules. +* **`list-templates`**: Lists the names of available templates to create a solution. * **`get-source`**: Downloads the source code of a module. * **`generate-proxy`**: Generates client side proxies to use HTTP API endpoints. * **`remove-proxy`**: Removes previously generated client side proxies. diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 2603cb0436..8b8480669f 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -32,6 +32,22 @@ Configure(options => > Multi-Tenancy is disabled in the ABP Framework by default. However, it is **enabled by default** when you create a new solution using the [startup template](Startup-Templates/Application.md). `MultiTenancyConsts` class in the solution has a constant to control it in a single place. +### AbpMultiTenancyOptions: Handle inactive and non-existent tenants. + +The `MultiTenancyMiddlewareErrorPageBuilder` of `AbpMultiTenancyOptions` is used to handle inactive and non-existent tenants. + +It will respond to an error page by default, you can change it if you want, eg: only output the error log and continue ASP NET Core's request pipeline. + +```csharp +Configure(options => +{ + options.MultiTenancyMiddlewareErrorPageBuilder = async (context, exception) => + { + // Handle the exception. + }; +}); +``` + ### Database Architecture ABP Framework supports all the following approaches to store the tenant data in the database; diff --git a/docs/en/Themes/LeptonXLite/mvc.md b/docs/en/Themes/LeptonXLite/AspNetCore.md similarity index 100% rename from docs/en/Themes/LeptonXLite/mvc.md rename to docs/en/Themes/LeptonXLite/AspNetCore.md diff --git a/docs/en/Themes/LeptonXLite/angular.md b/docs/en/Themes/LeptonXLite/angular.md index 805ababd69..3edb7e6b1a 100644 --- a/docs/en/Themes/LeptonXLite/angular.md +++ b/docs/en/Themes/LeptonXLite/angular.md @@ -79,4 +79,4 @@ To change the logos and brand color of `LeptonX`, simply add the following CSS t ### Server Side -In order to migrate to LeptonX on your server side projects (Host and/or AuthServer projects), please follow the [Server Side Migration](mvc.md) document. +In order to migrate to LeptonX on your server side projects (Host and/or AuthServer projects), please follow the [Server Side Migration](AspNetCore.md) document. diff --git a/docs/en/Themes/LeptonXLite/blazor.md b/docs/en/Themes/LeptonXLite/blazor.md index f443336e7c..22bc4af285 100644 --- a/docs/en/Themes/LeptonXLite/blazor.md +++ b/docs/en/Themes/LeptonXLite/blazor.md @@ -16,7 +16,8 @@ LeptonX Lite has implementation for the ABP Framework Blazor WebAssembly & Blazo ## Installation {{if UI == "Blazor"}} -- Complete the [MVC Razor Pages Installation](mvc.md#installation) for the **HttpApi.Host** application first. _If the solution is tiered/micro-service, complete the MVC steps for all MVC applications such as **HttpApi.Host** and if identity server is separated, install to the **OpenIddict**_. +- Complete the [MVC Razor Pages Installation](AspNetCore.md#installation) for the **HttpApi.Host** application first. _If the solution is tiered/micro-service, complete the MVC steps for all MVC applications such as **HttpApi.Host** and if identity server is separated, install to the **OpenIddict**_. + - Add **Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme** package to your **Blazor WebAssembly** application with the following command: @@ -52,7 +53,7 @@ builder.RootComponents.Add("#ApplicationContainer"); {{if UI == "BlazorServer"}} -- Complete the [MVC Razor Pages Installation](mvc.md#installation) first. _If the solution is tiered/micro-service, complete the MVC steps for all MVC applications such as **HttpApi.Host** and **AuthServer**_. +- Complete the [MVC Razor Pages Installation](AspNetCore.md#installation) first. _If the solution is tiered/micro-service, complete the MVC steps for all MVC applications such as **HttpApi.Host** and **AuthServer**_. - Add **Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme** package to your **Blazor server** application with the following command: diff --git a/docs/en/UI/Blazor/Theming.md b/docs/en/UI/Blazor/Theming.md index c7f92189de..d8cec07cb3 100644 --- a/docs/en/UI/Blazor/Theming.md +++ b/docs/en/UI/Blazor/Theming.md @@ -27,7 +27,7 @@ Currently, three themes are **officially provided**: * The [Basic Theme](Basic-Theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**. * The [Lepton Theme](https://commercial.abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP Commercial](https://commercial.abp.io/) license. -* The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has a [commercial](https://docs.abp.io/en/commercial/latest/themes/lepton-x/commercial/blazor) and a [lite](../../Themes/LeptonXLite/blazor.md) version. +* The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has a [commercial](https://docs.abp.io/en/commercial/latest/themes/lepton-x/commercial/blazor) and a [lite](../../Themes/LeptonXLite/Blazor.md) version. ## Overall diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 7950313217..de33694c1d 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -721,7 +721,7 @@ }, { "text": "LeptonX Lite", - "path": "Themes/LeptonXLite/mvc.md" + "path": "Themes/LeptonXLite/AspNetCore.md" } ] }, @@ -837,7 +837,7 @@ }, { "text": "LeptonX Lite", - "path": "Themes/LeptonXLite/blazor.md" + "path": "Themes/LeptonXLite/Blazor.md" }, { "text": "Branding", @@ -1112,7 +1112,7 @@ }, { "text": "LeptonX Lite", - "path": "Themes/LeptonXLite/angular.md" + "path": "Themes/LeptonXLite/Angular.md" } ] }, diff --git a/docs/zh-Hans/Multi-Tenancy.md b/docs/zh-Hans/Multi-Tenancy.md index 8e92d10673..472dde9eab 100644 --- a/docs/zh-Hans/Multi-Tenancy.md +++ b/docs/zh-Hans/Multi-Tenancy.md @@ -34,6 +34,22 @@ namespace MyCompany.MyProject > 随着"Multi-tenancy ready"的概念,我们打算开发我们的代码和多租户方法兼容.然后它可以被用于多租户和非多租户的程序中,这取决于最终程序的需求. +### AbpMultiTenancyOptions: 处理不活跃或不存在的租户 + +`MultiTenancyMiddlewareErrorPageBuilder` 或 `AbpMultiTenancyOptions` 用于 处理不活跃或不存在的租户. + +默认情况下会响应错误页面, 你可以根据自己的需要更改它, 比如: 只输出错误日志并继续ASP NET Core的请求管道 + +```csharp +Configure(options => +{ + options.MultiTenancyMiddlewareErrorPageBuilder = async (context, exception) => + { + // Handle the exception. + }; +}); +``` + #### 定义实体 你可以在你的实体中实现 **IMultiTenant** 接口来实现多租户,例如: diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs index 4f89ef7b97..d3b66952ef 100644 --- a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs @@ -3,6 +3,8 @@ using System.Globalization; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Volo.Abp.MultiTenancy; namespace Volo.Abp.AspNetCore.MultiTenancy; @@ -21,6 +23,15 @@ public class AbpAspNetCoreMultiTenancyOptions TenantKey = TenantResolverConsts.DefaultTenantKey; MultiTenancyMiddlewareErrorPageBuilder = async (context, exception) => { + // Try to delete the tenant's cookie if it does not exist or is inactive. + var tenantResolveResult = context.RequestServices.GetRequiredService().Result; + if (tenantResolveResult != null && + tenantResolveResult.AppliedResolvers.Contains(CookieTenantResolveContributor.ContributorName)) + { + var options = context.RequestServices.GetRequiredService>().Value; + AbpMultiTenancyCookieHelper.SetTenantCookie(context, null, options.TenantKey); + } + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; ; context.Response.ContentType = "text/html"; @@ -31,8 +42,8 @@ public class AbpAspNetCoreMultiTenancyOptions await context.Response.WriteAsync($"

{message}

{details}
\r\n"); await context.Response.WriteAsync("\r\n"); - // Note the 500 spaces are to work around an IE 'feature' - await context.Response.WriteAsync(new string(' ', 500)); + // Note the 500 spaces are to work around an IE 'feature' + await context.Response.WriteAsync(new string(' ', 500)); }; } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index 4fda075521..7aba9ae849 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -49,6 +49,7 @@ public class AbpCliCoreModule : AbpModule options.Commands[AddPackageCommand.Name] = typeof(AddPackageCommand); options.Commands[AddModuleCommand.Name] = typeof(AddModuleCommand); options.Commands[ListModulesCommand.Name] = typeof(ListModulesCommand); + options.Commands[ListTemplatesCommand.Name] = typeof(ListTemplatesCommand); options.Commands[LoginCommand.Name] = typeof(LoginCommand); options.Commands[LoginInfoCommand.Name] = typeof(LoginInfoCommand); options.Commands[LogoutCommand.Name] = typeof(LogoutCommand); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListTemplatesCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListTemplatesCommand.cs new file mode 100644 index 0000000000..7274fb641d --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListTemplatesCommand.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.Commands.Templates; +using Volo.Abp.Cli.Http; +using Volo.Abp.Cli.ProjectBuilding; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json; +using Volo.Abp.Threading; + +namespace Volo.Abp.Cli.Commands; + +public class ListTemplatesCommand : IConsoleCommand, ITransientDependency +{ + public const string Name = "list-templates"; + + private readonly CliHttpClientFactory _cliHttpClientFactory; + private readonly IJsonSerializer _jsonSerializer; + private readonly ICancellationTokenProvider _cancellationTokenProvider; + private readonly IRemoteServiceExceptionHandler _remoteServiceExceptionHandler; + + public ILogger Logger { get; set; } + + public ListTemplatesCommand( + CliHttpClientFactory cliHttpClientFactory, + IJsonSerializer jsonSerializer, + ICancellationTokenProvider cancellationTokenProvider, + IRemoteServiceExceptionHandler remoteServiceExceptionHandler) + { + _cliHttpClientFactory = cliHttpClientFactory; + _jsonSerializer = jsonSerializer; + _cancellationTokenProvider = cancellationTokenProvider; + _remoteServiceExceptionHandler = remoteServiceExceptionHandler; + + Logger = NullLogger.Instance; + } + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + var templateNameSpaceLength = 26; + var templateDisplayNameSpaceLength = 32; + + var templates = await GetTemplatesAsync(); + + var output = new StringBuilder(Environment.NewLine + Environment.NewLine); + + output.AppendLine( + $"{"Template".PadRight(templateNameSpaceLength)}" + + $"{"Template Name".PadRight(templateDisplayNameSpaceLength)}" + + "Document Url"); + + output.AppendLine( + $"{"--------".PadRight(templateNameSpaceLength)}" + + $"{"-------------".PadRight(templateDisplayNameSpaceLength)}" + + "------------"); + + output.AppendLine(); + + foreach (var template in templates) + { + output.AppendLine( + $"{template.Name?.PadRight(templateNameSpaceLength)}" + + $"{template.DisplayName?.PadRight(templateDisplayNameSpaceLength)}" + + $"{template.DocumentUrl}"); + } + + Logger.LogInformation(output.ToString()); + } + + private async Task> GetTemplatesAsync() + { + var client = _cliHttpClientFactory.CreateClient(); + + using (var responseMessage = await client.GetAsync( + $"{CliUrls.WwwAbpIo}api/download/templates/", + _cancellationTokenProvider.Token + )) + { + await _remoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage); + var result = await responseMessage.Content.ReadAsStringAsync(); + return _jsonSerializer.Deserialize>(result); + } + } + + public string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(" abp list-templates"); + sb.AppendLine(""); + sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); + + return sb.ToString(); + } + + public string GetShortDescription() + { + return "Lists available templates to be created."; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs index fa1408e27b..7090853734 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs @@ -44,7 +44,7 @@ public abstract class ProjectCreationCommandBase AngularPwaSupportAdder angularPwaSupportAdder, InitialMigrationCreator initialMigrationCreator, ThemePackageAdder themePackageAdder, - ILocalEventBus eventBus) + ILocalEventBus eventBus) { ConnectionStringProvider = connectionStringProvider; SolutionPackageVersionFinder = solutionPackageVersionFinder; @@ -149,17 +149,38 @@ public abstract class ProjectCreationCommandBase SolutionName solutionName; if (MicroserviceServiceTemplateBase.IsMicroserviceServiceTemplate(template)) { - var slnFile = Directory.GetFiles(outputFolderRoot, "*.sln").FirstOrDefault(); - if (slnFile == null) + var slnPath = commandLineArgs.Options.GetOrNull(Options.MainSolution.Short, Options.MainSolution.Long); + + if (slnPath == null) + { + slnPath = Directory.GetFiles(outputFolderRoot, "*.sln").FirstOrDefault(); + } + else if (slnPath.EndsWith(".sln")) + { + Directory.SetCurrentDirectory(Path.GetDirectoryName(slnPath)); + outputFolderRoot = Path.GetDirectoryName(slnPath); + } + else if (!Directory.Exists(slnPath)) + { + slnPath = null; + } + else + { + Directory.SetCurrentDirectory(slnPath); + outputFolderRoot = slnPath; + slnPath = Directory.GetFiles(outputFolderRoot, "*.sln").FirstOrDefault(); + } + + if (slnPath == null) { - throw new CliUsageException("This command should be run inside a folder that contains a microservice solution!"); + throw new CliUsageException($"This command should be run inside a folder that contains a microservice solution! Or use -{Options.MainSolution.Short} parameter."); } - var microserviceSolutionName = Path.GetFileName(slnFile).RemovePostFix(".sln"); + var microserviceSolutionName = Path.GetFileName(slnPath).RemovePostFix(".sln"); - version ??= SolutionPackageVersionFinder.Find(slnFile); + version ??= SolutionPackageVersionFinder.Find(slnPath); solutionName = SolutionName.Parse(microserviceSolutionName, projectName); - outputFolder = MicroserviceServiceTemplateBase.CalculateTargetFolder(outputFolderRoot, projectName); + outputFolder = MicroserviceServiceTemplateBase.CalculateTargetFolder(outputFolderRoot, solutionName.ProjectName); uiFramework = uiFramework == UiFramework.NotSpecified ? FindMicroserviceSolutionUiFramework(outputFolderRoot) : uiFramework; } else @@ -206,10 +227,11 @@ public abstract class ProjectCreationCommandBase protected void ExtractProjectZip(ProjectBuildResult project, string outputFolder) { - EventBus.PublishAsync(new ProjectCreationProgressEvent { + EventBus.PublishAsync(new ProjectCreationProgressEvent + { Message = "Extracting the solution archieve" }, false); - + using (var templateFileStream = new MemoryStream(project.ZipContent)) { using (var zipInputStream = new ZipInputStream(templateFileStream)) @@ -345,10 +367,11 @@ public abstract class ProjectCreationCommandBase { if (MicroserviceServiceTemplateBase.IsMicroserviceServiceTemplate(projectArgs.TemplateName)) { - await EventBus.PublishAsync(new ProjectCreationProgressEvent { + await EventBus.PublishAsync(new ProjectCreationProgressEvent + { Message = "Building the microservice solution" }, false); - + CmdHelper.RunCmd("dotnet build /graphbuild", projectArgs.OutputFolder); } } @@ -361,11 +384,12 @@ public abstract class ProjectCreationCommandBase MicroserviceServiceTemplateBase.IsMicroserviceTemplate(projectArgs.TemplateName)) { Logger.LogInformation("Installing client-side packages..."); - - await EventBus.PublishAsync(new ProjectCreationProgressEvent { + + await EventBus.PublishAsync(new ProjectCreationProgressEvent + { Message = "Installing client-side packages" }, false); - + await InstallLibsService.InstallLibsAsync(projectArgs.OutputFolder); } } @@ -402,10 +426,11 @@ public abstract class ProjectCreationCommandBase return; } - await EventBus.PublishAsync(new ProjectCreationProgressEvent { + await EventBus.PublishAsync(new ProjectCreationProgressEvent + { Message = "Creating the initial DB migration" }, false); - + await InitialMigrationCreator.CreateAsync(Path.GetDirectoryName(efCoreProjectPath), isLayeredTemplate); } @@ -417,7 +442,7 @@ public abstract class ProjectCreationCommandBase if (isAngular && isPwa) { Logger.LogInformation("Adding PWA Support to Angular app."); - + AngularPwaSupportAdder.AddPwaSupport(projectArgs.OutputFolder); } } @@ -633,10 +658,16 @@ public abstract class ProjectCreationCommandBase { public const string Short = "pwa"; } - + + public static class MainSolution + { + public const string Short = "ms"; + public const string Long = "main-solution"; + } + public static class Theme { public const string Long = "theme"; } } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Templates/TemplateInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Templates/TemplateInfo.cs new file mode 100644 index 0000000000..c0c269de5b --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Templates/TemplateInfo.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.Cli.Commands.Templates; + +public class TemplateInfo +{ + public string Name { get; set; } + + public string DisplayName { get; set; } + + public string ShortDescription { get; set; } + + public string DocumentUrl { get; set; } + + public bool IsPro { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SolutionName.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SolutionName.cs index 784fca27ad..13f759c851 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SolutionName.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SolutionName.cs @@ -19,6 +19,8 @@ public class SolutionName public static SolutionName Parse(string fullName, string microserviceName) { + microserviceName = microserviceName.Replace(".", string.Empty); + return new SolutionName(fullName + "." + microserviceName, fullName, microserviceName); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularSourceCodeAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularSourceCodeAdder.cs index 9847a2b7a5..84e995f1fd 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularSourceCodeAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularSourceCodeAdder.cs @@ -246,6 +246,17 @@ public class AngularSourceCodeAdder : ITransientDependency foreach (var folder in angularPathsInDownloadedSourceCode) { + var nodeModulesFolder = Path.Combine(folder, "node_modules"); + var vscodeFolder = Path.Combine(folder, ".vscode"); + if (Directory.Exists(nodeModulesFolder)) + { + Directory.Delete(nodeModulesFolder, true); + } + if (Directory.Exists(vscodeFolder)) + { + Directory.Delete(vscodeFolder, true); + } + var projectsInFolder = Directory.GetDirectories(folder); if (projectsInFolder.Length == 1 && Path.GetFileName(projectsInFolder[0]) == "projects") @@ -286,7 +297,7 @@ public class AngularSourceCodeAdder : ITransientDependency Directory.Move(projectInFolder, destDirName); } - if (!Directory.GetFiles(folder).Any() && !Directory.GetDirectories(folder).Any()) + if (!Directory.GetDirectories(folder).Any()) { Directory.Delete(folder, true); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs index 8aa17596a2..e6c21544b3 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs @@ -427,11 +427,6 @@ public class SolutionModuleAdder : ITransientDependency await PublishEventAsync(9, $"Adding angular source code"); - if (newTemplate) - { - MoveAngularFolderInNewTemplate(modulesFolderInSolution, moduleName); - } - await AngularSourceCodeAdder.AddFromModuleAsync(solutionFilePath, angularPath); } @@ -449,30 +444,6 @@ public class SolutionModuleAdder : ITransientDependency } } - private static void MoveAngularFolderInNewTemplate(string modulesFolderInSolution, string moduleName) - { - var moduleAngularFolder = Path.Combine(modulesFolderInSolution, moduleName, "angular"); - - if (!Directory.Exists(moduleAngularFolder)) - { - return; - } - - var files = Directory.GetFiles(moduleAngularFolder); - var folders = Directory.GetDirectories(moduleAngularFolder); - - Directory.CreateDirectory(Path.Combine(moduleAngularFolder, moduleName)); - - foreach (var file in files) - { - File.Move(file, Path.Combine(moduleAngularFolder, moduleName, Path.GetFileName(file))); - } - foreach (var folder in folders) - { - Directory.Move(folder, Path.Combine(moduleAngularFolder, moduleName, Path.GetFileName(folder))); - } - } - private async Task DownloadSourceCodesToSolutionFolder(ModuleWithMastersInfo module, string modulesFolderInSolution, string version = null, bool newTemplate = false, bool newProTemplate = false) diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index 4dba728c51..aa79c81f50 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -162,13 +162,13 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { await PublishAsync( + AbpKafkaEventBusOptions.TopicName, eventType, eventData, new Headers { - { "messageId", System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")) } - }, - null + { "messageId", System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")) } + } ); } @@ -188,42 +188,31 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen new Headers { { "messageId", System.Text.Encoding.UTF8.GetBytes(outgoingEvent.Id.ToString("N")) } - }, - null + } ); } public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { - var producer = ProducerPool.Get(); + var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); var outgoingEventArray = outgoingEvents.ToArray(); - producer.BeginTransaction(); - try + + foreach (var outgoingEvent in outgoingEventArray) { - foreach (var outgoingEvent in outgoingEventArray) + var messageId = outgoingEvent.Id.ToString("N"); + var headers = new Headers { - var messageId = outgoingEvent.Id.ToString("N"); - var headers = new Headers - { - { "messageId", System.Text.Encoding.UTF8.GetBytes(messageId)} - }; - - producer.Produce( - AbpKafkaEventBusOptions.TopicName, - new Message - { - Key = outgoingEvent.EventName, - Value = outgoingEvent.EventData, - Headers = headers - }); - } + { "messageId", System.Text.Encoding.UTF8.GetBytes(messageId)} + }; - producer.CommitTransaction(); - } - catch (Exception e) - { - producer.AbortTransaction(); - throw; + producer.Produce( + AbpKafkaEventBusOptions.TopicName, + new Message + { + Key = outgoingEvent.EventName, + Value = outgoingEvent.EventData, + Headers = headers + }); } return Task.CompletedTask; @@ -253,47 +242,22 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen return Serializer.Serialize(eventData); } - public virtual async Task PublishAsync(Type eventType, object eventData, Headers headers, Dictionary headersArguments) - { - await PublishAsync( - AbpKafkaEventBusOptions.TopicName, - eventType, - eventData, - headers, - headersArguments - ); - } - - private Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers, Dictionary headersArguments) + private Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers) { var eventName = EventNameAttribute.GetNameOrDefault(eventType); var body = Serializer.Serialize(eventData); - return PublishAsync(topicName, eventName, body, headers, headersArguments); + return PublishAsync(topicName, eventName, body, headers); } private Task> PublishAsync( string topicName, string eventName, byte[] body, - Headers headers, - Dictionary headersArguments) + Headers headers) { var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); - return PublishAsync(producer, topicName, eventName, body, headers, headersArguments); - } - - private Task> PublishAsync( - IProducer producer, - string topicName, - string eventName, - byte[] body, - Headers headers, - Dictionary headersArguments) - { - SetEventMessageHeaders(headers, headersArguments); - return producer.ProduceAsync( topicName, new Message @@ -304,20 +268,6 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen }); } - private void SetEventMessageHeaders(Headers headers, Dictionary headersArguments) - { - if (headersArguments == null) - { - return; - } - - foreach (var header in headersArguments) - { - headers.Remove(header.Key); - headers.Add(header.Key, Serializer.Serialize(header.Value)); - } - } - private List GetOrCreateHandlerFactories(Type eventType) { return HandlerFactories.GetOrAdd( diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs index 0a31a3483a..691a29c1d2 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs @@ -41,16 +41,8 @@ public class ProducerPool : IProducerPool, ISingletonDependency { var producerConfig = new ProducerConfig(Options.Connections.GetOrDefault(connection)); Options.ConfigureProducer?.Invoke(producerConfig); - - if (producerConfig.TransactionalId.IsNullOrWhiteSpace()) - { - producerConfig.TransactionalId = Guid.NewGuid().ToString(); - } - - var producer = new ProducerBuilder(producerConfig).Build(); - producer.InitTransactions(DefaultTransactionsWaitDuration); + return new ProducerBuilder(producerConfig).Build(); - return producer; })).Value; } diff --git a/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js b/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js index 249f75ebb0..c143b7e75e 100644 --- a/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js +++ b/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js @@ -1,24 +1,23 @@ var abp = abp || {}; -(function () { +(function() { - abp.SwaggerUIBundle = function (configObject) { + abp.SwaggerUIBundle = function(configObject) { var excludeUrl = ["swagger.json", "connect/token"] var firstRequest = true; abp.appPath = configObject.baseUrl || abp.appPath; var requestInterceptor = configObject.requestInterceptor; - - configObject.requestInterceptor = async function (request) { - if(request.url.includes(excludeUrl[1])){ + configObject.requestInterceptor = async function(request) { + + if (request.url.includes(excludeUrl[1])) { firstRequest = true; } - if(firstRequest && !excludeUrl.some(url => request.url.includes(url))) - { - await fetch(`${abp.appPath}abp/Swashbuckle/SetCsrfCookie`,{ + if (firstRequest && !excludeUrl.some(url => request.url.includes(url))) { + await fetch(`${abp.appPath}abp/Swashbuckle/SetCsrfCookie`, { headers: request.headers }); firstRequest = false; @@ -28,8 +27,12 @@ var abp = abp || {}; if (antiForgeryToken) { request.headers[abp.security.antiForgery.tokenHeaderName] = antiForgeryToken; } - - if(requestInterceptor){ + + if (!request.headers["X-Requested-With"]) { + request.headers["X-Requested-With"] = "XMLHttpRequest"; + } + + if (requestInterceptor) { requestInterceptor(request); } return request; diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Microsoft/AspNetCore/Extensions/DependencyInjection/AbpAspNetCoreServiceCollectionExtensions.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Microsoft/AspNetCore/Extensions/DependencyInjection/AbpAspNetCoreServiceCollectionExtensions.cs new file mode 100644 index 0000000000..8d26a17835 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Microsoft/AspNetCore/Extensions/DependencyInjection/AbpAspNetCoreServiceCollectionExtensions.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Extensions.DependencyInjection; + +public static class AbpAspNetCoreServiceCollectionExtensions +{ + public static IServiceCollection ForwardIdentityAuthenticationForBearer(this IServiceCollection services, string jwtBearerScheme = "Bearer") + { + services.ConfigureApplicationCookie(options => + { + options.ForwardDefaultSelector = ctx => + { + string authorization = ctx.Request.Headers.Authorization; + if (!authorization.IsNullOrWhiteSpace() && authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + { + return jwtBearerScheme; + } + + return null; + }; + }); + + return services; + } +} diff --git a/npm/ng-packs/packages/core/src/lib/core.module.ts b/npm/ng-packs/packages/core/src/lib/core.module.ts index 58c128ac32..bcc071185f 100644 --- a/npm/ng-packs/packages/core/src/lib/core.module.ts +++ b/npm/ng-packs/packages/core/src/lib/core.module.ts @@ -136,6 +136,7 @@ export class CoreModule { return { ngModule: RootCoreModule, providers: [ + OAuthModule.forRoot().providers, LocaleProvider, CookieLanguageProvider, { @@ -190,7 +191,6 @@ export class CoreModule { useValue: localizationContributor(options.localizations), deps: [LocalizationService], }, - OAuthModule.forRoot().providers, ], }; } diff --git a/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts index 2054a0d529..824b013843 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts @@ -18,6 +18,8 @@ import { PermissionService } from '../services/permission.service'; export class PermissionDirective implements OnDestroy, OnChanges { @Input('abpPermission') condition: string | undefined; + @Input('abpPermissionRunChangeDetection') runChangeDetection = true; + subscription!: Subscription; constructor( @@ -38,7 +40,11 @@ export class PermissionDirective implements OnDestroy, OnChanges { .subscribe(isGranted => { this.vcRef.clear(); if (isGranted) this.vcRef.createEmbeddedView(this.templateRef); - this.cdRef.detectChanges(); + if (this.runChangeDetection) { + this.cdRef.detectChanges(); + } else { + this.cdRef.markForCheck(); + } }); } diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts index d465a60fa3..37d792787c 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts @@ -2,10 +2,12 @@ import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/je import { Subject } from 'rxjs'; import { PermissionDirective } from '../directives/permission.directive'; import { PermissionService } from '../services'; +import { ChangeDetectorRef } from '@angular/core'; describe('PermissionDirective', () => { let spectator: SpectatorDirective; let directive: PermissionDirective; + let cdr: ChangeDetectorRef; const grantedPolicy$ = new Subject(); const createDirective = createDirectiveFactory({ directive: PermissionDirective, @@ -30,7 +32,7 @@ describe('PermissionDirective', () => { grantedPolicy$.next(true); expect(spectator.query('#test-element')).toBeTruthy(); grantedPolicy$.next(false); - // expect(spectator.query('#test-element')).toBeFalsy(); // TODO: change detection problem should be fixed + expect(spectator.query('#test-element')).toBeFalsy(); }); }); @@ -41,6 +43,7 @@ describe('PermissionDirective', () => { { hostProps: { condition: '' } }, ); directive = spectator.directive; + cdr = (directive as any).cdRef as ChangeDetectorRef; }); it('should be created', () => { @@ -55,8 +58,22 @@ describe('PermissionDirective', () => { grantedPolicy$.next(false); expect(spectator.query('#test-element')).toBeFalsy(); grantedPolicy$.next(true); + expect(spectator.queryAll('#test-element')).toHaveLength(1); + }); + + it('should call detect changes method', () => { + const detectChanges = jest.spyOn(cdr, 'detectChanges'); + expect(spectator.query('#test-element')).toBeFalsy(); + spectator.setHostInput({ condition: 'test' }); + grantedPolicy$.next(true); + expect(spectator.query('#test-element')).toBeTruthy(); + expect(detectChanges).toHaveBeenCalled(); + grantedPolicy$.next(false); + expect(spectator.query('#test-element')).toBeFalsy(); + expect(detectChanges).toHaveBeenCalled(); grantedPolicy$.next(true); expect(spectator.queryAll('#test-element')).toHaveLength(1); + expect(detectChanges).toHaveBeenCalled(); }); describe('#subscription', () => { @@ -70,4 +87,35 @@ describe('PermissionDirective', () => { }); }); }); + describe('with runChangeDetection Input', () => { + beforeEach(() => { + spectator = createDirective( + '
Testing Permission Directive
', + { hostProps: { condition: '' } }, + ); + directive = spectator.directive; + cdr = (directive as any).cdRef as ChangeDetectorRef; + }); + it('should not call detectChanges method', () => { + const detectChanges = jest.spyOn(cdr, 'detectChanges'); + const markForCheck = jest.spyOn(cdr, 'markForCheck'); + expect(spectator.query('#test-element')).toBeFalsy(); + spectator.setHostInput({ condition: 'test' }); + + grantedPolicy$.next(true); + expect(spectator.query('#test-element')).toBeTruthy(); + expect(detectChanges).not.toHaveBeenCalled(); + expect(markForCheck).toHaveBeenCalled(); + + grantedPolicy$.next(false); + expect(spectator.query('#test-element')).toBeFalsy(); + expect(detectChanges).not.toHaveBeenCalled(); + expect(markForCheck).toHaveBeenCalled(); + + grantedPolicy$.next(true); + expect(spectator.queryAll('#test-element')).toHaveLength(1); + expect(detectChanges).not.toHaveBeenCalled(); + expect(markForCheck).toHaveBeenCalled(); + }); + }); }); diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html index 24fd4e0f54..4367bd098a 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html @@ -1,4 +1,8 @@ -
+
- +
diff --git a/npm/ng-packs/packages/theme-shared/src/lib/constants/validation.ts b/npm/ng-packs/packages/theme-shared/src/lib/constants/validation.ts index 463a7b01dc..65b759a21e 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/constants/validation.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/constants/validation.ts @@ -13,4 +13,8 @@ export const DEFAULT_VALIDATION_BLUEPRINTS = { range: 'AbpValidation::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]', required: 'AbpValidation::ThisFieldIsRequired.', url: 'AbpValidation::ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl', + passwordRequiresLower: 'AbpIdentity::Volo.Abp.Identity:PasswordRequiresLower', + passwordRequiresUpper: 'AbpIdentity::Volo.Abp.Identity:PasswordRequiresUpper', + passwordRequiresDigit: 'AbpIdentity::Volo.Abp.Identity:PasswordRequiresDigit', + passwordRequiresNonAlphanumeric: 'AbpIdentity::Volo.Abp.Identity:PasswordRequiresNonAlphanumeric', }; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/models/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/models/index.ts index ca6aa74124..4cb240e9ab 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/models/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/models/index.ts @@ -4,3 +4,4 @@ export * from './nav-item'; export * from './statistics'; export * from './toaster'; export * from './user-menu'; +export * from './validation'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/models/validation.ts b/npm/ng-packs/packages/theme-shared/src/lib/models/validation.ts new file mode 100644 index 0000000000..bcc53a34e7 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/models/validation.ts @@ -0,0 +1 @@ +export type PasswordRule = 'small' | 'capital' | 'number' | 'special'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts index fe7577477c..435afb76e6 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts @@ -4,10 +4,11 @@ import { HttpClient } from '@angular/common/http'; import { Component, Injector } from '@angular/core'; import { Validators } from '@angular/forms'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { validatePassword } from '@ngx-validate/core'; import { OAuthService } from 'angular-oauth2-oidc'; import { of } from 'rxjs'; -import { getPasswordValidators } from '../utils'; +import { getPasswordValidators, validatePassword } from '../utils'; +import { PasswordRule } from '../models/validation'; + @Component({ template: '', selector: 'abp-dummy' }) class DummyComponent {} @@ -47,8 +48,11 @@ describe('ValidationUtils', () => { configState.refreshAppState(); const validators = getPasswordValidators(spectator.inject(Injector)); + const passwordValidators = ['number', 'small', 'capital', 'special'].map( + (rule: PasswordRule) => validatePassword(rule), + ); const expectedValidators = [ - validatePassword(['number', 'small', 'capital', 'special']), + ...passwordValidators, Validators.minLength(6), Validators.maxLength(128), ]; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts b/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts index a9ec1d4270..cb201d224f 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts @@ -1,7 +1,8 @@ import { ABP, ConfigStateService } from '@abp/ng.core'; import { Injector } from '@angular/core'; -import { ValidatorFn, Validators } from '@angular/forms'; -import { PasswordRules, validatePassword } from '@ngx-validate/core'; +import { AbstractControl, ValidatorFn, Validators } from '@angular/forms'; +import { normalizeDiacritics, PasswordRules } from '@ngx-validate/core'; +import { PasswordRule } from "../models/validation"; const { minLength, maxLength } = Validators; @@ -31,7 +32,8 @@ export function getPasswordValidators(injector: Injector): ValidatorFn[] { requiredLength = +getRule('RequiredLength'); } - return [validatePassword(passwordRulesArr), minLength(requiredLength), maxLength(128)]; + const passwordValidators = passwordRulesArr.map(rule => validatePassword(rule)); + return [...passwordValidators, minLength(requiredLength), maxLength(128)]; } function getRuleFn(injector: Injector) { @@ -43,3 +45,39 @@ function getRuleFn(injector: Injector) { return (passwordRules[`Abp.Identity.Password.${key}`] || '').toLowerCase(); }; } +const errorMessageMap = { + small: 'passwordRequiresLower', + capital: 'passwordRequiresUpper', + number: 'passwordRequiresDigit', + special: 'passwordRequiresNonAlphanumeric', +}; + +export function validatePassword( + shouldContain: PasswordRule +): ValidatorFn { + return (control: AbstractControl) => { + if (!control.value) return null; + + const value = normalizeDiacritics(control.value); + + const regexMap = { + small: /.*[a-z].*/, + capital: /.*[A-Z].*/, + number: /.*[0-9].*/, + special: /.*[^0-9a-zA-Z].*/, + }; + const regex = regexMap[shouldContain]; + + const isValid = regex.test(value); + + if (isValid) { + return null; + } + + const error = errorMessageMap[shouldContain]; + + return { + [error]: true, + }; + }; +} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs index 8edcb2b6c6..0e5d86e4c7 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs @@ -1,5 +1,6 @@ using Blazorise.Bootstrap5; using Blazorise.Icons.FontAwesome; +using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.OpenApi.Models; using MyCompanyName.MyProjectName.Data; @@ -137,7 +138,7 @@ public class MyProjectNameModule : AbpModule { context.Services.Replace(ServiceDescriptor.Singleton()); } - + ConfigureUrls(configuration); ConfigureBundles(); ConfigureAutoMapper(context); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs index 72203cef02..b9d407572b 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs @@ -1,5 +1,6 @@ using Blazorise.Bootstrap5; using Blazorise.Icons.FontAwesome; +using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.OpenApi.Models; using MyCompanyName.MyProjectName.Data; @@ -133,7 +134,7 @@ public class MyProjectNameModule : AbpModule { var hostingEnvironment = context.Services.GetHostingEnvironment(); var configuration = context.Services.GetConfiguration(); - + if (hostingEnvironment.IsDevelopment()) { context.Services.Replace(ServiceDescriptor.Singleton()); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs index 62c14d681a..b9e6fe7590 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.OpenApi.Models; using MyCompanyName.MyProjectName.Data; @@ -120,7 +121,7 @@ public class MyProjectNameModule : AbpModule { var hostingEnvironment = context.Services.GetHostingEnvironment(); var configuration = context.Services.GetConfiguration(); - + if (hostingEnvironment.IsDevelopment()) { context.Services.Replace(ServiceDescriptor.Singleton()); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs index 4db85902c2..043d86a923 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.OpenApi.Models; using MyCompanyName.MyProjectName.Data; @@ -122,7 +123,7 @@ public class MyProjectNameModule : AbpModule { var hostingEnvironment = context.Services.GetHostingEnvironment(); var configuration = context.Services.GetConfiguration(); - + if (hostingEnvironment.IsDevelopment()) { context.Services.Replace(ServiceDescriptor.Singleton()); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs index 389597c523..5344e574bf 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.AspNetCore.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.OpenApi.Models; using MyCompanyName.MyProjectName.Data; using MyCompanyName.MyProjectName.Localization; @@ -126,7 +127,7 @@ public class MyProjectNameModule : AbpModule { var hostingEnvironment = context.Services.GetHostingEnvironment(); var configuration = context.Services.GetConfiguration(); - + if (hostingEnvironment.IsDevelopment()) { context.Services.Replace(ServiceDescriptor.Singleton()); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs index 976f91bf46..7fede74c7a 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.AspNetCore.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.OpenApi.Models; using MyCompanyName.MyProjectName.Data; using MyCompanyName.MyProjectName.Localization; @@ -127,7 +128,7 @@ public class MyProjectNameModule : AbpModule { var hostingEnvironment = context.Services.GetHostingEnvironment(); var configuration = context.Services.GetConfiguration(); - + if (hostingEnvironment.IsDevelopment()) { context.Services.Replace(ServiceDescriptor.Singleton()); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs index a3d2cf8057..2e251e86e9 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs @@ -2,6 +2,7 @@ using System.IO; using Blazorise.Bootstrap5; using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs index b2e4aab2da..a981a370a8 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs index 397f0a5db6..fb5079b2e9 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs @@ -1,6 +1,7 @@ using System; using System.IO; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs index 16d3ec947f..1c4acef254 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Blazorise.Bootstrap5; using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting;