{"id":19869781,"url":"https://github.com/aeagle/site-search.net","last_synced_at":"2026-01-30T20:32:36.401Z","repository":{"id":40859392,"uuid":"212212104","full_name":"aeagle/site-search.net","owner":"aeagle","description":".NET library to easily add site search to a .NET core web app using Lucene.NET as an in-process search engine","archived":false,"fork":false,"pushed_at":"2022-08-16T13:24:12.000Z","size":736,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-08-19T07:44:04.946Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aeagle.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-01T22:36:30.000Z","updated_at":"2023-12-23T14:17:31.000Z","dependencies_parsed_at":"2022-08-15T12:30:24.025Z","dependency_job_id":null,"html_url":"https://github.com/aeagle/site-search.net","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/aeagle/site-search.net","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aeagle%2Fsite-search.net","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aeagle%2Fsite-search.net/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aeagle%2Fsite-search.net/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aeagle%2Fsite-search.net/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aeagle","download_url":"https://codeload.github.com/aeagle/site-search.net/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aeagle%2Fsite-search.net/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28918451,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T20:25:28.696Z","status":"ssl_error","status_checked_at":"2026-01-30T20:25:13.426Z","response_time":66,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T16:06:54.682Z","updated_at":"2026-01-30T20:32:36.384Z","avatar_url":"https://github.com/aeagle.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SiteSearch.NET\n\n_Note SiteSearch.NET is work in progress_\n\nA simple full text search abstraction with an in-process Lucene.NET file-based implementation and .NET middleware allowing search interfaces to be quickly built.\n\n- Ingestion\n- Full text search\n- Sorting\n- Paging\n- Faceting\n\n## Why?\n\nThere a many full text search engines available. Popular Lucene based search engines for example are `Elastic` and `SOLR`. These usually require server clusters which provide redundency and highly available instances. For very simple search UIs on simple websites, this can often be overkill.\n\nThis abstraction:\n\n1) Simplifies setting up a search interface allowing common actions such as full text searches, paging, sorting, faceting.\n2) Provides a file-based Lucene implementation that allows quick searchable index of documents as an in-process search engine allowing it to be used on even the most basic website hosting.\n\nAlso, because all search functionality is abstracted a different implementation could be implemented (i.e. Elastic) later without affecting consuming code.\n\n## Aims\n\n- Provide out of the box common sorting, paging and faceting functionality found on most search interfaces\n- To provide middleware that eases driving a search interface\n- Allow flexibility in ingestion of content either offline or online via background jobs\n\n## Basic usage\n\nFirst we start with a class to represent searchable items:\n\n```csharp\npublic class SearchItem {\n    [Id]\n    [SearchAlias(\"id\")]\n    public string Id { get; set; }\n\n    [Store]\n    [SearchAlias(\"d\")]\n    public DateTime PublicationDate { get; set; }\n\n    [Store]\n    [SearchAlias(\"t\")]\n    public string Title { get; set; }\n\n    [Store]\n    [SearchAlias(\"p\")]\n    public string Precis { get; set; }\n\n    [Keyword, Store, TermFacet]\n    [SearchAlias(\"c\")]\n    [DisplayName(\"News category\")]\n    public string Category { get; set; }\n\n    [Keyword, Store]\n    public string Url { get; set; }\n\n    [SearchAlias(\"q\")]\n    public string Text =\u003e $\"{Title} {Precis} {Body}\".Trim();\n\n    [Keyword, TermFacet]\n    [SearchAlias(\"pd\")]\n    [DisplayName(\"Period\")]\n    public string MonthYear =\u003e PublicationDate.ToString(\"MMMM yyyy\");\n}\n```\n\n#### App startup\n\nSetup the search services:\n\n```csharp\npublic void ConfigureServices(IServiceCollection services)\n{\n    ...\n\n    string getRootIndexPath(IWebHostEnvironment hostingEnvironment) =\u003e\n        Path.Combine(hostingEnvironment.ContentRootPath, \"search-index\");\n\n    services.AddLuceneSearch\u003cSearchItem\u003e((opts, ctx) =\u003e opts\n        .IndexPath(getRootIndexPath(ctx.GetRequiredService\u003cIWebHostEnvironment\u003e()))\n    );\n\n    ...\n}\n```\n\nHere we pass the root path of the file-based index that will be used to store the content.\n\nSetup the search middleware:\n\n```csharp\npublic void Configure(IApplicationBuilder app)\n{\n    ...\n\n    app.UseSearch\u003cSearchItem\u003e(\n        \"/search\",\n        opts =\u003e opts\n            .FacetOn(x =\u003e x.Field(f =\u003e f.Category), maxFacets: 50)\n            .FacetOn(x =\u003e x.Field(f =\u003e f.MonthYear), maxFacets: 50)\n    );\n\n    ...\n}\n```\n\nWhen requests are made to the path specified `/search`, SiteSearch.NET will automatically perform searches based on the criteria passed in the query string of the request and place the results in a search context. Options passed here allow defaults to be applied to all searches.\n\n#### Ingestion\n\nEnsure the search index exists and ingest some content:\n\n```csharp\nawait searchIndex.CreateIndexAsync();\n\nusing (var context = searchIndex.StartUpdates())\n{\n    await context.IndexAsync(new SearchItem {\n        Id = \"1234\",\n        PublicationDate = new DateTime(2022, 1, 1),\n        Category = \"Category 1\",\n        Url = \"http://www.google.com/\",\n        Title = \"This is a indexed search item\",\n        Precis = \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet tincidunt magna, sed consequat lorem. Integer sit amet sollicitudin lorem, id luctus magna. Phasellus dapibus tellus magna, id porta velit fermentum non.\"\n    });\n    await context.IndexAsync(new SearchItem {\n        Id = \"4321\",\n        PublicationDate = new DateTime(2020, 1, 1),\n        Category = \"Category 2\",\n        Url = \"http://www.microsoft.com/\",\n        Title = \"This is another indexed search item\",\n        Precis = \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet tincidunt magna, sed consequat lorem. Integer sit amet sollicitudin lorem, id luctus magna. Phasellus dapibus tellus magna, id porta velit fermentum non.\"\n    });\n}\n\n```\n\n#### Exposing the search interface\n\nUsing dependency injection, you can inject the `SearchContext`. This context contains everything you need to display a search UI for the current search result. Here using MVC we pass it directly to a Razor view from the controller:\n\n```csharp\n\npublic class HomeController : Controller\n{\n    private readonly SearchContext searchContext;\n\n    public HomeController(\n        SearchContext searchContext)\n    {\n        this.searchContext = searchContext ?? throw new ArgumentNullException(nameof(searchContext));\n    }\n\n    [Route(\"search\")] // Matches the path configured on startup\n    public IActionResult Search()\n    {\n        return View(searchContext.Get\u003cSearchItem\u003e());\n    }\n}\n\n```\n\nRender the search results and related information:\n\n```html\n\u003cdiv id=\"search-results\"\u003e\n  \u003cform\u003e\n    \u003cinput type=\"text\" autofocus name=\"q\" value=\"@Model.CurrentCriteria.Term\" /\u003e\n    \u003cbutton type=\"submit\"\u003eSearch\u003c/button\u003e\n  \u003c/form\u003e\n\n  \u003cdiv class=\"search-facets\"\u003e\n    @if (Model.CurrentCriteria.FieldCriteria.Any()) {\n    \u003ch2\u003eApplied criteria\u003c/h2\u003e\n    \u003cul\u003e\n      @foreach (var criteria in Model.CurrentCriteria.FieldCriteria) {\n      \u003cli\u003e\n        @(criteria.Name): @criteria.Value \u003ca href=\"@criteria.RemoveUrl\"\u003eX\u003c/a\u003e\n      \u003c/li\u003e\n      }\n    \u003c/ul\u003e\n    } @foreach (var facetGroup in Model.FacetGroups) {\n    \u003ch2\u003e@facetGroup.DisplayName\u003c/h2\u003e\n    \u003cul\u003e\n      @foreach (var facet in facetGroup.Facets) {\n      \u003cli\u003e\u003ca href=\"@facet.DrillDownUrl\"\u003e@facet.Name\u003c/a\u003e (@facet.Count)\u003c/li\u003e\n      }\n    \u003c/ul\u003e\n    }\n  \u003c/div\u003e\n\n  @if (Model.Hits.Any()) {\n  \u003cp\u003eShowing @Model.Hits.Count() of @Model.TotalHits results ...\u003c/p\u003e\n\n  \u003carticle class=\"result-list\"\u003e\n    @foreach (var item in Model.Hits) {\n    \u003ch2\u003e\n      \u003ca href=\"@item.Url\"\u003e@Html.Raw(item.Title)\u003c/a\u003e\n    \u003c/h2\u003e\n    \u003cp\u003e@Html.Raw(item.Precis)\u003c/p\u003e\n    }\n  \u003c/article\u003e\n  } else {\n  \u003cp\u003eNo results found.\u003c/p\u003e\n  }\n\u003c/div\u003e\n```\n\n## To do\n\n- Sorting\n- Paging\n- Facet sorting\n- Range faceting\n- Custom analysers\n- Elastic implementation to check validity of abstraction\n- Example API driven React app\n\n## Test news article dataset\n\nThe SiteSearch.Test project demonstrates a search page using the following dataset:\n\n- https://www.kaggle.com/datasets/rmisra/news-category-dataset\n\nYou should download the dataset from Kaggle (requires registration) and place the archived json file `News_Category_Dataset_v2.json` in the `src/SiteSearch.Test` folder in the project before running the `SiteSearch.Test` project.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faeagle%2Fsite-search.net","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faeagle%2Fsite-search.net","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faeagle%2Fsite-search.net/lists"}