Asp.Net core get RouteData value from url - asp.net-core

I'm wokring on a new Asp.Net core mvc app. I defined a route with a custom constraint, which sets current app culture from the url. I'm trying to manage localization for my app by creating a custom IRequestCultureProvider which looks like this :
public class MyCustomRequestCultureProvider : IRequestCultureProvider
{
public Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
var language= httpContext.GetRouteValue("language");
var result = new ProviderCultureResult(language, language);
return Task.FromResult(result);
}
}
My MyCustomRequestCultureProvider is hit on every request, which is ok. My problem is that in the MVC pipeline, DetermineProviderCultureResult method from my provider is hit before the routing process, so httpContext.GetRouteValue("language") always return null.
In previous version of MVC, I had the possiblity to manually process my url through the routing process by doing this
var wrapper = new HttpContextWrapper(HttpContext.Current);
var routeData = RouteTable.Routes.GetRouteData(wrapper);
var language = routeData.GetValue("language")
I can't find a way to do the same thing in the new framewrok right now. Also, I want to use the route data to find out my langugae, analysing my url string with some string functions to find the language is not an option.

There isn't an easy way to do this, and the ASP.Net team hasn't decided to implement this functionality yet. IRoutingFeature is only available after MVC has completed the request.
I was able to put together a solution that should work for you though. This will setup the routes you're passing into UseMvc() as well as all attribute routing in order to populate IRoutingFeature. After that is complete, you can access that class via httpContext.GetRouteValue("language");.
Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// setup routes
app.UseGetRoutesMiddleware(GetRoutes);
// add localization
var requestLocalizationOptions = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US")
};
requestLocalizationOptions.RequestCultureProviders.Clear();
requestLocalizationOptions.RequestCultureProviders.Add(
new MyCustomRequestCultureProvider()
);
app.UseRequestLocalization(requestLocalizationOptions);
// add mvc
app.UseMvc(GetRoutes);
}
Moved the routes to a delegate (for re-usability), same file/class:
private readonly Action<IRouteBuilder> GetRoutes =
routes =>
{
routes.MapRoute(
name: "custom",
template: "{language=fr-FR}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
};
Add new middleware:
public static class GetRoutesMiddlewareExtensions
{
public static IApplicationBuilder UseGetRoutesMiddleware(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
var routes = new RouteBuilder(app)
{
DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
};
configureRoutes(routes);
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));
var router = routes.Build();
return app.UseMiddleware<GetRoutesMiddleware>(router);
}
}
public class GetRoutesMiddleware
{
private readonly RequestDelegate next;
private readonly IRouter _router;
public GetRoutesMiddleware(RequestDelegate next, IRouter router)
{
this.next = next;
_router = router;
}
public async Task Invoke(HttpContext httpContext)
{
var context = new RouteContext(httpContext);
context.RouteData.Routers.Add(_router);
await _router.RouteAsync(context);
if (context.Handler != null)
{
httpContext.Features[typeof (IRoutingFeature)] = new RoutingFeature()
{
RouteData = context.RouteData,
};
}
// proceed to next...
await next(httpContext);
}
}
You may have to define this class as well...
public class RoutingFeature : IRoutingFeature
{
public RouteData RouteData { get; set; }
}

Based on Ashley Lee's answer, here is an optimized approach that prevents duplicate route configuration.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// setup routes
var mvcRouter = BuildMvcRouter(app, routes =>
{
routes.MapRoute(
name: "custom",
template: "{language=fr-FR}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
// add route data initialization middleware
app.Use(next => SetRouteData(next, mvcRouter));
// add localization middleware
var requestLocalizationOptions = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US")
};
requestLocalizationOptions.RequestCultureProviders.Clear();
requestLocalizationOptions.RequestCultureProviders.Add(
new MyCustomRequestCultureProvider()
);
app.UseRequestLocalization(requestLocalizationOptions);
// add mvc routing middleware
app.UseRouter(mvcRouter);
}
This depends on the following two methods that must be added to the StartUp class:
private static IRouter BuildMvcRouter(IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
{
if (app == null) throw new ArgumentNullException(nameof(app));
if (configureRoutes == null) throw new ArgumentNullException(nameof(configureRoutes));
app.ApplicationServices.GetRequiredService<MiddlewareFilterBuilder>().ApplicationBuilder = app.New();
var routeBuilder = new RouteBuilder(app)
{
DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>()
};
configureRoutes(routeBuilder);
routeBuilder.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));
return routeBuilder.Build();
}
private static RequestDelegate SetRouteData(RequestDelegate next, IRouter router)
{
return async context =>
{
var routeContext = new RouteContext(context);
await router.RouteAsync(routeContext);
if (routeContext.Handler != null)
{
context.Features[typeof(IRoutingFeature)] = new RoutingFeature
{
RouteData = routeContext.RouteData
};
}
await next(context);
};
}

This has been made easier with the addition of the Endpoint Routing feature.
This article explains how to do it using the Endpoint Routing feature https://aregcode.com/blog/2019/dotnetcore-understanding-aspnet-endpoint-routing/
var endpointFeature = context.Features[typeof(Microsoft.AspNetCore.Http.Features.IEndpointFeature)]
as Microsoft.AspNetCore.Http.Features.IEndpointFeature;
Microsoft.AspNetCore.Http.Endpoint endpoint = endpointFeature?.Endpoint;
//Note: endpoint will be null, if there was no
//route match found for the request by the endpoint route resolver middleware
if (endpoint != null)
{
var routePattern = (endpoint as Microsoft.AspNetCore.Routing.RouteEndpoint)?.RoutePattern
?.RawText;
Console.WriteLine("Name: " + endpoint.DisplayName);
Console.WriteLine($"Route Pattern: {routePattern}");
Console.WriteLine("Metadata Types: " + string.Join(", ", endpoint.Metadata));
}

Related

Overriding RouteValueDictionary in a Constraint in ASP.NET Core

I've just started using ASP.NET Core MVC and I want one route (global slug) to go to multiple controllers and actions depending on what type of page I'm serving to the user. For example, I want to use {*slug} for category and product pages.
I'm trying to override the default controller and action in a constraint.
In past versions of MVC, you could change either values["controller"] or values["action"] in a constraint and it would re-route to the appropriate controller and action.
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "Category",
template: "{*slug}",
defaults: new { controller = "Page", action = "Home" },
constraints: new { slug = new PageConstraint() }
);
});
}
}
public partial class PageConstraint : IRouteConstraint
{
public virtual bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
{
if (routeDirection == RouteDirection.UrlGeneration)
{
return true;
}
var slug = values["slug"] != null ? values["slug"].ToString() : null;
if (string.IsNullOrWhiteSpace(slug))
{
// Homepage
return true;
}
// Get category service
var categoryService = httpContext.RequestServices.GetRequiredService<ICategoryService>();
var category = categoryService.GetBySlug(slug);
if (category == null)
{
return false;
}
values["controller"] = "Category";
values["action"] = "Listing";
values["category"] = category;
return true;
}
}
In PageConstraint, I look to see if the Category exists, and if it does, it changes the Controller to Category and the Action to Listing in the RouteValueDictionary. However, when I debug the code through, it still goes to the Controller Page and the Action Home.
Anyone know why this is happening, or whether there is a better way of doing this? I know I could have one action in a controller doing all the work that the constraint is, but I would prefer to house the code in separate controllers and actions.
For your requirement, you could try to implement custom IRouter
public class RouterFromAppSettings : IRouter
{
private readonly IRouter _defaulRouter;
private readonly IConfiguration _config;
public RouterFromAppSettings(IRouter defaulRouter
, IConfiguration config)
{
_defaulRouter = defaulRouter;
_config = config;
}
public async Task RouteAsync(RouteContext context)
{
var controller = _config.GetSection("Router").GetValue<string>("Controller");
var action = _config.GetSection("Router").GetValue<string>("Action");
context.RouteData.Values["controller"] = controller;
context.RouteData.Values["action"] = action;
await _defaulRouter.RouteAsync(context);
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
return _defaulRouter.GetVirtualPath(context);
}
}
And then register it in Startup.cs like
app.UseMvc(routes =>
{
routes.Routes.Insert(0, new RouterFromAppSettings(routes.DefaultHandler,Configuration));
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

HTTP Context is null inside a asp.net core controller

I'm using ASP.Net Core 2.1.1. I have an issue while calling HttpContext in my controller. When i want to use HttpContext the program returns NullReferenceException and says HttpContext.get returns null.
I'm very confused because it's inside a controller. can you help me with potential reasons for that?
CartController .cs
public class CartController : Controller
{
private readonly IProductServices _productServices;
private readonly ICartServices _cartServices;
public CartController(IProductServices productServices, ICartServices cartServices)
{
_productServices = productServices;
_cartServices = cartServices;
cartServices.Cart = GetCart();
}
public RedirectToActionResult AddToCart(int productID, string returnUrl)
{
ProductViewModel product = _productServices.GetByID(productID);
if (product != null)
{
_cartServices.AddItem(product, 1);
SaveCart(_cartServices.Cart);
}
return RedirectToAction("Index", new { returnUrl });
}
public RedirectToActionResult RemoveFromCart(int productID, string returnUrl)
{
ProductViewModel product = _productServices.GetByID(productID);
if (product != null)
{
_cartServices.RemoveLine(product);
SaveCart(_cartServices.Cart);
}
return RedirectToAction("Index", new { returnUrl });
}
public IActionResult Index(string returnUrl)
{
return View(new CartIndexViewModel()
{
Cart = GetCart(),
ReturnUrl = returnUrl
});
}
private CartViewModel GetCart()
{
return HttpContext.Session.GetJson<CartViewModel>("Cart") ?? new CartViewModel();
}
private void SaveCart(CartViewModel cart)
{
HttpContext.Session.SetJson<CartViewModel>("Cart", cart);
}
}
When this line calls: Cart = GetCart(), it returns null.
Startup.cs
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSession();
services.AddMemoryCache();
services.AddMvc();
services.RegisterStartupServices(Configuration);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: null,
template: "{category}/Page{page:int}",
defaults: new { controller = "Product", action = "List" }
);
routes.MapRoute(
name: null,
template: "Page{page:int}",
defaults: new { controller = "Product", action = "List", page = 1 }
);
routes.MapRoute(
name: null,
template: "{category}",
defaults: new { controller = "Product", action = "List", page = 1 }
);
routes.MapRoute(
name: null,
template: "",
defaults: new { controller = "Product", action = "List", page = 1 }
);
routes.MapRoute(
name: "default",
template: "{controller=Product}/{action=List}/{id?}"
);
});
}
}
I wrote application dependency injection codes in another assembly and call it from Sturtup.cs
StartupExtensions.cs
public static class StartupExtensions
{
public static void RegisterStartupServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<SportStoreDbContext>(x => x.UseSqlServer(configuration.GetConnectionString("SportStoreDatabase")));
MapperConfiguration mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new MappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
services.AddTransient<IProductServices, ProductServices>();
services.AddTransient<ICategoryServices, CategoryServices>();
services.AddTransient<ICartServices, CartServices>();
}
}
Thanks
You call your method GetCart inside your constructor :
public CartController(IProductServices productServices, ICartServices cartServices)
{
_productServices = productServices;
_cartServices = cartServices;
cartServices.Cart = GetCart();
}`
...
private CartViewModel GetCart()
{
return HttpContext.Session.GetJson<CartViewModel>("Cart") ?? new CartViewModel();
}
but the HttpContext property is not yet initialized. You can have a Http context only while processing a request.

asp.net core custom IRouter Dependency Injection

I'm creating a custom routing in asp.net core 2, where I check the path in a DB and update action and controller to the desired one.
I have this custom IRouter defined like this
public interface IRouteCustom : IRouter
{
}
public class RouteCustom : IRouteCustom
{
private readonly IRouter _innerRouter;
private readonly IMemoryCache _memoryCache;
private readonly IUnitOfWork _unitOfWork;
public RouteCustom(IRouter innerRouter, IMemoryCache memoryCache, IUnitOfWork unitOfWork)
{
_innerRouter = innerRouter ?? throw new ArgumentNullException(nameof(innerRouter));
_memoryCache = memoryCache;
_unitOfWork = unitOfWork;
}
public async Task RouteAsync(RouteContext context)
{
I check the routes in the DB using the _unitOfWork
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
Also I do the same here...
}
}
I have no problem with those functions and I'm able to select controller and action.
My problem is how to access the database, since I can't inject the IUnitOfWork dependency into de custom router.
I'm getting this error message:
'Cannot resolve 'IUnitOfWork' from root provider because it requires scoped service 'DbContext'.'
I have my ConfigureServices like this
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DbContext>(options => options.UseMySQL(configuration.GetConnectionString("DefaultClient")));
services.AddIdentity<Domain.EntitiesClient.Entities.ApplicationUser, Domain.EntitiesClient.Entities.ApplicationRole>().AddEntityFrameworkStores<DbContext>().AddDefaultTokenProviders();
services.AddScoped<IDbContext, DbContext>();
services.AddTransient<IUnitOfWork, UnitOfWork>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddMemoryCache();
services.AddMvc();
/*route custom*/
var supportedCultures = new[] { new CultureInfo("en-US"), new CultureInfo("es-ES"), new CultureInfo("it-IT") };
var optionsCulture = new RequestLocalizationOptions { DefaultRequestCulture = new RequestCulture("en-US", "en-US"), SupportedCultures = supportedCultures, SupportedUICultures = supportedCultures };
optionsCulture.RequestCultureProviders = new IRequestCultureProvider[] { new RouteDataRequestCultureProvider() { RouteDataStringKey = "culture", Options = optionsCulture } };
services.AddSingleton(optionsCulture);
}
And the Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.Routes.Add(new RouteCustom(routes.DefaultHandler
, routes.ServiceProvider.GetRequiredService<IMemoryCache>()
, app.ApplicationServices.GetService<IUnitOfWork>()
));
routes.MapRoute(name: "areas", template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}");
});
}
The problem is here
app.ApplicationServices.GetService<IUnitOfWork>()
I need to inject the IUnitOfWork in order to check the database, but I don't know how to do it. In other Middlewares I could inject the IUnitOfWork directly in the function, but in this case I can't do it in the
public async Task RouteAsync(RouteContext context)
How can I achieve this? I'm sure I'm doing something wrong here, but I have been reading a lot of articles and can't figure out the way.
Thanks.
UPDATE: POSSIBLE SOLUTION
The only solution I can think is remove the injection into the IRouter and get the service "manually" inside the RouteAsync method.
public async Task RouteAsync(RouteContext context)
{
var unitOfWork = context.HttpContext.RequestServices.GetRequiredService<IUnitOfWork>()
var routes = unitOfWork.Router.GetAll();
...
}
This way we have access to the database and it works good.
Is it a good approach?

Wanting ASP.NET Core Middleware to run after MVC Razor View Engine

I want to have a MiddleWare module run in ASP.NET Core after the MVC Razor View Engine has processed data. I can get it to run but it seems to not have collected all the data. I have a Tag Helper that that updates an DI object's collection but when the Middleware runs, the DI object's collection is empty. My startup.cs looks like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMiddleware<MyMiddleware>();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
And My MiddleWare is this:
public class MyMiddleware
{
private readonly RequestDelegate nextMiddleware;
private readonly IScriptManager _scriptManager;
public MyMiddleware(RequestDelegate next, IScriptManager scriptManager)
{
this.nextMiddleware = next;
_scriptManager = scriptManager;
}
public async Task Invoke(HttpContext context)
{
var cnt = _scriptManager.ScriptTexts.Count;
.. get HTML
Stream originalStream = context.Response.Body;
...
.. update HTML
await context.Response.WriteAsync(htmlData);
I do get the HTML I want but it seems that the collection in my DI does not get updated.
*** Notes - Possible but not working Result Filter
services.AddMvc(options =>
{
options.Filters.Add(new AppendToHtmlBodyFilter());
});
public class AppendToHtmlBodyFilter : TypeFilterAttribute
{
private readonly IScriptManager _scriptManager;
public AppendToHtmlBodyFilter():base(typeof(SampleActionFilterImpl))
{
}
private class SampleActionFilterImpl : IResultFilter
{
private readonly IScriptManager _scriptManager;
public SampleActionFilterImpl(IScriptManager scriptManager)
{
_scriptManager = scriptManager;
//_logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
}
public void OnResultExecuted(ResultExecutedContext context)
{
var cnt = _scriptManager.ScriptTexts.Count;
Stream originalStream = context.HttpContext.Response.Body;
using (MemoryStream newStream = new MemoryStream())
{
context.HttpContext.Response.Body = newStream;
context.HttpContext.Response.Body = originalStream;
newStream.Seek(0, SeekOrigin.Begin);
StreamReader reader = new StreamReader(newStream);
var htmlData = reader.ReadToEnd();
As far as i know there is no way to run a middleware after mvc in request pipeline. If you want to manipulate razor output, you can use filters. Result filter seems suitable for your case.
Result filters are ideal for any logic that needs to directly surround
view execution or formatter execution. Result filters can replace or
modify the action result that's responsible for producing the
response.
See official docs https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters#result-filters
Also see how to use dependency injection with a filter https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters#dependency-injection
Update
I couldn't get it work with result filter(it worked json result but didn't work viewresult).
However i found a good example with middleware: http://www.mikesdotnetting.com/article/269/asp-net-5-middleware-or-where-has-my-httpmodule-gone
public class MyMiddleware
{
private readonly RequestDelegate nextMiddleware;
private readonly IScriptManager _scriptManager;
public MyMiddleware(RequestDelegate next, IScriptManager scriptManager)
{
this.nextMiddleware = next;
_scriptManager = scriptManager;
}
public async Task Invoke(HttpContext context)
{
var cnt = _scriptManager.ScriptTexts.Count;
using (var memoryStream = new MemoryStream())
{
var bodyStream = context.Response.Body;
context.Response.Body = memoryStream;
await _next(context);
var isHtml = context.Response.ContentType?.ToLower().Contains("text/html");
if (context.Response.StatusCode == 200 && isHtml.GetValueOrDefault())
{
memoryStream.Seek(0, SeekOrigin.Begin);
using (var streamReader = new StreamReader(memoryStream))
{
var responseBody = await streamReader.ReadToEndAsync();
// update html
using (var amendedBody = new MemoryStream())
using (var streamWriter = new StreamWriter(amendedBody))
{
streamWriter.Write(responseBody);
amendedBody.Seek(0, SeekOrigin.Begin);
await amendedBody.CopyToAsync(bodyStream);
}
}
}
}
}
}

Custom Router for Maintenance

I would like my ASP.NET Core MVC site to route all requests to a particular controller/action method whenever a particular file exists. The idea is that I can display a "site down for maintenance" page on an as-needed basis, just by creating a particular empty file.
After playing around with writing some custom middleware, it dawned on me that I should be able to create a custom IRouter which does this. Every route request should map to the "maintenance" route when that particular file exists. But I can't get it to work.
Here's the definition of the custom router:
public class OfflineRouteHandler : IRouter
{
private readonly string _path;
private readonly IPAddress _remote;
public OfflineRouteHandler( string offlinePath, string remoteIP )
{
_path = offlinePath;
_remote = IPAddress.Parse( remoteIP );
}
public VirtualPathData GetVirtualPath( VirtualPathContext context )
{
// this returns a value...but RouteAsync is never called
StringBuilder path = new StringBuilder();
if( context.AmbientValues.ContainsKey( "controller" ) )
path.Append( context.AmbientValues["controller"] );
if( context.AmbientValues.ContainsKey("action") )
{
if( path.Length > 0 ) path.Append( "/" );
path.Append( context.AmbientValues["action"] );
}
return new VirtualPathData( this, "/" + path.ToString() );
}
public async Task RouteAsync( RouteContext context )
{
bool authorized = ( context.HttpContext.Connection.RemoteIpAddress == _remote );
if( File.Exists(_path) && authorized )
{
// just for testing... but the method never gets called
var i = 9;
i++;
}
context.IsHandled = true;
}
}
I invoke it in Configure() in Startup.cs as follows:
string offlinePath = Path.Combine( Directory.GetParent( env.WebRootPath ).FullName, "offline.txt" );
app.UseMvc(routes =>
{
routes.Routes.Add( new TemplateRoute( new OfflineRouteHandler( offlinePath, "50.3.3.2" ), "home/offline", new DefaultInlineConstraintResolver(routeOptions)) );
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
routeOptions gets passed into the call to Configure();
Any help would be greatly appreciated!
This is untested, but I went for the middleware approach.
It uses a similar approach to the UseStatusCodePagesWithReExecute function, in that it modifies the context.Request.Path with the path. I've loaded the settings for the OfflineMiddleware using the Options pattern.
Essentially, when the file exists, the middleware modifies the path to the path you want "home/offline", and continues execution up the mvc pipeline.
As we've added UseStaticFiles() before the middleware, any static files on your offline page will be served - only requests that make it through to the MVC pipeline will be intercepted, and all of these requests will hit home/offline.
public class Startup
{
//Partial Starup class
public void ConfigureServices(IServiceCollection services)
{
services.Configure<OfflineMiddlewareSettings>(settings =>
{
settings.OfflinePath = "thePath";
settings.RemoteIP = "50.3.3.2";
});
// Add framework services.
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IOptionsMonitor<MyValues> monitor)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app. UseStaticFiles();
app.UseMiddleware<OfflineMiddleware>();
app.UseMvc();
}
}
public class OfflineMiddleware
{
private readonly RequestDelegate _next;
private readonly string _path;
private readonly IPAddress _remote;
public OfflineMiddleware(RequestDelegate next, IOptions<OfflineMiddlewareSettings> settings)
{
_next = next;
_path = settings.Value.OfflinePath;
_remote = IPAddress.Parse(settings.Value.OfflinePath);
}
public async Task Invoke(HttpContext context)
{
bool authorized = (context.Connection.RemoteIpAddress == _remote);
if (File.Exists(_path) && authorized)
{
context.Request.Path = "home/offline";
}
await _next.Invoke(context);
}
}
public class OfflineMiddlewareSettings
{
public string OfflinePath { get; set; }
public string RemoteIP { get; set; }
}