From 2ab09db5f72a6bbe48fd786390dab7de1bdb2dda Mon Sep 17 00:00:00 2001 From: iJungleboy Date: Tue, 21 Jan 2025 18:33:31 +0100 Subject: [PATCH 1/4] minor docs --- Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModel.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModel.cs b/Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModel.cs index 6f75402c4..a53d4fa99 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModel.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModel.cs @@ -22,6 +22,7 @@ namespace ToSic.Sxc.Data.Model; /// { /// class MyPerson : DataModel /// { +/// public int Id => _entity.EntityId; /// public string Name => _entity.Get<string> ("Name"); /// } /// } @@ -57,14 +58,6 @@ void ICanWrap.Setup(IEntity baseItem, IModelFactory modelFactory) } private IModelFactory _modelFactory; - ///// - ///// The actual item which is being wrapped, in rare cases where you must access it from outside. - ///// - ///// It's only on the explicit interface, so it is not available from outside or inside, unless you cast to it. - ///// Goal is that inheriting classes don't access it to keep API surface small. - ///// - //ITypedItem ICanBeItem.Item => Item; - /// /// This is necessary so the object can be used in places where an IEntity is expected, /// like toolbars. From 865aa734fe0324610c10f441e8a831c1bcb8f693 Mon Sep 17 00:00:00 2001 From: iJungleboy Date: Tue, 21 Jan 2025 20:44:01 +0100 Subject: [PATCH 2/4] finalize naming of model base classes and attributes --- .../Custom/Hybrid/RazorTyped.cs | 1 - .../Custom/Hybrid/ApiTyped.cs | 1 - .../Custom/Hybrid/ApiTyped.cs | 1 - .../Custom.Hybrid/RazorTyped_TModel.cs | 1 - .../ModelTests/DataModelAnalyzerTests.cs | 8 +-- .../ModelTests/DataModelAnalyzerTypeTests.cs | 10 ++-- Src/Sxc/ToSic.Sxc/Apps/IAppDataTyped.cs | 1 - .../Apps/IAppTyped_TSettings_TResources.cs | 1 - .../Internal/AppTyped_TSettings_TResources.cs | 1 - Src/Sxc/ToSic.Sxc/Cms/Assets/IFileModel.cs | 6 +-- Src/Sxc/ToSic.Sxc/Cms/Assets/IFolderModel.cs | 6 +-- .../Cms/Assets/Internal/FileModelOfEntity.cs | 4 +- .../Assets/Internal/FolderModelOfEntity.cs | 4 +- Src/Sxc/ToSic.Sxc/Cms/Pages/IPageModel.cs | 6 +-- .../Cms/Pages/Internal/PageModelOfEntity.cs | 4 +- Src/Sxc/ToSic.Sxc/Cms/Sites/ISiteModel.cs | 6 +-- .../Cms/Sites/Internal/SiteModelOfEntity.cs | 4 +- Src/Sxc/ToSic.Sxc/Cms/Users/IUserModel.cs | 6 +-- Src/Sxc/ToSic.Sxc/Cms/Users/IUserRoleModel.cs | 6 +-- .../Cms/Users/Internal/UserModelOfEntity.cs | 4 +- .../Users/Internal/UserRoleModelOfEntity.cs | 4 +- .../ToSic.Sxc/Code/Customizer/Customizer.cs | 1 - .../Code/Customizer/ICodeCustomizer.cs | 1 - .../ToSic.Sxc/Code/Internal/IDynamicCode16.cs | 1 - .../ICmsView_TSettings_TResources.cs | 1 - .../Internal/CmsView_TSettings_TResources.cs | 1 - Src/Sxc/ToSic.Sxc/Custom.Data/CustomItem.cs | 8 +-- Src/Sxc/ToSic.Sxc/Custom.Hybrid/CodeTyped.cs | 1 - ...{ICanWrap.TData.cs => ICanWrap.TSource.cs} | 8 +-- .../Internal/Factory/ClassAttributeLookup.cs | 1 - .../Factory/CodeDataFactory_AsCustom.cs | 4 +- .../Internal/Factory/CodeDataFactory_Stack.cs | 1 - .../Internal/Factory/DataModelAnalyzer.cs | 53 +++++++------------ .../Data/Internal/Factory/IModelFactory.cs | 4 +- .../Data/Model/DataModelAttribute.cs | 38 ------------- .../Data/Model/DataModelConversion.cs | 25 --------- .../Attributes/ModelCreationAttribute.cs | 27 ++++++++++ .../Models/Attributes/ModelSourceAttribute.cs | 45 ++++++++++++++++ .../Internal}/DataModelHelpers.cs | 2 +- .../ModelFromEntity.cs} | 16 +++--- .../ModelFromEntity_Equatable.cs} | 14 ++--- .../ModelFromItem.cs} | 23 ++++---- .../ModelFromItem_Equatable.cs} | 20 +++---- .../ITypedItem_Relationships.cs | 1 - .../ConvertService/IConvertService16.cs | 1 - Src/Sxc/ToSic.Sxc/ToSic.Sxc.csproj | 1 + .../ToSic.Sxc/ToSic.Sxc.csproj.DotSettings | 3 ++ 47 files changed, 176 insertions(+), 210 deletions(-) rename Src/Sxc/ToSic.Sxc/Data/DataWrapper/{ICanWrap.TData.cs => ICanWrap.TSource.cs} (85%) delete mode 100644 Src/Sxc/ToSic.Sxc/Data/Model/DataModelAttribute.cs delete mode 100644 Src/Sxc/ToSic.Sxc/Data/Model/DataModelConversion.cs create mode 100644 Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelCreationAttribute.cs create mode 100644 Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelSourceAttribute.cs rename Src/Sxc/ToSic.Sxc/Data/{Model/Bases => Models/Internal}/DataModelHelpers.cs (98%) rename Src/Sxc/ToSic.Sxc/Data/{Model/Bases/DataModel.cs => Models/ModelFromEntity.cs} (83%) rename Src/Sxc/ToSic.Sxc/Data/{Model/Bases/DataModel_Equatable.cs => Models/ModelFromEntity_Equatable.cs} (76%) rename Src/Sxc/ToSic.Sxc/Data/{Model/Bases/DataModelOfItem.cs => Models/ModelFromItem.cs} (82%) rename Src/Sxc/ToSic.Sxc/Data/{Model/Bases/DataModelOfItem_Equatable.cs => Models/ModelFromItem_Equatable.cs} (66%) diff --git a/Src/Dnn/ToSic.Sxc.Dnn.Razor/Custom/Hybrid/RazorTyped.cs b/Src/Dnn/ToSic.Sxc.Dnn.Razor/Custom/Hybrid/RazorTyped.cs index 9a332c3a8..f31ccf146 100644 --- a/Src/Dnn/ToSic.Sxc.Dnn.Razor/Custom/Hybrid/RazorTyped.cs +++ b/Src/Dnn/ToSic.Sxc.Dnn.Razor/Custom/Hybrid/RazorTyped.cs @@ -4,7 +4,6 @@ using ToSic.Sxc.Code.Internal.CodeErrorHelp; using ToSic.Sxc.Code.Internal.CodeRunHelpers; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; using ToSic.Sxc.Dnn.Razor; using ToSic.Sxc.Dnn.Razor.Internal; using ToSic.Sxc.Engines; diff --git a/Src/Dnn/ToSic.Sxc.Dnn.WebApi/Custom/Hybrid/ApiTyped.cs b/Src/Dnn/ToSic.Sxc.Dnn.WebApi/Custom/Hybrid/ApiTyped.cs index 96dc6eaa9..34747a73e 100644 --- a/Src/Dnn/ToSic.Sxc.Dnn.WebApi/Custom/Hybrid/ApiTyped.cs +++ b/Src/Dnn/ToSic.Sxc.Dnn.WebApi/Custom/Hybrid/ApiTyped.cs @@ -13,7 +13,6 @@ using ToSic.Sxc.Adam; using ToSic.Sxc.Code.Internal; using ToSic.Sxc.Code.Internal.CodeRunHelpers; -using ToSic.Sxc.Data.Model; using ToSic.Sxc.Dnn.WebApi.Internal.Compatibility; using ToSic.Sxc.Internal; diff --git a/Src/Oqtane/ToSic.Sxc.Oqt.Server/Custom/Hybrid/ApiTyped.cs b/Src/Oqtane/ToSic.Sxc.Oqt.Server/Custom/Hybrid/ApiTyped.cs index 707ca2d07..f8a7ba6e7 100644 --- a/Src/Oqtane/ToSic.Sxc.Oqt.Server/Custom/Hybrid/ApiTyped.cs +++ b/Src/Oqtane/ToSic.Sxc.Oqt.Server/Custom/Hybrid/ApiTyped.cs @@ -18,7 +18,6 @@ using ToSic.Lib.Coding; using ToSic.Sxc.Code.Internal; using ToSic.Sxc.Code.Internal.CodeRunHelpers; -using ToSic.Sxc.Data.Model; using ToSic.Sxc.Internal; // ReSharper disable once CheckNamespace diff --git a/Src/Razor/ToSic.Sxc.Razor/Custom.Hybrid/RazorTyped_TModel.cs b/Src/Razor/ToSic.Sxc.Razor/Custom.Hybrid/RazorTyped_TModel.cs index fc7d5c2d4..acc4ff981 100644 --- a/Src/Razor/ToSic.Sxc.Razor/Custom.Hybrid/RazorTyped_TModel.cs +++ b/Src/Razor/ToSic.Sxc.Razor/Custom.Hybrid/RazorTyped_TModel.cs @@ -17,7 +17,6 @@ using ToSic.Sxc.Code.Internal.CodeRunHelpers; using ToSic.Sxc.Context; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; using ToSic.Sxc.Engines; using ToSic.Sxc.Internal; using ToSic.Sxc.Razor.Internal; diff --git a/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTests.cs b/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTests.cs index b963a2c5f..2e202657d 100644 --- a/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTests.cs +++ b/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTests.cs @@ -1,6 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Tests.DataTests.ModelTests; @@ -38,7 +38,7 @@ public void INotDecoratedStream() => private const string ForContentType1 = "Abc"; private const string StreamName1 = "AbcStream"; - [DataModel(ForContentTypes = ForContentType1, StreamNames = StreamName1)] + [ModelSource(ContentTypes = ForContentType1, Streams = StreamName1)] class Decorated: ICanWrapData; [TestMethod] @@ -63,7 +63,7 @@ public void InheritDecoratedStream() => private const string ForContentTypeReDecorated = "ReDec"; private const string StreamNameReDecorated = "ReDecStream"; - [DataModel(ForContentTypes = ForContentTypeReDecorated, StreamNames = StreamNameReDecorated + ",Abc")] + [ModelSource(ContentTypes = ForContentTypeReDecorated, Streams = StreamNameReDecorated + ",Abc")] class InheritReDecorated : InheritDecorated; [TestMethod] @@ -76,7 +76,7 @@ public void InheritReDecoratedStream() => private const string ForContentTypeIDecorated = "IDec"; private const string StreamNameIDecorated= "IRedecStream"; - [DataModel(ForContentTypes = ForContentTypeIDecorated, StreamNames = StreamNameIDecorated)] + [ModelSource(ContentTypes = ForContentTypeIDecorated, Streams = StreamNameIDecorated)] interface IDecorated: ICanWrapData; [TestMethod] diff --git a/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTypeTests.cs b/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTypeTests.cs index 5720b9371..9b30e2b56 100644 --- a/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTypeTests.cs +++ b/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTypeTests.cs @@ -2,7 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ToSic.Eav.Data; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Tests.DataTests.ModelTests; @@ -36,8 +36,8 @@ public void INotDecoratedType() => #region Decorated - should return the decorated type - [DataModelConversion(Map = [typeof(DataModelFrom)])] - class Decorated: DataModel; + [ModelCreation(Use = typeof(DecoratedEntity))] + class Decorated: ModelFromEntity; class DecoratedEntity : Decorated; @@ -60,7 +60,7 @@ public void InheritDecoratedType() => #region Inherit and redecorate, should return the newly decorated type - [DataModelConversion(Map = [typeof(DataModelFrom)])] + [ModelCreation(Use = typeof(InheritReDecoratedEntity))] class InheritReDecorated : InheritDecorated; class InheritReDecoratedEntity : InheritReDecorated; @@ -73,7 +73,7 @@ public void InheritReDecoratedType() => #region Interface decorated - should return the decorated type - [DataModelConversion(Map = [typeof(DataModelFrom)])] + [ModelCreation(Use = typeof(EntityOfIDecorated))] interface IDecorated : ICanWrapData; class EntityOfIDecorated : InheritReDecorated, IDecorated; diff --git a/Src/Sxc/ToSic.Sxc/Apps/IAppDataTyped.cs b/Src/Sxc/ToSic.Sxc/Apps/IAppDataTyped.cs index 57660d440..c929071af 100644 --- a/Src/Sxc/ToSic.Sxc/Apps/IAppDataTyped.cs +++ b/Src/Sxc/ToSic.Sxc/Apps/IAppDataTyped.cs @@ -1,7 +1,6 @@ using ToSic.Eav.DataSource; using ToSic.Eav.Metadata; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; namespace ToSic.Sxc.Apps; diff --git a/Src/Sxc/ToSic.Sxc/Apps/IAppTyped_TSettings_TResources.cs b/Src/Sxc/ToSic.Sxc/Apps/IAppTyped_TSettings_TResources.cs index 661379469..0f31cc14a 100644 --- a/Src/Sxc/ToSic.Sxc/Apps/IAppTyped_TSettings_TResources.cs +++ b/Src/Sxc/ToSic.Sxc/Apps/IAppTyped_TSettings_TResources.cs @@ -1,6 +1,5 @@ using ToSic.Eav.Apps; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; namespace ToSic.Sxc.Apps; diff --git a/Src/Sxc/ToSic.Sxc/Apps/Internal/AppTyped_TSettings_TResources.cs b/Src/Sxc/ToSic.Sxc/Apps/Internal/AppTyped_TSettings_TResources.cs index 0a390d01b..1815c0b03 100644 --- a/Src/Sxc/ToSic.Sxc/Apps/Internal/AppTyped_TSettings_TResources.cs +++ b/Src/Sxc/ToSic.Sxc/Apps/Internal/AppTyped_TSettings_TResources.cs @@ -2,7 +2,6 @@ using ToSic.Eav.Internal.Environment; using ToSic.Lib.DI; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; namespace ToSic.Sxc.Apps.Internal; diff --git a/Src/Sxc/ToSic.Sxc/Cms/Assets/IFileModel.cs b/Src/Sxc/ToSic.Sxc/Cms/Assets/IFileModel.cs index c4e304217..f8e4254e5 100644 --- a/Src/Sxc/ToSic.Sxc/Cms/Assets/IFileModel.cs +++ b/Src/Sxc/ToSic.Sxc/Cms/Assets/IFileModel.cs @@ -1,7 +1,7 @@ using ToSic.Eav.Apps.Assets; using ToSic.Sxc.Cms.Assets.Internal; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Cms.Assets; @@ -16,9 +16,7 @@ namespace ToSic.Sxc.Cms.Assets; /// * This is similar to the but still a bit different. /// For example, it has a property which is different from the property. /// -[DataModelConversion(Map = [ - typeof(DataModelFrom), -])] +[ModelCreation(Use = typeof(FileModelOfEntity))] [InternalApi_DoNotUse_MayChangeWithoutNotice("Still tweaking details and naming v19.0x")] public interface IFileModel: ICanWrapData { diff --git a/Src/Sxc/ToSic.Sxc/Cms/Assets/IFolderModel.cs b/Src/Sxc/ToSic.Sxc/Cms/Assets/IFolderModel.cs index c8426b086..d67520a88 100644 --- a/Src/Sxc/ToSic.Sxc/Cms/Assets/IFolderModel.cs +++ b/Src/Sxc/ToSic.Sxc/Cms/Assets/IFolderModel.cs @@ -1,7 +1,7 @@ using ToSic.Sxc.Adam; using ToSic.Sxc.Cms.Assets.Internal; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Cms.Assets; @@ -15,9 +15,7 @@ namespace ToSic.Sxc.Cms.Assets; /// * Not to be seen as final, since we may rename this type when we also /// * This is similar to the but still a bit different. For example, it has a property. /// -[DataModelConversion(Map = [ - typeof(DataModelFrom), -])] +[ModelCreation(Use = typeof(FolderModelOfEntity))] [InternalApi_DoNotUse_MayChangeWithoutNotice("Still tweaking details and naming v19.0x")] public interface IFolderModel: ICanWrapData { diff --git a/Src/Sxc/ToSic.Sxc/Cms/Assets/Internal/FileModelOfEntity.cs b/Src/Sxc/ToSic.Sxc/Cms/Assets/Internal/FileModelOfEntity.cs index 2cc9b59fb..ed068f61f 100644 --- a/Src/Sxc/ToSic.Sxc/Cms/Assets/Internal/FileModelOfEntity.cs +++ b/Src/Sxc/ToSic.Sxc/Cms/Assets/Internal/FileModelOfEntity.cs @@ -1,11 +1,11 @@ using ToSic.Eav.Apps.Assets; using ToSic.Eav.Apps.Assets.Internal; -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Cms.Assets.Internal; [PrivateApi("Still tweaking details and naming v19.0x")] -internal class FileModelOfEntity: DataModel, IFileModelSync, IFileModel +internal class FileModelOfEntity: ModelFromEntity, IFileModelSync, IFileModel { ///// ///// The ID of this asset (file/folder). diff --git a/Src/Sxc/ToSic.Sxc/Cms/Assets/Internal/FolderModelOfEntity.cs b/Src/Sxc/ToSic.Sxc/Cms/Assets/Internal/FolderModelOfEntity.cs index d866479ec..f2c734108 100644 --- a/Src/Sxc/ToSic.Sxc/Cms/Assets/Internal/FolderModelOfEntity.cs +++ b/Src/Sxc/ToSic.Sxc/Cms/Assets/Internal/FolderModelOfEntity.cs @@ -1,9 +1,9 @@ -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Cms.Assets.Internal; [PrivateApi("Still tweaking details and naming v19.0x")] -internal class FolderModelOfEntity: DataModel, IFolderModelSync, IFolderModel +internal class FolderModelOfEntity: ModelFromEntity, IFolderModelSync, IFolderModel { ///// //public int Id => ((ITypedItem)this).Id; diff --git a/Src/Sxc/ToSic.Sxc/Cms/Pages/IPageModel.cs b/Src/Sxc/ToSic.Sxc/Cms/Pages/IPageModel.cs index 381672fcc..0fe5d3c45 100644 --- a/Src/Sxc/ToSic.Sxc/Cms/Pages/IPageModel.cs +++ b/Src/Sxc/ToSic.Sxc/Cms/Pages/IPageModel.cs @@ -1,6 +1,6 @@ using ToSic.Sxc.Cms.Pages.Internal; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Cms.Pages; @@ -20,9 +20,7 @@ namespace ToSic.Sxc.Cms.Pages; /// * the previous internal implementation had a property called `Visible` which we finalized to `IsNavigation` to better clarify it purpose. /// * the previous internal implementation had a property called `Clickable` which we finalized to `IsClickable` to better clarify it purpose. /// -[DataModelConversion(Map = [ - typeof(DataModelFrom), -])] +[ModelCreation(Use = typeof(PageModelOfEntity))] [InternalApi_DoNotUse_MayChangeWithoutNotice] public interface IPageModel : ICanWrapData { diff --git a/Src/Sxc/ToSic.Sxc/Cms/Pages/Internal/PageModelOfEntity.cs b/Src/Sxc/ToSic.Sxc/Cms/Pages/Internal/PageModelOfEntity.cs index 607121d46..fb139a12a 100644 --- a/Src/Sxc/ToSic.Sxc/Cms/Pages/Internal/PageModelOfEntity.cs +++ b/Src/Sxc/ToSic.Sxc/Cms/Pages/Internal/PageModelOfEntity.cs @@ -1,8 +1,8 @@ -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Cms.Pages.Internal; -public class PageModelOfEntity: DataModel, IPageModel +public class PageModelOfEntity: ModelFromEntity, IPageModel { public int Id => _entity.EntityId; public int ParentId => _entity.Get(nameof(ParentId)); diff --git a/Src/Sxc/ToSic.Sxc/Cms/Sites/ISiteModel.cs b/Src/Sxc/ToSic.Sxc/Cms/Sites/ISiteModel.cs index 39ee4a95f..55fd38367 100644 --- a/Src/Sxc/ToSic.Sxc/Cms/Sites/ISiteModel.cs +++ b/Src/Sxc/ToSic.Sxc/Cms/Sites/ISiteModel.cs @@ -1,6 +1,6 @@ using ToSic.Sxc.Cms.Sites.Internal; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Cms.Sites; @@ -17,9 +17,7 @@ namespace ToSic.Sxc.Cms.Sites; /// /// * Introduced in v19.01 /// -[DataModelConversion(Map = [ - typeof(DataModelFrom), -])] +[ModelCreation(Use = typeof(SiteModelOfEntity))] [InternalApi_DoNotUse_MayChangeWithoutNotice] public interface ISiteModel : ICanWrapData { diff --git a/Src/Sxc/ToSic.Sxc/Cms/Sites/Internal/SiteModelOfEntity.cs b/Src/Sxc/ToSic.Sxc/Cms/Sites/Internal/SiteModelOfEntity.cs index 1fc7d2d6b..5e10c46e5 100644 --- a/Src/Sxc/ToSic.Sxc/Cms/Sites/Internal/SiteModelOfEntity.cs +++ b/Src/Sxc/ToSic.Sxc/Cms/Sites/Internal/SiteModelOfEntity.cs @@ -1,8 +1,8 @@ -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Cms.Sites.Internal; -public class SiteModelOfEntity: DataModel, ISiteModel +public class SiteModelOfEntity: ModelFromEntity, ISiteModel { public int Id => _entity.EntityId; public Guid Guid => _entity.EntityGuid; diff --git a/Src/Sxc/ToSic.Sxc/Cms/Users/IUserModel.cs b/Src/Sxc/ToSic.Sxc/Cms/Users/IUserModel.cs index 3d7af9b05..eba4ef21a 100644 --- a/Src/Sxc/ToSic.Sxc/Cms/Users/IUserModel.cs +++ b/Src/Sxc/ToSic.Sxc/Cms/Users/IUserModel.cs @@ -1,7 +1,7 @@ using ToSic.Eav.Context; using ToSic.Sxc.Cms.Users.Internal; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Cms.Users; @@ -19,9 +19,7 @@ namespace ToSic.Sxc.Cms.Users; /// /// * Introduced in v19.01 /// -[DataModelConversion(Map = [ - typeof(DataModelFrom), -])] +[ModelCreation(Use = typeof(UserModelOfEntity))] [InternalApi_DoNotUse_MayChangeWithoutNotice] public interface IUserModel : ICanWrapData { diff --git a/Src/Sxc/ToSic.Sxc/Cms/Users/IUserRoleModel.cs b/Src/Sxc/ToSic.Sxc/Cms/Users/IUserRoleModel.cs index 642b4af5d..9e98edf6e 100644 --- a/Src/Sxc/ToSic.Sxc/Cms/Users/IUserRoleModel.cs +++ b/Src/Sxc/ToSic.Sxc/Cms/Users/IUserRoleModel.cs @@ -1,6 +1,6 @@ using ToSic.Sxc.Cms.Users.Internal; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; using ToSic.Sxc.DataSources; namespace ToSic.Sxc.Cms.Users; @@ -18,9 +18,7 @@ namespace ToSic.Sxc.Cms.Users; /// /// * Introduced in v19.01 /// -[DataModelConversion(Map = [ - typeof(DataModelFrom), -])] +[ModelCreation(Use = typeof(UserRoleModelOfEntity))] [InternalApi_DoNotUse_MayChangeWithoutNotice] public interface IUserRoleModel : ICanWrapData { diff --git a/Src/Sxc/ToSic.Sxc/Cms/Users/Internal/UserModelOfEntity.cs b/Src/Sxc/ToSic.Sxc/Cms/Users/Internal/UserModelOfEntity.cs index 6fdc2c21d..1d16fb5dc 100644 --- a/Src/Sxc/ToSic.Sxc/Cms/Users/Internal/UserModelOfEntity.cs +++ b/Src/Sxc/ToSic.Sxc/Cms/Users/Internal/UserModelOfEntity.cs @@ -1,9 +1,9 @@ -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Cms.Users.Internal; -internal class UserModelOfEntity : DataModel, IUserModel +internal class UserModelOfEntity : ModelFromEntity, IUserModel { public string Email => _entity.Get(nameof(Email)); diff --git a/Src/Sxc/ToSic.Sxc/Cms/Users/Internal/UserRoleModelOfEntity.cs b/Src/Sxc/ToSic.Sxc/Cms/Users/Internal/UserRoleModelOfEntity.cs index a5a333b66..4b49de183 100644 --- a/Src/Sxc/ToSic.Sxc/Cms/Users/Internal/UserRoleModelOfEntity.cs +++ b/Src/Sxc/ToSic.Sxc/Cms/Users/Internal/UserRoleModelOfEntity.cs @@ -1,8 +1,8 @@ -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Cms.Users.Internal; -public class UserRoleModelOfEntity: DataModel, IUserRoleModel +public class UserRoleModelOfEntity: ModelFromEntity, IUserRoleModel { public int Id => _entity.EntityId; public string Name => _entity.Get(nameof(Name)); diff --git a/Src/Sxc/ToSic.Sxc/Code/Customizer/Customizer.cs b/Src/Sxc/ToSic.Sxc/Code/Customizer/Customizer.cs index 0698fc8d0..b00c67140 100644 --- a/Src/Sxc/ToSic.Sxc/Code/Customizer/Customizer.cs +++ b/Src/Sxc/ToSic.Sxc/Code/Customizer/Customizer.cs @@ -2,7 +2,6 @@ using ToSic.Sxc.Context; using ToSic.Sxc.Context.Internal; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; using ToSic.Sxc.DataSources; using ToSic.Sxc.Services.Internal; diff --git a/Src/Sxc/ToSic.Sxc/Code/Customizer/ICodeCustomizer.cs b/Src/Sxc/ToSic.Sxc/Code/Customizer/ICodeCustomizer.cs index f5bf174d9..2e82c8d8e 100644 --- a/Src/Sxc/ToSic.Sxc/Code/Customizer/ICodeCustomizer.cs +++ b/Src/Sxc/ToSic.Sxc/Code/Customizer/ICodeCustomizer.cs @@ -1,7 +1,6 @@ using ToSic.Sxc.Apps; using ToSic.Sxc.Context; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; // ReSharper disable once CheckNamespace namespace ToSic.Sxc.Code; diff --git a/Src/Sxc/ToSic.Sxc/Code/Internal/IDynamicCode16.cs b/Src/Sxc/ToSic.Sxc/Code/Internal/IDynamicCode16.cs index 22edc2270..17f96bf1b 100644 --- a/Src/Sxc/ToSic.Sxc/Code/Internal/IDynamicCode16.cs +++ b/Src/Sxc/ToSic.Sxc/Code/Internal/IDynamicCode16.cs @@ -2,7 +2,6 @@ using ToSic.Sxc.Apps; using ToSic.Sxc.Context; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; using ToSic.Sxc.Services; namespace ToSic.Sxc.Code.Internal; diff --git a/Src/Sxc/ToSic.Sxc/Context/CmsContext/ICmsView_TSettings_TResources.cs b/Src/Sxc/ToSic.Sxc/Context/CmsContext/ICmsView_TSettings_TResources.cs index 7429c6b65..e62244ba2 100644 --- a/Src/Sxc/ToSic.Sxc/Context/CmsContext/ICmsView_TSettings_TResources.cs +++ b/Src/Sxc/ToSic.Sxc/Context/CmsContext/ICmsView_TSettings_TResources.cs @@ -1,5 +1,4 @@ using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; namespace ToSic.Sxc.Context; diff --git a/Src/Sxc/ToSic.Sxc/Context/CmsContext/Internal/CmsView_TSettings_TResources.cs b/Src/Sxc/ToSic.Sxc/Context/CmsContext/Internal/CmsView_TSettings_TResources.cs index 425b2cd50..e985ba2ab 100644 --- a/Src/Sxc/ToSic.Sxc/Context/CmsContext/Internal/CmsView_TSettings_TResources.cs +++ b/Src/Sxc/ToSic.Sxc/Context/CmsContext/Internal/CmsView_TSettings_TResources.cs @@ -1,7 +1,6 @@ using ToSic.Lib.Helpers; using ToSic.Sxc.Blocks.Internal; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; namespace ToSic.Sxc.Context.Internal; diff --git a/Src/Sxc/ToSic.Sxc/Custom.Data/CustomItem.cs b/Src/Sxc/ToSic.Sxc/Custom.Data/CustomItem.cs index 1e43e5171..0acbee8a1 100644 --- a/Src/Sxc/ToSic.Sxc/Custom.Data/CustomItem.cs +++ b/Src/Sxc/ToSic.Sxc/Custom.Data/CustomItem.cs @@ -8,7 +8,7 @@ using ToSic.Sxc.Data; using ToSic.Sxc.Data.Internal; using ToSic.Sxc.Data.Internal.Factory; -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; using ToSic.Sxc.Images; using ToSic.Sxc.Services.Tweaks; @@ -57,15 +57,15 @@ namespace Custom.Data; /// - It's not abstract, even if the most common case is to inherit, as there are cases where you want to use it directly. /// [PublicApi] -[DataModel(ForContentTypes = DataModelAttribute.ForAnyContentType, Remarks = "Will work for any conversion, but inherited types will be restricted again")] +[ModelSource(ContentTypes = ModelSourceAttribute.ForAnyContentType)] public partial class CustomItem: ITypedItem, ICanWrap, IHasPropLookup { #region Explicit Interfaces for internal use - Setup, etc. - void ICanWrap.Setup(ITypedItem baseItem, IModelFactory modelFactory) + void ICanWrap.Setup(ITypedItem source, IModelFactory modelFactory) { - _item = baseItem; + _item = source; _modelFactory = modelFactory; } private IModelFactory _modelFactory; diff --git a/Src/Sxc/ToSic.Sxc/Custom.Hybrid/CodeTyped.cs b/Src/Sxc/ToSic.Sxc/Custom.Hybrid/CodeTyped.cs index f0d3ff83b..4d0e827c3 100644 --- a/Src/Sxc/ToSic.Sxc/Custom.Hybrid/CodeTyped.cs +++ b/Src/Sxc/ToSic.Sxc/Custom.Hybrid/CodeTyped.cs @@ -8,7 +8,6 @@ using ToSic.Sxc.Code.Internal.CodeRunHelpers; using ToSic.Sxc.Context; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; using ToSic.Sxc.Internal; using ToSic.Sxc.Services; diff --git a/Src/Sxc/ToSic.Sxc/Data/DataWrapper/ICanWrap.TData.cs b/Src/Sxc/ToSic.Sxc/Data/DataWrapper/ICanWrap.TSource.cs similarity index 85% rename from Src/Sxc/ToSic.Sxc/Data/DataWrapper/ICanWrap.TData.cs rename to Src/Sxc/ToSic.Sxc/Data/DataWrapper/ICanWrap.TSource.cs index c3a91a25e..50732ee41 100644 --- a/Src/Sxc/ToSic.Sxc/Data/DataWrapper/ICanWrap.TData.cs +++ b/Src/Sxc/ToSic.Sxc/Data/DataWrapper/ICanWrap.TSource.cs @@ -18,20 +18,20 @@ namespace ToSic.Sxc.Data; /// * Made visible in the docs for better understanding in v19.01 /// * The `Setup()` method is still internal, as the signature may still change /// -/// +/// /// The data type which can be accepted. /// Must be or (other types not supported for now). /// [InternalApi_DoNotUse_MayChangeWithoutNotice("may change or rename at any time")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] -public interface ICanWrap: ICanWrapData +public interface ICanWrap: ICanWrapData { /// /// Add the data to use for the wrapper. /// We are not doing this in the constructor, - /// because the object needs to have an empty (or future: maybe DI-compatible) constructor. + /// because the object needs to have an empty or DI-compatible constructor. /// [PrivateApi] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - internal void Setup(TData baseItem, IModelFactory modelFactory); + internal void Setup(TSource source, IModelFactory modelFactory); } \ No newline at end of file diff --git a/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/ClassAttributeLookup.cs b/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/ClassAttributeLookup.cs index 633fdf3d4..2f0e552d6 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/ClassAttributeLookup.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/ClassAttributeLookup.cs @@ -1,5 +1,4 @@ using ToSic.Eav.Plumbing; -using ToSic.Sxc.Data.Model; namespace ToSic.Sxc.Data.Internal.Factory; diff --git a/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/CodeDataFactory_AsCustom.cs b/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/CodeDataFactory_AsCustom.cs index 721999f63..8af32a1ba 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/CodeDataFactory_AsCustom.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/CodeDataFactory_AsCustom.cs @@ -1,7 +1,7 @@ using System.Collections; using Microsoft.Extensions.DependencyInjection; using ToSic.Sxc.Data.Internal.Typed; -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Data.Internal; @@ -55,7 +55,7 @@ internal TCustom GetOne(Func getItem, object id, bool skipType // Do Type-Name check var typeName = DataModelAnalyzer.GetContentTypeNames().Split(','); - if (typeName.FirstOrDefault() != DataModelAttribute.ForAnyContentType && !typeName.Any(tn => item.Type.Is(tn))) + if (typeName.FirstOrDefault() != ModelSourceAttribute.ForAnyContentType && !typeName.Any(tn => item.Type.Is(tn))) throw new($"Item with ID {id} is not a {typeName}. This is probably a mistake, otherwise use {nameof(skipTypeCheck)}: true"); return AsCustom(item); } diff --git a/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/CodeDataFactory_Stack.cs b/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/CodeDataFactory_Stack.cs index 322af7115..eb475dfe5 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/CodeDataFactory_Stack.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/CodeDataFactory_Stack.cs @@ -2,7 +2,6 @@ using ToSic.Eav.Data.PropertyLookup; using ToSic.Eav.Plumbing; using ToSic.Sxc.Data.Internal.Stack; -using ToSic.Sxc.Data.Model; namespace ToSic.Sxc.Data.Internal; diff --git a/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/DataModelAnalyzer.cs b/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/DataModelAnalyzer.cs index 0bcb7854a..867b42003 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/DataModelAnalyzer.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/DataModelAnalyzer.cs @@ -1,5 +1,5 @@ using ToSic.Sxc.Data.Internal.Factory; -using ToSic.Sxc.Data.Model; +using ToSic.Sxc.Data.Models; namespace ToSic.Sxc.Data.Internal; @@ -8,17 +8,17 @@ internal class DataModelAnalyzer /// /// Figure out the expected ContentTypeName of a DataWrapper type. - /// If it is decorated with then use the information it provides, otherwise + /// If it is decorated with then use the information it provides, otherwise /// use the type name. /// /// /// internal static string GetContentTypeNames() where TCustom : ICanWrapData => - ContentTypeNames.Get(a => + ContentTypeNames.Get(a => { // If we have an attribute, use the value provided (unless not specified) - if (a?.ForContentTypes != null) - return a.ForContentTypes; + if (a?.ContentTypes != null) + return a.ContentTypes; // If no attribute, use name of type var type = typeof(TCustom); @@ -37,9 +37,9 @@ internal static string GetContentTypeNames() where TCustom : ICanWrapDa /// /// internal static string GetStreamName() where TCustom : ICanWrapData => - StreamNames.Get(a => + StreamNames.Get(a => // if we have the attribute, use that - a?.StreamNames.Split(',').First().Trim() + a?.Streams.Split(',').First().Trim() // If no attribute, use name of type ?? typeof(TCustom).Name); private static readonly ClassAttributeLookup StreamNames = new(); @@ -53,37 +53,24 @@ internal static Type GetTargetType() // Find attributes which describe conversion var attributes = type - .GetCustomAttributes(typeof(DataModelConversion), false) - .Cast() + .GetCustomAttributes(typeof(ModelCreationAttribute), false) + .Cast() .ToList(); - - var map = attributes - .FirstOrDefault()?.Map? - .Select(m => - { - if (!m.IsGenericType) return null; - if (m.GetGenericTypeDefinition() != typeof(DataModelFrom<,,>)) - return null; - - var args = m.GetGenericArguments(); - return new { From = args[0], To = args[2] }; - }) - .Where(m => m != null) - .ToList(); - - if (map == null || map.Count == 0) + // 2025-01-21 temp + var implementation = attributes.FirstOrDefault()?.Use; + if (implementation != null) { - if (type.IsInterface) - throw new TypeInitializationException(type.FullName, - new($"Can't determine type to create of {type.Name} as it's an interface and doesn't have the proper Attributes")); - TargetTypes[type] = type; - return type; + TargetTypes[type] = implementation; + return implementation; } - var finalType = map.First().To; - TargetTypes[type] = finalType; - return finalType; + + if (type.IsInterface) + throw new TypeInitializationException(type.FullName, + new($"Can't determine type to create of {type.Name} as it's an interface and doesn't have the proper Attributes")); + TargetTypes[type] = type; + return type; } private static readonly Dictionary TargetTypes = new(); diff --git a/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/IModelFactory.cs b/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/IModelFactory.cs index a37ba98c3..cb37083b2 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/IModelFactory.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/IModelFactory.cs @@ -1,6 +1,4 @@ -using ToSic.Sxc.Data.Model; - -namespace ToSic.Sxc.Data.Internal; +namespace ToSic.Sxc.Data.Internal; /// /// An object which can create custom models. diff --git a/Src/Sxc/ToSic.Sxc/Data/Model/DataModelAttribute.cs b/Src/Sxc/ToSic.Sxc/Data/Model/DataModelAttribute.cs deleted file mode 100644 index 6b16aef6d..000000000 --- a/Src/Sxc/ToSic.Sxc/Data/Model/DataModelAttribute.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace ToSic.Sxc.Data.Model; - -/// -/// BETA / WIP: Mark DataModel objects/interfaces and specify what ContentType they are for. -/// -/// -/// Typical use is for custom data such as classes inheriting from -/// which takes an entity and then provides a strongly typed wrapper around it. -/// -/// History: New / WIP in v19.01 -/// -[InternalApi_DoNotUse_MayChangeWithoutNotice("may change or rename at any time")] -[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] -public class DataModelAttribute: Attribute -{ - /// - /// Determines which content-type names are expected when converting to this data model. - /// - /// - /// Usually this is checked when converting Entities to the custom data model. - /// If it doesn't match, will then throw an error. - /// - /// Typically just one value, such as "Article" or "Product". - /// But it will also support "*" for anything, or a comma-separated list of content-type names. - /// - /// History: WIP 19.01 - /// - public string ForContentTypes { get; init; } - - public string StreamNames { get; init; } - - /// - /// Just custom remarks, no technical functionality. - /// - public string Remarks { get; init; } - - public const string ForAnyContentType = "*"; -} \ No newline at end of file diff --git a/Src/Sxc/ToSic.Sxc/Data/Model/DataModelConversion.cs b/Src/Sxc/ToSic.Sxc/Data/Model/DataModelConversion.cs deleted file mode 100644 index 94ffbdc57..000000000 --- a/Src/Sxc/ToSic.Sxc/Data/Model/DataModelConversion.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace ToSic.Sxc.Data.Model; - -/// -/// BETA / WIP: Attribute to decorate interfaces which should be used to retrieve a data model. -/// -/// -/// It's primary property is the Map, which is an array of types that should be used to map the data model to. -/// -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false)] -[InternalApi_DoNotUse_MayChangeWithoutNotice] -public class DataModelConversion: Attribute -{ - public Type[] Map { get; init; } -} - -/// -/// Conversion map from one type - typically to a DataModel. -/// -/// -/// -/// -public class DataModelFrom - where TTo : class, TImplements, ICanWrap, new() - where TImplements : class - where TFrom : ICanBeEntity; \ No newline at end of file diff --git a/Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelCreationAttribute.cs b/Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelCreationAttribute.cs new file mode 100644 index 000000000..7364ba4f4 --- /dev/null +++ b/Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelCreationAttribute.cs @@ -0,0 +1,27 @@ +namespace ToSic.Sxc.Data.Models; + +/// +/// BETA / WIP: Attribute to decorate interfaces to specify a concrete type when creating the model. +/// +/// +/// ```c# +/// [ModelCreation(Use = typeof(PersonModel))] +/// interface IPersonModel : ICanWrapData +/// { +/// public string Name { get; } +/// } +/// ``` +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false)] +[InternalApi_DoNotUse_MayChangeWithoutNotice] +public class ModelCreationAttribute: Attribute +{ + /// + /// The type to use when creating a model of this interface. + /// + /// + /// It **must** match (implement or inherit) the type which is being decorated. + /// Otherwise, it will throw an exception. + /// + public Type Use { get; init; } +} diff --git a/Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelSourceAttribute.cs b/Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelSourceAttribute.cs new file mode 100644 index 000000000..002195792 --- /dev/null +++ b/Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelSourceAttribute.cs @@ -0,0 +1,45 @@ + +namespace ToSic.Sxc.Data.Models; + +/// +/// BETA / WIP: Mark custom models/interfaces to specify what data they expect. +/// +/// +/// This marks custom models to enable checks and more automation, such as: +/// +/// * Specify an alternate content type name than the default, which would have to match the class/interface name +/// * Ensure that the model is only used for specific content-type(s) which don't match the model name +/// * Allow the model to be used with all content types `*` +/// * Automatically find the best stream of data to use with the model, if it doesn't match the model name +/// +/// Typical use is for custom data such as classes inheriting from +/// which takes an entity and then provides a strongly typed wrapper around it. +/// +/// History: New / WIP in v19.01 +/// +[Lib.Documentation.InternalApi_DoNotUse_MayChangeWithoutNotice("may change or rename at any time")] +[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] +public class ModelSourceAttribute: Attribute +{ + /// + /// Determines which content-type names are expected when converting to this data model. + /// + /// + /// Usually this is checked when converting Entities to the custom data model. + /// If it doesn't match, will then throw an error. + /// + /// Typically just one value, such as "Article" or "Product". + /// But it will also support "*" for anything, or (future!) a comma-separated list of content-type names. + /// + /// History: WIP 19.01 + /// + public string ContentTypes { get; init; } + + /// + /// WIP, not officially supported yet. + /// + public string Streams { get; init; } + + [PrivateApi] + internal const string ForAnyContentType = "*"; +} \ No newline at end of file diff --git a/Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModelHelpers.cs b/Src/Sxc/ToSic.Sxc/Data/Models/Internal/DataModelHelpers.cs similarity index 98% rename from Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModelHelpers.cs rename to Src/Sxc/ToSic.Sxc/Data/Models/Internal/DataModelHelpers.cs index b1d37efae..9b4ff980a 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModelHelpers.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Models/Internal/DataModelHelpers.cs @@ -1,6 +1,6 @@ using ToSic.Sxc.Data.Internal; -namespace ToSic.Sxc.Data.Model; +namespace ToSic.Sxc.Data.Models; internal class DataModelHelpers { diff --git a/Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModel.cs b/Src/Sxc/ToSic.Sxc/Data/Models/ModelFromEntity.cs similarity index 83% rename from Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModel.cs rename to Src/Sxc/ToSic.Sxc/Data/Models/ModelFromEntity.cs index a53d4fa99..1b1148e26 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModel.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Models/ModelFromEntity.cs @@ -1,12 +1,12 @@ using Custom.Data; using ToSic.Sxc.Data.Internal; -namespace ToSic.Sxc.Data.Model; +namespace ToSic.Sxc.Data.Models; /// -/// BETA / WIP: Base class for **plain** custom data models and can be used in Razor Components. -/// It wraps a and provides a simple way to access the data. +/// BETA / WIP: Base class for **plain** data models and can be used in Razor Components. +/// It wraps an and provides a simple way to access the data. /// /// /// @@ -47,13 +47,13 @@ namespace ToSic.Sxc.Data.Model; /// - Released in v19.01 (BETA) /// [InternalApi_DoNotUse_MayChangeWithoutNotice("Still beta, name may change to CustomModelOfItem or something")] -public abstract partial class DataModel: ICanWrap, ICanBeEntity //, IHasPropLookup +public abstract partial class ModelFromEntity: ICanWrap, ICanBeEntity //, IHasPropLookup { #region Explicit Interfaces for internal use - Setup, etc. - void ICanWrap.Setup(IEntity baseItem, IModelFactory modelFactory) + void ICanWrap.Setup(IEntity source, IModelFactory modelFactory) { - _entity = baseItem; + _entity = source; _modelFactory = modelFactory; } private IModelFactory _modelFactory; @@ -92,12 +92,12 @@ void ICanWrap.Setup(IEntity baseItem, IModelFactory modelFactory) /// Override ToString to give more information about the current object /// public override string ToString() - => $"{nameof(DataModel)} Data Model {GetType().FullName} " + (_entity == null ? "without backing data (null)" : $"for id:{_entity.EntityId} ({_entity})"); + => $"{nameof(ModelFromEntity)} Data Model {GetType().FullName} " + (_entity == null ? "without backing data (null)" : $"for id:{_entity.EntityId} ({_entity})"); #region As... - /// + /// protected T As(object item) where T : class, ICanWrapData => DataModelHelpers.As(_modelFactory, item); diff --git a/Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModel_Equatable.cs b/Src/Sxc/ToSic.Sxc/Data/Models/ModelFromEntity_Equatable.cs similarity index 76% rename from Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModel_Equatable.cs rename to Src/Sxc/ToSic.Sxc/Data/Models/ModelFromEntity_Equatable.cs index 361e3ad8c..d345f9e44 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModel_Equatable.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Models/ModelFromEntity_Equatable.cs @@ -1,6 +1,6 @@ -namespace ToSic.Sxc.Data.Model; +namespace ToSic.Sxc.Data.Models; -partial class DataModel: IMultiWrapper, IEquatable +partial class ModelFromEntity: IMultiWrapper, IEquatable { bool IEquatable.Equals(IEntity other) => Equals(other); @@ -23,11 +23,11 @@ IEntity IMultiWrapper.RootContentsForEqualityCheck /// Ensure that the equality check is done correctly. /// If two objects wrap the same item, they will be considered equal. /// - /// first item to compare - /// second item to compare + /// first item to compare + /// second item to compare /// true, if both wrappers are the same type and wrap the same entity - public static bool operator ==(DataModel item1, DataModel item2) - => MultiWrapperEquality.IsEqual(item1, item2); + public static bool operator ==(ModelFromEntity a, ModelFromEntity b) + => MultiWrapperEquality.IsEqual(a, b); /// /// Ensure that the equality check is done correctly. @@ -36,6 +36,6 @@ IEntity IMultiWrapper.RootContentsForEqualityCheck /// first item to compare /// second item to compare /// false, if both wrappers are the same type and wrap the same entity - public static bool operator !=(DataModel item1, DataModel item2) + public static bool operator !=(ModelFromEntity item1, ModelFromEntity item2) => !MultiWrapperEquality.IsEqual(item1, item2); } \ No newline at end of file diff --git a/Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModelOfItem.cs b/Src/Sxc/ToSic.Sxc/Data/Models/ModelFromItem.cs similarity index 82% rename from Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModelOfItem.cs rename to Src/Sxc/ToSic.Sxc/Data/Models/ModelFromItem.cs index 048298143..c000d8929 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModelOfItem.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Models/ModelFromItem.cs @@ -2,12 +2,12 @@ using ToSic.Sxc.Blocks.Internal; using ToSic.Sxc.Data.Internal; -namespace ToSic.Sxc.Data.Model; +namespace ToSic.Sxc.Data.Models; /// -/// BETA / WIP: Base class for **plain** custom data models and can be used in Razor Components. -/// It wraps a and provides a simple way to access the data. +/// BETA / WIP: Base class for **plain** data models and can be used in Razor Components. +/// It wraps an and provides a simple way to access the data. /// /// /// @@ -23,6 +23,7 @@ namespace ToSic.Sxc.Data.Model; /// { /// class MyPerson : DataModelOfItem /// { +/// public int Id => _item.Id; /// public string Name => _item.String("Name"); /// } /// } @@ -47,32 +48,28 @@ namespace ToSic.Sxc.Data.Model; /// - Released in v19.01 (BETA) /// [InternalApi_DoNotUse_MayChangeWithoutNotice("Still beta, name may change to DataModelWithItem or something")] -public abstract partial class DataModelOfItem : ICanWrap, ICanBeItem, ICanBeEntity //, IHasPropLookup +public abstract partial class ModelFromItem : ICanWrap, ICanBeItem, ICanBeEntity { #region Explicit Interfaces for internal use - Setup, etc. - void ICanWrap.Setup(ITypedItem baseItem, IModelFactory modelFactory) + void ICanWrap.Setup(ITypedItem source, IModelFactory modelFactory) { - _item = baseItem; + _item = source; _modelFactory = modelFactory; } private IModelFactory _modelFactory; /// /// The actual item which is being wrapped, in rare cases where you must access it from outside. - /// /// It's only on the explicit interface, so it is not available from outside or inside, unless you cast to it. /// Goal is that inheriting classes don't access it to keep API surface small. /// ITypedItem ICanBeItem.Item => _item; /// - /// This is necessary so the object can be used in places where an IEntity is expected, - /// like toolbars. - /// + /// This is necessary so the object can be used in places where an IEntity is expected, like toolbars. /// It's an explicit interface implementation, so that the object itself doesn't broadcast this. /// - [PrivateApi] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] IEntity ICanBeEntity.Entity => _item.Entity; @@ -100,12 +97,12 @@ void ICanWrap.Setup(ITypedItem baseItem, IModelFactory modelFactory) /// Override ToString to give more information about the current object /// public override string ToString() - => $"{nameof(DataModelOfItem)} Data Model {GetType().FullName} " + (_item == null ? "without backing data (null)" : $"for id:{_item.Id} ({_item})"); + => $"{nameof(ModelFromItem)} Data Model {GetType().FullName} " + (_item == null ? "without backing data (null)" : $"for id:{_item.Id} ({_item})"); #region As... - /// + /// protected T As(object item) where T : class, ICanWrapData => DataModelHelpers.As(_modelFactory, item); diff --git a/Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModelOfItem_Equatable.cs b/Src/Sxc/ToSic.Sxc/Data/Models/ModelFromItem_Equatable.cs similarity index 66% rename from Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModelOfItem_Equatable.cs rename to Src/Sxc/ToSic.Sxc/Data/Models/ModelFromItem_Equatable.cs index c8a548d76..d35b73e48 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Model/Bases/DataModelOfItem_Equatable.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Models/ModelFromItem_Equatable.cs @@ -1,7 +1,7 @@  -namespace ToSic.Sxc.Data.Model; +namespace ToSic.Sxc.Data.Models; -partial class DataModelOfItem : IMultiWrapper, IEquatable +partial class ModelFromItem : IMultiWrapper, IEquatable { bool IEquatable.Equals(ITypedItem other) => Equals(other); @@ -24,19 +24,19 @@ IEntity IMultiWrapper.RootContentsForEqualityCheck /// Ensure that the equality check is done correctly. /// If two objects wrap the same item, they will be considered equal. /// - /// first item to compare - /// second item to compare + /// first item to compare + /// second item to compare /// true, if both wrappers are the same type and wrap the same entity - public static bool operator ==(DataModelOfItem item1, DataModelOfItem item2) - => MultiWrapperEquality.IsEqual(item1, item2); + public static bool operator ==(ModelFromItem a, ModelFromItem b) + => MultiWrapperEquality.IsEqual(a, b); /// /// Ensure that the equality check is done correctly. /// If two objects wrap the same item, they will be considered equal. /// - /// first item to compare - /// second item to compare + /// first item to compare + /// second item to compare /// false, if both wrappers are the same type and wrap the same entity - public static bool operator !=(DataModelOfItem item1, DataModelOfItem item2) - => !MultiWrapperEquality.IsEqual(item1, item2); + public static bool operator !=(ModelFromItem a, ModelFromItem b) + => !MultiWrapperEquality.IsEqual(a, b); } \ No newline at end of file diff --git a/Src/Sxc/ToSic.Sxc/Data/TypedInterfaces/ITypedItem_Relationships.cs b/Src/Sxc/ToSic.Sxc/Data/TypedInterfaces/ITypedItem_Relationships.cs index c432d1bf4..3ffdea4a2 100644 --- a/Src/Sxc/ToSic.Sxc/Data/TypedInterfaces/ITypedItem_Relationships.cs +++ b/Src/Sxc/ToSic.Sxc/Data/TypedInterfaces/ITypedItem_Relationships.cs @@ -1,5 +1,4 @@ using ToSic.Sxc.Data.Internal.Docs; -using ToSic.Sxc.Data.Model; namespace ToSic.Sxc.Data; diff --git a/Src/Sxc/ToSic.Sxc/Services/ConvertService/IConvertService16.cs b/Src/Sxc/ToSic.Sxc/Services/ConvertService/IConvertService16.cs index 3a1ccad0a..f88b93747 100644 --- a/Src/Sxc/ToSic.Sxc/Services/ConvertService/IConvertService16.cs +++ b/Src/Sxc/ToSic.Sxc/Services/ConvertService/IConvertService16.cs @@ -1,6 +1,5 @@ using ToSic.Sxc.Code.Internal.Documentation; using ToSic.Sxc.Data; -using ToSic.Sxc.Data.Model; // 2024-01-22 2dm // Remove all convert methods which are just missing the optional parameters, to make the API smaller. diff --git a/Src/Sxc/ToSic.Sxc/ToSic.Sxc.csproj b/Src/Sxc/ToSic.Sxc/ToSic.Sxc.csproj index dfc484f87..508590b91 100644 --- a/Src/Sxc/ToSic.Sxc/ToSic.Sxc.csproj +++ b/Src/Sxc/ToSic.Sxc/ToSic.Sxc.csproj @@ -90,6 +90,7 @@ + diff --git a/Src/Sxc/ToSic.Sxc/ToSic.Sxc.csproj.DotSettings b/Src/Sxc/ToSic.Sxc/ToSic.Sxc.csproj.DotSettings index 2061050da..cc79ab0b8 100644 --- a/Src/Sxc/ToSic.Sxc/ToSic.Sxc.csproj.DotSettings +++ b/Src/Sxc/ToSic.Sxc/ToSic.Sxc.csproj.DotSettings @@ -84,6 +84,9 @@ True True True + True + True + True True True True From 7e7d266cea48af08d49919ddcda3e0649398fa90 Mon Sep 17 00:00:00 2001 From: iJungleboy Date: Wed, 22 Jan 2025 00:46:19 +0100 Subject: [PATCH 3/4] Version bump 19.03 --- Src/Dnn/ToSic.Sxc.Dnn/DnnPackageBuilder/ReleaseNotes.txt | 2 +- Src/Dnn/ToSic.Sxc.Dnn/DnnPackageBuilder/ToSic.Sxc.Dnn.dnn | 8 ++++---- .../ToSic.Sxc.Oqt.Package/ToSic.Sxc.Oqtane.Install.nuspec | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Src/Dnn/ToSic.Sxc.Dnn/DnnPackageBuilder/ReleaseNotes.txt b/Src/Dnn/ToSic.Sxc.Dnn/DnnPackageBuilder/ReleaseNotes.txt index 68fa1d0ea..3bb458d03 100644 --- a/Src/Dnn/ToSic.Sxc.Dnn/DnnPackageBuilder/ReleaseNotes.txt +++ b/Src/Dnn/ToSic.Sxc.Dnn/DnnPackageBuilder/ReleaseNotes.txt @@ -1,5 +1,5 @@
-
v19.02.00
+
v19.03.00
Part of module installation is the deletion of unneeded data. In an infrequent case, You could get a timeout exception. This is not a show-stopper. Simply reload the page so DNN can continue clean-up until all unnecessary data is deleted and the module is installed.
diff --git a/Src/Dnn/ToSic.Sxc.Dnn/DnnPackageBuilder/ToSic.Sxc.Dnn.dnn b/Src/Dnn/ToSic.Sxc.Dnn/DnnPackageBuilder/ToSic.Sxc.Dnn.dnn index 412e0e732..037b05a45 100644 --- a/Src/Dnn/ToSic.Sxc.Dnn/DnnPackageBuilder/ToSic.Sxc.Dnn.dnn +++ b/Src/Dnn/ToSic.Sxc.Dnn/DnnPackageBuilder/ToSic.Sxc.Dnn.dnn @@ -1,6 +1,6 @@  - + Content 2sxc is a DNN Extension to create attractive and designed content. It solves the common problem, allowing the web designer to create designed templates for different content elements, so that the user must only fill in fields and receive a perfectly designed and animated output. icon.png @@ -79,7 +79,7 @@ @@ -130,7 +130,7 @@ ToSic.SexyContent.DnnBusinessController [DESKTOPMODULEID] - 01.00.00,08.11.00,08.12.00,09.00.00,10.00.00,11.00.00,12.00.00,13.00.00,13.01.00,13.04.00,14.00.00,15.00.00,15.02.00,16.00.00,16.07.01,17.00.00,18.00.00,18.03.00,19.00.00,19.02.00 + 01.00.00,08.11.00,08.12.00,09.00.00,10.00.00,11.00.00,12.00.00,13.00.00,13.01.00,13.04.00,14.00.00,15.00.00,15.02.00,16.00.00,16.07.01,17.00.00,18.00.00,18.03.00,19.00.00,19.03.00 @@ -642,7 +642,7 @@ - + App 2sxc App is an extension that allows to install and use a 2sxc app. icon-app.png diff --git a/Src/Oqtane/ToSic.Sxc.Oqt.Package/ToSic.Sxc.Oqtane.Install.nuspec b/Src/Oqtane/ToSic.Sxc.Oqt.Package/ToSic.Sxc.Oqtane.Install.nuspec index 0a30a2fc6..939e26438 100644 --- a/Src/Oqtane/ToSic.Sxc.Oqt.Package/ToSic.Sxc.Oqtane.Install.nuspec +++ b/Src/Oqtane/ToSic.Sxc.Oqt.Package/ToSic.Sxc.Oqtane.Install.nuspec @@ -2,7 +2,7 @@ ToSic.Sxc.Oqtane.Install - 19.02.00 + 19.03.00 2sic internet solutions GmbH, Switzerland 2sic internet solutions GmbH, Switzerland 2sxc CMS and Meta-Module for Oqtane From 3a3f4591c349b1d1b8e17ad57eecc58e924a97b4 Mon Sep 17 00:00:00 2001 From: iJungleboy Date: Wed, 22 Jan 2025 09:49:14 +0100 Subject: [PATCH 4/4] implementing CustomModel #3557 --- .../DataModelAnalyzerTestAccessors.cs | 12 ++-- .../ModelTests/DataModelAnalyzerTests.cs | 37 ++++++++---- Src/Sxc/ToSic.Sxc/Apps/IAppDataTyped.cs | 6 +- .../ToSic.Sxc/Apps/Internal/AppDataTyped.cs | 26 +++++++- Src/Sxc/ToSic.Sxc/Custom.Data/CustomItem.cs | 8 ++- Src/Sxc/ToSic.Sxc/Custom.Data/CustomModel.cs | 23 ++++++++ .../Factory/CodeDataFactory_AsCustom.cs | 47 ++++++++++----- .../Internal/Factory/DataModelAnalyzer.cs | 59 ++++++++++++++++--- .../Attributes/ModelCreationAttribute.cs | 2 +- .../Models/Attributes/ModelSourceAttribute.cs | 4 +- .../Data/Models/Internal/DataModelHelpers.cs | 6 +- 11 files changed, 182 insertions(+), 48 deletions(-) create mode 100644 Src/Sxc/ToSic.Sxc/Custom.Data/CustomModel.cs diff --git a/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTestAccessors.cs b/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTestAccessors.cs index 499c8a08d..87a9ebf6c 100644 --- a/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTestAccessors.cs +++ b/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTestAccessors.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using ToSic.Sxc.Data; using ToSic.Sxc.Data.Internal; @@ -6,13 +7,16 @@ namespace ToSic.Sxc.Tests.DataTests.ModelTests; internal static class DataModelAnalyzerTestAccessors { - public static string GetContentTypeNameTac() + //public static string GetContentTypeNameTac() + // where T : class, ICanWrapData + //=> DataModelAnalyzer.GetContentTypeNameCsv(); + public static (List List, string Flat) GetContentTypeNamesTac() where T : class, ICanWrapData - => DataModelAnalyzer.GetContentTypeNames(); + => DataModelAnalyzer.GetContentTypeNamesList(); - public static string GetStreamNameTac() + public static (List List, string Flat) GetStreamNameListTac() where T : class, ICanWrapData - => DataModelAnalyzer.GetStreamName(); + => DataModelAnalyzer.GetStreamNameList(); public static Type GetTargetTypeTac() where T : class, ICanWrapData diff --git a/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTests.cs b/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTests.cs index 2e202657d..66ce415f2 100644 --- a/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTests.cs +++ b/Src/Sxc.Tests/ToSic.Sxc.Tests/DataTests/ModelTests/DataModelAnalyzerTests.cs @@ -9,10 +9,11 @@ public class DataModelAnalyzerTests : TestBaseSxcDb { private void AssertTypeName(string name) where T : class, ICanWrapData => - Assert.AreEqual(name, DataModelAnalyzerTestAccessors.GetContentTypeNameTac()); - private void AssertStreamName(string name) + Assert.AreEqual(name, DataModelAnalyzerTestAccessors.GetContentTypeNamesTac().Flat); + + private void AssertStreamNames(string namesCsv) where T : class, ICanWrapData => - Assert.AreEqual(name, DataModelAnalyzerTestAccessors.GetStreamNameTac()); + Assert.AreEqual(namesCsv, DataModelAnalyzerTestAccessors.GetStreamNameListTac().Flat); class NotDecorated: ICanWrapData; @@ -22,18 +23,34 @@ public void NotDecoratedDataModelType() => [TestMethod] public void NotDecoratedDataModelStream() => - AssertStreamName(nameof(NotDecorated)); + AssertStreamNames(nameof(NotDecorated)); + + [TestMethod] + public void NotDecoratedDataModelStreamList() => + AssertStreamNames(nameof(NotDecorated)); + + class NotDecoratedModel : ICanWrapData; + [TestMethod] + public void NotDecoratedModelStreamList() => + AssertStreamNames(nameof(NotDecoratedModel) + "," + nameof(NotDecorated)); + + // Objects starting with an "I" won't have the "I" removed in the name checks + class INotDecoratedModel : ICanWrapData; + + [TestMethod] + public void INotDecoratedModelStreamList() => + AssertStreamNames(nameof(INotDecoratedModel) + ",INotDecorated"); interface INotDecorated: ICanWrapData; [TestMethod] public void INotDecoratedType() => - AssertTypeName(nameof(INotDecorated).Substring(1)); + AssertTypeName(nameof(INotDecorated) + ',' + nameof(INotDecorated).Substring(1)); [TestMethod] public void INotDecoratedStream() => - AssertStreamName(nameof(INotDecorated)); + AssertStreamNames(nameof(INotDecorated) + ",NotDecorated"); private const string ForContentType1 = "Abc"; @@ -47,7 +64,7 @@ public void DecoratedType() => [TestMethod] public void DecoratedStream() => - AssertStreamName(StreamName1); + AssertStreamNames(StreamName1); class InheritDecorated : Decorated; @@ -58,7 +75,7 @@ public void InheritDecoratedType() => [TestMethod] public void InheritDecoratedStream() => - AssertStreamName(nameof(InheritDecorated)); + AssertStreamNames(nameof(InheritDecorated)); private const string ForContentTypeReDecorated = "ReDec"; @@ -71,7 +88,7 @@ public void InheritReDecoratedType() => AssertTypeName(ForContentTypeReDecorated); [TestMethod] public void InheritReDecoratedStream() => - AssertStreamName(StreamNameReDecorated); + AssertStreamNames(StreamNameReDecorated + ",Abc"); private const string ForContentTypeIDecorated = "IDec"; @@ -85,6 +102,6 @@ public void IDecoratedType() => [TestMethod] public void IDecoratedStream() => - AssertStreamName(StreamNameIDecorated); + AssertStreamNames(StreamNameIDecorated); } \ No newline at end of file diff --git a/Src/Sxc/ToSic.Sxc/Apps/IAppDataTyped.cs b/Src/Sxc/ToSic.Sxc/Apps/IAppDataTyped.cs index c929071af..866bcefa6 100644 --- a/Src/Sxc/ToSic.Sxc/Apps/IAppDataTyped.cs +++ b/Src/Sxc/ToSic.Sxc/Apps/IAppDataTyped.cs @@ -64,7 +64,7 @@ public interface IAppDataTyped: IDataSource /// public IEnumerable GetAll(NoParamOrder protector = default, string typeName = default, bool nullIfNotFound = default) - where T : class, ICanWrapData, new(); + where T : class, ICanWrapData; /// /// Get a single item from the app of the specified type. @@ -78,7 +78,7 @@ public IEnumerable GetAll(NoParamOrder protector = default, string typeNam /// Released in v17.03. /// T GetOne(int id, NoParamOrder protector = default, bool skipTypeCheck = false) - where T : class, ICanWrapData, new(); + where T : class, ICanWrapData; /// @@ -93,7 +93,7 @@ T GetOne(int id, NoParamOrder protector = default, bool skipTypeCheck = false /// Released in v17.03. /// public T GetOne(Guid id, NoParamOrder protector = default, bool skipTypeCheck = false) - where T : class, ICanWrapData, new(); + where T : class, ICanWrapData; #endregion } \ No newline at end of file diff --git a/Src/Sxc/ToSic.Sxc/Apps/Internal/AppDataTyped.cs b/Src/Sxc/ToSic.Sxc/Apps/Internal/AppDataTyped.cs index 1230dbb5e..ee55be9de 100644 --- a/Src/Sxc/ToSic.Sxc/Apps/Internal/AppDataTyped.cs +++ b/Src/Sxc/ToSic.Sxc/Apps/Internal/AppDataTyped.cs @@ -1,4 +1,5 @@ using ToSic.Eav.Apps.Internal.Api01; +using ToSic.Eav.DataSource; using ToSic.Eav.DataSource.Internal.Caching; using ToSic.Eav.DataSources.Internal; using ToSic.Lib.DI; @@ -42,10 +43,31 @@ private ServiceKit16 Kit /// IEnumerable IAppDataTyped.GetAll(NoParamOrder protector, string typeName, bool nullIfNotFound) { - var streamName = typeName ?? DataModelAnalyzer.GetStreamName(); + //var streamName = typeName ?? DataModelAnalyzer.GetStreamName(); + //var errStreamName = streamName; + + var streamNames = typeName == null + ? DataModelAnalyzer.GetStreamNameList() + : ([typeName], typeName); // Get the list - will be null if not found - var list = GetStream(streamName, nullIfNotFound: nullIfNotFound); + IDataStream list = null; + foreach (var streamName2 in streamNames.List) + list ??= GetStream(streamName2, nullIfNotFound: true); + + + //// If we didn't find it, check if the stream name is *Model and try without that common suffix + //if (list == null && streamName.EndsWith("Model")) + //{ + // var shorterName = streamName.Substring(0, streamName.Length - 5); + // errStreamName += "," + shorterName; + // list = GetStream(shorterName, nullIfNotFound: true); + //} + + // If we didn't find anything yet, then we must now try to re-access the stream + // but in a way which will throw an exception with the expected stream names + if (list == null && !nullIfNotFound) + list = GetStream(/*errStreamName*/streamNames.Flat, nullIfNotFound: false); return list == null ? null diff --git a/Src/Sxc/ToSic.Sxc/Custom.Data/CustomItem.cs b/Src/Sxc/ToSic.Sxc/Custom.Data/CustomItem.cs index 0acbee8a1..c13495c21 100644 --- a/Src/Sxc/ToSic.Sxc/Custom.Data/CustomItem.cs +++ b/Src/Sxc/ToSic.Sxc/Custom.Data/CustomItem.cs @@ -30,7 +30,7 @@ namespace Custom.Data; /// ```c# /// namespace AppCode.Data /// { -/// class MyPerson : CustomItem +/// class MyPerson : Custom.Data.CustomItem /// { /// // New custom property /// public string Name => _item.String("Name"); @@ -273,7 +273,8 @@ public IEnumerable Parents(NoParamOrder noParamOrder = default, stri public IMetadata Metadata => _item.Metadata; /// - public IField Field(string name, NoParamOrder noParamOrder = default, bool? required = default) => _item.Field(name, noParamOrder, required); + public IField Field(string name, NoParamOrder noParamOrder = default, bool? required = default) + => _item.Field(name, noParamOrder, required); #region Core Data: Id, Guid, Title, Type @@ -365,6 +366,7 @@ protected IEnumerable AsList(IEnumerable source, NoParamOrder /// /// Get by name should never throw an error, as it's used to get null if not found. /// - object ICanGetByName.Get(string name) => (this as ITypedItem).Get(name, required: false); + object ICanGetByName.Get(string name) + => (this as ITypedItem).Get(name, required: false); } \ No newline at end of file diff --git a/Src/Sxc/ToSic.Sxc/Custom.Data/CustomModel.cs b/Src/Sxc/ToSic.Sxc/Custom.Data/CustomModel.cs new file mode 100644 index 000000000..96b0eb320 --- /dev/null +++ b/Src/Sxc/ToSic.Sxc/Custom.Data/CustomModel.cs @@ -0,0 +1,23 @@ +using ToSic.Sxc.Data.Models; + +// ReSharper disable once CheckNamespace +namespace Custom.Data; + +/// +/// Base class for custom models. Similar to but without predefined public properties or methods. +/// +/// +/// This is a lightweight custom object which doesn't have public properties +/// like `Id` or methods such as `String(...)`. +/// +/// It's ideal for data models which need full control, +/// like for serializing or just to reduce the API surface. +/// +/// You can access the underlying (protected) `_item` property to get the raw data. +/// And it also has the (protected) `As<...>()` conversion for typed sub-properties. +/// +/// History: New in 19.03 +/// +[PublicApi] +[ModelSource(ContentTypes = "*")] +public class CustomModel: ModelFromItem; \ No newline at end of file diff --git a/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/CodeDataFactory_AsCustom.cs b/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/CodeDataFactory_AsCustom.cs index 8af32a1ba..3605ab0da 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/CodeDataFactory_AsCustom.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/CodeDataFactory_AsCustom.cs @@ -31,19 +31,31 @@ public TCustom AsCustomFrom(TData item) var bestType = DataModelAnalyzer.GetTargetType(); var newT = ActivatorUtilities.CreateInstance(serviceProvider, bestType) as TCustom; - // Should be an ITypedItemWrapper, but not enforced in the signature - if (newT is ICanWrap withMatchingSetup) - withMatchingSetup.Setup(item, this); - // DataModelOfEntity can also be filled from Typed (but ATM not the other way around) - else if (newT is ICanWrap forEntity && item is ICanBeEntity canBeEntity) - forEntity.Setup(canBeEntity.Entity, this); - else - throw new($"The custom type {typeof(TCustom).Name} does not implement {nameof(ICanWrap)}. This is probably a mistake."); - return newT; + switch (newT) + { + // Should be an ITypedItemWrapper, but not enforced in the signature + case ICanWrap withMatchingSetup: + withMatchingSetup.Setup(item, this); + return newT; + // In some cases the type of the data is already a model, so we need to unwrap it + case ICanWrap forItem when item is ICanBeItem canBeItem: + forItem.Setup(canBeItem.Item, this); + return newT; + // DataModelOfEntity can also be filled from Typed (but ATM not the other way around) + case ICanWrap forEntity when item is ICanBeEntity canBeEntity: + forEntity.Setup(canBeEntity.Entity, this); + return newT; + // In some cases we can only wrap an item, but the data is an entity-based model + case ICanWrap forTypedItem when item is ICanBeEntity canBeEntity: + forTypedItem.Setup(AsItem(canBeEntity.Entity), this); + return newT; + default: + throw new($"The custom type {typeof(TCustom).Name} does not implement {nameof(ICanWrap)}. This is probably a mistake."); + } } internal TCustom GetOne(Func getItem, object id, bool skipTypeCheck) - where TCustom : class, ICanWrapData, new() + where TCustom : class, ICanWrapData { var item = getItem(); if (item == null) @@ -54,10 +66,17 @@ internal TCustom GetOne(Func getItem, object id, bool skipType return AsCustom(item); // Do Type-Name check - var typeName = DataModelAnalyzer.GetContentTypeNames().Split(','); - if (typeName.FirstOrDefault() != ModelSourceAttribute.ForAnyContentType && !typeName.Any(tn => item.Type.Is(tn))) - throw new($"Item with ID {id} is not a {typeName}. This is probably a mistake, otherwise use {nameof(skipTypeCheck)}: true"); - return AsCustom(item); + var typeNames = DataModelAnalyzer.GetContentTypeNamesList(); + + // Check all type names if they are `*` or match the data ContentType + if (typeNames.List.Any(t => t == ModelSourceAttribute.ForAnyContentType || item.Type.Is(t))) + return AsCustom(item); + + throw new( + $"Item with ID {id} is not a '{typeNames.Flat}'. " + + $"This is probably a mistake, otherwise use '{nameof(skipTypeCheck)}: true' " + + $"or apply an attribute [{nameof(ModelSourceAttribute)}({nameof(ModelSourceAttribute.ContentTypes)} = \"expected-type-name\")] to your model class. " + ); } diff --git a/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/DataModelAnalyzer.cs b/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/DataModelAnalyzer.cs index 867b42003..9fa39d7c9 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/DataModelAnalyzer.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Internal/Factory/DataModelAnalyzer.cs @@ -13,7 +13,7 @@ internal class DataModelAnalyzer /// /// /// - internal static string GetContentTypeNames() where TCustom : ICanWrapData => + internal static string GetContentTypeNameCsv() where TCustom : ICanWrapData => ContentTypeNames.Get(a => { // If we have an attribute, use the value provided (unless not specified) @@ -31,18 +31,61 @@ internal static string GetContentTypeNames() where TCustom : ICanWrapDa }); private static readonly ClassAttributeLookup ContentTypeNames = new(); + /// + /// Figure out the expected ContentTypeName of a DataWrapper type. + /// If it is decorated with then use the information it provides, otherwise + /// use the type name. + /// + /// + /// + internal static (List List, string Flat) GetContentTypeNamesList() where TCustom : ICanWrapData + => ContentTypeNamesList.Get(a => UseSpecifiedNameOrDeriveFromType(a?.ContentTypes)); + private static readonly ClassAttributeLookup<(List List, string Flat)> ContentTypeNamesList = new(); + /// /// Get the stream names of the current type. /// /// /// - internal static string GetStreamName() where TCustom : ICanWrapData => - StreamNames.Get(a => - // if we have the attribute, use that - a?.Streams.Split(',').First().Trim() - // If no attribute, use name of type - ?? typeof(TCustom).Name); - private static readonly ClassAttributeLookup StreamNames = new(); + internal static (List List, string Flat) GetStreamNameList() where TCustom : ICanWrapData + => StreamNames.Get(attribute => UseSpecifiedNameOrDeriveFromType(attribute?.Streams)); + + private static (List List, string Flat) UseSpecifiedNameOrDeriveFromType(string names) + where TCustom : ICanWrapData + { + var list = !string.IsNullOrWhiteSpace(names) + ? names.Split(',').Select(n => n.Trim()).ToList() + : CreateListOfNameVariants(typeof(TCustom).Name, typeof(TCustom).IsInterface); + return (list, string.Join(",", list)); + } + + private static readonly ClassAttributeLookup<(List List, string Flat)> StreamNames = new(); + + /// + /// Take a class/interface name and create a list + /// which also checks for the same name without leading "I" or without trailing "Model". + /// + private static List CreateListOfNameVariants(string name, bool isInterface) + { + // Start list with initial name + List result = [name]; + // Check if it ends with Model + var nameWithoutModelSuffix = name.EndsWith("Model") + ? name.Substring(0, name.Length - 5) + : null; + if (nameWithoutModelSuffix != null) + result.Add(nameWithoutModelSuffix); + + if (isInterface && name.Length > 1 && name.StartsWith("I") && char.IsUpper(name, 1)) + { + result.Add(name.Substring(1)); + if (nameWithoutModelSuffix != null) + result.Add(nameWithoutModelSuffix.Substring(1)); + } + + return result; + } + internal static Type GetTargetType() { diff --git a/Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelCreationAttribute.cs b/Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelCreationAttribute.cs index 7364ba4f4..1ec95f984 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelCreationAttribute.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelCreationAttribute.cs @@ -14,7 +14,7 @@ /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false)] [InternalApi_DoNotUse_MayChangeWithoutNotice] -public class ModelCreationAttribute: Attribute +public sealed class ModelCreationAttribute: Attribute { /// /// The type to use when creating a model of this interface. diff --git a/Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelSourceAttribute.cs b/Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelSourceAttribute.cs index 002195792..4d1c846af 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelSourceAttribute.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Models/Attributes/ModelSourceAttribute.cs @@ -17,9 +17,9 @@ namespace ToSic.Sxc.Data.Models; /// /// History: New / WIP in v19.01 /// -[Lib.Documentation.InternalApi_DoNotUse_MayChangeWithoutNotice("may change or rename at any time")] +[InternalApi_DoNotUse_MayChangeWithoutNotice("may change or rename at any time")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] -public class ModelSourceAttribute: Attribute +public sealed class ModelSourceAttribute: Attribute { /// /// Determines which content-type names are expected when converting to this data model. diff --git a/Src/Sxc/ToSic.Sxc/Data/Models/Internal/DataModelHelpers.cs b/Src/Sxc/ToSic.Sxc/Data/Models/Internal/DataModelHelpers.cs index 9b4ff980a..82c34a214 100644 --- a/Src/Sxc/ToSic.Sxc/Data/Models/Internal/DataModelHelpers.cs +++ b/Src/Sxc/ToSic.Sxc/Data/Models/Internal/DataModelHelpers.cs @@ -16,7 +16,11 @@ internal static TCustom As(IModelFactory modelFactory, object item) null => null, ITypedItem typedItem => modelFactory.AsCustomFrom(typedItem), IEntity entity => modelFactory.AsCustomFrom(entity), - _ => throw new($"Type {typeof(TCustom).Name} not supported, only {typeof(IEntity)} and {nameof(ITypedItem)} are allowed as data"), + ICanBeItem canBeItem => modelFactory.AsCustomFrom(canBeItem.Item), + ICanBeEntity canBeEntity => modelFactory.AsCustomFrom(canBeEntity.Entity), + _ => throw new( + $"Type {typeof(TCustom).Name} not supported. " + + $"Only {typeof(IEntity)}, {nameof(ITypedItem)}, {nameof(ICanBeEntity)} and {nameof(ICanBeItem)} are allowed as data"), };