The glass mapper will return null object or (no items) for SitecoreQuery and SitecoreChildren attribute that are placed on the GlassModels. These attributes don't take any such parameter where I can specify them to return items if they don't exist in the the context lanaguge. The items e.g. exist in EN but don't exist in en-ES. I need to put a lot of null check in my views to avoid Null exception and makes the views or controller very messy. It is lot of boiler plate code that one has to write to make it work. In Page Editor the SitecoreChildren returns item and content authors can create items in that langauge version by editing any field on the item. This automatically creates the item in that langauge. However the same code will fail in Preview mode as SitecoreChidren will return null and you see null pointer exception. SitecoreQuery doesn't return any items in page editor and then Content Authors wont be able to create items in Page editor. To make the experience good if we can pass a parameter to SiteocreQuery attribute so it disable VsersionCount and returns the items if they dont exist in that langauge.
-
Could you send me an example of the model, rendering and error message you receive to mike @ glass.lu? – Michael Edwards Oct 30 '14 at 18:12
-
Michael I will try to formulate an email and send it over to you. What Kevin suggested below is working like a charm. – T Malik Oct 31 '14 at 01:13
1 Answers
This is actually not possible. There is an issue on GitHub which would make it easy to create a custom attribute to handle this very easy. Currently you need to create a new type mapper and copy all the code from the SitecoreQueryMapper
. I have written a blog post here about how you can create a custom type mapper. You need to create the following classes (example for the SitecoreQuery
).
New configuration:
public class SitecoreSharedQueryConfiguration : SitecoreQueryConfiguration
{
}
New attribute:
public class SitecoreSharedQueryAttribute : SitecoreQueryAttribute
{
public SitecoreSharedQueryAttribute(string query) : base(query)
{
}
public override AbstractPropertyConfiguration Configure(PropertyInfo propertyInfo)
{
var config = new SitecoreSharedQueryConfiguration();
this.Configure(propertyInfo, config);
return config;
}
}
New type mapper:
public class SitecoreSharedQueryTypeMapper : SitecoreQueryMapper
{
public SitecoreSharedQueryTypeMapper(IEnumerable<ISitecoreQueryParameter> parameters)
: base(parameters)
{
}
public override object MapToProperty(AbstractDataMappingContext mappingContext)
{
var scConfig = Configuration as SitecoreQueryConfiguration;
var scContext = mappingContext as SitecoreDataMappingContext;
using (new VersionCountDisabler())
{
if (scConfig != null && scContext != null)
{
string query = this.ParseQuery(scConfig.Query, scContext.Item);
if (scConfig.PropertyInfo.PropertyType.IsGenericType)
{
Type outerType = Glass.Mapper.Sc.Utilities.GetGenericOuter(scConfig.PropertyInfo.PropertyType);
if (typeof(IEnumerable<>) == outerType)
{
Type genericType = Utilities.GetGenericArgument(scConfig.PropertyInfo.PropertyType);
Func<IEnumerable<Item>> getItems;
if (scConfig.IsRelative)
{
getItems = () =>
{
try
{
return scContext.Item.Axes.SelectItems(query);
}
catch (Exception ex)
{
throw new MapperException("Failed to perform query {0}".Formatted(query), ex);
}
};
}
else
{
getItems = () =>
{
if (scConfig.UseQueryContext)
{
var conQuery = new Query(query);
var queryContext = new QueryContext(scContext.Item.Database.DataManager);
object obj = conQuery.Execute(queryContext);
var contextArray = obj as QueryContext[];
var context = obj as QueryContext;
if (contextArray == null)
contextArray = new[] { context };
return contextArray.Select(x => scContext.Item.Database.GetItem(x.ID));
}
return scContext.Item.Database.SelectItems(query);
};
}
return Glass.Mapper.Sc.Utilities.CreateGenericType(typeof(ItemEnumerable<>), new[] { genericType }, getItems, scConfig.IsLazy, scConfig.InferType, scContext.Service);
}
throw new NotSupportedException("Generic type not supported {0}. Must be IEnumerable<>.".Formatted(outerType.FullName));
}
{
Item result;
if (scConfig.IsRelative)
{
result = scContext.Item.Axes.SelectSingleItem(query);
}
else
{
result = scContext.Item.Database.SelectSingleItem(query);
}
return scContext.Service.CreateType(scConfig.PropertyInfo.PropertyType, result, scConfig.IsLazy, scConfig.InferType, null);
}
}
}
return null;
}
public override bool CanHandle(AbstractPropertyConfiguration configuration, Context context)
{
return configuration is SitecoreSharedQueryConfiguration;
}
}
And configure the new type mapper in your glass config (mapper and parameters for the constructor):
container.Register(Component.For<AbstractDataMapper>().ImplementedBy<SitecoreSharedQueryTypeMapper>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemPathParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemIdParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemIdNoBracketsParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemEscapedPathParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemDateNowParameter>>().LifeStyle.Transient);
You can then simply change the SitecoreQuery
attribute on your model to SitecoreSharedQuery
:
[SitecoreSharedQuery("./*")]
public virtual IEnumerable<YourModel> YourItems { get; set; }
For the children you could either use the shared query mapper and querying the children or create the same classes for a new SitecoreSharedChildren
query.
Edit: Added bindings for IEnumerable<ISitecoreQueryParameter>
as they are missing and therefor it threw an error.

- 4,717
- 3
- 24
- 47
-
Thanks Kevin. It is quite tricky to get this to work. I haven't tried this and will give it a try. After puting more thought into it, to me it seems that Glass mapper is sort of doing the right thing. Otherwise how would you handle scenarios where Content don't want to show certain items in that langauge and if we are forcing it like this then we will never be able to achieve it. I know that if we use Fallback module than it helps it by putting setting the tempaltes for which version enfrocing is required. – T Malik Oct 30 '14 at 01:19
-
In addition I think that this solution gives us some flexibility and we can choose to have this behavior for certain templates. So it is definitely worth a try and thanks again for taking your time to document it. Hopefully I will give it a try in day or 2 and mark it answered. – T Malik Oct 30 '14 at 01:21
-
Couldn't resit and tried it, I am getting this error When I register the component : Can't create component 'Company.GlassExtensions.SitecoreSharedQueryTypeMapper' as it has dependencies to be satisfied. 'Company.GlassExtensions.SitecoreSharedQueryTypeMapper' is waiting for the following dependencies: - Service 'System.Collections.Generic.IEnumerable`1[[Glass.Mapper.Sc.DataMappers.SitecoreQueryParameters.ISitecoreQueryParameter, Glass.Mapper.Sc, Version=3.2.2.44, Culture=neutral, PublicKeyToken=null]]' which was not registered. – T Malik Oct 30 '14 at 03:00
-
Also Just to calrify I tried to register before and after container.Install(new SitecoreInstaller(config)); but same result. In the SitecoreInstaller() class I do see these components being registered. So not sure what is going on. – T Malik Oct 30 '14 at 03:10
-
@TMalik did you read my blog post (http://ctor.io/shared-field-references-in-glass-mapper/)? There is a bit a better example on how to register the mapper. And you are completely right with your first comment. You should not use this mapper for all your properties. We use this property only if all items we want to map only have shared fields and therefor don't need a version in each language. Otherwise consider to use the normal `SitecoreQuery` attribute. – Kevin Brechbühl Oct 30 '14 at 06:08
-
I read your blog and it is related to Sitecore Field. Based on that I have following code var config = new Config(); container.Register(Component.For
().ImplementedBy – T Malik Oct 30 '14 at 13:13().LifeStyle.Transient); container.Install(new SitecoreInstaller(config));. But still I get the same error. I dont think I need to copy any code from your blog as it is related to Sitecore Field. This solution for SitecoreQuery attribute. -
@TMalik yes, just referenced my blog post as the procedure (what to add etc.) is the same, just the code different. But I forgot something, you need to configure also the bindings for `IEnumerable
` in the glass configuration. I've updated my answer. Hope that solves the problem. – Kevin Brechbühl Oct 30 '14 at 13:50 -
Thanks a bunch Kevin and this actually worked. My only confusion is why do we have to register for all these components. Isn't the GM already doing it in the SitecoreInstall() ? – T Malik Oct 30 '14 at 16:33
-
@TMalik maybe the configuration only fits to the the default `SitecoreQuery` mapper. I actually don't know exactly why this is needed sorry. – Kevin Brechbühl Oct 30 '14 at 16:35
-
I guess we need Mike Edwards to answer this question. Hopefully he will one day stumble upon this post :) – T Malik Oct 30 '14 at 16:37