UrlHelper sometimes not returning the base url - c#

I'm creating a .NET Core 2.1 web api and am experiencing issues with the UrlHelper. I use the Link method to determine links from one resource to another. Most of the time it is working just fine, but sometimes the resulting string lacks the base url.
E.g.:
Request 1: http://localhost:1234/api/books?pageNumber=1&pageSize=10 OK
Request 2: http:///api/books?pageNumber=1&pageSize=10 not OK
And it goes back and forth between the correct and the wrong result, sometimes all of them are wrong, other times all of them are OK.
ConfigureServices:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IUrlHelper, UrlHelper>(factory =>
{
var actionContext = factory.GetService<IActionContextAccessor>().ActionContext;
return new UrlHelper(actionContext);
});
Example method for creating the links (pagination):
public string CreateResourceUri(
ResourceUriType type,
string name,
IUrlHelper urlHelper,
object additionalValues = null)
{
dynamic values = CreateValues().Merge(additionalValues);
switch (type)
{
case ResourceUriType.PreviousPage:
values.pageNumber = values.pageNumber - 1;
return urlHelper.Link(
name,
values);
case ResourceUriType.NextPage:
values.pageNumber = values.pageNumber + 1;
return urlHelper.Link(
name,
values);
default:
return urlHelper.Link(
name,
values);
}
}
Any ideas on what's going wrong here would be greatly appreciated.

Related

Web API and C# out Values

I have a service that contains a lot of functions with out parameters to return a few extra parameters.
I was wondering if it's possible to call a regular asp.NET web api service with out parameters and receive a value(in the form of out parameters, separate from the return value) from the service.
If it is possible, could you elaborate on what I need to do to achieve this?
Any help will be well appreciated.
No, this is not possible. The response from WebAPI will be a normal HTTP response with a body where the serialized returned data will be.
Of course, as usual, your response can be a complex object to serialize and you can include those out returns as members of it. For example:
public IHttpActionResult GetResponse(int id)
{
int outputInt;
string outputString;
YourMethodWithOutParameters(id, out outputInt, out outputString);
return Ok(new
{
Id = id,
OutputInt = outputInt,
OutputString = outputString,
});
}

Getting POST data from WebAPI

We're working on developing an application that uses Plivo for sending and receiving SMS messages. For every request that Plivo sends, they also send a signature in the HTTP header so that we can verify the request came from Plivo and not from a random user.
https://www.plivo.com/docs/xml/request/#validation
To do this validation, we require the POST content as a query string (eg: To=15555555555&From=11234567890&TotalRate=0&Units=1&Text=Text!&TotalAmount=0&Type=sms&MessageUUID=2be622bc-79f8-11e6-8dc0-06435fceaad7).
Current solution
This is what we have so far:
private bool VerifyPlivo(object thing, HttpRequestMessage Request)
{
if (Request.Headers.Contains("X-Plivo-Signature"))
{
Dictionary<string, string> reqParams = (from x in thing.GetType().GetProperties() select x).ToDictionary(x => x.Name, x => (x.GetGetMethod().Invoke(thing, null) == null ? "" : x.GetGetMethod().Invoke(thing, null).ToString()));
IEnumerable<string> headerValues = Request.Headers.GetValues("X-Plivo-Signature");
string signature = headerValues.FirstOrDefault();
return XPlivoSignature.Verify(Request.RequestUri.ToString(), reqParams, signature, plivoToken);
}
else
{
return false;
}
}
[Route("RecieveSMS")]
[HttpPost]
public HttpResponseMessage RecieveSMS(PlivoRecieveSMS req)
{
if (!VerifyPlivo(req, Request))
{
return new HttpResponseMessage(HttpStatusCode.Forbidden);
}
... // do actual work here
}
This works by using the object that it maps to PlivoRecieveSMS and doing some reflection to get the properties and values, and sticking them in a Dictionary. This works well especially given our lack of the preferred solution...
Preferred solution
Right now, we require a model (PlivoRecieveSMS) to map the data, and then do introspection to find the key/values. We would like to move the logic to an extension of System.Web.Http.AuthorizeAttribute, so that we can do something as simple as:
[AuthorizedPlivoApi]
[Route("RecieveSMS")]
[HttpPost]
public HttpResponseMessage RecieveSMS(PlivoRecieveSMS req)
{
... // do actual work here
}
The actual authorization is done in AuthorizedPlivoApi - if it's not valid, the request never reaches the controller. But we cannot do this at the moment because we can't map it to a specific object inside of AuthorizedPlivoApi.
I would like to access the POST key's / values directly, or perhaps map it to a dynamic object that isn't pre-defined before hand. If I can do that, we can then achieve our preferred solution.
tl;dr: is there any way to push application/x-www-form-urlencoded data from a POST request into a Dictionary<string,string>() without using a specific model?

OData route exception

I am new to this so i will start with the code and after that i will explain.
The problem is this
[HttpGet, ODataRoute("({key})")]
public SingleResult<Employee> GetByKey([FromODataUri] string key)
{
var result = EmployeesHolder.Employees.Where(id => id.Name == key).AsQueryable();
return SingleResult<Employee>.Create<Employee>(result);
}
[HttpGet, ODataRoute("({key})")]
public SingleResult<Employee> Get([FromODataUri] int key)
{
var result = EmployeesHolder.Employees.Where(id => id.Id == key).AsQueryable();
return SingleResult<Employee>.Create<Employee>(result);
}
I have those 2 actions but for one i want to search by a string and for the other by number (although this is not the problem). If i leave it this way it will work for the (int) case but for the string "....odata/Employees('someName')" i will get a : HTTP 404 (and it's normal) but if i try to be more specific with the method which takes a string
Code in webApiConfig.
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Employee>("Employees");
builder.Function("GetByKeyFromConfig").Returns<SingleResult<Employee>>().Parameter<string>("Key");
Code in Controller
[ODataRoutePrefix("Employees")]
public class FooController : ODataController
{
[HttpGet, ODataRoute("GetByKeyFromConfig(Key={key})")]
public SingleResult<Employee> GetByKey([FromODataUri] string key)
{ ... }
}
i get an expcetion
{"The complex type
'System.Web.Http.SingleResult`1[[OData_Path.Employee, OData_Path,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' refers to the
entity type 'OData_Path.Employee' through the property 'Queryable'."}
If i change the return type in for the method in WebApiConfig
builder.Function("GetByKeyFromConfig").Returns<Employee>().Parameter<string>("Key");
I get this which i have no idea why.
{"The path template 'Employees/GetByKeyFromConfig(Key={key})' on the
action 'GetByKey' in controller 'Foo' is not a valid OData path
template. The request URI is not valid. Since the segment 'Employees'
refers to a collection, this must be the last segment in the request
URI or it must be followed by an function or action that can be bound
to it otherwise all intermediate segments must refer to a single
resource."}
I have searched and tried to get explanation , but each time i found an answer it does not work. i am struggling for days.
After the updates taken from the 2 answers
still have the Invalid OData path template exception
WebApiConfig Code
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Employee>("Employees").EntityType.HasKey(p => p.Name);
var employeeType = builder.EntityType<Employee>();
employeeType.Collection.Function("GetByKey").Returns<Employee>().Parameter<int>("Key");
config.EnableUnqualifiedNameCall(unqualifiedNameCall: true);
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: builder.GetEdmModel());
Controller Code
[EnableQuery, HttpGet, ODataRoute("Employees/GetByKey(Key={Key})")]
public SingleResult<Employee> GetByKey([FromODataUri] int Key)
{
var single = Employees.Where(n => n.Id == Key).AsQueryable();
return SingleResult<Employee>.Create<Employee>(single);
}
I've also tried using a specific Namespace
builder.Namespace = "NamespaceX";
[EnableQuery, HttpGet, ODataRoute("Employees/NamespaceX.GetByKey(Key={Key})")]
And
[EnableQuery, HttpGet, ODataRoute("Employees/NamespaceX.GetByKey")]
While you can solve your problem with OData functions, a cleaner solution would be to use alternate keys. As Fan indicated, Web API OData provides an implementation of alternate keys that will allow you to request Employees by name or number with a more straightforward syntax:
GET /Employees(123)
GET /Employees(Name='Fred')
You will need to add the following code to your OData configuration.
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;
// config is an instance of HttpConfiguration
config.EnableAlternateKeys(true);
// builder is an instance of ODataConventionModelBuilder
var edmModel = builder.GetEdmModel() as EdmModel;
var employeeType = edmModel.FindDeclaredType(typeof(Employee).FullName) as IEdmEntityType;
var employeeNameProp = employeeType.FindProperty("Name");
edmModel.AddAlternateKeyAnnotation(employeeType, new Dictionary<string, IEdmProperty> { { "Name", employeeNameProp } });
Make sure you add the alternate key annotations before you pass the model to config.MapODataServiceRoute.
In your controller, add a method to retrieve Employees by name (very similar to the GetByKey method in your question).
[HttpGet]
[ODataRoute("Employees(Name={name})")]
public IHttpActionResult GetEmployeeByName([FromODataUri] string name)
{
var result = EmployeesHolder.Employees.FirstOrDefault(e => e.Name == name);
if (result == null)
{
return this.NotFound();
}
return this.Ok(result);
}
Can this document about function support for OData/Webapi help you? http://odata.github.io/WebApi/#04-06-function-parameter-support there is some problem in your second approach.
you are call with Employees/GetByKeyFromConfig(Key={key}) so you should declare the function like:
builder.EntityType<Employee>().Collection.Function("GetByKeyFromConfig")
you should call with namespace, like Employees/yournamespace.GetByKeyFromConfig
The first scenario can you use this feature? http://odata.github.io/WebApi/#04-17-Alternate-Key
Hope this can help,Thanks.
Update:
Function declaration is right now, the error message shown because your ODataRoute is wrong, remove your ODataRoute, that function can be called as Employees/Default.GetByKey(1), WebAPI/OData can route to this function by default.
Or add the namespace to ODataRoute, it's Default by default, change builder.Namespace is not right, you have to change the function's namespace:
var func = employeeType.Collection.Function("GetByKey").Returns<Employee>().Parameter<int>("Key");
func.Namespace = "NamespaceX";
Then ODataRoute like [EnableQuery, HttpGet, ODataRoute("Employees/NamespaceX.GetByKey(Key={Key})")] should work.
There are two issues with the code you paste,
1. You try to bind a function to Employees collection, the model build is incorrect.
It should be something like
var employeeType = builder.EntityType();
employeeType .Collection.Function("GetByKeyFromConfig").Returns().Parameter("Key");
You can refer to examples in link https://github.com/OData/ODataSamples/tree/master/WebApi/v4/ODataFunctionSample for different ways to bind functions.
In the ODataRoute, we either need to specify the function with namespace or enable unqualified function call via this config in register method "config.EnableUnqualifiedNameCall(unqualifiedNameCall: true);".
Refer to link http://odata.github.io/WebApi/#03-03-attrribute-routing and search unqualified.
Let me know if this does not resolve your issue.

UrlHelper.Action includes undesired additional parameters

I have a method in the controller ApplicationsController, in which I need to get the base URL for an action method:
public ActionResult MyAction(string id)
{
var url = Url.Action("MyAction", "Applications");
...
}
The problem is that this includes the string id from the current route data, when I need the URL without (the URL is used to fetch content from a CMS on a URL-based lookup).
I have tried passing null and new { } as the routeValues parameter to no avail.
The matching route is as follows (above all other routes):
routes.MapLowercaseRoute(
name: "Applications",
url: "applications/{action}/{id}",
defaults: new { controller = "Applications",
action = "Index", id = UrlParameter.Optional });
I've seen a couple of other questions touch on this but none of them seem to have a viable solution. At present, I am resorting to hardcoding the path in the controller; however, I'd like to be able to abstract this into an action filter, so I need to be able to generate the URL.
Is there a clean/conventional way to prevent this behaviour?
Ok, I see the problem. It's something called "Segment variable reuse". When generating the routes for outbound URLs, and trying to find values for each of the segment variables in a route’s URL pattern, the routing system will look at the values from the current request. This is a behavior that confuses many programmers and can lead to a lengthy debugging session. The routing system is keen to make a match against a route, to the extent that it will reuse segment variable values from the incoming URL. So I think you have to override the value like Julien suggested :
var url = Url.Action("MyAction", "Applications", new { id = "" })
Ended up getting around this with a different approach. The only way I could come up with to prevent arbitrarily-named route values from being inserted into the generated URL was to temporarily remove them from RouteData when calling Url.Action. I've written a couple of extension methods to facilitate this:
public static string NonContextualAction(this UrlHelper helper, string action)
{
return helper.NonContextualAction(action,
helper.RequestContext.RouteData.Values["controller"].ToString());
}
public static string NonContextualAction(this UrlHelper helper, string action,
string controller)
{
var routeValues = helper.RequestContext.RouteData.Values;
var routeValueKeys = routeValues.Keys.Where(o => o != "controller"
&& o != "action").ToList();
// Temporarily remove routevalues
var oldRouteValues = new Dictionary<string, object>();
foreach (var key in routeValueKeys)
{
oldRouteValues[key] = routeValues[key];
routeValues.Remove(key);
}
// Generate URL
string url = helper.Action(routeValues["Action"].ToString(),
routeValues["Controller"].ToString());
// Reinsert routevalues
foreach (var kvp in oldRouteValues)
{
routeValues.Add(kvp.Key, kvp.Value);
}
return url;
}
This allows me to do this in an action filter where I won't necessarily know what the parameter names for the action are (and therefore can't just pass an anonymous object as in the other answers).
Still very much interested to know if someone has a more elegant solution, however.
Use a null or empty value for id to prevent Url.Action from using the current one:
var url = Url.Action("MyAction", "Applications", new { id = "" })
I was not entirely comfortable with the altering, transient or otherwise, of the RouterData in #AntP's otherwise fine solution. Since my code for creating the links was already centralized, I borrowed #Tomasz Jaskuλa and #AntP to augment the ExpandoObject, I was already using.
IDictionary<string,object> p = new ExpandoObject();
// Add the values I want in the route
foreach (var (key, value) in linkAttribute.ParamMap)
{
var v = GetPropertyValue(origin, value);
p.Add(key, v);
}
// Ideas borrowed from https://stackoverflow.com/questions/20349681/urlhelper-action-includes-undesired-additional-parameters
// Null out values that I don't want, but are already in the RouteData
foreach (var key in _urlHelper.ActionContext.RouteData.Values.Keys)
{
if (p.ContainsKey(key))
continue;
p.Add(key, null);
}
var href = _urlHelper.Action("Get", linkAttribute.HRefControllerName, p);

RouteTester issues with optional parameter in routing

I have a MVC4 WebApi project with routing that is working correctly with an optional "id" parameter in the route:
routes.Add(new ApiRouteInfo
{
Name = this.AreaName.ToLower() + "_readingsplans",
RouteTemplate = baseUrl + "/plans/readingalerts/{id}",
Defaults = new
{
area = this.AreaName.ToLower(),
controller = "ReadingAlerts",
id = RouteParameter.Optional
}
});
When making an actual request the routing works to hit either the GetAll or Get method in the controller methods:
public HttpResponseMessage GetAll(BaseQueryFilter filter)
public HttpResponseMessage Get(int id)
But in the unit test, the RouteTester object always hits the Get method, not the GetAll.
Works:
Assert.AreEqual(ReflectionHelper.GetMethodName((ReadingAlertsController p) => p.Get(It.IsAny<int>())), routeTester.GetActionName());
Fails:
Assert.AreEqual(ReflectionHelper.GetMethodName((ReadingAlertsController p) => p.GetAll(null)), routeTester.GetActionName());
I've tried passing in an actual filter object instead of null but that doesn't change the outcome at all.
I know I can fix it by creating two different routes, but I'm a bit reluctant since the current routing does work for everything except the unit test.
Any suggestions?
Did you look at this? It explains a lot about unit testing web api and it may be useful to you.
I found a stackoverflow thread which describes how to test out the route. I am using something similar that I found on the net, but I am willing to try it.
Here is another article with a similar implementation. This is what I am using and having a similar issue with.
--Updated--
I believe I found the fix for the issue. Using the article mentioned above, I replaced the 'GetActionDescriptor()' function with the following:
private HttpActionDescriptor GetActionDescriptor()
{
if (controllerContext.ControllerDescriptor == null)
GetControllerType();
var actionSelector = new ApiControllerActionSelector();
var results = actionSelector.GetActionMapping(controllerContext.ControllerDescriptor);
try
{
return actionSelector.SelectAction(controllerContext);
}
catch
{
var subActions = results[request.RequestUri.Segments.Last()];
var action = subActions.FirstOrDefault(a => a.SupportedHttpMethods.First(m => m.Method == request.Method.Method) != null);
return action;
}
}

Categories

Resources