Get routing information about a URL - asp.net

I've got an ASP.NET MVC website that needs to display user-provided URLs stored in the DB. The way they're displayed will be different depending on how that URL would be routed if that URL refers to the website itself.
For example, supposing the website is foo.com:
URL stored in DB: foo.com/pie/3/nutrition
Controller is "pie"
Action is "nutrition"
ID is 3
The way the link is formatted depends on all three of these.
How would I extract this information correctly? Can I query the URL routing device?
Note: "Use a regular expression" type of answers don't interest me -- the site, action, or controller names could change, the website may be accessible through multiple site names and ports, etc...

You may find the RouteInfo class illustrated in this blog post useful:
public class RouteInfo
{
public RouteData RouteData { get; private set; }
public RouteInfo(Uri uri, string applicationPath)
{
RouteData = RouteTable.Routes.GetRouteData(new InternalHttpContext(uri, applicationPath));
}
private class InternalHttpContext : HttpContextBase
{
private readonly HttpRequestBase request;
public InternalHttpContext(Uri uri, string applicationPath)
{
this.request = new InternalRequestContext(uri, applicationPath);
}
public override HttpRequestBase Request
{
get { return this.request; }
}
}
private class InternalRequestContext : HttpRequestBase
{
private readonly string appRelativePath;
private readonly string pathInfo;
public InternalRequestContext(Uri uri, string applicationPath)
{
this.pathInfo = uri.Query;
if (string.IsNullOrEmpty(applicationPath) || !uri.AbsolutePath.StartsWith(applicationPath, StringComparison.OrdinalIgnoreCase))
{
this.appRelativePath = uri.AbsolutePath.Substring(applicationPath.Length);
}
else
{
this.appRelativePath = uri.AbsolutePath;
}
}
public override string AppRelativeCurrentExecutionFilePath
{
get { return string.Concat("~", appRelativePath); }
}
public override string PathInfo
{
get { return this.pathInfo; }
}
}
}
You could use it like this:
public ActionResult Index()
{
Uri uri = new Uri("http://foo.com/pie/3/nutrition");
RouteInfo routeInfo = new RouteInfo(uri, this.HttpContext.Request.ApplicationPath);
RouteData routeData = routeInfo.RouteData;
string controller = routeData.GetRequiredString("controller");
string action = routeData.GetRequiredString("action");
string id = routeData.Values["id"] as string;
...
}

From the section about unit testing in this: scott guthrie blog post
you can do something like this:
MockHttpContext httpContxt = new MockHttpContext("foo.com/pie/3/nutrition");
RouteData routeData = new routes.GetRouteData(httpContext);
where routes is the RouteCollection you used to initialize your routes in your application. Then you can interrogate routeData["controller"] etc
the post is about an early version of MVC, so the class names may have changed since then.

Related

Asp.net Core Razor Pages Access Constant Route Value

Within the Asp.net Core 3.x Razor Pages web site, I need to set multiple predefined constant route names pointing to the single page and then access them on the particular cshtml.cs PageModel.
options.Conventions.AddPageRoute("/propertylist", "/properties/category1");
options.Conventions.AddPageRoute("/propertylist", "/properties/category2");
options.Conventions.AddPageRoute("/propertylist", "/properties/category3");
// I don't want any other categories to reach to the propertylist page so I didn't set it as '/properties/{categoryName}'
And then inside the PropertyList.cshtml.cs, I need to know what category was used as the route data
public async Task OnGet()
{
// Get the route data to find the category name
}
I have come up with this solution. First I've created a route constraint.
public class PropertyListRouteConstraint : IRouteConstraint
{
private string[] categories = new[] {
Constants.Routes.Categories.Category1,
Constants.Routes.Categories.Category2,
Constants.Routes.Categories.Category3
};
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
{
return categories.Contains(values[routeKey]);
}
}
Then in Startup.cs, I removed the previous route names and added this
services.Configure<RouteOptions>(options =>
{
options.ConstraintMap.Add("allowedpropertycats", typeof(PropertyListRouteConstraint));
});
In PropertyList.cshtml
#page "/properties/{categoryname:allowedpropertycats}"
In PropertyList.cshtml.cs
public class PropertyListModel : PageModel
{
[BindProperty(SupportsGet = true)]
public string CategoryName { get; set; }
public async Task OnGet()
{
...
}
}
So CategoryName contains the value.

IConfigureOptions<T> is not creating scoped options

Typically Options are singleton. However i am building options from the database, and one of the Options property is password which keep changing every month. So i wanted to create Scoped instance of Options. I am using IConfigureOptions<T> like below to build Options from the database
public class MyOptions
{
public string UserID {get;set;}
public string Password {get;set;
}
public class ConfigureMyOptions : IConfigureOptions<MyOptions>
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public ConfigureMyOptions(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public void Configure(MyOptions options)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var provider = scope.ServiceProvider;
using (var dbContext = provider.GetRequiredService<MyDBContext>())
{
options.Configuration = dbContext.MyOptions
.SingleOrDefault()
.Select(x => new MyOptions()
{
UserID = x.UserID,
Password = x.Password
});
}
}
}
}
Use it in controller
public class HomeController : BaseController
{
private readonly MyOptions _options;
public HomeController(IOptions<MyOptions> option)
{
_options = option.Value;
}
[HttpGet]
[Route("home/getvalue")]
public string GetValue()
{
// do something with _options here
return "Success";
}
}
I want to create an instance of MyOptions for every new request so register it as Scoped in startup.cs
services.AddScoped<IConfigureOptions<MyOptions>, ConfigureMyOptions>();
However, when i put debugger inside ConfigureMyOptions's Configure method it only gets hit once for the first request. For next request onward the container returns the same instance (like singleton).
How do i set the scope here so MyOptions will get created for each request?
Use IOptionsSnapshot instead of IOptions in your controller and it will recreate options per request.
Why doesn't work with IOptions:
.AddOptions extension method of Configuration API registers the OptionsManager instance as a singlethon for IOptions<>
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
and OptionsManager class uses caching internally:
public virtual TOptions Get(string name)
{
name = name ?? Options.DefaultName;
// Store the options in our instance cache
return _cache.GetOrAdd(name, () => _factory.Create(name));
}
The following issue on github helped to find above: OptionsSnapshot should always be recreated per request

ASP.NET Core Custom Parameter Binding

I have a situation in which I would like to do custom parameter binding for an api controller in ASP.NET core.In WebAPI 2.0 it was possible to perform custom binding to primitive types by implementing various interfaces such as IValueProvider and providing a ValueProviderFactory. This does not seem the case with ASP.NET core in as far as what I understand from the documentation I found here.
I did notice this SO post which lead me to this article which overrides the behavior for the MutableObjectModelBinder. It would appear I could do something along those lines such as:
[HttpGet]
[Route("api/{domain}/[controller]")]
public IEnumerable<string> Get([ModelBinder(BinderType = typeof(MyCustomBinder))]string orderby)
{
//Do stuff here
}
This doesn't necessarily seem right to me since I am just dealing with a primitive type however I cannot seem to find any documentation for another way of doing this.
Create a binder provider class for your custom type
public class MyCustomBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(MyCustom))
{
return new BinderTypeModelBinder(typeof(MyCustomBinder));
}
return null;
}
}
and register it in the services
services.AddMvc(c =>
{
c.ModelBinderProviders.Insert(0, new MyCustomBinderProvider());
});
And the custom binder can go like
public class MyCustomBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(MyCustom))
{
return TaskCache.CompletedTask;
}
var parameters = new Dictionary<string, string>();
foreach (var parameter in bindingContext.ModelType.GetProperties())
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(parameter.Name);
if (valueProviderResult.FirstValue != null)
{
parameters.Add(parameter.Name, valueProviderResult.FirstValue);
}
}
var result = Activator.CreateInstance(bindingContext.ModelType);
//write your custom code to map the result with the parameters
bindingContext.Result = ModelBindingResult.Success(result);
return TaskCache.CompletedTask;
}
}
Your custom type class
[ModelBinder(BinderType = typeof(MyCustomBinder))]
public class MyCustom
{
public int Page { get; set; }
public int Rows { get; set; }
}
and your controller can take the custom class as query string parameter
[HttpGet("data")]
public DataTransferObject GetData(MyCustom query)
{
}
Migrating OP's solution from the question to an answer, with meta commentary trimmed:
I just decided to go with a helper class to parse the parameter due to having to meet deadlines.

MVC5 attribute based routing for subdomains?

How can one perform subdomain based URL routing in ASP.NET MVC5 using attribute based routing? I'm aware of this post but we're trying to move to a cleaner attribute based approach and I would like to move my route
from http://domain.com/Account/Logout/
to http://my.domain.com/Account/Logout/
Without subdomain routing, this standard code works:
[RoutePrefix("Account")]
public class AccountController : ApiController
{
[Route("Logout")]
public IHttpActionResult Logout()
{
// logic
}
}
To add subdomain based routing, I wrote a custom attribute and a custom constraint. Basically replaced the Route attributes so I can specify the subdomain but my custom SubdomainRoute attribute doesn't work. My attempt is below. I presume a better implementation would also customize the RoutePrefix attribute to specify subdomains ...
SubdomainRoute
public class SubdomainRouteAttribute : RouteFactoryAttribute
{
public SubdomainRouteAttribute(string template, string subdomain) : base(template)
{
Subdomain = subdomain;
}
public string Subdomain
{
get;
private set;
}
public override RouteValueDictionary Constraints
{
get
{
var constraints = new RouteValueDictionary();
constraints.Add("subdomain", new SubdomainRouteConstraint(Subdomain));
return constraints;
}
}
}
SubdomainRouteConstraint
public class SubdomainRouteConstraint : IRouteConstraint
{
private readonly string _subdomain;
public SubdomainRouteConstraint(string subdomain)
{
_subdomain = subdomain;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return httpContext.Request.Url != null && httpContext.Request.Url.Host.StartsWith(_subdomain);
}
}
Any ideas on how to make this work?

WebApi: mapping parameter to header value

I've done a few searches but haven't seem to find anything...
Using WebApi, I would like to map an input parameter to a header value: e.g.
E.g. in controller:
public User GetUser(int id){
...
return user;
}
I want WebApi to map the id parameter to a header value (e.g. X-Auth: 1234)... rather than an URL parameter.
Is this supported?
I don't think this is supported out of the box, like for example with the [FromBody] attribute.
It seems you should be able to achieve this functionality by using Model Binders, as described here. In the model binder you have access to the request and its headers, so you should be able to read the header and set its value to the bindingContext.Model property.
Edit: Reading the article further, it seems a custom HttpParameterBinding and a ParameterBindingAttribute is a more appropriate solution, or at least I would go this way. You could implement a generic [FromHeader] attribute, which does the job. I am also fighting the same problem, so I will post my solution once I have it in place.
Edit 2: Here is my implementation:
public class FromHeaderBinding : HttpParameterBinding
{
private string name;
public FromHeaderBinding(HttpParameterDescriptor parameter, string headerName)
: base(parameter)
{
if (string.IsNullOrEmpty(headerName))
{
throw new ArgumentNullException("headerName");
}
this.name = headerName;
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
IEnumerable<string> values;
if (actionContext.Request.Headers.TryGetValues(this.name, out values))
{
actionContext.ActionArguments[this.Descriptor.ParameterName] = values.FirstOrDefault();
}
var taskSource = new TaskCompletionSource<object>();
taskSource.SetResult(null);
return taskSource.Task;
}
}
public abstract class FromHeaderAttribute : ParameterBindingAttribute
{
private string name;
public FromHeaderAttribute(string headerName)
{
this.name = headerName;
}
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
return new FromHeaderBinding(parameter, this.name);
}
}
public class MyHeaderAttribute : FromHeaderAttribute
{
public MyHeaderAttribute()
: base("MyHeaderName")
{
}
}
Then you can use it like this:
[HttpGet]
public IHttpActionResult GetItem([MyHeader] string headerValue)
{
...
}
Hope that helps.
WebApi on DotNet Core has a has some additional attributes for extracting data from the request. Microsoft.AspNetCore.Mvc.FromHeaderAttribute will read from the request head.
public ActionResult ReadFromHeader([FromHeader(Name = "your-header-property-name")] string data){
//Do something
}
Thank you filipov for the answer.. I took your code and modified it a bit to suit my needs. I am posting my changes here in case anyone can make use of this.
I made 2 changes.
I liked the idea of the FromHeaderAttribute, but without subclassing. I made this class public, and require the user to set the param name.
I needed to support other data types besides string. So I attempt to convert the string value to the descriptor's parameterType.
Use it like this:
[HttpGet]
public void DeleteWidget(long widgetId, [FromHeader("widgetVersion")] int version)
{
...
}
And this is my FromHeaderBinding
public class FromHeaderBinding : HttpParameterBinding
{
private readonly string _name;
public FromHeaderBinding(HttpParameterDescriptor parameter, string headerName)
: base(parameter)
{
if (string.IsNullOrEmpty(headerName)) throw new ArgumentNullException("headerName");
_name = headerName;
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
IEnumerable<string> values;
if (actionContext.Request.Headers.TryGetValues(_name, out values))
{
var tempVal = values.FirstOrDefault();
if (tempVal != null)
{
var actionValue = Convert.ChangeType(tempVal, Descriptor.ParameterType);
actionContext.ActionArguments[Descriptor.ParameterName] = actionValue;
}
}
var taskSource = new TaskCompletionSource<object>();
taskSource.SetResult(null);
return taskSource.Task;
}
}

Resources