Tuesday, May 9, 2017

Create ASP.NET MVC Web Site Search with Lucene.Net


I will show how to implement in few minutes search engine for your website or windows application or any other type of application.
It's pretty easy and I saw the very good performance as well.

I will be using Lucene.NET open source: https://lucenenet.apache.org/

High-level architecture diagram:


So, let get started from Indexing process, it's extremely simple code, I just using console application:

Page Model in order to create Pages list to Index. Phase II, you can change this list to a function that can crawl your pages in a recursive manner on your website, then you don't need to have this list.

 public class Page
 {        
        public string PageTitle { set; get; }

        public string PageBody { set; get; }

        public string PageUrl { set; get; }
 }

static void Main(string[] args)
{
            const string directoryPath = @"C:\GitSource\Search Web Client\App_Data\LuceneIndexes";
            _directory = FSDirectory.Open(directoryPath);
            var files = _directory.ListAll();

            //Delete previous Indexing Results
            foreach (var file in files)
            {
                _directory.DeleteFile(file);
            }

            Analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30);
            Writer = new IndexWriter(_directory, Analyzer, IndexWriter.MaxFieldLength.UNLIMITED);            
            CreateIndex();                                   
            Console.WriteLine("Indexing DONE");
            Console.ReadKey();

 }

private static void CreateIndex()
{
            var pages = new List<Page>
           {
                    new Page {PageTitle = "Index", PageUrl = "http://localhost:63461/"},
                    new Page {PageTitle = "About", PageUrl = "http://localhost:63461/Home/About"},
                    new Page {PageTitle = "Contact", PageUrl = "http://localhost:63461/Home/Contact"}
            };

             foreach (var page in pages)
            {
                   page.PageBody = GetPageBody(page.PageUrl);
                   var doc = new Document();
                  doc.Add(new Field("postUrl", page.PageUrl, Field.Store.YES,       
                                                                           Field.Index.NOT_ANALYZED));
                   doc.Add(new Field("postTitle", page.PageTitle, Field.Store.YES, 
                                                                           Field.Index.NOT_ANALYZED));
                   doc.Add(new Field("postBody", page.PageBody, Field.Store.YES, 
                                                                           Field.Index.ANALYZED));
                   Writer.AddDocument(doc);
                   Console.WriteLine(string.Format("Indexing Page {0}", page.PageTitle));
            }

               Writer.Optimize();
               Writer.Flush(true, true, true);
               Writer.Commit();
               Writer.Dispose();
}

Indexing Application Ready. 
After running this process you will find Lucene indexing files under:

      const string directoryPath = @"C:\GitSource\Search Web Client\App_Data\LuceneIndexes";

Now let see how we can search in these indexing files using Lucene.NET search engine.
I will create ASP.NET MVC application for instance. I will add one new page - "Search" and searching in 3 pages defined previously in Indexing application above:

                    //Home
                    new Page {PageTitle = "Index", PageUrl = "http://localhost:63461/"},
                    //About
                    new Page {PageTitle = "About", PageUrl = "http://localhost:63461/Home/About"},
                    //Contact
                    new Page {PageTitle = "Contact", PageUrl = "http://localhost:63461/Home/Contact"}



Let's add a new controller to HomeController in order to get Search Page and add search functionality for POST call from cshtml:

public ActionResult Search(SearchingQuery searchingQuery)
{
            if (searchingQuery == null)
            {
                throw new ArgumentException("Searching Query Can't be NULL");
            }

            if (string.IsNullOrEmpty(searchingQuery.Query))
            {
                return View();
            }


            var searchResults = new List<SearchResults>();
            string indexDirectory = Server.MapPath("~/App_Data/LuceneIndexes");
            var analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30);
            IndexSearcher searcher = new IndexSearcher(FSDirectory.Open(indexDirectory));
            var parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_30, "postBody", analyzer);
            var searchQuery = parser.Parse(searchingQuery.Query);
            TopDocs hits = searcher.Search(searchQuery, 200);
            int results = hits.TotalHits;          

            for (int i = 0; i < results; i++)
            {
                   Document doc = searcher.Doc(hits.ScoreDocs[i].Doc);
                   var searchResult = new SearchResults
                   {
                       PageUrl = doc.Get("postUrl"),
                       PageTitle = doc.Get("postTitle"),
                       PageBody = doc.Get("postBody")
                  };
                   searchResults.Add(searchResult);               
              }

            analyzer.Close();
            searcher.Dispose();

            return View(searchResults);

 }

and the Model for SearchResults:

 public class SearchResults
{
        public string PageUrl { get; set; }
        public string PageTitle { get; set; }
        public string PageBody { get; set; }
}

Search.cshtml

<div class="container">
    <div class="row">
        @using (Html.BeginForm("Search", "Home", FormMethod.Post))
        {
            <div class="col-lg-4 col-lg-offset-4">
                <input type="text" id="Query" name="Query" placeholder="Please Search Something..." />
                <button type="submit">Search</button>
            </div>
        }
    </div>
    <div class="row">
        <h2>Results</h2>
    </div>
    <div class="row">       
            @if (Model == null || Model.Count == 0)
            {
                <div class="row">
                    Sorry, No Results Found
                </div>
            }
            else
            {
                <ul>
                    @foreach (var result in Model)
                    {
                    <li>
                        <a href="@result.PageUrl">@result.PageTitle</a>
                    </li>
                    }
                </ul>
            }                
    </div>
</div>

Web Site Structure



That's it :)
Thanks!








Monday, February 22, 2016

Creating Bootstrap Modal Confirmation Window (ASP.NET MVC RAZOR and Java Script)

HTML

Lets assume we have DELETE button (link) on UI:



<a onclick="return desinto.core.openYesNoModal('@Url.Action("DeleteAdvertiserCampaingById", "AdvertiserProfile", new {Id = @campaign.Id})', 'You are about deleting this Campaing, Are You Sure?')" href="#">Delete</a>      

Once user clicked on this button we gonna show modal confirmation window (bootstrap) and we have this window like injected Partial View on the same (for example) HTML:


@Html.Partial("YesNoPromtModal")

Partial's HTML:

<div id="myModal" class="modal fade">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                <h4 class="modal-title">Confirmation</h4>
            </div>
            <div class="modal-body">
                <p id="promptDescription"></p>              
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal" id="cancelBtn">Close</button>
                <a href="" class="btn btn-primary" id="saveChangesBtn">Save Changes</a>
            </div>
        </div>
    </div>
</div>



and finally Java Script for handling close and save changes buttons:

var desinto = {} || desinto;
desinto.core = {} || desinto.core;

(function (ns) {

    ns.openYesNoModal = function (linkForYes, description) {
        $('#saveChangesBtn').attr('href', linkForYes);
        $('#promptDescription').append(description);
        $('#myModal').modal('show');

        $('#cancelBtn').click(function () {
            $('#promptDescription').html('');
            $('#saveChangesBtn').removeAttr("href");    
        });    
    }  
   
})(desinto.core);




Thursday, November 20, 2014

Simple JQuery Grid Plugin

Grid implemented in js file:

jQuery.fn.Grid = function(options) {
    if(options == undefined || options.data == undefined) return;

    var html = "";
    var realColumnsNames = [];
    var headerColumnsNames = [];
    var columns = [];
    html += '<table id="table">';
    html += '<thead id="tableHead">';
    html += '<tr id="tableHeadTr">';
    if(options.columns == undefined || options.columns.length == 0) {
        for (var objName in options.data[0]) {
            html += '<th id="' + objName + '">' + objName + '</th>';
            realColumnsNames.push(objName);
        }
    }
    else {
        for (var i = 0; i < options.columns.length; i++) {
            objName = options.columns[i];
            html += '<th id="' + objName + '">' + objName + '</th>';
        }
        for (var objName in options.data[0]) {
            realColumnsNames.push(objName);

        }
    }
        html += '</tr>';
        html += '</thead>';

        $.each(options.data, function (index, value) {
            html += "<tr class='tableRow'>";
            for (var i = 0; i < realColumnsNames.length; i++) {
                html += "<td>" + value[realColumnsNames[i]] + "</td>";
            }
            html += "</tr>";
        })

        html += '</table>';

        this[0].innerHTML = html;
};

And Usage in HTML:

 <body>

        <div id="Grid"></div>
        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
        <script src="js/Grid.js"></script>  
        <script>
            var data = JSON.parse('[{"Name" : "Name", "Surname" : "SName"},' +
                                    '{"Name" : "Name", "Surname" : "SName"}, ' +
                                    '{"Name" : "Name", "Surname" : "SName"},' +
                                    '{"Name" : "Name", "Surname" : "SName"}]');

            var options = {
                data: data,
                columns: ['First Name', 'Second Name']
            };

            $('#Grid').Grid(options);
        </script>
    </body>

Wednesday, July 16, 2014

ASP.NET MVC CACHING with TIME STAMP EXAMPLE

CACHE MANAGER:

using System;
using System.Runtime.Caching;
 
namespace Quasar.Bll
{
    public class CacheManager : ICacheProvider
    {
      private ObjectCache Cache { get { return MemoryCache.Default; } }
 
      public object Get(string key)
      {
          return Cache[key];
      }
 

      public void Set(string key, object data, int cacheTime)
      {
       var policy = new CacheItemPolicy {
                     AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime)
                                        };
 
       Cache.Add(new CacheItem(key, data), policy);
      }
 
      public bool IsSet(string key)
      {
          return (Cache[key] != null);
      }
 
      public void Invalidate(string key)
      {
          Cache.Remove(key);
      }
 
   }
}


CACHE USAGE in BLL:

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Quasar.Bll.Models;

namespace Quasar.Bll
{
    public class ProductsBusinessObject
    {
        public List<ProductSearchResult> GetProductData(string searchText)
        {
            var cache = new CacheManager();

            var products = cache.Get(searchText) as List<ProductSearchResult>;

            if (products != null)
            {
                return products;
            }

            var rawData = new Dal.ProductsDao().GetProductsData(searchText);

            var searchedProducts = from prod in rawData
                select
                    new ProductSearchResult
                    {
                       Breadcrumb = prod.Categories,
                       LastUpdated = prod.LastUpdated,
                       Price = float.Parse(prod.Price.ToString()),
                       ProductImage = prod.ProductMainImageUrl,
                       ProductLink = prod.ProductLink,
                       ProductTitle = prod.ProductTitle,                       
                       Provider = prod.SiteNameToCrawle,
                       ProviderLogo = ProvidersLogoManager.GetProviderLogo(prod.SiteNameToCrawle)
                    };

            var result = searchedProducts.ToList();

            cache.Set(searchText, result, 1);

            return result;
        }
    }
}

Tuesday, May 27, 2014

Managing Resources with ASP.NET MVC and Self Hosting

In Self Hosting ASP.NET MVC application we have 2 options to manage resources :

- Files System
- Embedded Resources

Manage resources on File System just like IIS do it.
Get Css example. In order to bring JS or Image, just change contentType:

Controller:

 public class ActiveDirectoryAuthenticationController : ResourcesController
 {        
        public ActionResult Index()
        {         
            return View();
        }    
 }


ResourcesController


public class ResourcesControllerController
{
    public HttpResponseBase GetResource(string resourceId)
    {
         Response.Clear();
         Response.ContentType = "text/css";
         Response.Write(Resources.ResourceManager.GetObject(resourceId));
         Response.End();
         return Response;
    }        
}

CSHTML:

<a href="@Url.Action("GetResource", "Resources", new { resourceId = some.css})"></a>


Resource File:


Monday, July 1, 2013

CREATE CUSTOM HTTP HANDLER in Mvc Application based on Xml Rules

CREATE CUSTOM HTTP HANDLER in Mvc Application based on Xml File Rules

Creating Handler for Mvc Application, Deserialize Rules Xml and Invoke Relevant Contoller / Action




public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
            var virtualPath = HttpContext.Current.Request.CurrentExecutionFilePath.TrimStart('/');

            var routeInfo = RoutesBus.GetIdByRoute(virtualPath);

            if (routeInfo == null)
            {
                return new MvcHandler(requestContext);
            }

            var latestRouteInfo = RoutesBus.GetRouteById(routeInfo.Route_RelatedID, routeInfo.Route_RelatedType);

            if (latestRouteInfo == null)
            {
                return new MvcHandler(requestContext);
            }

            if (routeInfo.RouteURL != latestRouteInfo.RouteURL)
            {
                HttpContext.Current.Response.Status = "301 Moved Permanently";
                HttpContext.Current.Response.StatusCode = 301;
                HttpContext.Current.Response.RedirectLocation = '/' + latestRouteInfo.Route_EntireURL;
                HttpContext.Current.Response.End();
            }

            var showPageTypeControllerRules = Utilities.SerializationHelper.Desirialization<rules>
                                                                                                                                (ShowPageTypeControllerRulesXml);
            Utilities.RouteFactoryByPageType.GetRouteDataAccordingToShowPageRules(requestContext, 
                                                                                                                           showPageTypeControllerRules, routeInfo);

            return new MvcHandler(requestContext);
}

... Desirialization (trivial, but anyway)

public class SerializationHelper
    {
        public static T Desirialization<T>(string filePath)
        {
            var serializer = new XmlSerializer(typeof(T));
            var reader = new StreamReader(filePath);
            var result = (T)serializer.Deserialize(reader);
            reader.Close();
            return result;
        }

    }

AND GetRouteDataAccordingToShowPageRules And Invoke Relevant Controller

public static void GetRouteDataAccordingToShowPageRules
(RequestContext requestContext, rules showPageTypeControllerRules, RouteInfoObject routeInfo)
{
            var relevantRule = (from r in showPageTypeControllerRules.Items where r.name == routeInfo.Route_RelatedType  
                                              select r).FirstOrDefault();

            if (relevantRule == null) return;

            requestContext.RouteData.Values["controller"] = relevantRule.controller;
            requestContext.RouteData.Values["action"] = relevantRule.action;
            requestContext.RouteData.Values["eventId"] = routeInfo.Route_RelatedID;

 }

Wednesday, March 13, 2013

Web Data Grid with filtering on all fields and full paging functionality

Web Data Grid with filtering on all fields and full paging functionality
(JQuery, Link.js)

I worked on it very hard whole day, hope you will enjoy and save your day :-)

VIEW (razor):

@model IEnumerable<Enigma.Models.Product>
 
@{
    ViewBag.Title = "CreateNewOrder";
    Layout = "~/Views/Shared/_LayoutCustomer.cshtml";
}
 
@Styles.Render("~/Content/Customer/CreateNewOrder.css")
 
 
<div id="searchArea"> 
    <input class='searchFields' id='searchX1' type='text'>
    <input class='searchFields' id='searchName' type='text'>
    <input class='searchFields' id='searchSize' type='text'>
    <input class='searchFields' id='searchPriceFrom' type='text'>
    <input class='searchFields' id='searchPriceTo' type='text'>
    <input class='searchFields' id='searchQuantityInPackage' type='text'>
    <input class='searchFields' id='searchSupplierName' type='text'>
</div>
 
<div id="mainDiv"></div>
<a id="PrevPage" href="#">Prev</a>
<a id="NextPage" href="#">Next</a>
<input type="submit" value="Create" />
@Html.ActionLink("Cancel Order""CustomerIndex""CustomerIndex")

JAVA SCRIPT OF GRID:
 
<script>
 
    var pagingIndexStep = 10;
    var origPagingIndexStep = 10;
    var globalPagingIndexMin = -origPagingIndexStep;
    var globalPagingIndexMax = 0;
    var model = @Html.Raw(Json.Encode(Model));    
    var modelSize = model.length;
    var origModel = model;
    var newModel = model;
    var reachedMaximum;
    var reachedMinimum;
    var pageNumber = 0;
    ShowNextPage(model);    
 
    function CutModelAccordingToIndex(model, startWith, endWith) {
        newModel = {};
        for (var i = startWith; i < endWith; i++) {
            newModel[i] = model[i];
        }
        
        return newModel;
    }
        
    function DrawGridByModel(newModel) {
        if (newModel.length == 0) return;
        var localModel = newModel;
        var html = "";       
        html += "<table class='productTable'>";
        html += "<thead class='tableThead'>";
        html += "<tr>";
        html += "<th>X1</th>";
        html += "<th>Name</th>";
        html += "<th>Size</th>";
        html += "<th>Price</th>";
        html += "<th>Quantity In Package</th>";
        html += "<th>Supplier Name</th>";
        html += "<th>Start Date</th>";
        html += "<th>End Date</th>";
        html += "<th>Add to Order</th>";
        html += "</tr>";
        html += "</thead>";
        html += "<tbody class='tableBody'>";
        html += "<tr>";        
        html += "<td></td>";
        html += "<td></td>";
        html += "<td></td>";
        html += "</tr>";
        $.each(localModel, function(key, value) {
            html += "<tr>";
            html += "<td class='x1_row'>" + value.x1 + "</td>";
            html += "<td class='name_row'>" + value.name + "</td>";
            html += "<td class='size_row'>" + value.size + "</td>";
            html += "<td class='price_row'>" + value.price + "</td>";
            html += "<td class='quantity_in_package_row'>" + value.quantity_in_package + "</td>";
            html += "<td class='supplier_name_row'>" + value.supplier_name + "</td>";
            html += "<td class='start_date_row'>" + value.period_start_date + "</td>";
            html += "<td class='end_date_row'>" + value.period_end_date + "</td>";
            html += "<td class='check_row'><input id='addToOrderButton' type='checkbox'></td>";
            html += "</tr>";
        });
        if (model.length > 0) {
            html += "<tr>";
            html += "<td class='total_pages'>Page: " + pageNumber +
                                           " From: " + Math.ceil(model.length / pagingIndexStep) + 
                                           " Total Results:" + model.length + "</td>";
            html += "</tr>";
        }
        html += "</tbody>";
        html += "</table>";
        $("#mainDiv").html(html);
        UpEvents();
    }
  
    $('#NextPage').click(ShowNextPage);
    $('#PrevPage').click(ShowPrevPage);        
    
    function ShowNextPage() {        
        var nextButton = $('#NextPage');
        var prevButton = $('#PrevPage');
        if (reachedMaximum || model.length == 0) return; 
        pageNumber++;
        globalPagingIndexMin += pagingIndexStep;
        globalPagingIndexMax += pagingIndexStep;
        if (globalPagingIndexMax >= modelSize) {
            newModel = CutModelAccordingToIndex(model, globalPagingIndexMin, modelSize);
            DrawGridByModel(newModel);
            nextButton.addClass("prevNextButtonsDisabled");           
            reachedMaximum = true;
        } else {
            newModel = CutModelAccordingToIndex(model, globalPagingIndexMin, globalPagingIndexMax);
            DrawGridByModel(newModel);           
            reachedMaximum = false;
        }                
        prevButton.addClass("prevNextButtonsEnabled");
        reachedMinimum = false;
       
    }
    
    function ShowPrevPage() {
        var nextButton = $('#NextPage');
        var prevButton = $('#PrevPage');
        if (reachedMinimum || (globalPagingIndexMin - pagingIndexStep) < 0 || model.length == 0) return;
        pageNumber--;
        globalPagingIndexMin -= pagingIndexStep;
        globalPagingIndexMax -= pagingIndexStep;
        if (globalPagingIndexMin < 1) {
            newModel = CutModelAccordingToIndex(model, globalPagingIndexMin, globalPagingIndexMax);
            DrawGridByModel(newModel);
            prevButton.addClass("prevNextButtonsDisabled");          
            reachedMinimum = true;
        } else {
            newModel = CutModelAccordingToIndex(model, globalPagingIndexMin, globalPagingIndexMax);
            DrawGridByModel(newModel);              
        } 
        reachedMaximum = false;
        nextButton.addClass("prevNextButtonsEnabled");
        
    }      
    
    function CreateNewModelAccordingToCrteria()
    {
        var queryResult;
        var searchByX1Val = $('#searchX1').val();
        var searchName = $('#searchName').val();
        var searchSize = $('#searchSize').val();        
     
        queryResult = origModel;
        if (searchByX1Val && searchByX1Val.length > 0) {          
            queryResult = Enumerable.From(queryResult)
                   .Where(" x => x['x1']== " + searchByX1Val).ToArray(); 
        }        
        if (searchName && searchName.length > 0) {                       
            queryResult = Enumerable.From(queryResult)
                   .Where("x => x['name']== '" + searchName + "'").ToArray(); 
        }               
        if (searchSize && searchSize.length > 0) {                       
            queryResult = Enumerable.From(queryResult)
                   .Where("x => x['size']== '" + searchSize + "'").ToArray(); 
        }  
        
        model = queryResult;  
        pagingIndexStep = model.length > origPagingIndexStep ? origPagingIndexStep : model.length;
       
        globalPagingIndexMin = -pagingIndexStep;
        globalPagingIndexMax = 0;
        pageNumber = 0;
        modelSize = model.length;
        reachedMaximum = false;
        reachedMinimum = false;        
        ShowNextPage();
    }       
 
    function AddProductToOrder() {
        if($(this).attr('checked')) {
            
            alert($(this).parent().parent()[0].cells[0].innerText);
        } else {
            alert('REMOVED');
        }
    }
 
    function UpEvents(){
        $(".check_row input[type=checkbox]").click(AddProductToOrder);
    }
   
    $('#searchX1').keyup(CreateNewModelAccordingToCrteria);
    $('#searchName').keyup(CreateNewModelAccordingToCrteria);  
    $('#searchSize').keyup(CreateNewModelAccordingToCrteria);  
    
</script>