I'm pretty new to C# and need to realise a REST Service so i stumbled over Grapevine.
I need to have parts of the URL of the service handed over on service start via config file but I don't manage to hand over the value "clientId" of the config file to the Route's Pathinfo because it's not constant.
Here's the part of the code:
[RestResource(BasePath = "/RestService/")]
public class Rest_Resource
{
public string clientId = ConfigurationManager.AppSettings["ClientId"];
[RestRoute(PathInfo = clientId + "/info")]//<-how do I fill Pathinfo with dynamic values?
public IHttpContext GetVersion(IHttpContext context)
{....}
}
I'm using grapevine v4.1.1 as nuget package in visual studio.
While it is possible to change attribute values at runtime, or even use dynamic attributes, an easier solution in this case might be to not use the auto discovery feature exclusively, but use a hybrid approach to route registration.
Consider the following class that contains two rest routes, but only one of them is decorated with the attribute:
[RestResource(BasePath = "/RestService/")]
public class MyRestResources
{
public IHttpContext ManuallyRegisterMe(IHttpContext context)
{
return context;
}
[RestRoute(PathInfo = "/autodiscover")]
public IHttpContext AutoDiscoverMe(IHttpContext context)
{
return context;
}
}
Since you want to register the first route using a value that is not known until runtime, we can manually register that route:
// Get the runtime value
var clientId = "someValue";
// Get the method info
var mi = typeof(MyRestResources).GetMethod("ManuallyRegisterMe");
// Create the route
var route = new Route(mi, $"/RestService/{clientId}");
// Register the route
server.Router.Register(route);
This takes care of manually registering our route that needs a runtime value, but we still want the other routes to be automatically discovered. Since the router will only autodiscover if the routing table is empty when the server starts, we'll have to tell the router when to scan the assemblies. You can do this either before or after you manually register the route:
server.Router.ScanAssemblies();
Related
I need to create a single page that is going to be a part of an existing ASP.NET MVC website that is only open or accessible if came from allowed websites (Referrer). Our partners are planning
to load this page using an iFrame. This page allows any user to quickly enter the details and save into
our database.
example: www.mysecuredwebsite.com/publicpage
Since this is going to be public single page only and no logins, the only security is
only allow if the referrer is in the list of allowed sites.
Example:
www.abc.com
www.randomwebsite.com
www.amazingsite.com
Any request from any other sites will be redirected to an error page.
What is the best approach to this problem? I'm thinking of creating a custom attribute that will be used
to decorate the controller which then reads
a list of allowed sites from the app.config orany type of config.
Or maybe from the SQLDB since the site is using SQLDB? The list I think will be frequently change
depending on the growing number of clients. It should be easily configurable and does not require
redeployment.
Any thoughts? Thanks!
You can use two methods
Method 1: Use ActionFilterAttribute.
Method 2: Use the Application_BeginRequest method in the Global.asax file.
Note: It is better to put the allowed urls in the database so that it can be easily changed.
I will explain the first method
The following filter checks whether UrlReferrer is allowed. If it
is not allowed, it will be referred to the error page.
add UrlReferrerFilterAttribute
using System.Web.Mvc;
using System.Web.Routing;
namespace yournamespace
{
public class UrlReferrerFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string urlReferrer = string.Empty;
if (filterContext.HttpContext.Request.UrlReferrer != null)
{
urlReferrer = filterContext.HttpContext.Request.UrlReferrer.AbsoluteUri;
if(!db.UrlReferrerTable.Any(a => a.Url == urlReferrer))
{
var values = new RouteValueDictionary(new
{
action = "ErrorPage",
controller = "Home"
});
filterContext.Result = new RedirectToRouteResult(values);
}
}
base.OnActionExecuting(filterContext);
}
}
}
now use
[UrlReferrerFilter]
public ActionResult YourAction()
{
//..................
//..................
}
I have a situation where a site may need a link to redirect to certain controllers based on database results.
For example:
site.com/abcd
needs to return the result from a Item Controller, which would normally be called as /item/view/123
The key here is that I can't hard code the abcd into the routing. And some links may go to an Item Controller, others may go to an Orders controller.
I've tried a catchall route to a controller than then loads up the desired controller, but the environment is not set so it does not work properly (it can't find the views).
You can get whatever behavior you desire by implementing IRouter as in this answer, including basing your logic on data from an external source (such as a config file or database).
This is much more flexible than a catchall route because it lets you choose the controller on the fly.
public class MyRoute : IRouter
{
private readonly IRouter innerRouter;
public MyRoute(IRouter innerRouter)
{
if (innerRouter == null)
throw new ArgumentNullException("innerRouter");
this.innerRouter = innerRouter;
}
public async Task RouteAsync(RouteContext context)
{
var requestPath = context.HttpContext.Request.Path.Value;
if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
{
// Trim the leading slash
requestPath = requestPath.Substring(1);
}
if (!requestPath.StartsWith("abcd"))
{
return;
}
//Invoke MVC controller/action
var oldRouteData = context.RouteData;
var newRouteData = new RouteData(oldRouteData);
newRouteData.Routers.Add(this.innerRouter);
newRouteData.Values["controller"] = "Item";
newRouteData.Values["action"] = "View";
newRouteData.Values["id"] = 123;
try
{
context.RouteData = newRouteData;
await this.innerRouter.RouteAsync(context);
}
finally
{
// Restore the original values to prevent polluting the route data.
if (!context.IsHandled)
{
context.RouteData = oldRouteData;
}
}
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
VirtualPathData result = null;
var values = context.Values;
var controller = Convert.ToString(values["controller"]);
var action = Convert.ToString(values["action"]);
var id = Convert.ToString(values["id"]);
if ("Item".Equals(controller) && "View".Equals(action))
{
result = new VirtualPathData(this, "abcd?id=" + id);
context.IsBound = true;
}
// IMPORTANT: Always return null if there is no match.
// This tells .NET routing to check the next route that is registered.
return result;
}
}
Usage
// Add MVC to the request pipeline.
app.UseMvc(routes =>
{
routes.Routes.Add(new MyRoute(
innerRouter: routes.DefaultHandler)
);
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
// Uncomment the following line to add a route for porting Web API 2 controllers.
// routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}");
});
The GetVirtualPath should mirror what the RouteAsync does. RouteAsync converts a URL into route values, and the GetVirtualPath should convert the same route data back into the same URL.
The easiest way to accomplish this is to use a data structure to create a two-way mapping between these 2 data points (as in the linked answer) so you don't have to continually change the logic within these 2 methods. This data structure should be cached and not do anything too resource intensive, since every request will use it to determine where to send each URL.
Alternatively, you could create a separate route for each of your individual pieces of logic and register them all at application startup. However, you need to ensure they are registered in the correct order and that each route will only match the correct set of URLs and correct set of RouteValues.
NOTE: For a scenario such as this you should almost never need to use RedirectToAction. Keep in mind redirecting will send an HTTP 302 request to the browser, which tells it to lookup another location on your server. This is unnecessary overhead in most cases because it is much more efficient just to route the initial request to the controller you want.
I am trying to set up an Asp.Net forms site with an API.
I have succeeded in adding in selective authentication, so that pages starting "\api\" do not get redirected, but instead challenge for basic authentication.
I am now trying to use MS Web Api 2 to do the API routing.
The idea is to be as RESTful as possible. I have a resource, a TradableItem, and initially I would want to allow API users to use HTTP GET in one of two ways.
If the API user passes no item key, the user receives a list of possible item keys
["ABC","DEF"...]
If the API user passes an item key as part of the URI, eg "/api/tradables/abc", a representation of the TradableItem is returned for the one with the key=ABC. (To my understanding, this is standard REST behaviour).
In Global.ASAX's Application_Start() function I have a route map like so...
RouteTable.Routes.MapHttpRoute(
name: "TradableItemVerbs",
routeTemplate: "api/tradables/{item}",
defaults: new { item = System.Web.Http.RouteParameter.Optional, controller = "Tradable" });
The TradableController.cs file looks like this...
public class TradableController : ApiController
{
private static CustomLog logger = new CustomLog("TradableController");
// GET api/<controller>
public IEnumerable<string> GetKeys()
{
var prefix = "GetKeys() - ";
string msg = "";
msg = "Function called, returning list of tradable pkeys...";
logger.Debug(prefix + msg);
// Get a list of tradable items
return TradableManager.GetTradablePkeys();
}
// GET api/<controller>/<pkey>
public string GetTradable(string pkey)
{
string msg = string.Format("Would get Tradable data for key: >{0}<", pkey);
return msg;
}
}
The problem is that only the GetKeys() function fires, whether I call GET to "/api/tradables" or "/api/tradables/abc".
For reference, using VS2015 Community, IIS 7.5, targeting .Net 4.6.1. I used Rick Strahl's blog as a guide on this (among other sources).
http://weblog.west-wind.com/posts/2012/Aug/21/An-Introduction-to-ASPNET-Web-API#HTTPVerbRouting
please, change the name of your param to item (because, this is the name define in the routes):
public string GetTradable(string item)
{
....
}
or when you call the method be explicit with the parameter name: /api/tradables?pkey=abc
I am trying to create Odata method that satisfy url like
domain:port/products/100/RedirectUrl()
[ODataRoute("{id}/RedirectUrl()")]
public IHttpActionResult RedirectUrl(int id)
{
return Redirect("myUrl" + id);
}
but i got exception like
The path template '{id}/RedirectUrl()' on the action 'RedirectUrl' in
controller 'Products' is not a valid OData path template
My Webapi config contains
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
builder.EntityType<Product>().Function("RedirectUrl").Returns<IHttpActionResult>();
Any way to achieve this?
I think you want to enable Key as segment, track this https://github.com/OData/WebApi/pulls, then you can set the UrlConventions.ODataUrlConventions to enable it. Or you have to override the DefaultODataPathHandler to achieve this, may need to copy some private method like Parse.
I'm building a web api using WCF web api preview 6, currently I'm stuck with a little problem. I would like to have an operation handler to inject an IPrincipal to the operation in order to determine which user is making the request. I already have that Operation Handler and is already configured. But I noticed that when I decorate the operation with the WebInvoke attribute and simultaneously the operation receives an IPrincipal and other domain object, the system throws an exception telling me:
The HttpOperationHandlerFactory is unable to determine the input parameter that should be associated with the request message content for service operation 'NameOfTheOperation'. If the operation does not expect content in the request message use the HTTP GET method with the operation. Otherwise, ensure that one input parameter either has it's IsContentParameter property set to 'True' or is a type that is assignable to one of the following: HttpContent, ObjectContent1, HttpRequestMessage or HttpRequestMessage1.
I do not know what is happening here. To give you some background I'll post some of my code to let you know how am I doing things.
The operation:
[WebInvoke(UriTemplate = "", Method = "POST")]
[Authorization(Roles = "")]
public HttpResponseMessage<dto.Diagnostic> RegisterDiagnostic(dto.Diagnostic diagnostic, IPrincipal principal)
{
......
}
WCF web api knows when to inject the IPrincipal because I decorate the operation with a custom Authorization attribute.
The configuration in the Global file:
var config = new WebApiConfiguration() {EnableTestClient = true};
config.RegisterOAuthHanlder(); //this is an extension method
routes.SetDefaultHttpConfiguration(config);
routes.MapServiceRoute<MeasurementResource>("Measurement");
routes.MapServiceRoute<DiagnosticResource>("Diagnostic");
Then the RegisterOAuthHandler method adds an operation handler to the operation if it's been decorated with the custom authorization attibute. this is how it looks:
public static WebApiConfiguration RegisterOAuthHanlder(this WebApiConfiguration conf)
{
conf.AddRequestHandlers((coll, ep, desc) =>
{
var authorizeAttribute = desc.Attributes.OfType<AuthorizationAttribute>().FirstOrDefault();
if (authorizeAttribute != null)
{
coll.Add(new OAuthOperationHandler(authorizeAttribute));
}
});
return conf;
}
public static WebApiConfiguration AddRequestHandlers(
this WebApiConfiguration conf,
Action<Collection<HttpOperationHandler>, ServiceEndpoint, HttpOperationDescription> requestHandlerDelegate)
{
var old = conf.RequestHandlers;
conf.RequestHandlers = old == null ? requestHandlerDelegate : (coll, ep, desc) =>
{
old(coll, ep, desc);
};
return conf;
}
Can somebody help me with this? Thank you in advanced!
Try wrapping your Diagnostic param in ObjectContent i.e. ObjectContent<Diagnostic>. Then you will use the ReadAs() method to pull out the object.
It should work.