{"id":23050832,"url":"https://github.com/geta/geta-notfoundhandler","last_synced_at":"2025-11-03T17:37:35.449Z","repository":{"id":38257495,"uuid":"338361520","full_name":"Geta/geta-notfoundhandler","owner":"Geta","description":"The popular NotFound handler for ASP.NET Core and Optimizely, enabling better control over your 404 page in addition to allowing redirects for old URLs that no longer works.","archived":false,"fork":false,"pushed_at":"2025-03-20T15:15:22.000Z","size":246228,"stargazers_count":21,"open_issues_count":51,"forks_count":18,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-03-28T19:08:30.568Z","etag":null,"topics":["404error","aspnetcore","cms","commerce","notfound","optimizely"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Geta.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-12T15:46:58.000Z","updated_at":"2025-03-25T19:21:38.000Z","dependencies_parsed_at":"2024-10-30T09:17:52.916Z","dependency_job_id":"4ed23c3f-50ab-450b-89ed-357ab92d5c7b","html_url":"https://github.com/Geta/geta-notfoundhandler","commit_stats":{"total_commits":306,"total_committers":11,"mean_commits":"27.818181818181817","dds":"0.16013071895424835","last_synced_commit":"ca1f3730af6cdbdd797cc2c0d541143216e1acc5"},"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2Fgeta-notfoundhandler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2Fgeta-notfoundhandler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2Fgeta-notfoundhandler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2Fgeta-notfoundhandler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Geta","download_url":"https://codeload.github.com/Geta/geta-notfoundhandler/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247242680,"owners_count":20907134,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["404error","aspnetcore","cms","commerce","notfound","optimizely"],"created_at":"2024-12-15T23:38:45.794Z","updated_at":"2025-11-03T17:37:35.443Z","avatar_url":"https://github.com/Geta.png","language":"C#","readme":"\n# NotFound Handler for ASP.NET Core and Optimizely\n\n[![Build](https://github.com/Geta/geta-notfoundhandler/actions/workflows/build.yml/badge.svg)](https://github.com/Geta/geta-notfoundhandler/actions/workflows/build.yml)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Geta_geta-notfoundhandler\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Geta_geta-notfoundhandler)\n[![Platform](https://img.shields.io/badge/Platform-.NET%205-blue.svg?style=flat)](https://docs.microsoft.com/en-us/dotnet/)\n[![Platform](https://img.shields.io/badge/Optimizely-%2012-orange.svg?style=flat)](http://world.episerver.com/cms/)\n\n## Description\n\nThe popular NotFound handler for ASP.NET Core and Optimizely, enabling better control over your 404 page in addition to allowing redirects for old URLs that no longer works.\n\nThe perfect companion if you're transitioning to your site from another system and cannot keep the URL structure, or plan to do major re-structuring of your content.\n\n![](images/redirects.png)\n\n## Features\n\n* The NotFound handler stores the redirects in the database. Editors can add redirects without any deployments.\n* All redirects are edited in the Admin UI as shown above. After the add-on is installed and a proper 404 page has been created, no changes to the application is needed to add new redirects.\n* You can import and export redirects as XML from the Admin UI.\n* Handles partial and \"full\" URLs and can redirect out of the site by using fully qualified URLs for the \"New url\" field.\n* Supports wildcard redirects.\n* By using fully qualified URLs in the \"Old url\" field, they will only apply for that specific site. Editing redirects is done for all sites in the same UI.\n\n# Installation\n\nThe command below will install Admin UI and NotFound handler into your ASP.NET Core project.\n\n```\nInstall-Package Geta.NotFoundHandler.Admin\n```\n\nIf you need only the handler, then you can install it by the command below.\n\n```\nInstall-Package Geta.NotFoundHandler\n```\n\nFor the Optimizely project, you would want to install Admin UI integration package.\n\n```\nInstall-Package Geta.NotFoundHandler.Optimizely\n```\n\nFor the Optimizely Commerce project, if you want to use Automatic redirects, install:\n\n```\nInstall-Package Geta.NotFoundHandler.Optimizely.Commerce\n```\n\nThe package can be found in the [Optimizely Nuget Feed](https://nuget.episerver.com/package/?id=Geta.NotFoundHandler).\n\n# Configuration\n\nAdd the NotFound handler in the Startup.cs in the `ConfigureServices` method. Below is an example with all available configuration you can set.\nFor Optimizely project, also call `AddOptimizelyNotFoundHandler` - it will add Admin UI in the Optimizely admin UI.\n\n```\npublic void ConfigureServices(IServiceCollection services)\n{\n    var connectionString = ... // Retrieve connection string here\n    services.AddNotFoundHandler(o =\u003e\n    {\n        o.UseSqlServer(connectionstring);\n        o.BufferSize = 30;\n        o.ThreshHold = 5;\n        o.HandlerMode = FileNotFoundMode.On;\n        o.IgnoredResourceExtensions = new[] { \"jpg\", \"gif\", \"png\", \"css\", \"js\", \"ico\", \"swf\", \"woff\" };\n        o.Logging = LoggerMode.On;\n        o.LogWithHostname = false;\n        o.ActiveStatusCodes = new int[] { StatusCodes.Status404NotFound };\n        o.AddProvider\u003cNullNotFoundHandlerProvider\u003e();\n    });\n\n    services.AddOptimizelyNotFoundHandler(o =\u003e\n    {\n        o.AutomaticRedirectsEnabled = true;\n        o.AddOptimizelyCommerceProviders();\n    });\n\n...\n}\n```\n\nThe first and the mandatory configuration is a connection string. Use `UseSqlServer` method to set up the database connection string.\n\nCall the `AddOptimizelyNotFoundHandler` method in Optimizely projects. To enable Automatic redirects, you should set `AutomaticRedirectsEnabled` to `true` and for Optimizely Commerce projects call `AddOptimizelyCommerceProviders`.\n\nIn addition, the configuration can be read from the `appsettings.json`:\n\n```\n\"Geta\": {\n    \"NotFoundHandler\": {\n        \"BufferSize\":  40\n    }\n}\n```\n\nThe configuration from the `appsettings.json` will override any configuration set in the Startup. Note that you cannot provide a connection string or add providers in the `appsetings.json`. All other settings are supported.\n\nNext, initialize NotFound handler in the `Configure` method as the first registration. It will make sure that NotFound handler will catch all 404 errors.\nFor Optimizely project, also call `UseOptimizelyNotFoundHandler`. This will make sure that any updates are synchronized between servers (on DXP, for example).\n\n```\npublic void Configure(IApplicationBuilder app)\n{\n    app.UseNotFoundHandler();\n    app.UseOptimizelyNotFoundHandler();\n...\n}\n```\n\nAlso, you have to add Razor pages routing support.\n\n```\napp.UseEndpoints(endpoints =\u003e\n{\n    endpoints.MapRazorPages();\n});\n```\n\n## Settings\n\n**HandlerMode** You can turn off the redirects by setting `HandlerMode` to `Off`. Default is `On`.\n\n### Logging\nSuggestions for NotFound rules require 404 requests to be logged to the database.\n\nLogging of 404 requests is buffered to shield your application from Denial of Service attacks. By default, logging will happen for every 30'th error. You can change this setting in the configuration and set `bufferSize` to `0` to log the errors immediately. This is not recommended as you will be vulnerable to massive logging to your database. You can control how much you would like to log by specifying a threshold value. This value determines how frequently 404 errors are allowed to be logged.\n\n**Important!** Even if the threshold is set low, you can still receive a lot of requests in the 404 log. In the Admin view (follow \"Administer\" link in gadget) you can delete suggestions (logged 404 requests). You can find all the logged items in the `NotFoundHandler.Suggestions` table in your database if you want to manually clear the logged requests (this will not remove any redirects).\n\n![](https://raw.githubusercontent.com/Geta/geta-notfoundhandler/master/doc/img/Administer.png)\n\n**Logging**: Turn logging `On` or `Off`. Default is `On`\n\n**BufferSize**: Size of memory buffer to hold 404 requests. Default is 30\n\n**Threshold**: Average maximum allowed requests per second. Default is 5\n\n * Example 1:\n   * bufferSize is set to 100, threshold is set to 10\n   * Case: 100 errors in 5 seconds - (diff = seconds between first logged request and the last logged request in the buffer).\n   * 100 / 5 = 20. Error frequency is higher than threshold value. Buffered requests will not get logged, the entire buffer will be discarded.\n * Example 2:\n   * bufferSize is 100, threshold is 10\n   * Case: 100 errors in 15 seconds\n   * 100 / 15 = 6. Error frequency is within threshold value. Buffered requests will get logged.\n\nIf the `bufferSize` is set to `0`, the `threshold` value will be ignored, and every request will be logged immediately.\n\n**LogWithHostname**: Set to `true` to include hostname in the log. Useful in a multisite environment with several hostnames/domains. Default is `false`\n\n**ActiveStatusCodes**: A integerlist with the status codes that NotFoundHandler will be active on. (Ex. ```options.ActiveStatusCodes = new int[] { StatusCodes.Status404NotFound, StatusCodes.Status410Gone };```)\n\n### Specifying ignored resources\n\n**IgnoredResourceExtensions**\n\nBy default, requests to files with the following extensions will be ignored by the redirect module: `jpg,gif,png,css,js,ico,swf,woff`\n\nIf you want to specify this yourself, add `IgnoredResourceExtensions` to the configuration.\n\n### Specifying ignored URLs\n\nIf certain URLs should be ignored, you can use the `IgnoreSuggestionsUrlRegexPattern` option:\n\n```\nservices.AddNotFoundHandler(o =\u003e\n{\n    o.IgnoreSuggestionsUrlRegexPattern = @\"^(https?:\\/\\/[^\\/]+)?\\/(api|episerverapi|globalassets|siteassets)\";\n});\n```\n\nWhen a URL matches the specified regex pattern, suggestions will be skipped.\n\n\n## Restricting access to the Admin UI\n\nBy default, only users of `Administrators` role can access Admin UI. But you can configure your authorization policy when registering the NotFound handler.\n\n```\n services.AddNotFoundHandler(o =\u003e { },\n            policy =\u003e\n            {\n                policy.RequireRole(\"MyRole\");\n            });\n```\n\nYou can set up any policy rules you want.\n\n## Import\n\nFor details see [Import redirects for 404 handler](https://getadigital.com/blog/import-redirects-for-404-handler/) article.\n\n# Custom 404 Page\n\nTo set up 404 page, you can use any method ASP.NET Core provides.\n\nOne of the simplest solutions is adding a controller and a view for it that would display an error page:\n\n```\n[Route(\"error\")]\npublic class ErrorController : Controller\n{\n    [Route(\"404\")]\n    public IActionResult PageNotFound()\n    {\n        return View();\n    }\n}\n```\n\nThen register status code pages in the Startup's `Configure` method before NotFound handler registration:\n\n```\npublic void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n{\n    app.UseStatusCodePagesWithReExecute(\"/error/{0}\");\n    app.UseNotFoundHandler();\n\n...\n}\n```\n\nRegistering before the NotFound handler will make sure that a NotFound handler already checked the error and only those errors that were not handled by NotFound handler will be redirected to the error page.\n\n# Custom Handlers\nIf you need more advanced or custom logic to create redirects, you can implement an INotFoundHandler.\n\n1. Create a class that implements `Geta.NotFoundHandler.Core`\n2. In the `public string RewriteUrl(string url)` method, add your custom logic\n3. Register the handler in the configuration.\n\n```\nservices.AddNotFoundHandler(o =\u003e\n{\n        o.AddProvider\u003cCustomProductRedirectHandler\u003e();\n});\n```\n\nThis is especially useful for rewrites that follow some kind of logic, like checking the querystring for and id or some other value you can use to look up the page.\n\nHere is an example using Optimizely Find to look up a product by code:\n\n```csharp\npublic class CustomProductRedirectHandler : INotFoundHandler\n{\n    public RewriteResult RewriteUrl(string url)\n    {\n        if(url.Contains(\"productid\"))\n        {\n            // Give it a thorough look - see if we can redirect it\n            Url uri = new Url(url);\n            var productId = uri.QueryCollection.GetValues(\"productid\").FirstOrDefault();\n            if (productId != null \u0026\u0026 string.IsNullOrEmpty(productId) == false)\n            {\n                SearchResults\u003cFindProduct\u003e results = SearchClient.Instance.Search\u003cFindProduct\u003e()\n                    .Filter(p =\u003e p.Code.MatchCaseInsensitive(productId))\n                    .GetResult();\n                if (results.Hits.Any())\n                {\n                    // Pick the first one\n                    SearchHit\u003cFindProduct\u003e product = results.Hits.FirstOrDefault();\n                    return product.Document.ProductUrl;\n                }\n            }\n        }\n        return RewriteResult.Empty;\n    }\n}\n```\n\n**Note!** Make sure the code you add has good performance, it could be called a lot. If you're querying a database or a search index, you might want to add caching and perhaps Denial Of Service prevention measures.\n\n# Automatic redirects\n\nAutomatic redirects is a feature that when enabled will create redirects for content that is moved.\n\nSee the *Configuration* section how to enable it.\n\nOnce you enabled Automatic redirects, you should run *[Geta NotFoundHandler] Index content URLs* scheduled job. It will index all URLs of content and will start monitoring those for changes.\n\nNow Automatic redirects will create redirects on content move. It will create redirects with the old URLs by checking the indexed URLs for the content and new URLs of the new place where a content is moved to. After that, it will index new URLs too.\n\nIt will monitor primary, secondary and SEO URLs:\n- a primary URL will be redirected to the new primary URL,\n- all secondary URLs will be redirected to the new primary URL,\n- a SEO URL is redirected to the new SEO URL if possible and to the new primary URL, if not possible.\n\nOptimizely Content Cloud supports only primary URLs and Optimizely Commerce supports all three types of URLs.\n\n# Scheduled jobs\n\nScheduled job - process that runs in background\n- Suggestions cleanup job - shipped with the package, contains process that cleans up suggestions table.\nThis job is configured by default to remove records older than 14 days. You can adjust the retention period or timeout as needed.\n```\nservices.AddNotFoundHandler(o =\u003e\n{\n    o.SuggestionsCleanupOptions.DaysToKeep = 30;\n    o.SuggestionsCleanupOptions.Timeout = 30 * 60;\n});\n```\n\nScheduler - mechanism that triggers scheduled jobs in a recurrent manner\n- InternalScheduler - default scheduler, included in the core package, a scheduler that uses [Coravel](https://docs.coravel.net/).\n  To enable the scheduler, you need to enable UseInternalScheduler flag. Additionally, you can adjust the scheduler run interval:\n```\nservices.AddNotFoundHandler(o =\u003e\n{\n    ...\n    o.UseInternalScheduler = true;\n    o.InternalSchedulerCronInterval = \"0 0 * * *\" // by default it's configured to run daily at midnight\n});\n```\n- OptimizelyScheduler - uses Optimizely to schedule job runs.\nAn Optimizely scheduled job was added - \u003ccode\u003e[Geta NotFoundHandler] Suggestions cleanup job\u003c/code\u003e.\n\nAdditionally, there are two optimizely scheduled jobs responsible for:\n- *[Geta NotFoundHandler] Index content URLs* - as mentioned before, this job indexes URLs of content. Usually, it is required to run this job only once. All new content is automatically indexed. But if for some reason content publish events are not firing when creating new content (for example, during the import), then you should set this job to run frequently.\n- *[Geta NotFoundHandler] Register content move redirects* - this job creates redirects based on registered moved content. Normally, this job is not required at all, but there might be situations when content move is registered but redirect creation is not completed. This could happen during deployments. In this case, you can manually run this job or schedule it to run time to time to fix such issues.\n\n\n# Troubleshooting\n\nThe module has extensive logging. Turn on debug logging for the `Geta.NotFoundHandler` namespace in your logging configuration.\n\n# Usage\n\n## Wildcards\n\nIf you want to redirect many addresses below a specific one to one new URL, set this to true. If we get a wild card match on this URL, the new URL will be used in its raw format and the old URL will not be appended to the new one.\n\nFor example, if we have a redirect: `/a` to `/b`, then:\n- with wildcard setting it will redirect `/a/1` to `/b`\n- without wildcard setting it will redirect `/a/1` to `/b/1`\n\n## 🏁 Getting Started\n\n### 📦 Prerequisites\n\nEnsure your system is properly configured to meet all prerequisites for Geta Foundation Core listed [here](https://github.com/Geta/geta-foundation-core#%EF%B8%8F-prerequisites)\n\n### 🐑 Cloning the repository\n\n```bash\n    git clone https://github.com/Geta/geta-notfoundhandler.git\n    cd geta-notfoundhandler\n    git submodule update --init\n```\n\n### 🚀 Running with Aspire (Recommended)\n```bash\n    # Windows\n    cd sub/geta-foundation-core/src/Foundation.AppHost\n    dotnet run\n\n    # Linux / MacOS\n    sudo env \"PATH=$PATH\" bash\n    chmod +x sub/geta-foundation-core/src/Foundation/docker/build-script/*.sh\n    cd sub/geta-foundation-core/src/Foundation.AppHost\n    dotnet run\n```\n\n### 🖥️ Running as Standalone\n```bash\n   # Windows\n   cd sub/geta-foundation-core\n   ./setup.cmd\n   cd ../../src/Geta.NotFoundHandler.Web\n   dotnet run\n\n   # Linux / MacOS\n   sudo env \"PATH=$PATH\" bash\n   cd sub/geta-foundation-core\n   chmod +x *.sh\n   ./setup.sh\n   cd ../../src/Geta.NotFoundHandler.Web\n   dotnet run\n```\n\nIf you run into any issues, check the FAQ section [here](https://github.com/Geta/geta-foundation-web?tab=readme-ov-file#faq) \n\n---\n\nCMS username: admin@example.com\n\nPassword: Episerver123!\n\n# Contributing\nIf you can help please do so by contributing to the package!\nReach out package maintainer for additional details if needed.\n\n## Package Maintainer\n\nhttps://github.com/marisks\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeta%2Fgeta-notfoundhandler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeta%2Fgeta-notfoundhandler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeta%2Fgeta-notfoundhandler/lists"}