Sitecore: Generic GlassMapper GetChildren<T> Method - c#

I'd like to create a generic method to get glass casted items of T.
What I have so far is:
private static List<T> GetChildren<T>(Item parentItem) where T: class {
var lstChildItems = parentItem.Children.Where(child => child.TemplateID.Equals(T.TemplateId)).ToList();
List<T> lstChildren = lstChildItems.Select(c => c.GlassCast<T>()).ToList();
return lstChildren;
}
In my example T.TemplateId can't be resolved because T is only marked as class. Does TemplateId exist in some kind of interface or what do I have to enter instead of class?

If you want to get the TypeConfiguration:
var ctx = new SitecoreContext();
var typeConfig = ctx.GlassContext.TypeConfigurations[typeof(T)];
var templateId = (config as SitecoreTypeConfiguration).TemplateId;
//ofc check for nulls, but you get the point
But I personally like to utilize the InferType possibilities:
public interface ISitecoreItem
{
[SitecoreChildren(InferType = true)]
IEnumerable<ISitecoreItem> Children { get; set; }
}
[SitecoreType]
public class News : ISitecoreItem
{
public string Title { get; set; }
public virtual IEnumerable<ISitecoreItem> Children { get; set; }
}
private static IEnumerable<T> GetChildren<T>(this Item parentItem) where T : ISitecoreItem
{
var parentModel = item.GlassCast<ISitecoreItem>();
return parentModel.Children.OfType<T>();
}
//usage:
var newsItems = parentItem.GetChildren<News>();
The InferType option will give you the most specific available Type that Glass can find. So anything deriving from ISitecoreItem can be fetched like this.

Related

Adding items to a list without knowing the element type

I need to fill a list with specific data. This list is a property of another object. The elements of that List have the following rules:
They contain a int-Property named "Id" which is writable
They have a constructor without parameters
This is the code so far: (I did not add any plausiblity checks or error handling to keep this example simple)
private void SetListProperty(string propertyName, object target, int[] ids) {
PropertyInfo property=target.GetProperty(propertyName);
Type propertyType=property.PropertyType();
Type elementType=propertyType.GetElementType();
PropertyInfo elementId=elementType.GetProperty("Id");
var targetList=new List<>(elementType); // PseudoCode. Does not work
foreach (int id in ids) {
var element=Activator.CreateInstance(elementType);
elementId.SetValue(element, id);
targetList.Add(element); // PseudoCode. Does not work
}
property.SetValue(target, targetList);
}
Example for calling that method:
public class User {
public int Id {get;set;}
public string Name {get;set;}
}
public class House {
public int Id {get;set;}
public string HouseName {get;set;]
}
public class Group {
public string Name {get;set;}
public List<User> Users {get;set;}
public List<House> Houses {get;set;}
}
var group=new Group { Name="nerds"};
SetListProperty("Users"), group, new int[] {1,2,3,4,5});
SetListProperty("Houses"), group, new int[] {1,2,3,4,5});
So after calling that method, group should contain a property Users that has 5 elements each with the Id set.
I've seen similar questions here about creating a List of an unknown Type, but not how to actually add single items of an unknown type to that list.
(Update)
I assume my problem was not clear enough in the beginning. Inside that method I do NOT know the property Type. Not even if it is a list.
I only have the property name as string.
I scrapped the original answer (below) to truly address doing this without knowing the type of property OTHER than that it is either an item that has an id...or it is an enumerable of objects that have an Id.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace StackOverflowTests
{
[TestClass]
public class StackOverflow_49181925Tests
{
public interface IHasId
{
int Id { get; set; }
}
public class Foo : IHasId
{
public int Id { get; set; }
}
public class HasAFoo
{
public Foo Foo { get; set; }
}
public class HasManyFoos
{
public IEnumerable<Foo> Foos { get; set; }
}
public void SetPropertyIds(object target, string propertyName, IEnumerable<int> ids)
{
var property = target.GetType().GetProperty(propertyName);
var propertyType = property.PropertyType;
//is it enumerable?
if (typeof(IEnumerable).IsAssignableFrom(propertyType))
{
var objectType = propertyType.GetGenericArguments().First();
var list = Activator.CreateInstance(typeof(List<>).MakeGenericType(objectType)) as IList;
foreach(var id in ids)
{
var obj = Activator.CreateInstance(objectType) as IHasId;
((IHasId)obj).Id = id;
list.Add(obj);
}
property.SetValue(target, list);
}
else
{
if(ids.Count() != 1) throw new ApplicationException("You're trying to set multiple Ids to a single property.");
var objectType = propertyType;
var obj = Activator.CreateInstance(objectType);
((IHasId)obj).Id = ids.First();
property.SetValue(target, obj);
}
}
[TestMethod]
public void TestHasAFoo()
{
var target = new HasAFoo();
this.SetPropertyIds(target, "Foo", new[] { 1 });
Assert.AreEqual(target.Foo.Id, 1);
}
[TestMethod]
public void TestHasManyFoos()
{
var target = new HasManyFoos();
this.SetPropertyIds(target, "Foos", new[] { 1, 2 });
Assert.AreEqual(target.Foos.ElementAt(0).Id, 1);
Assert.AreEqual(target.Foos.ElementAt(1).Id, 2);
}
}
}
ORIGINAL ANSWER BELOW
Lots of different ways of accomplishing this. The third way which implements an interface can give you a lot of help since most of your business models probably have some sort of Id. The Linq version though is short and sweet. These are all just variations on a theme.
Using Linq:
group.Users = new[]{1,2,3}.Select(s=>new User{Id = s}).ToList();
Using Generics:
private IList<T> IntsToModels<T>(IEnumerable<int> ints, Action<T,int> setId) where T : new(){
return ints.Select(s=>{
var t = new T();
setId(t,s);
return t;
}).ToList();
}
Using Generics and Interfaces
public interface IHasID{
int Id{get;set;}
}
//implement the IHasID interface
public class User : IHasID...
private IEnumerable<T> ToModels(IEnumerable<int> ints) where T : IHasID, new(){
foreach(var i in ints){
yield return new T{Id = i};
}
}

Create object of type specified in a string

I have a bunch of classes generated by EF that are simple tables and have similar structures:
public class Contact
{
public int ID { get; set; }
public string Description { get; set; }
}
public class Member
{
public int ID { get; set; }
public string Description { get; set; }
}
I've also got a method for returning an object of a specified type:
public T GetInstance<T>(string type)
{
return (T)Activator.CreateInstance(Type.GetType(type));
}
What I want to do is something like this:
public ActionResult GetAll(string ClassType) // ClassType will be the name of one of the classes above
{
Object LookupType = GetInstance<Object>(ClassType);
List<LookupType> AllList = new List<LookupType>();
AllList = repo.GetAll<LookupType>().ToList<LookupType>(); // some generic method that will return a list;
}
This makes the compiler mad because I'm using a variable (LookupType) rather than a true type to build the list. However, neither of these work either:
List<LookupType.GetType()> Items = new List<LookupType.GetType()>();
List<typeof(LookupType)> Items = new List<typeof(LookupType)>();
Both cause an error - "Using generic type List requires 1 type argument".
Is there a proper way to do this? Is there a way to convert ClassType directly to a type without first making it an object (from which I hope to derive the type)?
Try using the CreateInstance method
SomeObject someObject = new SomeObject();
Type type = someObject.GetType();
Type listType = typeof(List<>).MakeGenericType(new [] { type });
IList list = (IList)Activator.CreateInstance(listType);
You cannot do it with C#!!
Compiler must to know the parameter type.
In that maybe you would like to accomplish, interfaces will help you
public class Contact: IIdDescription
{
public int ID { get; set; }
public string Description { get; set; }
}
public class Member: IIdDescription
{
public int ID { get; set; }
public string Description { get; set; }
}
public interface IIdDescription
{
int ID { get; set; }
string Description { get; set; }
}
public ActionResult GetAll(string type)
{
var AllList = new List<IIdDescription>();
if(type == Member.GetType().Name)
AllList = repo.Set<Member>().Cast<IIdDescription >().ToList();
if(type == Contact.GetType().Name)
AllList = repo.Set<Contact>().Cast<IIdDescription >().ToList();
...
}
and into your view use interface as model, something like
#model IEnumerable<IIdDescription>
If you don't know the type ahead of time maybe using a list of dynamic objects can help.
var item = GetInstance<Contact>("Namespace.Contact");
var items = new List<dynamic>();
items.Add(item);
You can then access the types like so...
Contact contact = items[0];
Just be aware that using dynamic can be expensive.

Get a generic type from JSON in WebAPI

I'd like to create a Web Api method that will accept JSON and a string with they name of the Type.
So far I have something like this:
public void Write(string typeName, string jsonData)
{
var myType = Type.GetType(typeName);
var fromJsonString = JsonConvert.DeserializeObject<OutgoingEnvelope<myType>>(jsonData);
}
OutgoingEnvelope would be defined as this:
public class OutgoingEnvelope<T>
{
public string TypeId { get; set; }
public OutgoingEnvelope()
{
Documents = new List<T>();
}
public List<T> Documents { get; set; }
}
Currently I'm getting the message:
'myType' is a variable but is used like a type.
Our ultimate goal is to be able to get JSON data and turn it into the appropriate class dynamically.
When you use a generic in that way the type must be known at compile time. It would be similar if you tried to create a new instance of myType using var instance = new myType();, this would also not compile for the same reason (more or less).
The deserializer on JsonConvert (see DeserializeObject) offers a non generic version that you could then cast later if you wanted to. This has some requirements though.
You have to define an interface for your container and optionally the generic type parameter
The generic interface parameter must be marked as covariant (out keyword)
This is a self contained example that executes, the only thing missing is getting the Type from a string instead of hard coded.
public class SomeController {
public void Write()
{
var objectToSerialize = new OutgoingEnvelope<SomeDocument>()
{
Documents = new List<SomeDocument>() {new SomeDocument() {Name = "Hi"}},
TypeId = "Some type"
};
var json = JsonConvert.SerializeObject(objectToSerialize);
// var myType = Type.GetType(typeName);
var myType = typeof(OutgoingEnvelope<SomeDocument>);
var fromJsonString = JsonConvert.DeserializeObject(json, myType) as IOutgoingEnvelope<IDocumentType>;
if(fromJsonString == null)
throw new NullReferenceException();
}
}
public interface IDocumentType
{
string Name { get; set; }
// known common members in the interface
}
public class SomeDocument : IDocumentType
{
public string Name { get; set; }
}
public interface IOutgoingEnvelope<T> where T : IDocumentType
{
string TypeId { get; set; }
IEnumerable<T> Documents { get; }
}
public class OutgoingEnvelope<T> : IOutgoingEnvelope<T> where T : IDocumentType
{
public string TypeId { get; set; }
public OutgoingEnvelope()
{
Documents = new List<T>();
}
public IEnumerable<T> Documents { get; set; }
}
try something like this:
var myType = Type.GetType(typeName);
var template = typeof(OutgoingEnvelope<>);
var typeToSet = template.MakeGenericType(myType);
var fromJsonString = JsonConvert.DeserializeObject<OutgoingEnvelope<typeToSet>>(jsonData);
it should work.

Get a collection based on generic type

I have a class that contains a number of generic collections of different types.
public class InMemoryDatabase
{
public HashSet<Car> Cars { get; set; }
public HashSet<Truck> Trucks { get; set; }
public HashSet<Bike> Bikes { get; set; }
}
How can I retrieve a collection based on the generic type?
E.g.
public HashSet<TEntity> GetCollection<TEntity>()
{
//how to retrieve collection from InMemoryDatabase class?
}
which could be called as follows:
HashSet<Bike> = GetCollection<Bike>();
Update
Note that the implementation of GetCollection method should have no prior knowledge of what types of collections are in the InMemoryDatabase class as it gets called from a base class. The solution should be generic. I guess using reflection could be one possible approach?
Sounds like you want one HashSet per type:
public class InMemoryDatabase
{
private Dictionary<Type, IEnumerable> _hashSets = new Dictionary<Type, IEnumerable>();
// Returns or creates a new HashSet for this type.
public HashSet<T> GetCollection<T>()
{
Type t = typeof(T);
if (!_hashSets.ContainsKey(t))
{
_hashSets[t] = new HashSet<T>();
}
return (_hashSets[t] as HashSet<T>);
}
}
There's multiple ways to do it. One way is to simply test the type and return the appropriate field:
public HashSet<TEntity> GetCollection<TEntity>()
{
var type = typeof(TEntity);
if(type == typeof(Bike))
return (HashSet<TEntity>)(object)Bikes;
if(type == typeof(Car))
return (HashSet<TEntity>)(object)Cars;
if(type == typeof(Truck))
return (HashSet<TEntity>)(object)Trucks;
throw new InvalidOperationException();
}
The intermediary cast to object is needed because the compiler doesn't know that TEntity is actually one of those types in that case, so we use type erasure to make sure the cast works. It should be optimized out during the JIT step since the method is built for that particular type.
This may seem a little crude, but it works even if you don't provide a valid type for TEntity...
public class InMemoryDatabase
{
public HashSet<TEntity> GetCollection<TEntity>()
{
var a = this.GetType().GetProperties();
HashSet<TEntity> retVal = null;
foreach (var name in a.Select(propertyInfo => propertyInfo.Name))
{
retVal = this.GetType().GetProperty(name).GetValue(this, null) as HashSet<TEntity>;
if (retVal != null)
{
break;
}
}
return retVal;
}
public HashSet<Car> Cars { get; set; }
public HashSet<Truck> Trucks { get; set; }
public HashSet<Bike> Bikes { get; set; }
}
The best approach I see is to reflect the class once and build a "collection selector map" to be used by the generic function like this
public class InMemoryDatabase
{
static readonly Dictionary<Type, Func<InMemoryDatabase, object>> collectionMap;
static InMemoryDatabase()
{
collectionMap =
(from p in typeof(InMemoryDatabase).GetProperties()
where p.CanRead &&
p.PropertyType.IsGenericType &&
p.PropertyType.GetGenericTypeDefinition() == typeof(HashSet<>)
select new
{
Type = p.PropertyType.GetGenericArguments()[0],
Selector = (Func<InMemoryDatabase, object>)Delegate.CreateDelegate(
typeof(Func<InMemoryDatabase, object>), p.GetMethod)
}).ToDictionary(e => e.Type, e => e.Selector);
}
public HashSet<TEntity> GetCollection<TEntity>()
{
return (HashSet<TEntity>)collectionMap[typeof(TEntity)](this);
}
// Collections
public HashSet<Car> Cars { get; set; }
public HashSet<Truck> Trucks { get; set; }
public HashSet<Bike> Bikes { get; set; }
// ...
}

Using Reflection and GetValue problems

I have an abstract class that looks like so:
public abstract class PageObjectsBase
{
public abstract string FriendlyName { get; }
public abstract string PageObjectKeyPrefix { get; }
public abstract string CollectionProperty { get; }
}
And a class that derives from PageObjectsBase:
public class PageRatingList : PageObjectsBase
{
public IList<PageRating> PageRatings { get; set; }
public PageRatingList()
{
this.PageRatings = new List<PageRating>();
}
public override string CollectionProperty
{
get
{
var collectionProperty = typeof(PageRatingList).GetProperties().FirstOrDefault(p => p.Name == "PageRatings");
return (collectionProperty != null) ? collectionProperty.Name : string.Empty;
}
}
public override string FriendlyName
{
get
{
return "Page feedback/rating";
}
}
public override string PageObjectKeyPrefix
{
get
{
return "pagerating-";
}
}
}
And a PageRating class which PageRatingList.PageRatings is holding a collection of:
public class PageRating : PageObjectBase
{
public int Score { get; set; }
public string Comment { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
The PageRatingList is being stored in a database (EPiServer's Dynamic Data Store, more specifically using the Page Object Manager). I need to create some reporting functionality and am essentially loading all reports that derive from PageObjectBase. When it comes to returning the data, the code will never know at compile time what type of data it is to load, so I am using Reflection. In my reporting class I have:
//this gives me the right type
var type = Type.GetType("MyNameSpace.PageRatingList", true);
var startPageData = this._contentRepository.Get<PageData>(startPage);
PageObjectManager pageObjectManager = new PageObjectManager(startPageData);
//this loads the instances from the DB
var props = pageObjectManager.LoadAllMetaObjects()
.FirstOrDefault(o => o.StoreName == "Sigma.CitizensAdvice.Web.Business.CustomEntity.PageRatingList");
//this gives me 4 PropertyInfo objects (IList: PageRatings, string : CollectionProperty, string :FriendlyName, string : PageObjectKeyPrefix)
var properties = props.Value.GetType().GetProperties();
I can then iterate through the PropertyInfo objects using:
foreach (var property in properties)
{
//extract property value here
}
The issue I am having is that I cannot figure out how to get the value of each of the propertyinfo objects. In addition, one of those properties is type List and again we wont know the type of T until runtime. So I also need some logic that checks if one of the PropertyInfo objects is of type List and then provides access to each of the properties in the List - the List being of type PageRating.
Can anyone help here? I've not really used reflection in the past so I am winging my way through it, rightly or wrongly!
Many thanks
Al
I may be missunderstanding the problem, but i think you may use something like this:
var props = new PageRatingList(); /*actual instanse of the object, in your case, i think "props.Value" */
var properties = typeof(PageRatingList).GetProperties();
foreach (var property in properties)
{
if (property.PropertyType == typeof(IList<PageRating>))
{
IList<PageRating> list = (IList<PageRating>)property.GetValue(props);
/* do */
}
else
{
object val = property.GetValue(props);
}
}
Hope this helps to find your solution.

Categories