I need a way to configure my contract (method) to take a variable number of parameters. Because you should be able to pass 2 or 10 parameters to this end point.
Btw, the reason I return a Stream is because I serialize my data to XML manually (not important).
ServiceInterface:
[OperationContract]
Stream UpdateAgent(string token, string agentId, string newAgentName, string param1);
Service implementation:
[WebGet(UriTemplate = "/update_agent/{token}/{agentId}/{newAgentName}/{param1}")]
public Stream UpdateAgent(string token, string agentId, string newAgentName, string param1)
{
//do stuff here
}
This method is only available with this URI request:
/update_agent/<long number of chars and numbers>/123456/John Silver/<some ID of associated data>
But I want to be able to pass more params of strings, if I want to. I know that alters the end point of the contract - but is this possible?
To clarify, the following should trigger the same endpoint:
/update_agent/<long number of chars and numbers>/123456/John Silver/dom_81/pos_23
/update_agent/<long number of chars and numbers>/123456/John Silver/dom_120/dat_12/pos_10
/update_agent/<long number of chars and numbers>/123456/John Silver/con_76
Can anyone help me - because clearly I can't make 10,000 methods taking care of each extra parameter...
This does not appear to be supported.
However, Microsoft has been made aware of this issue and there is a work-around:
You can get the desired effect by
omitting the Query string from the
UriTemplate on your WebGet or
WebInvoke attribute, and using
WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters
from within your handlers to inspect,
set defaults, etc. on the query
parameters.
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=451296&wa=wsignin1.0
From SO : Optional query string parameters in URITemplate in WCF?
I have solved my own problem, by doing the following:
Installing URL Rewrite 2.0 (link)
Configured a rewrite rule through my Web.config file:
Web.config section: configuration/system.webServer/
<rules>
<rule name="UpdateAgentUrlRewrite" stopProcessing="true">
<match url="^service/update_agent/([^/]+)/(agent_\d+)/([^/]+)/(.*)$" />
<action type="Rewrite" url="Service.svc/update_agent/{R:1}/{R:2}/{R:3}?input={R:4}" appendQueryString="false" logRewrittenUrl="true" />
</rule>
</rules>
This regex, I made, will transform an URL like this:
/service/update_agent/123a456b789c012d/agent_1/New Agent Name/d_1/e_2/f_3/g_4
||
/Service/update_agent/123a456b789c012d/agent_1/New%20Agent%20Name?input=d_1/e_2/f_3/g_4
Which means I can hit the same Service Endpoint no matter how much I append in the URL, and then just extract the query parameters with this code:
var context = WebOperationContext.Current;
if(context != null)
{
NameValueCollection queryParams = context.IncomingRequest.UriTemplateMatch.QueryParameters;
//contains a keyvalue pair:
// {
// key = "input";
// value = "e_2/f_3/g_4";
// }
}
You can do a template like so:
/update_agent/{token}/{agentId}/{newAgentName}/{*params}
to put the rest of the path into the params variable, and then parse each param out for yourself inside the method.
Related
In my ASP.NET MVC 4 application I have an action on controller that gets URL string as a parameter:
public ActionResult Get(string url)
{
var hash = TextUtil.GenerateShortStringHash(url);
...
return Content(html, "text/html");
}
The request looks like this: http://localhost:37779/api/get/http:%2F%2Fwww.mysite.com
But on some level application automatically replaces double slashes with single one.
Where does this happen? Is there any way to prevent such behavior? Thanks.
My suspicion is that because it's part of the hierarchical portion of the URL it's automatically converting the double slashes to a single slash because double slashes aren't allowed in that portion of the URL. Because URLs contain characters that aren't allowed in the hierarchical portion of the URL, it's best to specify it (suitably encoded) as part of the query string (if a GET request) or in the form parameters (for a POST).
http://localhost:37779/api/get/?url=http:%2F%2Fwww.mysite.com
I completely agree with #tvanfosson that such special characters should be passed as a query string parameter instead of using the path portion of the url. Scott Hanselman wrote a nice blog post explaining the challenges you will face if you attempt to pass such characters.
This being said, you could make it work using double encoding:
http://localhost:37779/api/get/http%253A%252F%252Fwww.mysite.com
and in your controller action:
public ActionResult Get(string url)
{
var hash = TextUtil.GenerateShortStringHash(HttpUtility.UrlDecode(url));
...
return Content(html, "text/html");
}
In order for this to work you need to add the following to your web.config to enable double encoding:
<system.webServer>
<security>
<requestFiltering allowDoubleEscaping="true"/>
</security>
</system.webServer>
and also explicitly define the invalid characters so that : and / are not part of them or you will get 400 Bad Request:
<system.web>
<httpRuntime requestPathInvalidCharacters="<,>" />
</system.web>
I have an controller called Test Controller and the method name is Test
The Test Method accepts one parameter. But when the parameter contains value having space slash the web api is giving error. I am using WEB API 2.
[Route("Test/{companyName}")]
[AcceptVerbs("GET", "POST")]
[System.Web.HHttpGet]
public HttpResponseMessage Test(string companyName)
{
}
the parameter value is BTL / Force Motor Ltd.
I have tried but nothing happened.
<uri>
<schemeSettings>
<add name="http" genericUriParserOptions="DontUnescapePathDotsAndSlashes" />
</schemeSettings>
</uri>
You need to URL Encode the values you are sending to your API, like this:
http://yourApiDomainName/api/yourControllerName/?companyName=BTL%20%2F%20Force%20Motor%20Ltd
[SPACE] when URL encoded beomes: %20
[Forward Slash] when URL encoded becomes: %2F
you dont need to http decode the values in your controller, as these values will be decoded by the framework as soon as they reach your controller. So you will see 'BTL%20%2F%20Force%20Motor%20Ltd' as 'BTL / Force Motor Ltd' inside your controller.
for full list of URL Encodings see this:
http://www.w3schools.com/tags/ref_urlencode.asp
Your issue has nothing to do with WebAPI itself but how ASP.Net handles some specific Urls. This may also affect any dots (".") that get passed in to your API. Here's what worked for me:
Add this line to your web.config under system.web
<httpRuntime relaxedUrlToFileSystemMapping="true" />
Phil Haacked has a great article that goes into more detail.
Caveat: I have an MVC site as an area in a .Net WebForms site. I'm explaining that in advance in the case that it sheds light on my issue.
Now the issue I am having is that I am routing values that could contain special characters like an apostrophe (single quote). If i do not encode the value it routes properly however, my Kendo MVC Grid creates a invalid template when using the unencoded single quote as a filter.
http://{base}/{area}/{controller}/{view}?customerName=Justin's%20Instance - Throws Invalid Template Error
http://{base}/{area}/{controller}/{view}?customerName=Justin's%20Instance - No Error
So I thought the easiest solution was to properly encode my querystring parameter prior to passing it as a route value. That resulted in a double encoding situation. Then I found out about MvcHtmlString.Create which is specifically designed to tell the routing system not to re-encode the string value. However, it is still double encoding.
var customerNameEncoded = MvcHtmlString.Create(HttpUtility.HtmlEncode(model.Name));
var routeResult = RedirectToAction("ManageCustomer", new { customerName = customerNameEncoded });
return routeResult;
This is the Url that is created:
http://{base/{area}/{controller}/{view}?customerName=Justin%26%2339%3Bs%20Instance
As you can see the ' is being encoded again. This is throwing the following error.
> System.Web.HttpRequestValidationException A potentially dangerous
> Request.QueryString value was detected from the client
> (customerName="Justin's Instance").
The web.config for the MVC area has the following tag: validateRequest="false"
The web.config for the overall website has the following: httpRuntime requestValidationMode="2.0"
Any ideas as to why this string is being double encoded and how to stop it from doing so?
There are two different types of encoding mixed here.
One is the HTML encoding which creates those &39; escape sequences.
Second is URL encoding which makes the %20 escape sequences.
I don't know what Kendo MVC Grid is by I think I know what the result of your action should be. Try this code
public ActionResult Index()
{
var customerNameEncoded = "Justin's Instance";
var url = Url.Action("ManageCustomer", new { customerName = customerNameEncoded });
return Redirect(url);
}
public ActionResult ManageCustomer(string customerName)
{
ViewBag.CustomerName = customerName;
return View();
}
If you stop the Index method just before it returns you'll see that url content is
"/Home/ManageCustomer?customerName=Justin%27s%20Instance"
what is the properly encoded URL.
You can also check what you get as customerName in ManageCustomer action and you'll see it's
"Justin's Instance"
Don't get confused by what you may see in the browser's address bar. Some browsers (like Firefox) do not show the encoded URL and may show something like ".../ManageCustomer?customerName=Justin's Instance"
1) Query string parameters are URL-encoded, not HTML-encoded. (% notation, rather than &# entities)
2) The config settings validateRequest and requestValidationMode apply to ASP.Net WebForms, not MVC. You must add the [ValidateInput(false)] attribute to the controller methods, as Bryan commented.
I am trying to use a controller as an image handler, but how do i pass in a path to it?
Right now it looks like this (works for images without a path):
public void GetImage(string parameter)
{
var imageHandler = UnityGlobalContainer.Container.Resolve<IImageHandler>();
imageHandler.ProcessRequest(parameter);
}
But if i try to send in the path folder1\folder2\folder3\picture.jpg then it fails.
#Html.ActionLink("Show", "GetImage", "Utility", new { parameter = #"folder1\folder2\folder3\picture.jpg" }, new { })
produces this:
http://localhost:58359/Utility/GetImage/folder1%5Cfolder2%5Cfolder3%5Cpicture.jpg
and that leads to:
HTTP Error 400 - Bad Request.
How can i pass in a path to the controller using the normal mvc approach?
(I am using backward slashes and not forward slashes)
I have also tested using HttpUtility.UrlEncode on the parameter.
According to your code: The produced link in the html page should be:
http://localhost:58359/Utility/GetImage?parameter=folder1%5Cfolder2%5Cfolder3%5Cpicture.jpg
and the parameter variable should be correctly set to "folder1\folder2\folder3\picture.jpg" in the action method.
Notice that you might be vulnerable to directory traversal here.
In .NET 4.0 beta 2, the CLR team has offered a workaround.
Add this to your web.config file:
<uri>
<schemeSettings>
<add name="http" genericUriParserOptions="DontUnescapePathDotsAndSlashes" />
</schemeSettings>
</uri>
This causes the Uri class to behave according to the RFC describing URIs, allowing for slashes to be escaped in the path without being unescaped. The CLR team reports they deviate from the spec for security reasons, and setting this in your .config file basically makes you take ownership of the additional security considerations involved in not unescaping the slashes.
Can you not just decode the parameter?
http://msdn.microsoft.com/en-us/library/6196h3wt.aspx
Instead of calling the filename parameter 'parameter', and defining it in your route, call it 'filename' and DON'T define it in your route.
Your action code will be the same, but the filename will stop being part of the route and just be an ordinary URL parameter.
If you're afflicted by this season's fashion for disliking URL parameters, then you might find this repugnant, but that's just fashion and can safely be ignored.
Personally, I wouldn't pass paths like this into a web app, because I would be absolutely paranoid about creating traversal threats by mistake - I only ever pass in path-free filenames, validate them against a list and then fetch the file.
I am just learning ASP.NET MVC, and I have recently found the [RequireHttps] attribute to automatically redirect a GET request to use SSL, like so...
[RequireHttps] //apply to all actions in controller
public class SomeController
{
[RequireHttps] //apply to this action only
public ActionResult SomeAction()
{
...
}
}
When using IIS Express as the development server, this successfully redirects the request from http://localhost:55945/... to https://localhost/....
However on my development system, my project is using HTTPS on port 44300 (this was configured automatically by Visual Studio 2010), and I have not yet found any way to tell MVC to use that port number in the redirection, so that it goes to the required https://localhost:43300/... instead.
I really expected this to be automatic, given the SSL port number was set automatically by Visual Studio, and as far as I can tell, this must be effecting all developers using the [RequireHttps] attribute in MVC3. In my searching for a solution, I have seen a few patchy "work around" solutions, but nothing that appears to be definitively "the right way" to fix it.
So doing things "the right way", what do I change (either in the source code, or in my project configuration) to tell the [RequireHttps] attribute in MVC3 to use the HTTPS port that my project is configured to be using?
Or alternatively, is there some other completely different and better "right way" to set up SSL support in an MVC3 project, that does not have this problem?
The RequireHttpsAttribute is quite simple and it cannot be parametrized to redirect to a specific port. If you really need this you could create a subclass and override the HandleNonHttpsRequest method to compose the redirect url differently.
protected override void HandleNonHttpsRequest(AuthorizationContext filterContext) {
base.HandleNonHttpsRequest(filterContext);
// redirect to HTTPS version of page
string url = "https://" + filterContext.HttpContext.Request.Url.Host + ":" + MyConfig.SslPort + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
However, if your entire site runs with HTTPS you could just configure in VS in the web project properties (Web -> Start Action -> Start URL) to open the correct HTTPS url with your port and not use the redirect feature for local testing.
I have this same issue, and I'm solving it by using both the RequireHttpsAttribute and a URL rewrite rule in Web.config. The rewrite rule matches the nonstandard port numbers, and executes ahead of the attribute. You can use a Web.config transform to remove the rewrite rule on deployment, but if you leave it in it shouldn't have any effect. In production you'll use standard port number that the rule won't match. Then the attribute will catch it.
Here's the rule:
<system.webServer>
<rewrite>
<rules>
<!-- Redirect HTTP requests to HTTPS, using the non-standard development ports.
On deployment, this rule is removed, and the RequireHttpAttribute filter globally applied
in SlicerWeb.FilterConfig takes over. This rewrite rule executes before the attribute
would be applied, and so can apply the port numbers -->
<rule name="HTTPS redirect" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
<add input="{SERVER_PORT}" pattern="60470" />
</conditions>
<action type="Redirect" url="https://{SERVER_NAME}:44300/{R:1}" redirectType="Found" />
</rule>
</rules>
</rewrite>
</system.webServer>
The downside to this approach is that you're not relying on the attribute when running locally. But if you've applied it once globally, instead of adding it to every controller, that's OK in my opinion.
A few things that might be helpful.
There is a version of the source code of RequireHttpsAttribute in this thread: Where is the source for RequireHttpsAttribute?
There is also a nearly identical class called RequireSslAttribute on codeplex mentioned on the same thread. http://aspnet.codeplex.com/SourceControl/changeset/view/63930#391756
Here is a sample of an attribute that can be used to toggle either from http to https or the reverse based on the TargetUriScheme property. It also includes properties for specifying port numbers.
I have chosen to use an #if DEBUG block in my constructor to set my local development ports when I am building under the Debug configuration. This works for me since I always build under release when deploying, in which case the port numbers will default to null and will be left out of the url's.
The port numbers can also be set when applying the attribute to an action method. I could also see hooking these to a config file or some other configuration source to determine the port numbers at runtime.
public class ToggleHttpHttpsAttribute : FilterAttribute, IAuthorizationFilter
{
//supported uri scheme values
public enum UriScheme
{
Http,
Https
}
public ToggleHttpHttpsAttribute(
UriScheme uriScheme = UriScheme.Http)
{
TargetUriScheme = uriScheme;
#if DEBUG
//set DEBUG ports
HttpPort = 55892;
HttpsPort = 44301;
#endif
}
private UriScheme TargetUriScheme { get; set; }
public int? HttpPort { get; set; }
public int? HttpsPort { get; set; }
public void OnAuthorization(AuthorizationContext filterContext)
{
if(filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
bool isHttps = filterContext.HttpContext.Request.IsSecureConnection;
if ((isHttps && TargetUriScheme == UriScheme.Http) || (!isHttps && TargetUriScheme == UriScheme.Https))
{
ToggleUriScheme(filterContext);
}
}
private void ToggleUriScheme(AuthorizationContext filterContext)
{
//only allow toggle if GET request
if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("ToggleHttpHttpsAttribute can only be used on GET requests.");
}
filterContext.Result = GetRedirectResult(filterContext);
}
private RedirectResult GetRedirectResult(AuthorizationContext filterContext)
{
string prefix = TargetUriScheme == UriScheme.Http
? "http://"
: "https://";
int? port = TargetUriScheme == UriScheme.Http
? HttpPort
: HttpsPort;
string url = string.Format(
"{0}{1}{2}{3}",
prefix,
filterContext.HttpContext.Request.Url.Host,
port == null ? "" : string.Format(":{0}", port),
filterContext.HttpContext.Request.RawUrl);
return new RedirectResult(url);
}
}
I'm quite puzzled as to why I have had to spend hours trying to get this right. I did a URL rewrite similar to the way Scott Hanselman explains it here:
http://www.hanselman.com/blog/WorkingWithSSLAtDevelopmentTimeIsEasierWithIISExpress.aspx
This works perfectly and I just take out the rewrite rule on my production web server.