I recently saw code like:
public String myMethodHere(#RequestParam(PARAM_FOO_ID) Integer fooId,
Model model) {
//some implementation
}
private static final String PARAM_FOO_ID = "fooId";
Is there a benefit in making a request param a static final String?
I didn't see it referenced anywhere else on SO and the examples in the Spring docs don't do this.
There is not benefit in terms of performance as strings are interned anyway. But this way, it is more maintainable.
If you have several controller methods that accepts "fooId" as request parameter, you can just reuse PARAM_FOO_ID in all those controllers. Then if the client side decides to change the request parameter to send to your rest endpoint, all it takes is just a change to the value of PARAM_FOO_ID then your controller is ready to accommodate those changes.
Related
I am trying to create a .NET5 WebApi delete method in a controller class where this method receives several "ids" that will be used for deleting some entities.
I realized when building the delete request on the client side that specifying a content does not make sense. So I was guided to pass ids on the Uri, hence the use of the "FromUri" attribute:
// DELETE: api/ProductionOrders/5
[HttpDelete("ProductionOrders")]
public IActionResult DeleteProductionOrder([System.Web.Http.FromUri]int[] ids)
{
//code
}
If this is a reasonable approach, is there a better way to build this Uri from the client-side? Imagine instead of an array of ints I had a complex type. How can I serialized this and put into the Uri?
For this example I end up building up a URI like this:
http://localhost:51081/api/ProductionOrders?ids=25563&ids=25533
Personally, if I have to pass a List or a complex type I would map values from the Body via JSON. The DELETE allow using body. And then just decorate your param with [FromBody] attribute.
Despite some recommendations not to use the message body for DELETE requests, this approach may be appropriate in certain use cases.
This allows better extensibility in case you need to change how the data is coming.
In your case with ids I’d create new class like this:
public class RequestEntity {
[JsonPropertyName("Ids")]
public List<int> Ids { get; set; }
}
And then when calling this method, send the Body along with the request.
{
"Ids": [25392, 254839, 25563]
}
In a future you can pass complex objects just by changing what is send to server and implement complex logic.
I have to provide some read endpoints for our EF6 entities on an ASP.NET API that conform to the OData specification. Entity retrieval works well based upon functions that accept a System.Web.Http.OData.Query.ODataQueryOptions<TEntity> instance.
Now, according to the docs, that implementation of OData does not support $count.
We would, however, like to offer at least the capability to retrieve the total count of a (filtered) data set as shown in the docs, like (by slightly combining several of those samples):
http://host/service/Products/$count($filter=Price gt 5.00)
(Based on the spec, I understand that this should be a valid, specification-conformant OData query for the number of products whose price is greater than 5¤. Please correct me if I'm wrong.)
Now, retrieving the count based on the IQueryable returned from ODataQuerySettings.ApplyTo is trivial. So is capturing requests to this route:
[Route("$count({queryOptions})")]
public int Count(ODataQueryOptions<ProductEntity> queryOptions)
The only bit that is missing is that the queryOptions portion of the route should be parsed into the ODataQueryOptions<ProductEntity> instance. On other OData endpoints, this works without any further ado. However, even when I call this endpoint with a $filter, all I am getting is an "empty" (i.e. initialized to default values) ODataQueryOptions<ProductEntity> instance with no filter set.
Alternatively, I can declare my web API endpoint like this:
[Route("$count({queryOptions})")]
public int Count(string rawQueryOptions)
Within this method, rawQueryOptions contains the query options that I wish to pass to OData, that is, parse to populate an ODataQueryOptions<ProductEntity> instance.
This must be very straightforward as the very same thing happens for any other OData endpoint. For a comparison:
[Route("")]
public IEnumerable<object> Filter(ODataQueryOptions<ProductEntity> queryOptions)
This works; the query options are populated as expected, unlike it is the case with my above endpoint.
How can I populate my OData query options instance based on the string extracted from my route?
Some more things I have tried:
Applied [FromUri] to the queryOptions parameter.
Applied [ODataQueryParameterBinding] to the queryOptions parameter.
Although the syntax is a little bit different to your request, the .Net OData Implementation has the support you need OOTB, if you're asking this question at all, that indicates that you are trying to add OData support to your standard API.
Given that you have EF6 already on an ASP.Net API... Why not just expose the OData controllers on another route? In this way you get the full implementation of query support without ever needing to parse the QueryOptions at all.
Updated
If adding new controllers in a separate route is not suitable you can easily upgrade your existing ApiControllers and Implement OData routes in place of the existing ones.
ODataController inherits from ApiController but includes some helper methods that simplify working with OData response conventions, so upgrading in place is generally non-breaking.
As an example, the following is the only controller code that is needed to allow all the supported OData Query Options to return data from an EF DbSet, this includes full support for $select, $expand, $filter, $apply and even $count across a nested $filter.
public partial class ResidentsController : ODataController
{
protected MyEF.Context db = new MyEF.Context();
[EnableQuery]
public async Task<IHttpActionResult> Get(ODataQueryOptions<MyEF.Resident> options)
{
return Ok(db.Residents);
}
}
The magic that allows this is not the ODataController itself, the EnableQueryAttribute parses/translates the QueryOptions and applies this to the Linq to Entities expression that is returned from the method.
The final component to make this work is to register the routes, this is a little bit more involved than standard API because you need to define the EdmModel first, but in doing so we never need to parse the incoming query parameters.
a minimal example to configure the model and routes for the above controller might look like this:
public static void Register(HttpConfiguration config)
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Resident>("Residents");
IEdmModel model = builder.GetEdmModel();
// To enable $select and $filter on all fields by default
config.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
// can also be configured like this
config.SetDefaultQuerySettings(new Microsoft.AspNet.OData.Query.DefaultQuerySettings()
{
EnableCount = true,
EnableExpand = true,
EnableFilter = true,
EnableOrderBy = true,
EnableSelect = true,
MaxTop = null
});
// Map the routes from the model using OData Conventions
config.MapODataServiceRoute("odata", "odata", model);
}
How to Configure the $count syntax you desire
although your expected syntax for count of filtered collections is not supported OOTB, the syntax that is supported is very close, so you could easily manipulate the query with a URL re-write module
Your expected syntax:
http://host/service/Products/$count($filter=Price gt 5.00)
.Net Supported syntax
http://host/service/Products/$count?$filter=Price gt 5.00
OwinMiddleware:
/// <summary>
/// Rewrite incoming OData requests that are implemented differently in the .Net pipeline
/// </summary>
public class ODataConventionUrlRewriter : OwinMiddleware
{
private static readonly PathString CountUrlSegments = PathString.FromUriComponent("/$count");
public ODataConventionUrlRewriter(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
// OData spec says this should work: http://host/service/Products/$count($filter=Price gt 5.00)
// But in .Net the filter needs to be in the query: http://host/service/Products/$count?$filter=Price gt 5.00
var regex = new System.Text.RegularExpressions.Regex(#"\/\$count\((.+)\)$");
var match = regex.Match(context.Request.Path.Value);
if(match != null && match.Success)
{
// So move the $filter expression to a query option
// We have to use redirect here, we can't affect the query inflight
context.Response.Redirect($"{context.Request.Uri.GetLeftPart(UriPartial.Authority)}{regex.Replace(context.Request.Path.Value, "/$count")}?{match.Groups[1].Value}");
}
else
await Next.Invoke(context);
}
}
Add to Startup.cs, before registering OData routes
app.Use(typeof(ODataConventionUrlRewriter));
`I need to contact an API which requires that every request contain the signature of all parameters and url + nonce.
Example:
#GET("/users/{type}")
public void getUsers(
#Path("type") String type,
#Query("sort") boolean sort)
I should add a X-Signature header with contains signature(nonce+"/users/"+type+"sort="+sort).
I thought I could do this with a RequestInterceptor's addHeader(String name, String value) but I can`t as the signature varies for every request.
Is there a smart way to do this with Retrofit or will I just have to manually sign every request?
Am I right in thinking that your signature is generated from [nonce]+[path]+[query params]
You could look at implementing a custom client and passing this into your RestAdapter.Builder().setClient(new CustomClient) method.
Something like CustomClient extends OkClient and then override the execute(Request) method. You will need to create a new Request object and pass that to super.execute(updatedRequest).
#Override
public Response execute(Request request) throws IOException {
List<Header> headers = new ArrayList<>();
// do work here to parse the request.getUrl() and extract path/params and generate the signature
headers.addAll(request.getHeaders());
headers.add(new Header("X-Signature", "signature"));
Request updated = new Request(request.getMethod(), request.getUrl(), headers, request.getBody());
return super.execute(updated);
}
If however there is no consistency to the generation of the signature then you will need to create the signature manually and add a #Header value in your call to your client.
I have a webforms project, and am attempting to run some code that allows me to make a call to an MVC route and then render the result within the body of the web forms page.
There are a couple of HttpResponse/Request/Context wrappers which I use to execute a call to an MVC route, e.g.:
private static string RenderInternal(string path)
{
var responseWriter = new StringWriter();
var mvcResponse = new MvcPlayerHttpResponseWrapper(responseWriter, PageRenderer.CurrentPageId);
var mvcRequest = new MvcPlayerHttpRequestWrapper(Request, path);
var mvcContext = new MvcPlayerHttpContextWrapper(Context, mvcResponse, mvcRequest);
lock (HttpContext.Current)
{
new MvcHttpHandlerWrapper().PublicProcessRequest(mvcContext);
}
...
The code works fine for executing simple MVC routes, for e.g. "/Home/Index". But I can't specify any query string parameters (e.g. "/Home/Index?foo=bar") as they simply get ignored. I have tried to set the QueryString directly within the RequestWrapper instance, like so:
public class MvcPlayerHttpRequestWrapper : HttpRequestWrapper
{
private readonly string _path;
private readonly NameValueCollection query = new NameValueCollection();
public MvcPlayerHttpRequestWrapper(HttpRequest httpRequest, string path)
: base(httpRequest)
{
var parts = path.Split('?');
if (parts.Length > 1)
{
query = ExtractQueryString(parts[1]);
}
_path = parts[0];
}
public override string Path
{
get
{
return _path;
}
}
public override NameValueCollection QueryString
{
get
{
return query;
}
}
...
When debugging I can see the correct values are in the "request.QueryString", but the values never get bound to the method parameter.
Does anyone know how QueryString values are used and bound from an http request to an MVC controller action?
It seems like the handling of the QueryString value is more complex than I anticipated. I have a limited knowledge of the internals of the MVC Request pipeline.
I have been trying to research the internals myself and will continue to do so. If I find anything I will update this post appropriately.
I have also created a very simple web forms project containing only the code needed to produce this problem and have shared it via dropbox: https://www.dropbox.com/s/vi6erzw24813zq1/StackMvcGetQuestion.zip
The project simply contains one Default.aspx page, a Controller, and the MvcWrapper class used to render out the result of an MVC path. If you look at the Default.aspx.cs you will see a route path containing a querystring parameter is passed in, but it never binds against the parameter on the action.
As a quick reference, here are some extracts from that web project.
The controller:
public class HomeController : Controller
{
public ActionResult Index(string foo)
{
return Content(string.Format("<p>foo = {0}</p>", foo));
}
}
The Default.aspx page:
protected void Page_Load(object sender, EventArgs e)
{
string path = "/Home/Index?foo=baz";
divMvcOutput.InnerHtml = MvcWrapper.MvcPlayerFunctions.Render(path);
}
I have been struggling with this for quite a while now, so would appreciate any advice in any form. :)
MVC framework will try to fill the values of the parameters of the action method from the query string (and other available data such as posted form fields, etc.), that part you got right. The part you missed is that it does so by matching the name of the parameter with the value names passed in. So if you have a method MyMethod in Controller MyController with the signature:
public ActionResult MyMethod(string Path)
{
//Some code goes here
}
The query string (or one of the other sources of variables) must contain a variable named "Path" for the framework to be able to detect it. The query string should be /MyController/MyMethod?Path=Baz
Ok. This was a long debugging session :) and this will be a long response, so bear with me :)
First how MVC works. When you call an action method with input parameters, the framework will call a class called "DefaultModelBinder" that will try and provide a value for each basic type (int, long, etc.) and instance of complex types (objects). This model binder will depend on something called the ValueProvider collection to look for variable names in query string, submitted forms, etc. One of the ValueProviders that interests us the most is the QueryStringValueProvider. As you can guess, it gets the variables defined in the query string. Deep inside the framework, this class calls HttpContext.Current to retrieve the values of the query string instead of relying on the ones being passed to it. In your setup this is causing it to see the original request with localhost:xxxx/Default.aspx as the underlying request causing it to see an empty query string. In fact inside the Action method (Bar in your case) you can get the value this.QueryString["variable"] and it will have the right value.
I modified the Player.cs file to use a web client to make a call to an MVC application running in a separate copy of VS and it worked perfectly. So I suggest you run your mvc application separately and call into it and it should work fine.
Ok, so I can't seem to find decent Windows Azure examples. I have a simple hello world application that's based on this tutorial. I want to have custom output instead of JSON or XML. So I created my interface like:
[ServiceContract]
public interface IService
{
[OperationContract]
[WebInvoke(UriTemplate = "session/create", Method = "POST")]
string createSession();
}
public class MyService : IService
{
public string createSession()
{
// get access to POST data here: user, pass
string sessionid = Session.Create(user, pass);
return "sessionid=" + sessionid;
}
}
For the life of me, I can't seem to figure out how to access the POST data. Please help. Thanks!
If you have an HttpContext there may be a Request object that would have the form data. I'm basing part of this off the ASP.Net tag on this question, so if that is incorrect then there may be the need to handle this another way but it looks a lot like a web service to my mind.
EDIT: HttpRequest is the class that has the Form property that should be where the POST data is stored if this is an HTTP request. This is part of System.Web so it should be ready to be used pretty easily, as I recall.
Sample code showing the Request.Form property:
int loop1;
NameValueCollection coll;
//Load Form variables into NameValueCollection variable.
coll=Request.Form;
// Get names of all forms into a string array.
String[] arr1 = coll.AllKeys;
for (loop1 = 0; loop1 < arr1.Length; loop1++)
{
Response.Write("Form: " + arr1[loop1] + "<br>");
}
This presumed there was an HttpRequest instance around.
WCF Simplified Part 4: Comparing the Request/Reply and One-Way Patterns passes in a parameter so that your "createSession" method would have to take in those strings it would appear. I'm used to the ASP.Net world where there are some built-in objects like Request, Response, Server, Application and Session.
Yes, if you did try changing the method signature as there are ways to pass in parameters in that last example I linked though I don't know if that would work in your case or not.