ploeh / hyprlinkr Goto Github PK
View Code? Open in Web Editor NEWA URI building helper library for ASP.NET Web API
License: MIT License
A URI building helper library for ASP.NET Web API
License: MIT License
Hyprlinkr should have a logo, to be used as icon for its NuGet package, etc.
More information about issue:
http://stackoverflow.com/q/15047701
Working on a project, where controllers are being registered at startup time... Ie. I don't know which controllers actually exist at compiletime....
However, when using hyprlinkr I have to supply the controllertype to the GetUri method. Is there a non-typsafe way of calling GetUri ?
/Søren
Hi Guys.
I've been following the article here:
And am getting an error:
The route string returned by System.Web.Http.Routing.UrlHelper.Route(string, IDictionary) is null, which indicates an error. This can happen if the Action Method identified by the RouteLinker.GetUri method doesn’t have a matching route with the name “”, or if the route parameter names don’t match the method arguments.
Is that a common exception you've come across before?
Regards
I've been trialling porting Hyprlinkr to use Paket.
At present, there are 2 open issues that have material effect fsprojects/Paket#163 and fsprojects/Paket#161
(Side note: #164 should be fixed v soon and could waste time...)
However, even at this point, aside from the workarounds which should not be necessary for long, switching just involves the removal of explicit build versions from 2 lines in build.config
and addition of an explicti ref to NuGet.CommandLine
in the sln
-level packages.config
.
I'd be happy to do a PR if you want but I assume given how doing a paket convert-from-nuget
a good intro to Paket and that the best time to do this kind of mucking about is when someone is adding something concrete to act as a ship vehicle I'm happy for this issue to be read, acked and closed.
While the following workaround works for MVC, AsyncController
is not available on WebAPI, is there a workaround for it?
Uri actual = linker.GetUriAsync((AsyncController c) => c.Get(id)).Result;
Hi,
do you plan to add support for async controllers ?
btw great library.
Thanks
I've started using Hyperlinkr just a week or so ago, not long after I started working with Web Api 2.
I came across what looks like an issue to me, but it could also be that my expectation is wrong.
So, my problem is that while processing a request with a single Id parameter I try to get a link to a parameterless resource served by another controller and the resulting link includes the Id of the current request.
I see that the very same behaviour is exhibited by the HomeController
in ExampleService.
E.g. the response for http://localhost:6788/custom/route/5 is
{
"links":
[{
"href": "http://localhost:6788/custom/route/5",
"rel": "self"
}, {
"href": "http://localhost:6788/home/5",
"rel": "http://sample.ploeh.dk/rels/home"
}],
"name": "5"
}
The second "href" value "http://localhost:6788/home/5"
corresponds to the expression Href = this.linker.GetUri<HomeController>(r => r.Get()).ToString()
.
I cannot see why it should end with /5
.
Am I missing something?
Thanks,
Roman.
I realize that features for hyprlinkr are now fixed, so perhaps you could consider adding support for generating Uri Templates (ala http://tools.ietf.org/html/rfc6570) in v-next?
Is it possible to have Hyprlinkr support complex type parameters inside the MethodCallExpression passed to linker.GetUri?
The scenario I'm trying to accomplish is in trying to get the Uri of an action that accepts a complex type as the key.
I.e.
public Contact Get(ContactQuery query)
{
var contact = this.repository.GetContact(query.Id, query.UserName);
contact.Link = new Link()
{
Href = _linker.GetUri<ContactsController>(x => x.Get(query)).ToString(),
Rel = "self",
Title = contact.ToString()
};
return contact;
}
Where ContactQuery is defined as:
[FromUri]
public class ContactQuery
{
public int Id { get; set; }
public string UserName { get; set; }
}
When stepping into my CustomRouteDispatcher, I can see that the route linker has passed the routeValues with the type name as the value for the "query" key.
routeValues["query"]
"WebAPI.Controllers.Models.ContactQuery"
I "think" this is because the GetValue implementation in RouteLinker only calls ToString()
private static object GetValue(MethodCallExpression methodCallExp,
ParameterInfo p)
{
var arg = methodCallExp.Arguments[p.Position];
var lambda = Expression.Lambda(arg);
return lambda.Compile().DynamicInvoke().ToString();
}
Ideally, the values of the ContactQuery object, (Id and UserName) need to be pulled out and added to the route values.
Is this possible with the current implementation or is there a way that the RouteLinker can be extended so that I can customise this behaviour?
Appreciate your help.
Joe.
I am trying to setup HyprLinkr in order to be injected using castle windsor , as stated here in this
question
however I get the following error:
{code}
Handler for Ploeh.Hyprlinkr.IResourceLinkParser was not found.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: Castle.MicroKernel.Handlers.HandlerException: Handler for Ploeh.Hyprlinkr.IResourceLinkParser was not found.
Source Error:
Line 29: }
Line 30:
Line 31: var controller = (IController) kernel.Resolve(controllerType) as Controller;
Line 32:
Line 33: if (controller != null)
Source File: c:\git\websites\TempSearch\app\TempSearch.Web\Infrastructure\Ioc\WindsorControllerFactory.cs Line: 31
Stack Trace:
[HandlerException: Handler for Ploeh.Hyprlinkr.IResourceLinkParser was not found.]
Castle.MicroKernel.Resolvers.DefaultDependencyResolver.TryGetHandlerFromKernel(DependencyModel dependency, CreationContext context) +315
Castle.MicroKernel.Resolvers.DefaultDependencyResolver.ResolveFromKernelByType(CreationContext context, ComponentModel model, DependencyModel dependency) +80
[DependencyResolverException: Missing dependency.
Component TempSearch.ApplicationServices.Data.Services.TempSearchService has a dependency on Ploeh.Hyprlinkr.IResourceLinkParser, which could not be resolved.
Make sure the dependency is correctly registered in the container as a service, or provided as inline argument.]
Castle.MicroKernel.Resolvers.DefaultDependencyResolver.ResolveFromKernelByType(CreationContext context, ComponentModel model, DependencyModel dependency) +538
Castle.MicroKernel.Resolvers.DefaultDependencyResolver.Resolve(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency) +38
Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.CreateConstructorArguments(ConstructorCandidate constructor, CreationContext context) +430
Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.Instantiate(CreationContext context) +52
Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.InternalCreate(CreationContext context) +30
Castle.MicroKernel.ComponentActivator.AbstractComponentActivator.Create(CreationContext context, Burden burden) +27
Castle.MicroKernel.Lifestyle.AbstractLifestyleManager.CreateInstance(CreationContext context, Boolean trackedExternally) +57
Castle.MicroKernel.Lifestyle.<>c__DisplayClass1.b__0(Action1 afterCreated) +29 Castle.MicroKernel.Lifestyle.Scoped.DefaultLifetimeScope.GetCachedInstance(ComponentModel model, ScopedInstanceActivationCallback createInstance) +68 Castle.MicroKernel.Lifestyle.ScopedLifestyleManager.Resolve(CreationContext context, IReleasePolicy releasePolicy) +149 Castle.MicroKernel.Handlers.DefaultHandler.ResolveCore(CreationContext context, Boolean requiresDecommission, Boolean instanceRequired, Burden& burden) +282 Castle.MicroKernel.Handlers.DefaultHandler.Resolve(CreationContext context, Boolean instanceRequired) +35 Castle.MicroKernel.Resolvers.DefaultDependencyResolver.ResolveFromKernelByType(CreationContext context, ComponentModel model, DependencyModel dependency) +170 Castle.MicroKernel.Resolvers.DefaultDependencyResolver.Resolve(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency) +38 Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.CreateConstructorArguments(ConstructorCandidate constructor, CreationContext context) +430 Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.Instantiate(CreationContext context) +52 Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.InternalCreate(CreationContext context) +30 Castle.MicroKernel.ComponentActivator.AbstractComponentActivator.Create(CreationContext context, Burden burden) +27 Castle.MicroKernel.Lifestyle.AbstractLifestyleManager.CreateInstance(CreationContext context, Boolean trackedExternally) +57 Castle.MicroKernel.Lifestyle.<>c__DisplayClass1.<Resolve>b__0(Action
1 afterCreated) +29
Castle.MicroKernel.Lifestyle.Scoped.DefaultLifetimeScope.GetCachedInstance(ComponentModel model, ScopedInstanceActivationCallback createInstance) +68
Castle.MicroKernel.Lifestyle.ScopedLifestyleManager.Resolve(CreationContext context, IReleasePolicy releasePolicy) +149
Castle.MicroKernel.Handlers.DefaultHandler.ResolveCore(CreationContext context, Boolean requiresDecommission, Boolean instanceRequired, Burden& burden) +282
Castle.MicroKernel.Handlers.DefaultHandler.Resolve(CreationContext context, Boolean instanceRequired) +35
Castle.MicroKernel.DefaultKernel.ResolveComponent(IHandler handler, Type service, IDictionary additionalArguments, IReleasePolicy policy) +154
Castle.MicroKernel.DefaultKernel.Castle.MicroKernel.IKernelInternal.Resolve(Type service, IDictionary arguments, IReleasePolicy policy) +74
TempSearch.Web.Infrastructure.Ioc.WindsorControllerFactory.GetControllerInstance(RequestContext requestContext, Type controllerType) in c:\git\websites\TempSearch\app\TempSearch.Web\Infrastructure\Ioc\WindsorControllerFactory.cs:31
System.Web.Mvc.DefaultControllerFactory.CreateController(RequestContext requestContext, String controllerName) +169
Castle.Proxies.Invocations.IControllerFactory_CreateController.InvokeMethodOnTarget() +155
Castle.DynamicProxy.AbstractInvocation.Proceed() +116
Glimpse.Core.Extensibility.AlternateMethod.NewImplementation(IAlternateMethodContext context) +71
Castle.DynamicProxy.AbstractInvocation.Proceed() +595
Castle.Proxies.IControllerFactoryProxy.CreateController(RequestContext requestContext, String controllerName) +193
System.Web.Mvc.MvcHandler.ProcessRequestInit(HttpContextBase httpContext, IController& controller, IControllerFactory& factory) +270
System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +147
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +12288259
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +288
{code}
The RouteLinker
class doesn't own the request and is not responsible for disposing it - that's the job of the WebApi infrastructure.
I think it is actually a bug to dispose of a resource that is owned by someone else.
A general question: Do you want me to update the version number when making changes and include that in the pull request? I think versioning is your responsibility, that's why I haven't done that so far.
What about the Readme? Currently, it doesn't reflect the fact that the default API route has been changed. Additionally it doesn't show an example for the ResourceLinkVerifier. Should I have changed the Readme along with the source code changes?
The example application also doesn't contain examples for the features I added. Should I have added them?
In the case I don't want to create a RouteLinker, what do you think about extending the UrlHelper inside an ApiController? Something like this:
https://gist.github.com/jbogard/7918898
Obviously I'd namespace this accordingly so that it's opt-in.
How do you use it with attributeRouting?
The default API route in new Web API projects is now named DefaultApi
and not API Default
. Because of this, the default value of API Default
that is used by DefaultRouteDispatcher
is somewhat useless.
Shouldn't this be changed?
Hi Mark,
I would like to create a link to a method that receives POSTs (later I will be using the other verbs too).
I have an Action Method on my RoutingRuleController:
[HttpPost]
public ResourceResponse<RoutingRule> Add([FromBody] RoutingRule routingRule)
{
...
}
I then have the following Route declared:
_routes.MapHttpRoute(
name: "CreateItem",
routeTemplate: "api/{controller}",
defaults: new
{
action = "post"
},
constraints: new
{
httpMethod = new HttpMethodConstraint(HttpMethod.Post)
}
);
(ResourceResponse is our implementation of HAL compatible objects)
When I use hyprlinkr to generate a link to this method:
var href = _linker.GetUri<RoutingRuleController>(c => c.Add(new RoutingRule()));
it generates:
http://dogsmanager:9600/api/routingrule?routingRule=Dogs.Contract.Models.RoutingRule
when really it should be:
http://dogsmanager:9600/api/routingrule
(the documented naming convention in my HAL link relations indicate that this should be a POST of the RoutingRule resource, so that is how we communicate to the client the correct method to use).
In this case, any param that is decorated with [FromBody] should not be included in the generated Uri
Is the library only intended for linking to GET methods?
BTW, it also feels weird doing:
var href = _linker.GetUri<RoutingRuleController>(c => c.Add(new RoutingRule()));
But if I do:
var href = _linker.GetUri<RoutingRuleController>(c => c.Add(null));
I get NullRefException. Is HyprLinkr invoking the controller method?
Do you think that it is possible to generate link templates or do you have any suggestion about how to extend it to support them?
For example:
var uri = linker.GetUri<FooController>(r => r.GetById(linker.Template("myid")));
to generate
http://localhost/api/foo/{myid}
Or
var uri = linker.GetUri<FooController>(r => r.GetById(linker.Template));
to generate
http://localhost/api/foo/{id}
ScalarRouteValueQuery throws a NullReferenceException on line 109 (var value = lambda.Compile().DynamicInvoke().ToString();
) when you pass null
for the value of one of the parameters. In my limited testing, removing the ToString
got around the issue although I didn't look into why the ToString
was there in the first place.
First of all, thank you for your work.
Have you thought about the possibility of supporting relative Uri generation in addition to absolute ones?
If that is something that you would be OK with, I'm available to contribute.
Thanks
Are there plans to implement the reversal of link generation?
Imagine, in a controller action that creates a new object, a link to the newly created object is returned, just like in the Post
method here. Hyprlinkr could be used to create that link instead of the GetContactLocation
method.
But now, lets assume that each contact has a list of related contacts. When you add a new contact, these related contacts would be supplied as URIs in the request. To be able to correctly link the new contact to the related contacts in the domain model, you would at least need to extract the IDs from the URIs.
Wouldn't it be nice to use Hyprlinkr to somehow get these related objects or their IDs?
What are your thoughts on this?
Hi,
I am using it in a web api 2 project along with the attribute routing. I have a function as below:
// GET /api/authors/1/books
[Route("~/api/authors/{authorId:int}/books")]
public IEnumerable GetByAuthor(int authorId) { ... }
When calling this function as:
var link=linker.GetUri(a=>a.GetByAuther(id)).ToString();
It returns as http://localhost/api/auther/1
regards,
rahman
I am using attribute routing in my controllers, using the [Route]
attribute with route names, and I noticed that Hyprlinkr always assumes that there's a route named 'DefaultApi' to generate the Uris, which does not work with attribute routing.
Is that correct, or am I missing something?
Thank you
I have a controller action which has the following signature
public async Task Delete(int id);
it returns Task so that the webapi will return a 204 No Content by default. However the RouteLinker.GetUriAsync is type parameterized and expects the 2nd argument to be a parameterized Task.
I think there should be a non generic version of the method that has the following signature:
public Task<Uri> GetUriAsync<T>(Expression<Func<T, Task>> method);
Assume the following scenario:
You have an generic abstract class deriving from ApiController
. This class implements the default actions for GetAll
, GetById
, Add
, Update
and Delete
.
Now you have several controllers deriving from that base class, e.g.
public class SubjectsController : CrudControllerBase<SubjectModel>
{
}
When RouteLinker
is asked for an URI for one of the actions of the base class, it returns an URI with the baseclass as controller, although that URI is not working. The correct URI would be to use the controller supplied as type argument.
linker.GetUri<SubjectController>(c => c.GetAll());
This produces http://<host>/api/crudbase´1
instead of http://<host>/api/subjects
.
I think the problem is two-fold:
DefaultRouteDispatcher
sets the route value controller
to the class that declares the action method. However, a new implementation of IRouteDispatcher
doesn't solve that problem because the generic type argument passed to RouteLinker.GetUri<T>
is not forwarded to IRouteDispatcher
IRouteDispatcher
leads to an ArgumentNullException
, because RouteLinker
doesn't provide a fallback value in case the dispatcher didn't provide a controller.I think the solution needs to be to forward the specified controller type to IRouteDispatcher
.
I created two failing tests to better demonstrate my findings:
https://github.com/dhilgarth/Hyprlinkr/blob/bug/ControllerWithAbstractBaseClass/Hyprlinkr.UnitTest/RouteLinkerTests.cs#L212
When we use action definition with HttpRequestMessage
parameter
public HttpResponseMessage Get(HttpRequestMessage request, int id)
{
}
this.Url.GetLink<StatusValuesController>(a => a.Get(request, key));
produce url like this
/apiv1/Values/3?request=Method%3A%20POST%2C%20RequestUri...
request
parameter is not a part of routing and it souldn't be in generated url.
It only works fine if I pass null for request
parameter
this.Url.GetLink<StatusValuesController>(a => a.Get(null, key));
Especially with multiple routes for nested resources it is necessary to use different route dispatchers.
The route dispatcher is only used in the call to GetUri, and thus an overload that accepts an IRouteDispatcher
is possible.
Use case:
// uses default dispatcher or dispatcher that has been passed into the constructor:
model.Language =
linker.GetUri<LanguagesController>(c => c.GetById(entity.Language.Id));
// uses the specific dispatcher for that action:
model.Comments =
linker.GetUri<PostsController>(c => c.GetCommentsOfPosts(entity.Id),
new MyRouteDispatcher("Posts Routes"));
// uses the DefaultRouteDispatcher for the specified route:
model.Comments =
linker.GetUri<PostsController>(c => c.GetCommentsOfPosts(entity.Id), "Posts Routes");
Implementation is trivial and can be done by me, if you agree with this overload.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.