Custom Attribute Searching in NopCommerce 3.3 Part 2 - The Dynamic Way

In my previous article, i showed you how you can do custom attribute searching nopCommerce 3.3. However, that was not the recommend and right way, in this article i will show you can make it and follow the dynamic way.

So, first i will create a partial view named _SearchPartial inside the Themes -> DefaultClean -> Views -> Shared directory to make it available for all views to be used for searching. Now, inside this partial view, i will first pull categories from database. So, i will first import some nopCommerce namespaces and then write code to pull out categories from database;
@using Nop.Core.Infrastructure;
@using Nop.Core;
@using Nop.Core.Caching;
@using Nop.Services.Catalog;
@using Nop.Services.Localization;
@using Nop.Core.Domain.Catalog;
@{
    var AvailableCategories = new List<SelectListItem>();
    var customerRolesIds = EngineContext.Current.Resolve<IWorkContext>().CurrentCustomer.CustomerRoles
                .Where(cr => cr.Active).Select(cr => cr.Id).ToList();

    string cacheKey = string.Format(Nop.Web.Infrastructure.Cache.ModelCacheEventConsumer.SEARCH_CATEGORIES_MODEL_KEY,
         EngineContext.Current.Resolve<IWorkContext>().WorkingLanguage.Id, string.Join(",", customerRolesIds),
         EngineContext.Current.Resolve<IStoreContext>().CurrentStore.Id);
    var categories = EngineContext.Current.Resolve<ICacheManager>().Get(cacheKey, () =>
    {
        var categoriesModel = new List<Nop.Web.Models.Catalog.SearchPagingFilteringModel.CategoryModel>();
        //all categories
        foreach (var c in EngineContext.Current.Resolve<ICategoryService>().GetAllCategories())
        {
            //generate full category name (breadcrumb)
            string categoryBreadcrumb = "";
            var breadcrumb = c.GetCategoryBreadCrumb(EngineContext.Current.Resolve<ICategoryService>(),
                EngineContext.Current.Resolve<Nop.Services.Security.IAclService>(),
                EngineContext.Current.Resolve<Nop.Services.Stores.IStoreMappingService>());
            for (int i = 0; i <= breadcrumb.Count - 1; i++)
            {
                categoryBreadcrumb += breadcrumb[i].GetLocalized(x => x.Name);
                if (i != breadcrumb.Count - 1)
                    categoryBreadcrumb += " >> ";
            }
            categoriesModel.Add(new Nop.Web.Models.Catalog.SearchPagingFilteringModel.CategoryModel()
            {
                Id = c.Id,
                Breadcrumb = categoryBreadcrumb
            });
        }
        return categoriesModel;
    });
    if (categories.Count > 0)
    {
        //first empty entry
        AvailableCategories.Add(new SelectListItem()
        {
            Value = "",
            Text = @T("Categories") + ": [" + EngineContext.Current.Resolve<ILocalizationService>().GetResource("common.all") + "]"
        });
        //all other categories
        foreach (var c in categories)
        {
            AvailableCategories.Add(new SelectListItem()
            {
                Value = c.Id.ToString(),
                Text = c.Breadcrumb
                //Selected = !String.IsNullOrEmpty(Request.QueryString["Cid"]) ? c.Id == Convert.ToInt32(Request.QueryString["Cid"]) : false
            });
        }
    }

As you can see at the top, i have imported the out of the box namespaces and then i am calling the interfaces through dependency resolver of nopCommerce and then simply adding the items into a SelectListItem.

In similar fashon i can query now the specification attributes as shown below;
    //specs
    var specs = EngineContext.Current.Resolve<ISpecificationAttributeService>().GetSpecificationAttributes().Take(4);
    var allFilters = new List<SpecificationAttributeOptionFilter>();

    foreach (var sa in specs)
    {
        var sepcAttributes = EngineContext.Current.Resolve<ISpecificationAttributeService>().
                                    GetSpecificationAttributeOptionsBySpecificationAttribute(sa.Id);
        foreach (var sao in sepcAttributes)
        {
            allFilters.Add(new SpecificationAttributeOptionFilter
            {
                SpecificationAttributeId = sa.Id,
                SpecificationAttributeName = sa.GetLocalized(x => x.Name, EngineContext.Current.Resolve<IWorkContext>().WorkingLanguage.Id),
                SpecificationAttributeDisplayOrder = sa.DisplayOrder,
                SpecificationAttributeOptionId = sao.Id,
                SpecificationAttributeOptionName = sao.GetLocalized(x => x.Name, EngineContext.Current.Resolve<IWorkContext>().WorkingLanguage.Id),
                SpecificationAttributeOptionDisplayOrder = sao.DisplayOrder
            });
        }

        //sort loaded options
        allFilters = allFilters.OrderBy(saof => saof.SpecificationAttributeDisplayOrder)
            .ThenBy(saof => saof.SpecificationAttributeName)
            .ThenBy(saof => saof.SpecificationAttributeOptionDisplayOrder)
            .ThenBy(saof => saof.SpecificationAttributeOptionName).ToList();
    }

Next i will write a code to display all this information in  dropdown;

    //Display
    if (AvailableCategories.Count > 0)
    {
    <div class="control-group">
        <div class="controls">
        @Html.DropDownList("Cid", AvailableCategories)
    </div> 
    </div> 
    }

    //specs display
    var FilteredItemsGroups = allFilters.GroupBy(x => x.SpecificationAttributeName);
    foreach (var group in FilteredItemsGroups)
    {
    <div class="control-group">
        <div class="controls">
        @{
            var selectList = new List<SelectListItem>();
            //first empty entry
            selectList.Add(new SelectListItem()
            {
                Value = "",
                Text = group.Key + ": [" + EngineContext.Current.Resolve<ILocalizationService>().GetResource("common.all") + "]"
            });
            foreach (var sao in group)
            {
                selectList.Add(new SelectListItem
                {
                    Text = sao.SpecificationAttributeOptionName,
                    Value = sao.SpecificationAttributeOptionId.ToString()
                });
            }
        }
            @Html.DropDownList("Specs", selectList, new { id = System.Text.RegularExpressions.Regex.Replace(group.Key, @"[^A-Za-z0-9_\.~]+", "-") })
        </div>
    </div>
    }
}

So, that's how we can pull out the attributes. Also at the end, i have used regular expression to give a unique id to each dropdownlist and remove the spaces. Now, i will use this partial view in my home page;

                    @using (Html.BeginRouteForm("ProductSearch", FormMethod.Get, new { @class = "form-horizontal", id = "quick-search" }))
                    {
                        <input type="hidden" name="Q" value="" />
                        <input type="hidden" name="Cid" value="true">
                        <input type="hidden" name="Isc" value="true">
                        <input type="hidden" name="As" value="true">
                        <table class="table table-hover table-striped table-border">
                            <tbody>
                                <tr>
                                    <td>
                                        @Html.Partial("_SearchPartial")
                                    </td>
                                    <td>
                                        <label class="inline">
                                            From
                                            <input class="price-from span4" id="Pf" name="Pf" type="text" value="" />
                                            To
                                            <input class="price-to valid span4" id="Pt" name="Pt" type="text" value="" />
                                        </label>
                                    </td>
                                    <td>
                                        <input type="submit" class="btn btn-primary pull-right btn-small" value="@T("Search")" />
                                    </td>

                                </tr>
                            </tbody>
                        </table>
                    }

You can style it the way you want, i will just style it quickly in a table and also keep the hidden fiends. If you want to put textbox then remove the input with name Q and name the textbox as Q (for query). So, that's it let's run it and see the results.

Search Category

Search URL

and here is the result.

Product Search Result

if you don't see the expected result, you can also to try to recheck and make sure that you have enable filtering in product specification attributes tab. That's it for today, i hope you find this useful and have a good day :)

5.0 2

Existing reviews

Source Code
Hi,
can you publishing the source code ?
From: Guest | Date: 12/5/2014 8:49 PM
Was this review helpful? Yes / No (0/1)
Copy and paste the code
You can copy and paste the code
From: Guest | Date: 2/14/2015 6:07 PM
Was this review helpful? Yes / No (0/0)


Write your own review
Bad Excellent