Search code examples
c#sitecoresitecore8glass-mapper

Sitecore Lucene Search With GlassMapper not working


I have been trying to teach myself Sitecore for the past couple of weeks.
At the moment i am trying to create a list of Recipes for users to search through.

However every Recipe contains Ingredients, Lucene returned these Ingredients as strings containing Item ID's. I wanted to have a List of Ingredients in my code so i gave GlassMapper a shot.

So i excluded the Ingredient list in my code from Lucene by changing the name so Lucene couldn't find the field. I then set-up GlassMapper to fill the Ingredient list. The list stays null however.

How do i make GlassMapper fill this list for me?

My code:
Recipe class

[SitecoreType(TemplateId= "{1CF86642-6EC5-4B26-B8A7-1B2EC41F7783}")]
public class Recipe : SearchResultItem
{
    [SitecoreId]
    public Guid Id { get { return base.ItemId.Guid; } }
    public virtual string RecipeName { get; set; }
    public virtual string BookName { get; set; }
    public virtual IEnumerable<Ingredient> _Ingredients { get; set; }
    public virtual int AmountOfPeople { get; set; }
}

Ingredient class

[SitecoreType(TemplateId = "{730A0D54-A697-4DAA-908A-279CD24A9F41}")]
public class Ingredient : SearchResultItem
{
    [SitecoreId]
    Guid Id { get; }
    [IndexField("Name")]
    public virtual string IngredientName { get; set; }
}

GlassMapperScCustom class (I've only edited this method)

    public static IConfigurationLoader[] GlassLoaders()
    {
        var attributes = new SitecoreAttributeConfigurationLoader("Receptenboek");

        var loader = new SitecoreFluentConfigurationLoader();
        var config = loader.Add<Recipe>();


        config.Id(x => x.ItemId);
        config.Info(x => x.Language).InfoType(SitecoreInfoType.Language);
        config.Info(x => x.Version).InfoType(SitecoreInfoType.Version);

        config.Field(x => x._Ingredients);
        config.Info(x => x.Uri).InfoType(SitecoreInfoType.Url);
        return new IConfigurationLoader[] {attributes, loader };

    }

Recipe Controller

    [HttpGet]
    public ActionResult Index() 
    {
        List<Recipe> recipes;
        IQueryable<Recipe> query;

        string index = string.Format("sitecore_{0}_index", Sitecore.Context.Database.Name);
        var sitecoreService = new SitecoreService(Sitecore.Context.Database.Name);
        string search = WebUtil.GetQueryString("search");

        using (var context = ContentSearchManager.GetIndex(index).CreateSearchContext())
        {
            if (!string.IsNullOrEmpty(search))
            {
                query = context.GetQueryable<Recipe>().Where(p => p.Path.Contains("/sitecore/Content/Home/Recipes/")).Where(p => p.TemplateName == "Recipe").Where(p => p.RecipeName.Contains(search));
            }
            else
            {
                search = "";
                query = context.GetQueryable<Recipe>().Where(p => p.Path.Contains("/sitecore/Content/Home/Recipes/")).Where(p => p.TemplateName == "Recipe");
            }
            recipes = query.ToList();
            foreach( var r in recipes)
            {
                sitecoreService.Map(r);
                Sitecore.Diagnostics.Log.Audit("SWELF" + r.RecipeName + "- " + r.BookName + " -  " + r.AmountOfPeople + " - " + r.Name + "--" +  r._Ingredients.Count(), this);

            }
        }
        RecipesViewModel bvm = new RecipesViewModel() { Recipes = recipes, Search = search };
        return View(bvm);
    }

Solution

  • After playing around for a while i decided to split my search and mapping a little more. I used my Recipe model with Lucene and created a ViewModel to map the fields to with GlassMapper.

    The Recipe class did not change.
    The Ingredient class did not change.
    The GlassMapperScCustom class was not needed so i restored its default.

    RecipeViewModel class
    After mapping to this class the Ingredient list had the correct amount of ingredients however all its fields where null. After looking around on the internet a little more i found this stackoverflow post: Why isn't my Enumerable getting populated by Glass.Mapper?
    I decided to give the SitecoreFieldType a go and it did the trick!

    [SitecoreType(TemplateId = "{1CF86642-6EC5-4B26-B8A7-1B2EC41F7783}", AutoMap = true)]
    public class RecipeViewModel : BaseFields
    {
        [SitecoreId]
        public ID Id { get; set; }
        public virtual string RecipeName { get; set; }
        public virtual string BookName { get; set; }
        [SitecoreField(FieldId = "{D1603482-7CBC-4E55-9CCB-E51DC0FC5A0B}", FieldType = SitecoreFieldType.Multilist)]
        public virtual IEnumerable<IngredientViewModel> Ingredients { get; set; }
        public virtual int AmountOfPeople { get; set; }
    }
    

    Recipe Controller
    It turned out to be mapping the wrong way aswel. I found an example of a list being mapped to another list using SitecoreService.GetItem<>()

        [HttpGet]
        public ActionResult Index()
        {
            List<RecipeViewModel> recipes;
            List<Recipe> query;
    
            string index = string.Format("sitecore_{0}_index", Sitecore.Context.Database.Name);
            var sitecoreService = new SitecoreService(Sitecore.Context.Database.Name);
            string search = WebUtil.GetQueryString("search");
    
            //Search with Lucene
            using (var context = ContentSearchManager.GetIndex(index).CreateSearchContext())
            {
                if (!string.IsNullOrEmpty(search))
                {
                    query = context.GetQueryable<Recipe>().Where(p => p.Path.Contains("/sitecore/Content/Home/Recipes/")).Where(p => p.TemplateName == "Recipe").Where(p => p.RecipeName.Contains(search)).ToList();
                }
                else
                {
                    search = "";
                    query = context.GetQueryable<Recipe>().Where(p => p.Path.Contains("/sitecore/Content/Home/Recipes/")).Where(p => p.TemplateName == "Recipe").ToList();
                }
            }
            //Map to ViewModel
            recipes = query.Select(x => sitecoreService.GetItem<RecipeViewModel>(x.ItemId.Guid)).ToList();
    
            RecipesViewModel bvm = new RecipesViewModel() { Recipes = recipes, Search = search };
            return View(bvm);
        }
    

    One more problem
    Because my ViewModel does not inherit from SearchResultItem a lot of useful fields where lost in the mapping. To keep the fields I needed from the SearchResultItem I made a BaseFields class for my ViewModel to inherit. I only needed Url for now but this can be easily expanded with more fields.

    public class BaseFields
    {
        public virtual string Url { get; set; }
    }