Is there a way to list all available routes in a Nancy application? - nancy

I'm implementing an API via a web service, using Nancy.
I'd like to have a /help or /docs page that programmatically lists all of the available routes, so that I can provide API users with automatically generated/updated documentation.
Any ideas on how to accomplish this? (Inside a route handler, "this.routes" gives access to a collection of defined routes - but only on the current NancyModule. I'd need a programmatic way to list all registered routes, not just ones in the current module)

Not exactly what you need, but there is also a built in dashboard panel in Nancy. To enable it do:
public class CustomBootstrapper : DefaultNancyBootstrapper
{
protected override DiagnosticsConfiguration DiagnosticsConfiguration
{
get { return new DiagnosticsConfiguration { Password = #"secret"}; }
}
}
And then you can access it on {yournancyapp}/_nancy
https://github.com/NancyFx/Nancy/wiki/Diagnostics

You can do it by taking a dependency on the IRouteCacheProvider and calling GetCache - we actually do this in one of our demos in the main repo:
https://github.com/NancyFx/Nancy/blob/master/src/Nancy.Demo.Hosting.Aspnet/MainModule.cs#L13

Example of how to use IRouteCacheProvider like #grumpydev mentioned in this answer:
// within your module
public class IndexModule : NancyModule
{
// add dependency to IRouteCacheProvider
public IndexModule(Nancy.Routing.IRouteCacheProvider rc)
{
routeCache = rc;
Get["/"] = GetIndex;
}
private Nancy.Routing.IRouteCacheProvider routeCache;
private dynamic GetIndex(dynamic arg)
{
var response = new IndexModel();
// get the cached routes
var cache = routeCache.GetCache();
response.Routes = cache.Values.SelectMany(t => t.Select(t1 => t1.Item2));
return response;
}
}
public class IndexModel
{
public IEnumerable<Nancy.Routing.RouteDescription> Routes { get; set; }
}
You can get the routing information like Path and Method from the list of Nancy.Routing.RouteDescription. For example with this view:
<!DOCTYPE html>
<html>
<body>
<p>Available routes:</p>
<table>
<thead><tr><th>URL</th><th>Method</th></tr></thead>
<tbody>
#Each.Routes
<tr><td>#Current.Path</td><td>#Current.Method</td></tr>
#EndEach
</tbody>
</table>
</body>
</html>

Related

Messages between HtmlPage and AndroidApp using GeckoView

Unfortunetly, I using a platform where WebView is not available, so, I can't make use of the simplicity of JavascriptInterface to interact from my webpage with my app.
Is there a complete (straight forward) example out there explaining how to Interact a page with my Android app using Geckoview?
I tried steps on this page (and others):
https://firefox-source-docs.mozilla.org/mobile/android/geckoview/consumer/web-extensions.html
Frankly speaking, I never saw a page hiding so many details like that.
Html page as simple as this, hosted (lets say) in "http://example.com/x.html":
<html>
<script>
function appToPage(s)
{
log('received:' + s);
}
function pageToApp(s)
{
// do something to send s to app
log('sent:' + s);
}
function log(s)
{
var x = document.getElementById('x');
x.innerHTML += '<br>' + s;
}
var i = 0;
</script>
<body>
<input type=button onclick="pageToApp('helloFromPage' + (i++))" value="SEND">
<div id="x"></div>
</body>
</html>
<script>
log('started');
</script>
Android side:
public class MainActivity extends AppCompatActivity
{
protected void onCreate(Bundle savedInstanceState)
{
...
//all the stuff needed for GeckoView and extensions
geckoSession.loadUri("http://example.com/x.html");
...
}
// when some user press some button in the browser
public void onSendButtonClick()
{
// do somenthing to call appToPage("helloFromApp");
}
// this should be called when a message arrives
public void onMessageFromPage(String s) // or whatever object as parameter (JSON, for example)
{
Log.d("msgFromPage", s)
}
...
}
I'm also using Geckoview from a month ago. To interact with your Website content you have to use a web extension.
There are two methods:
messaging
port messaging
I'm sharing an example link: https://searchfox.org/mozilla-central/source/mobile/android/examples

Ho do i pass a model with data from the DB to an ABP.IO Layout Hook?

trying to setup a multi-tenant site using ABP.io framework 3.1.
I am trying to set the <meta keywords (amongst other tags) in the page html head. I am attempting to get the values from a database field for the current tenant so the meta keywords will be specific for the tenant.
I tried to follow the sample that is available here: https://docs.abp.io/en/abp/latest/UI/AspNetCore/Customization-User-Interface#layout-hooks where they inject a google analytics script code into the head tag.
this is fine, as it is static text, but when i try to load the partial page with a model it throws an error of expecting a different model to that which is passed in.
So far i have the Notification View Componet
Public class MetaKeywordViewComponent : AbpViewComponent
{
public async Task<IViewComponentResult> InvokeAsync() {
return View("/Pages/Shared/Components/Head/MetaKeyword.cshtml"); //, meta);
}
}
and the cshtml page
#using MyCompany.MyProduct.Web.Pages.Shared.Components.Head
#model MetaKeywordModel
#if (Model.SiteData.Keywords.Length > 0)
{
<meta content="#Model.SiteData.Keywords" name="keywords" />
}
and the cshtml.cs file as
public class MetaKeywordModel : MyProductPageModel
{
private readonly ITenantSiteDataAppService _tenantSiteDataAppService;
public TenantSiteDataDto SiteData { get; private set; }
public MetaKeywordModel(ITenantSiteDataAppService tenantSiteDataAppService)
{
_tenantSiteDataAppService = tenantSiteDataAppService;
}
public virtual async Task<ActionResult> OnGetAsync()
{
if (CurrentTenant != null)
{
SiteData = await _tenantSiteDataAppService.GetSiteDataAsync();
}
return Page();
}
}
but when i run the program i get the following error.
An unhandled exception has occurred while executing the request.
System.InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook.LayoutHookViewModel', but this ViewDataDictionary instance requires a model item of type 'MyCompany.MyProduct.TenantData.Dtos.TenantSiteDataDto'.
How do i pass the data from my database into the page to be rendered if i can't use my model?
Any help tips or tricks would be greatly appreciated.
Regards
Matty
ViewComponent is different from the razor page.
See https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-3.1#view-components
You should inject the service in view component class directly. like:
public class MetaKeywordViewComponent : AbpViewComponent
{
private readonly ITenantSiteDataAppService _tenantSiteDataAppService;
public MetaKeywordViewComponent(ITenantSiteDataAppService tenantSiteDataAppService)
{
_tenantSiteDataAppService = tenantSiteDataAppService;
}
public async Task<IViewComponentResult> InvokeAsync()
{
return View("/Pages/Shared/Components/Head/MetaKeyword.cshtml",
await _tenantSiteDataAppService.GetSiteDataAsync());
}
}
In addition, you can refer https://github.com/abpframework/abp/blob/42f37c5ff01ad853a5425d15539d4222cd0dab69/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/PageAlerts/PageAlertsViewComponent.cs

ASP.NET MVC | ActionResult not getting called when going back to previous page

I understand that the title of the question may be vague but then that's the best way I could come up with to explain my issue at hand.
I'm overriding the OnActionExecuting function to manage my session related activities and allow/ deny requests to authorized/ unauthorized users, respectively. Along with tracking of the session, I'm also using the OnActionExecuting to load user available features for the current page into a temporary class and accessing from the view using ajax call.
namespace MyApp.Controllers
{
public class TESTController : Controller
{
[SessionTimeout]
public ActionResult Index()
{
return this.View();
}
}
}
public class SessionTimeoutAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
if (ctx.Session["AppUser"] == null)
{
// Redirect to the login page
// Or deny request
}
else
{
var controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var actionName = filterContext.ActionDescriptor.ActionName;
var methodType = ((ReflectedActionDescriptor)filterContext.ActionDescriptor).MethodInfo.ReturnType;
if (methodType == typeof(ActionResult))
{
// Load all user access rights for the current page into a temporary memory
// by using the Action and Controller name
}
}
base.OnActionExecuting(filterContext);
}
}
The above works like a charm.. But the issue is when the user clicks on the back button of the browser or hits the backspace key. In that case, the OnActionExecuting function is never called for the ActionResult and further I am unable to load the current page access rights for the user.
Thanks & Regards,
Kshitij
Adding the following to my ActionResult made the above code to work.
[SessionTimeout]
[OutputCache(Duration = 0, NoStore = true)]
public ActionResult SomeView()
{
return this.View();
}

How to replace Blazor default "soft 404" with an actual 404 status code response

The default Blazor approach to 404 is to create a soft 404 in App.razor, but I would like to adhere to search engine best practices to actually return the 404 status code while displaying a 404 page on Azure.
I tried to remove the element in App.razor to see if I could force a 404, however, that did not compile.
Any suggestions?
I use this code. It works well.
I created Error404Layout. I use this layout for NotFound part.
<Router AppAssembly="#typeof(Program).Assembly" PreferExactMatches="#true">
<Found Context="routeData">
<RouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="#typeof(Error404Layout)">
<h2>Oops! That page can't be found.</h2>
</LayoutView>
</NotFound>
Error404Layout content below
#inherits LayoutComponentBase
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor httpContextAccessor
#Body
#code {
protected override void OnInitialized()
{
httpContextAccessor.HttpContext.Response.StatusCode = 404;
}
}
You have to add this code in startup.cs / services method
public void ConfigureServices(IServiceCollection services)
{
....
services.AddHttpContextAccessor();
}
I can see 404 status code
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-3.1 does state:
Additionally, again for security reasons, you must not use
IHttpContextAccessor within Blazor apps. Blazor apps run outside of
the context of the ASP.NET Core pipeline and the HttpContext isn't
guaranteed to be available within the IHttpContextAccessor, nor it is
guaranteed to be holding the context that started the Blazor app.
So you need to create a wrapper around IHttpContextAccessor to limit this when Blazor is being server side prerendered.
I was able to return 404 http status codes when using server side prerendering in the Blazor WebAssembly App (ASP.Net Core Hosted) Template
When I pointed the browser to http://localhost/fetchdata it returned a page. I wanted this to return a 404 status code as an example. This was possible using dependency injection and a stub class.
In BlazorApp1.Client I added a IResponse.cs file:
namespace BlazorApp1.Client {
public interface IResponse {
void SetNotFound();
}
}
In BlazorApp1.Client I added a ResponseStub.cs file:
namespace BlazorApp1.Client {
public class ResponseStub : IResponse {
public void SetNotFound() {
// Do nothing if we are browser side
}
}
}
In FetchData.razor in BlazorApp1.Client I added:
#inject BlazorApp1.Client.IResponse Response
and in the code section:
protected override void OnInitialized() {
Response.SetNotFound();
}
In Program.cs in BlazorApp1.Client I added:
builder.Services.AddScoped<IResponse, ResponseStub>();
Then in BlazorApp1.Server, in Startup.cs I added under ConfigureServices:
services.AddHttpContextAccessor();
services.AddScoped<IResponse, Response>();
and under Configure I replaced:
endpoints.MapFallbackToFile("index.html");
with:
endpoints.MapFallbackToPage("/_Host");
Then create the Server implementation of IResponse in Response.cs:
using BlazorApp1.Client;
using Microsoft.AspNetCore.Http;
using System.Net;
namespace BlazorApp1.Server {
public class Response : IResponse {
private readonly IHttpContextAccessor _httpContextAccessor;
public Response(IHttpContextAccessor accessor) {
_httpContextAccessor = accessor;
}
public void SetNotFound() {
_httpContextAccessor.HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
}
}
}
And finally I create a _Host.cshtml file in BlazorApp1.Server/Pages:
#page "/fallback"
#namespace BlazorPrerendering.Server.Pages
#using BlazorApp1.Client
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#{
Layout = "_Layout";
}
<app>
<component type="typeof(App)" render-mode="ServerPrerendered" />
</app>
<div id="blazor-error-ui">
An unhandled error has occurred.
Reload
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
#ergin-Çelik Your solution works very well. I only had to add the following check because I got an error that the response had already been sent.
I have add this as answer because code formatting is better here.
protected override void OnInitialized()
{
if (_httpContextAccessor.HttpContext != null &&
!_httpContextAccessor.HttpContext.Response.HasStarted)
{
_httpContextAccessor.HttpContext.Response.StatusCode = (int) HttpStatusCode.NotFound;
}
}

Spring MVC: How to map server unknown URLs to the main application page?

We're using angular-route to map URLs to templates. The application works in a way that if for e.g. we're navigating from http://servername/appName to http://servername/appName/page1, the URL on the browser changes and the templates loads successfully.
The problem is that when the page is refreshed (or accessing directly http://servername/appName/page1), we're getting 404 error from the server. It seems like the default handler does not map unknown URLs to the default app page.
How can we make make the server return the default app page for all these angularjs URLs?
The code is below:
#Controller
public class HomeController {
#RequestMapping("/")
public String home() {
return "/WEB-INF/views/nbcalendar.html";
}
}
app configuration:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.appname.web"})
public class MvcConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
You can set a error handler in the web.xml configuration. Note! This web.xml configures your app inside the Servlet container (E.G. tomcat), it's not a Spring-MVC setting.
Add something like:
<error-page>
<error-code>404</error-code>
<location>/MY_HANDLER</location>
</error-page>
Where MY_HANDLER either is your default location, or something like a jsp that logs the event then forwards to the default location.
Hope that helps.
You can use wildcard expressions in your #RequestMapping to match your application and all it's subpages.
#RequestMapping("/**")
public String home() {
return "/WEB-INF/views/nbcalendar.html";
}
I was able to resolve it by adding the additional URLs to the home controller:
#Controller
public class HomeController {
private static String DEFUALT_PAGE = "/WEB-INF/views/main.jsp";
#RequestMapping("/")
public String home() {
return DEFUALT_PAGE;
}
#RequestMapping("/welcome")
public String welcome() {
return DEFUALT_PAGE;
}
#RequestMapping("/cluster/{id}")
public String cluster() {
return DEFUALT_PAGE;
}
}
This isn't a generic solution and requires to add any new URL manually, but it's working well

Resources