Variable Placeholder Ignored - servicestack

I setup two routes for my service:
GET /foo
GET /foo/{Name}
The metadata page correctly lists:
GET /foo
GET /foo/{Name}
But when I browse to /baseurl/foo/NameValueHere I get
The operation 'NameValueHere' does not exist for this service
Am I missing some configuration in my (self-hosted) apphost?
Edit:
Additional information
I didn't like the Route attribute over my DTOs, so I wrote a simple extension method for IServiceRoutes.
public static IServiceRoutes AddFromServiceAttributes(this IServiceRoutes routes)
{
var myServiceTypes = typeof(MyServiceBase).Assembly.GetDerivedTypesOf<MyServiceBase>();
var routedMethods = myServiceTypes.SelectMany(type => type.GetMethodsWithAttribute<MyServiceRouteAttribute>());
foreach (var routedMethod in routedMethods)
{
var routesFound = routedMethod.GetAttributes<MyServiceRouteAttribute>();
foreach (var route in routesFound)
{
// request type can be inferred from the method's first param
// and the same for allowed verbs from the method's name
// [MyServiceRoute(typeof(ReqType), "/foo/{Name}", "GET")]
// [MyServiceRoute("/foo/{Name}", "GET")]
// [MyServiceRoute(typeof(ReqType), "/foo/{Name}")]
if (route.RequestType == null)
{
route.RequestType = routedMethod.GetParameterListFromCache().First().ParameterType;
}
if (string.IsNullOrWhiteSpace(route.Verbs))
{
var upperRoutedMethodName = routedMethod.Name.ToUpperInvariant();
route.Verbs = upperRoutedMethodName != "ANY" ? upperRoutedMethodName : null;
}
routes.Add(route.RequestType, route.RestPath, route.Verbs);
}
}
return routes;
}
I call this method in AppHost.Configure, along with AddFromAssembly:
this.SetConfig(new EndpointHostConfig { ServiceStackHandlerFactoryPath = "service" });
// some container registrations here
this.Routes.AddFromServiceAttributes().AddFromAssembly();
What puzzles me is that the metadata page shows routes correctly.
DTOs are very simple, and they do include the Name string property.
class Foo { public string Name { get; set; } }
Edit 2:
I removed the MyServiceRouteAttribute attribute and reused ServiceStack's RouteAttribute.
Request DTO Types are inferred from 1st method param.
Edit 3:
Probably I managed to solve this. I was preprending /json/reply in the url.
http://localhost/service/json/reply/foo/NameValueHere <- not working
http://localhost/service/foo/NameValueHere <- working
I thought both the content-type and reply-type tokens were mandatory.

Related

ServiceStack Render Razor Fails to Find View

This is a self hosted project. There is a Views\Member.cshtml file that is set to copy always as content. The following when run returns null for the razorView. I seem to be missing something here.
var razorView = razor.GetViewPage("Member"); //e.g. /Views/Member.cshtml
var html = razor.RenderToHtml(razorView, em);
Console.WriteLine(html);
Furthermore I've tried this and it returns file not found although the file is there:
var html = razor.RenderToHtml(AppDomain.CurrentDomain.BaseDirectory +
"Views\\" + "Member.cshtml", em);
Also, is there a way to have a service gateway return the rendered razor?
Member.cshtml exists: https://db.tt/xuOSAjEj31
razor: https://db.tt/qeApkAEZGH
AppHost.cs:
Plugins.Add(new RazorFormat() {
// ScanRootPath = AppDomain.CurrentDomain.BaseDirectory + "Views"
} );
I've tested this with the ServiceStack Self Host with Razor project in ServiceStackVS after changing each .cshtml to Copy if newer, changed AppHost.Configure() to just:
public override void Configure(Container container)
{
this.Plugins.Add(new RazorFormat());
}
Then added the HelloView Service below:
[Route("/views/hello/{Name}")]
public class HelloView
{
public string Name { get; set; }
}
public class MyServices : Service
{
public object Any(Hello request)
{
return new HelloResponse { Result = "Hello, {0}!".Fmt(request.Name) };
}
public object Any(HelloView request)
{
var razor = HostContext.GetPlugin<RazorFormat>();
var helloView = razor.GetViewPage("Hello");
var response = Any(request.ConvertTo<Hello>());
var html = razor.RenderToHtml(helloView, response);
return html;
}
}
Which works as expected where razor.GetViewPage("Hello") returns the view in Views\Hello.cshtml and it returns the generated html.
Also, is there a way to have a service gateway return the rendered razor?
The Service Gateway is used to call Services which return Typed Response DTOs, not generated html views. You can use base.Gateway to access the Service Gateway in Razor Views.

How to programmatically assign roles and permissions to services and/or RequestDTOs

Statically I set access to my services like so:
[Authenticate]
public class AppUserService : Service
{
[RequiredRole("Administrator")]
public object Post(CreateAppUser request)
{
//.....
}
}
How can I do this programmatically?
I will let the user create roles using a GUI. Then I will present a list of available methods, e.g. by providing the methods using improved code like:
var appHost = HostContext.AppHost;
var restPaths = appHost.RestPaths;
foreach (var restPath in restPaths)
{
var reqType = restPath.RequestType;
string verbs = string.Empty;
if (restPath.Verbs != null)
{
var counter = 0;
foreach (var verb in restPath.Verbs)
{
if (counter > 0) verbs += ", ";
verbs += verb;
counter++;
}
}
Debug.WriteLine($"Path: {restPath.Path} | Verbs: {verbs} | Name: {reqType.Name} FullName: {reqType.FullName}");
}
The code above outputs something like
Path: /appusers | Verbs: POST | Name: CreateAppUser FullName: MyServer.ServiceModel.DTOs.Request.CreateAppUser
So I could show in my UI the Name property of the RequestType and let him define, what roles are allowed to call this method. So the user may create a role called 'User Management' and allow members of this role to execute CreateAppUser.
Using annotations I would write
[RequiredRole("Administrator", "User Management")]
public object Post(CreateAppUser request)
{ .... }
Is this anyhow possible in C# code?
ServiceStack does have a way to dynamically Add Attributes at runtime, e.g:
typeof(CreateAppUser)
.AddAttributes(new RequiredRoleAttribute("Administrator", "User Management"));
Which is an alternative to declaring attributes statically, but it's still not a solution for a data-driven authorization system.
But you could add your own custom Authorization logic in addition to ServiceStack's built-in attributes by validating it in your Service:
public ICustomAuth CustomAuth { get; set; }
public object Post(CreateAppUser request)
{
var session = base.GetSession();
var requiresRoles = CustomAuth.GetRequiredRoles(request.GetType());
var hasAllRoles = requiresRoles.All(r =>
session.HasRole(r, base.AuthRepository))
if (!hasAllRoles)
throw new UnauthorizedAccessException("Requires all roles");
}
If you do this a lot you will want to refactor the custom validation logic into a single reusable method, or if you prefer into a custom RequestFilter attribute.

MVC 5 FileContentResult Action Result Permission and Redirect

I have an MVC 5 application that allows users to download files that are stored in the database. I am using the FileContentResult action method to do this.
I can restrict access to this method throughout the application, but a smart user can figure out the action URL and paste something like this (localhost:50000/Home/FileDownload?id=13) into their browser and have access to download any file by just changing the parameter.
I want to restrict users from doing this. Only allow the Administrator role AND users that have a specific permission that can only be determined by a database call to download files.
What I am looking for is that If an user uses the URL to download a file and does not have the proper permissions, I want to redirect the user with a message.
I would like to do something like the code below or similar, but I get the following error: Cannot implicitly convert type 'System.Web.Mvc.RedirectToRouteResult' to 'System.Web.Mvc.FileContentResult'
I understand that I can not use return RedirectToAction("Index") here, just looking for some ideas on how to handle this problem.
public FileContentResult FileDownload(int id)
{
//Check user has file download permission
bool UserHasPermission = Convert.ToInt32(context.CheckUserHasFileDownloadPermission(id)) == 0 ? false : true;
if (User.IsInRole("Administrator") || UserHasPermission)
{
//declare byte array to get file content from database and string to store file name
byte[] fileData;
string fileName;
//create object of LINQ to SQL class
//using LINQ expression to get record from database for given id value
var record = from p in context.UploadedFiles
where p.Id == id
select p;
//only one record will be returned from database as expression uses condtion on primary field
//so get first record from returned values and retrive file content (binary) and filename
fileData = (byte[])record.First().FileData.ToArray();
fileName = record.First().FileName;
//return file and provide byte file content and file name
return File(fileData, "text", fileName);
}
else
{
TempData["Message"] = "Record not found";
return RedirectToAction("Index");
}
}
Since both FileContentResult and RedirectToRouteResult are inherited from ActionResult, simply use ActionResult instead of FileContentResult for your action's return type:
public ActionResult FileDownload(int id)
{
if(IsUserCanDownloadFile()) // your logic here
{
// fetch the file
return File(fileData, "text", fileName);
}
return RedirectToAction("Index");
}
Or if you prefer attributes, you could write your very own authorize attribute to check permissions:
public class FileAccessAttribute : AuthorizeAttribute
{
private string _keyName;
public FileAccessAttribute (string keyName)
{
_keyName = keyName;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// imagine you have a service which could check the Permission
return base.AuthorizeCore(httpContext)
|| (this.ContainsKey
&& _permissionService.CanDownload(httpContext.User.Identity.GetUserId(),
int.Parse(this.KeyValue.ToString()));
}
private bool ContainsKey
{
get
{
// for simplicity I just check route data
// in real world you might need to check query string too
return ((MvcHandler)HttpContext.Current.Handler).RequestContext
.RouteData.Values.ContainsKey(_keyName);
}
}
private object KeyValue
{
get
{
return ((MvcHandler)HttpContext.Current.Handler)
.RequestContext.RouteData.Values[_keyName];
}
}
}
Now you could decorate the custom attribute on your actions:
[FileAccess("id", Roles ="Administrator")]
public FileContentResult FileDownload(int id)
{
// fetch the file
return File(fileData, "text", fileName);
}

Attribute Routing with BaseClass implementation: Correcltly listed but still fails

I've set attribute routing on a controller class which inherits a base class where I handle I18N culture set/selection logic (as described in article ASP.NET MVC 5 Internationalization) but that process fails, although route seemed to be set correctly.
[RoutePrefix("{culture}")]
public class HomeController : BaseController
{
public ActionResult Index()
{
return View();
}
[Route("Hakkimda")]
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
When I try to get to link I see grey screen of death on browser with this on address bar:
http://localhost:53530/tr-tr/Hakkimda?MS_DirectRouteMatches=System.Collections.Generic.List%601%5BSystem.Web.Routing.RouteData%5D
I believe the problem is the way base controller implements I18N logic which is based on BeginExecuteCore overloading.
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
string cultureName = RouteData.Values["culture"] as string;
// Attempt to read the culture cookie from Request
if (cultureName == null)
cultureName = Request.UserLanguages != null && Request.UserLanguages.Length > 0 ? Request.UserLanguages[0] : null; // obtain it from HTTP header AcceptLanguages
// Validate culture name
cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
if (RouteData.Values["culture"] as string != cultureName) {
// Force a valid culture in the URL
RouteData.Values["culture"] = cultureName.ToLowerInvariant(); // lower case too
// Redirect user
Response.RedirectToRoute(RouteData.Values);
}
// Modify current thread's cultures
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
return base.BeginExecuteCore(callback, state);
}
Probably execution precedence of BeginExecuteCore and routing have some mismatch but my knowledge on both don't suffice to solve it.
I've seen this article(What’s New in ASP.NET MVC 5.2 : Attribute routing improvements) but example provided there was a bit different and because it's new there aren't other examples on the net.
mr-anton's answer will stop you getting rubbish in the address bar but It'll also stop the language changing.
I had this issue after a change from MVC5 to MVC5.2
This answer says it is a Microsoft issue
The workaround is to see if the route data is in a nested route key
var routeData = RouteData;
if (routeData.Values.ContainsKey("MS_DirectRouteMatches"))
{
routeData = ((IEnumerable<System.Web.Routing.RouteData>)routeData.Values["MS_DirectRouteMatches"]).First();
}
string cultureName = routeData.Values["culture"] as string;
And then it just works.
remove this code
if (RouteData.Values["culture"] as string != cultureName) {
// Force a valid culture in the URL
RouteData.Values["culture"] = cultureName.ToLowerInvariant(); // lower case too
// Redirect user
Response.RedirectToRoute(RouteData.Values);
}

web api get route template from inside handler

I searched a lot before putting the questions here but the more I search the more confused I get.
So I have created an handler and I am trying to get the route like this:
public class ExecutionDelegatingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (securityAuthority.VerifyPermissionToExecute(request.GetRouteData().Route.RouteTemplate, request.Headers))
{
return base.SendAsync(request, cancellationToken);
}
else
{
httpResponseMessage.StatusCode = HttpStatusCode.Unauthorized;
}
}
}
GetRouteData returns null so I can't get to the RouteTemplate property but I can
see the route there in a list deep in the stack. I found so many different ways which one can use to get the route, but those methods evaluate to null as well. I am a bit lost on how to get something so simple done. I am using self host for development but will use IIS for deployment.
UPDATE 1
I forgot to put here what else I had tried:
//NULL
request.GetRouteData();
//EMPTY
request.GetRequestContext().Configuration.Routes.GetRouteData(request).Route.RouteTemplate;
//EMPTY
request.GetConfiguration().Routes.GetRouteData(request).Route.RouteTemplate;
The route works just fine, but strangely if I try to get the controller to service that request I get a 404... if I just step over that I will get to the controller just fine.
HttpControllerDescriptor httpControllerDescriptor = request.GetRequestContext().Configuration.Services.GetHttpControllerSelector().SelectController(request);
IHttpController httpController = httpControllerDescriptor.CreateController(request);
I am using autofac to discover all the routes which I am defining just like:
[Route("queries/organization/clients")]
[HttpGet]
public ClientInitialScreenModel GetClients()
{
return OrganizationModelsBuilder.GetClientInitialScreen();
}
UPDATE 2
If I GetRouteData gets called after the line above, I am able to get the route template:
base.SendAsync(request, cancellationToken);
var routeData = request.GetRouteData();
So maybe I misunderstood the whole picture and I cant get the route template before the handler that resolves which controller to execute for the request does its work... is that the case?
For reference this is the handler I am working on:
public class ExecutionDelegatingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var securityAuthority = (ISecurityAuthority) request.GetDependencyScope().GetService(typeof (ISecurityAuthority));
var configuration = (IWebApiConfiguration)request.GetDependencyScope().GetService(typeof(IWebApiConfiguration));
var tsc = new TaskCompletionSource<HttpResponseMessage>();
var httpResponseMessage = new HttpResponseMessage();
if (request.RequestUri.AbsolutePath.Equals(configuration.CommandGatewayUrl, StringComparison.InvariantCultureIgnoreCase))
{
var apiMessage = JsonConvert.DeserializeObject<ApiCommandEnvelope>(request.Content.ReadAsStringAsync().Result);
if (securityAuthority != null && !securityAuthority.VerifyPermissionToExecute(apiMessage, request.Headers))
{
httpResponseMessage.StatusCode = HttpStatusCode.Unauthorized;
}
else
{
var messageProcessor = (IWebApiMessageProcessor)request.GetDependencyScope().GetService(typeof(IWebApiMessageProcessor));
var reponse = messageProcessor.HandleRequest(apiMessage);
httpResponseMessage.StatusCode = (HttpStatusCode) reponse.StatusCode;
if (!string.IsNullOrEmpty(reponse.Content))
{
httpResponseMessage.Content = new StringContent(reponse.Content);
}
}
}
else
{
if (securityAuthority != null && !securityAuthority.VerifyPermissionToExecute(request.GetRouteData().Route.RouteTemplate, request.Headers))
{
httpResponseMessage.StatusCode = HttpStatusCode.Unauthorized;
}
else
{
return base.SendAsync(request, cancellationToken);
}
}
tsc.SetResult(httpResponseMessage);
return tsc.Task;
}
UPDATE 3
The code runs fine in a non self hosting environment, so this is more like a self host issue.
The Web Api still has a lot to improve. It was tricky to find a way to get this working and I just hope this saves other guys from spending all the time I did.
var routeTemplate = ((IHttpRouteData[]) request.GetConfiguration().Routes.GetRouteData(request).Values["MS_SubRoutes"])
.First().Route.RouteTemplate;
I had a similar issue, but was able to get the route inside the message handler by the following:
request.GetConfiguration().Routes.GetRouteData(request).Route.RouteTemplate;
The answer from Marco (shown below) is correct so long as there isn't more than one route defined with the same HttpMethod. The .First() will grab the 1st route defined in that specific ApiController, but this doesn't ensure it grabs the correct one. If you use the ControllerContext to get the Route, you can be sure you've got the exact endpoint you want.
Marco's:
var routeTemplate = ((IHttpRouteData[])request.GetConfiguration()
.Routes.GetRouteData(request).Values["MS_SubRoutes"])
.First().Route.RouteTemplate;
The code:
((IHttpRouteData[])request.GetConfiguration()
.Routes.GetRouteData(request).Values["MS_SubRoutes"])
actually returns a collection of IHttpRouteData, and it contains a record for each endpoint which has the same HttpMethod (Post, Get, etc)... The .First() doesn't guarantee you get the one you want.
Guaranteed To Grab Correct Endpoint's RouteTemplate:
public static string GetRouteTemplate(this HttpActionContext actionContext)
{
return actionContext.ControllerContext.RouteData.Route.RouteTemplate;
}
I used an extension method so to call this you'd do:
var routeTemplate = actionContext.GetRouteTemplate();
This will assure that you get the specific RouteTemplate from the endpoint making the call.
I think you can get route Data from request.Properties property and easy to unit test.
/// <summary>
/// Gets the <see cref="System.Web.Http.Routing.IHttpRouteData"/> for the given request or null if not available.
/// </summary>
/// <param name="request">The HTTP request.</param>
/// <returns>The <see cref="System.Web.Http.Routing.IHttpRouteData"/> or null.</returns>
public static IHttpRouteData GetRouteData(this HttpRequestMessage request)
{
if (request == null)
{`enter code here`
throw Error.ArgumentNull("request");
}
return request.GetProperty<IHttpRouteData>(HttpPropertyKeys.HttpRouteDataKey);
}
private static T GetProperty<T>(this HttpRequestMessage request, string key)
{
T value;
request.Properties.TryGetValue(key, out value);
return value;
}
Reference link of code

Resources