LinkGenerator returns null - c#

I defined a Blazor page with the following directive:
#page "section/{name}/details"
and I'm trying to generate a link to it from another page using the LinkGenerator:
var absoluteUri = this.LinkGenerator.GetUriByPage(this.HttpContextAccessor.HttpContext, "/section/{name}/details", values: new { name = "mysection" });
Anyway, absoluteUri is always null.
What am I missing?

The LinkGenerator is used for generating URLs for the server-side endpoint routing. It is able to generate links to endpoints that are known to the endpoint routing mechanism, i.e. things like MVC actions, Razor pages, or named routes.
Blazor pages are part of the client-side routing mechanism though (yes, even with server-side Blazor), and as such they don’t take part in the server-side routing mechanism. That’s why I don’t believe that the LinkGenerator could possibly generate URLs for client-side routing.
Unfortunately, I am not aware of an alternative that uses Blazor routing. So I think you will have to generate the URL manually, or build your own tool to generate the link. Since you are using server-side Blazor, you do have access to the HttpContext, so you can use that to generate an absolute URL. Something like this would probably work:
#inject IHttpContextAccessor httpContextAccessor
…
#code {
string GetUrl(string sectionName)
{
var request = httpContextAccessor.HttpContext.Request;
return request.Scheme + "://" + request.Host + request.PathBase + $"section/{sectionName}/details";
}
}

Related

How to Register Custom IRouter in .Net 7 MVC Application?

I have a custom IRouter implementation and I can't figure out how to register it in a .Net 7 MVC application.
What I am trying to accomplish is this: Incoming requests have the form of https://example.com/{id} and when such a request comes in I need to hit the database to retrieve the controller and action for that {id}, do some checks on it and if everything looks right pass the request on to the default router along with the entire RequestContext. The reason behind that is that such an url is valid only for a given time and a subset of visiting users. Also the underlying action and controller must not be guessable by looking at the url.
What I came up with is a cutom IRouter implementation (which also allows me to create those Urls) but I can't seem to figure out how to register on application startup.
Is using a custom IRouter still the right approach in .Net 7? How do I register one? Or am I totally on the wrong track?
One option is to switch back from endpoint routing:
builder.Services.AddMvc(options => options.EnableEndpointRouting = false);
app.UseMvc(routeBuilder => routeBuilder.Routes.Add(new CustomRouter(routeBuilder.DefaultHandler)));
UPD
Alternative approach is to use MapDynamicControllerRoute. Something along this lines:
builder.Services.AddScoped<MyDynamic>();
// ...
app.MapDynamicControllerRoute<MyDynamic>("/{*internalid}");
public class MyDynamic: DynamicRouteValueTransformer
{
public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
if (values.TryGetValue("internalid", out var value) && value is string s && s == "777") // simulate some search for id
{
values["controller"] = "Home";
values["action"] = "test";
}
return new ValueTask<RouteValueDictionary>(values);
}
}
Note that since you are still using the default routing pipeline this looks like security through obscurity which in general is not a good approach and probably you should better implement appropriate security restrictions (you should not rely on the "impossibility" to guess the actual routes).

ASP.NET Core Identity email confirmation URL for React routes

I'm using React router for my SPA and ASP.Net Core as an API backend using Identity for authentication. At the moment I'm trying to add email confirmation as part of the user registration process. What's troubling me is how to generate the confirmation URL without hard-coding the URL path into the backend.
This is what I'm currently working with:
// Somewhere in my UserService.cs...
// '_urlHelper' is an `IUrlHelper` injected into my service
var routeUrl = _urlHelper.RouteUrl("ConfirmEmail_Route",
new EmailConfirmationRequest { Email = email, Token = token },
requestScheme);
// Send the URL in some nicely formatted email
await _emailSender.SendConfirmationEmail(email, routeUrl);
// My API controller action to handle email confirmation
[HttpPost(Name = "ConfirmEmail_Route")]
public async Task<ActionResult> ConfirmEmail([FromBody] EmailConfirmationRequest payload)
{
var response = await _userService.ConfirmEmail(payload.Token, payload.Email);
...
}
The trouble is, that this generates a URL with a path like "/api/auth/ConfirmEmail?Email=..." but my React route is configured to handle a path like "/ConfirmEmail?Email=...". This means, when opening the URL the browser is obviously reaching the API controller action directly rather than going through my SPA (ignoring the fact the action expects a POST request).
All this makes good sense because _urlHelper.RouteUrl(...) only sees the controller actions within ASP.Net Core itself and knows nothing about the routes React uses. What I could do is hard-coding it somewhat like this:
var routeUrl = $"{requestScheme}://{hostname}/ConfirmEmail?Email={email}&Token={token}";
... which is not very versatile (I need to consider how to handle port number, subdomains etc.).
Are there any good alternatives I haven't been able to find yet?
Edit 26/12/2020:
It seems there's a little confusion about what the roles of my SPA and API backend are. To elaborate, this is my setup in Startup.cs (using .Net Core 2.1):
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Other irrelevant setup left out for brevity
// ...
app.UseAuthentication();
// Setup routing
// Specific routes are defined in controllers
app.UseMvc();
app.MapWhen(ctx => ctx.Request.Path.Value.StartsWith("/api"), builder =>
{
builder.UseStatusCodePagesWithReExecute("/api/error/{0}");
});
app.MapWhen(ctx => !ctx.Request.Path.Value.StartsWith("/api"), builder =>
{
builder.UseStatusCodePagesWithReExecute("/error/{0}");
builder.UseMvc(routes =>
{
// For any path not beginning with "/api" return the SPA (Javascript) bundle
routes.MapSpaFallbackRoute("spa-fallback", defaults: new { controller = "Home", action = "Index" });
});
});
}
In other words: the REST API is supposed to be seen as an individual entity not caring about rendering a view but only exposing the functionality communicating in JSON. The React SPA is a bundled Javascript file that takes care of all the UI rendering and communicating with the REST API for signup, login and whatnot. All is secured using JWT (tokens) for authentication.
This means that the REST API does not care about the paths/URLs that the SPA uses to navigate the user through the application (at least as far as possible I'd like the API to be agnostic about how a client/SPA handles the URLs/UI/navigation - but in this case I might make an exception if necessary). So, there's no controller actions in the backend matching the routes used in the React routes SPA, which makes it difficult to _urlHelper.RouteUrl(...) but I'm open for suggestions.
According you comment, "My API backend doesn't really know about the routes the SPA uses".
What I think is you can ask frontend to pass the URL what they want when click the link in email.
User register will make a POST request to backend with
{
account: xxx
confirmEmailUrl: "https://www.example/confirmEmail"
// Front-end pass what URL they want and create the corresponding page on their side
// So they need to create the page by themselves
}
Send email
var url = "https://www.example/confirmEmail" + userId
SendEmail(url)
So when user get email and click the link in email, it will redirect to the corresponding page created by frontend and you don't need to know any about frontend
3.Call the confirm API in frontend.
They need to implement this by themselves.
In the confirm page they created when page loaded.
Get userId from query string and call the API you provide

ASP.NET CORE 3.1 Custom Razor Page Routing for Customers

I have a asp.net web forms I am converting to .NET CORE. The website has many users managing multiple customers in the system. The users need to be able to manage customers from multiple browser tabs. My web old application simply passed the parameter CustomerId, and I had to manage the CustomerId value across multiple pages which was a pain. All pages had to use the query ?CustomerId=XXX
In ASP.net Core 3, is there is simple way to do this in routes and construct URLs? I would like to add a route that looks like this?
http://mywebsite.com/customer/XXX/SettingsPage this would route to a razor page /Pages/SettingsPage.cshtml
http://mywebsite.com/customer/XXX/AdminArea/Users/Edit1 this would route to an area with a razor page /Areas/AdminArea/Users/Edit.cshtml
Then in my code I can create URLs from the route and fetch the XXX value and query the customer table.
After reading this post Tab specific cookies without using sessionStorage or any HTML5 features
I used the URL rewrite engine in asp.net core. There is the sample project found here https://github.com/aspnet/AspNetCore.Docs/tree/master/aspnetcore/fundamentals/url-rewriting/samples/3.x/SampleApp
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(#"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2", skipRemainingRules: true)
.AddRewrite(#"customer(\d+)\/(.*)$", "$2?customerid=$1", skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(RewriteRules.MethodRules.RedirectXmlFileRequests)
.Add(RewriteRules.MethodRules.RewriteTextFileRequests)
.Add(new RewriteRules.RedirectImageRequests(".png", "/png-images"))
.Add(new RewriteRules.RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}

ASP.NET Core RazorPages to force AnchorTagHelper (asp-page) to use lowercase routes

I am using RazorPages in ASP.NET Core v2.0, and I was wondering if anyone knows how to force the AnchorTagHelper to use lowercase?
For example, in my .cshtml mark-up, I would have the following anchor tag using the asp-page tag helper:
<a asp-page="/contact-us">Contact Us</a>
The output that I am looking for
// i want this
Contact Us
// i get this
Contact Us
To give it more context to why this is important to my web app, please imagine a long url like what you would see at stackoverflow.com
https://stackoverflow.com/questions/anchortaghelper-asp-page-to-use-lowercase
-- VS --
https://stackoverflow.com/Questions/Anchortaghelper-asp-page-to-use-lowercase
Any help would be appreciated!
I have looked at these other references but no luck:
https://github.com/aspnet/Mvc/issues/6393
How do you enforce lowercase routing in ASP.NET Core MVC 6?
Add the following to your ConfigureServices method in Startup.cs. It can be added before or after services.AddMvc();
services.AddRouting(options =>
{
options.LowercaseUrls = true;
});
Now for the anchor tag
<a asp-page="/Contact-Us">Contact Page</a>
And the output
Contact Page
It's a bit easier now, simply add this to your ConfigureServices method in Startup.cs.
services.Configure<RouteOptions>(options =>
{
options.LowercaseUrls = true;
});
The url helper that will generate the target link will use the metadata that is being generated for routes. For razor pages, this uses the exact file path to identify the route.
Starting with ASP.NET Core 2.1, you will likely be able to adjust the route just like MVC routes using the Route attribute. At least it’s planned for the 2.1 release.
Until then, you will have to configure specific routes for your pages in the ConfigureServices method:
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AddPageRoute("/ContactUs", "contact-us");
});
This will keep the existing route available for use, so if you do not want that, you will need to replace the route selector for this page. There is no built-in way to do this, but it’s not too difficult to do it when you know what to do.
To avoid code duplication and to make everything nicer, we’ll just create a ReplacePageRoute extension method, that basically matches the usage of the AddPageRoute above:
services.AddMvc()
.AddRazorPagesOptions(options => {
options.Conventions.ReplacePageRoute("/Contact", "contact-us");
});
The implementation of that method is directly inspired by that AddPageRoute:
public static PageConventionCollection ReplacePageRoute(this PageConventionCollection conventions, string pageName, string route)
{
if (conventions == null)
throw new ArgumentNullException(nameof(conventions));
if (string.IsNullOrEmpty(pageName))
throw new ArgumentNullException(nameof(pageName));
if (route == null)
throw new ArgumentNullException(nameof(route));
conventions.AddPageRouteModelConvention(pageName, model =>
{
// clear all existing selectors
model.Selectors.Clear();
// add a single selector for the new route
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Template = route,
}
});
});
return conventions;
}

URL Routing across multiple subdomains

I find myself in a difficult situation. We're working on an ASP.NET MVC 2 application which is comprised of multiple sections. It is a design goal to have these sections span across several subdomains. Each subdomain will have its own controller.
The challenge is that our hosting provider's control panel allows two forms of redirection for subdomains, and neither of them seem to fit the bill. The choices are:
Redirecting to URL. Choice is given whether to redirect to an exact destination or a destination relative to the request URL.
Redirecting to a specific folder in my hosting space.
I'll try to illustrate the intended behaviour. Assuming the default route is {controller}/{action}/{id}, I'd like the URL http://subdomain.welcome.com/a/b be handled by the MVC Application like http://welcome.com/subdomain/a/b.
The URL redirection could solve this problem, except for the fact that the user sees a URL change occur in the browser. We don't want the client to see the redirection occur.
Redirecting to our MVC apps root folder doesn't work at all. The app doesn't pick up the request and a 4xx error gets passed back by IIS.
edit:
In the interest of finding an answer, I'll simplify this a bit. The "redirect to URL" doesn't do what I want so that leaves redirecting to a folder.
If I'm redirecting a subdomain to the root folder of my MVC App and IIS wont pick up the requests, is this a limitation of IIS or my provider?
Can you make your hosting website host headers respond to *.mydomain.com? Meaning, can your website take request for any sub domain of your primary domain? If so, then reference this post on how to handle subdomain routing in MVC apps and you should be good to go.
I would update the code in the post to this however, to make the code more succinct. In any case, make sure you have your 404 errors in place for people attempting to go to subdomains that don't exist.
public class ExampleRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var url = httpContext.Request.Headers["HOST"];
var index = url.IndexOf(".");
if (index < 0)
return null;
var subDomain = url.Substring(0, index);
var routeData = new RouteData(this, new MvcRouteHandler());
routeData.Values.Add("controller", subdomain); //attempts to go to controller action of the subdomain
routeData.Values.Add("action", "Index"); //Goes to the Index action on the User2Controller
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//Implement your formating Url formating here
return null;
}
}
Not sure if this is overkill (this is actually used to serve pages from a zip file or resource file, etc), BUT... perhaps you could use a Virtual Path Provider?..
Implement a class that inherits from VirtualPathProvider, and register it in global startup like so:
HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());
Then implement a class that inherits from VirtualFile and serve it from the GetFile() override in your virtual path provider implementation:
public override VirtualFile GetFile( string virtualPath )
{
if( IsVirtualFile(virtualPath) )
return new MyVirtualFile(virtualPath);
return base.GetFile(virtualPath);
}
Note: IsVirtualFile is a function you would have to implement, based on the rules you have regarding the URL format, etc.

Categories

Resources