Interface for ModelsBuilder Generated Models in v12
p
I have an abstract class into which I pass a type and the type is currently constrained to a class of type
PublishedContentModel
. In my abstract class I need to access the
ModelTypeAlias
property but can't since it is not defined anywhere in the
PublishedContentModel
hierarchy of classes, only directly on the generated class. What is the best way of tackling this? It would have been good if the models perhaps implemented an interface that defined the available properties that are included on every generated class.
a
Hi @ProNotion , If I understand you correctly, you can use the _publishedSnapshotAccessor and do something like this:
Copy code
MyPublishedModel.GetModelPropertyType(_publishedSnapshotAccessor, x => x.CustomTitle)?.Alias);
p
@Ambert Thanks for the reply. Here is the class definition:
Copy code
public abstract class BaseDoctypeApiController<T> : ContentApiItemControllerBase where T : PublishedContentModel
I am then trying to get the ContentType as follows:
Copy code
var publishedContentItems =
            publishedSnapshot.Content.GetByContentType(T.GetModelContentType(_publishedSnapshotAccessor)).Select(x => x.Key);
But this is where I am coming unstuck because
T.GetModelContentType
cannot be resolved by the compiler.
a
When you only have a reference to the type via generics, I don't think it's possible without the use of reflection. But something like this should do it:
Copy code
csharp
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Umbraco.Cms.Core.Models.PublishedContent;

namespace code.Packages.ModelsBuilder {

    public static class ModelsBuilderUtils {

        public static bool TryGetModelTypeAlias<TModel>([NotNullWhen(true)] out string? result) where TModel : PublishedContentModel {

            // Get a reference to the "ModelTypeAlias" field via reflection
            var field = typeof(TModel)
                .GetField("ModelTypeAlias", BindingFlags.Public | BindingFlags.Static);

            // Get the value of the field
            result = field?
                .GetValue(null) as string;

            // Method is successful if "result" is not null
            return result is not null;

        }

    }

}
p
> When you only have a reference to the type via generics, I don't think it's possible without the use of reflection. I was starting to think that might be the case. I was also surprised that there was no interface for the models and as far as I can tell no ability to define one of your own. Thanks for the example, I will take a look at that approach.
a
Declaring interfaces with static methods is not something that has been possible prior to .NET 7. But with .NET 7 you should be able to create a common interface that declares the static fields and methods normally added by Models Builder. Then if your generated methods implement this interface, you can refer to the interface instead of
PublishedContentModel
. I guess it makes sense to add this to Umbraco, but for now you have to do this on your own. I'm maintaining my own version of Models Builder that has some extended features, so I might look into adding something there, as having a shared type declaring these fields and methods does make good sense. https://marketplace.umbraco.com/package/limbo.umbraco.modelsbuilder
p
Thanks very much for sharing this @Anders Bjerner - looks like something I could be interested in.
a
On second thought - this might not be possible 🤔 It might be for the static methods, and if
ModelTypeAlias
was a property. But it's a field/constant, so not sure they can be declared in an interface and then classes implementing the interface would have to implement it as well.
p
At this stage all I really need is to be able to call is
T.GetModelContentType(_publishedSnapshotAccessor)
so you have given me some new avenues to explore, thank you.
a
At least for
GetModelPropertyType
, as it's a method, you can create an interface like this (I think - I haven't tested it yet):
Copy code
csharp
    public interface IPublishedContentModel<TModel> {

        [return: global::System.Diagnostics.CodeAnalysis.MaybeNull]
        static abstract IPublishedPropertyType GetModelPropertyType<TValue>(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression<Func<TModel, TValue>> selector);

    }
p
But the generated models would need to inherit that interface and there is no way to add that to the models is there?
a
The process is very manual, but for each generated model you can add a partial class. You can then make your partial class implement the interface. I think that should work - also with the native ModelsBuilder.
p
Yes, but a lot of manual work and potential step that may be missed in the future so thinking that for the moment reflection might be the better option. I'm just putting something together to test.
It's not the route I wanted to take but for now I've just modified my constructor on the Base class to take a string with the content type alias - I can pass it in using
MyModel.ModelTypeAlias
then in the base controller I can get the content type using
Copy code
var contentType =  publishedSnapshot.Content.GetContentType(_contentTypeAlias);
var publishedContentItems =           publishedSnapshot.Content.GetByContentType(contentType);
It would have been preferred to do it like this:
Copy code
public class MyApiController : BaseDoctypeApiController<MyModel>
...but time is not on my side at the moment so will try and come back to it later on.
3 Views