ServiceStack Render Razor Fails to Find View - servicestack

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.

Related

Custom error pages in servicestack

How do I configure ServiceStack to serve specific error pages (404, 500, etc.) depending on the type of error being returned?
Currently, I'm using the RawHttpHandler below code to ensure that a request for a HTML file is authenticated. However, if the user specifies a non-existent file or endpoint, how can I have it return my 404.html page.
this.RawHttpHandlers.Add(httpReq =>
{
var session = httpReq.GetSession();
if(!session.IsAuthenticated) {
var isHtmlFileRequest = httpReq.PathInfo.EndsWith(".html");
if(isHtmlFileRequest && !files.Any(s => httpReq.PathInfo.ToLower().Contains(s))) {
return new RedirectHttpHandler {
AbsoluteUrl = "/Login.html"
};
}
}
return null;
});
The Error Handling wiki shows different ways to Customize Handling of Exceptions in ServiceStack, e.g you can redirect 404 errors to /404.cshtml with:
public override void Configure(Container container)
{
this.CustomHttpHandlers[HttpStatusCode.NotFound] =
new RazorHandler("/404");
}
CustomHttpHandlers can be any IServiceStackHandler which is just a HttpHandler that supports both ASP.NET and HttpListener requests. The easiest way to create one is to just inherit from IServiceStackHandler. Here's an example of a Custom Static File Handler similar to StaticFileHandler except it only writes the specified filePath instead of using the HTTP Request path:
public class CustomStaticFileHandler : HttpAsyncTaskHandler
{
string filePath;
public CustomStaticFileHandler(string filePath)
{
this.filePath = filePath;
}
public override void ProcessRequest(HttpContextBase context)
{
var httpReq = context.ToRequest(GetType().GetOperationName());
ProcessRequest(httpReq, httpReq.Response, httpReq.OperationName);
}
public override void ProcessRequest(IRequest request, IResponse response,
string operationName)
{
response.EndHttpHandlerRequest(skipClose: true, afterHeaders: r =>
{
var file = HostContext.VirtualPathProvider.GetFile(filePath);
if (file == null)
throw new HttpException(404, "Not Found");
r.SetContentLength(file.Length);
var outputStream = r.OutputStream;
using (var fs = file.OpenRead())
{
fs.CopyTo(outputStream, BufferSize);
outputStream.Flush();
}
}
}
}
This can then be registered as normal, i.e:
public override void Configure(Container container)
{
this.CustomHttpHandlers[HttpStatusCode.NotFound] =
new CustomStaticFileHandler("/404.html");
}

Specific TableController name not working

I have an extremely odd error and wondered if anyone knew the reason for this.
When I create a new DataObject and TableController called Content and ContentController respectively, it doesn't register the tablecontroller and the help documentation it automatically generates has lost its styling.
I can't connect to the controller at all but all other controllers work as expected.
If I just rename it to DataController and that's just the name of the controller, not the dataobject everything works perfectly.
Is ContentController a reserved word of some kind or is this just specifically happening on my machine?
public class DataController : TableController<Content>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MobileContext context = new MobileContext();
DomainManager = new EntityDomainManager<Content>(context, Request, Services);
}
// GET tables/Content
public IQueryable<Content> GetAllContent()
{
return Query();
}
// GET tables/Content/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<Content> GetContent(string id)
{
return Lookup(id);
}
// PATCH tables/Content/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<Content> PatchContent(string id, Delta<Content> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/Content/48D68C86-6EA6-4C25-AA33-223FC9A27959
public async Task<IHttpActionResult> PostContent(Content item)
{
Content current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/Content/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeleteContent(string id)
{
return DeleteAsync(id);
}
}
An MVC project will create an application directory called Content. This will override your route mapping to the ContentController.
You can get around this if desired through changing RouteMaps and other trickery although probably the simpliest answer is to change the name of the controller...

(Not Found) Error in Azure Mobile Services .NET Backend

Been stuck with that error till madness phases ... Please help
I have created an Azure Mobile Service .NET backend, and am now trying to call its Post function from a Xamarin Android client
I initialize and call the Insert async function (these are just snippets from my code)
private static IMobileServiceTable<Todo> _todoMobileServiceTable;
public static bool? InitializeAms()
{
try
{
CurrentPlatform.Init();
_mobileServiceClient = new MobileServiceClient(applicationUrl, applicationKey);
_todoMobileServiceTable = _mobileServiceClient.GetTable<Todo>();
return true;
}
catch (MalformedURLException malformedUrlException)
{
ReportHelper.Report(Tag, "There was an error creating the Mobile Service. Verify the URL", true, malformedUrlException);
}
catch (Exception exception)
{
ReportHelper.Report(Tag, "Error occurred during initialization of Azure Mobile Services", true, exception);
}
return null;
}
_todoMobileServiceTable.InsertAsync(Todo);
I get the following error when calling .InsertAsync(Todo)
The request could not be completed. (Not Found)
N.B:
Azure storage client is not available for xamarin yet, and I have no other choice other than to use this dirty fork which is 1 year old and is made for iOS not Android (although it works fine with azure mobile service javascript) https://github.com/zgramana/IOSAzureBlobUploader
It works if I use the browser 'try it out' button but it doesn't work when I call it from the xamarin client app.
It works from the xamarin client app if I use the javascript mobile service
This error occurs both on the local azure mobile service and the published one online
Here is the WebApiConfig class
namespace Service.Ams
{
public static class WebApiConfig
{
public static void Register()
{
// Use this class to set configuration options for your mobile service
ConfigOptions options = new ConfigOptions();
// Use this class to set WebAPI configuration options
HttpConfiguration config = ServiceConfig.Initialize(new ConfigBuilder(options));
// To display errors in the browser during development, uncomment the following
// line. Comment it out again when you deploy your service for production use.
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
Database.SetInitializer(new ServiceAmsInitializer());
}
}
public class ServiceAmsInitializer : ClearDatabaseSchemaIfModelChanges<ServiceAmsDbContext>
{}
}
Here is the TableController class
namespace Service.Ams.Controllers
{
public class TodoItemController : TableController<TodoItem>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
ServiceAmsDbContext serviceAmsDbContext = new ServiceAmsDbContext();
DomainManager = new EntityDomainManager<TodoItem>(serviceAmsDbContext, Request, Services);
}
// GET tables/TodoItem
[AuthorizeLevel(AuthorizationLevel.Admin)]
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
// GET tables/TodoItem/55D11C86-6EA6-4C44-AA33-337FC9A27525
[AuthorizeLevel(AuthorizationLevel.Admin)]
public SingleResult<TodoItem> GetTodoItem(string id)
{
return Lookup(id);
}
// PATCH tables/TodoItem/55D11C86-6EA6-4C44-AA33-337FC9A27525
[AuthorizeLevel(AuthorizationLevel.Admin)]
public Task<TodoItem> PatchTodoItem(string id, Delta<TodoItem> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/TodoItem/55D11C86-6EA6-4C44-AA33-337FC9A27525
[AuthorizeLevel(AuthorizationLevel.Anonymous)]
public async Task<IHttpActionResult> PostTodoItem(TodoItem item)
{
string storageAccountName;
string storageAccountKey;
// Try to get the Azure storage account token from app settings.
if (
!(Services.Settings.TryGetValue("STORAGE_ACCOUNT_NAME", out storageAccountName) |
Services.Settings.TryGetValue("STORAGE_ACCOUNT_ACCESS_KEY", out storageAccountKey)))
Services.Log.Error("Could not retrieve storage account settings.");
// Set the URI for the Blob Storage service.
Uri blobEndpoint = new Uri(string.Format("http://127.0.0.1:10000/{0}/", storageAccountName));
// Create the BLOB service client.
CloudBlobClient blobClient = new CloudBlobClient(blobEndpoint, new StorageCredentials(storageAccountName, storageAccountKey));
// Create a container, if it doesn't already exist.
CloudBlobContainer container = blobClient.GetContainerReference(item.ContainerName);
await container.CreateIfNotExistsAsync();
// Create a shared access permission policy.
BlobContainerPermissions containerPermissions = new BlobContainerPermissions
{
PublicAccess = BlobContainerPublicAccessType.Blob
};
// Enable anonymous read access to BLOBs.
container.SetPermissions(containerPermissions);
// Define a policy that gives write access to the container for 5 minutes.
SharedAccessBlobPolicy sasPolicy = new SharedAccessBlobPolicy
{
SharedAccessStartTime = DateTime.UtcNow,
SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(5),
Permissions = SharedAccessBlobPermissions.Write
};
// Get the SAS as a string.
item.SasQueryString = container.GetSharedAccessSignature(sasPolicy);
// Set the URL used to store the image.
item.ImageLqUri = string.Format("{0}{1}/{2}", blobEndpoint, item.ContainerName, item.ResourceNameLq);
item.ImageHqUri = string.Format("{0}{1}/{2}", blobEndpoint, item.ContainerName, item.ResourceNameHq);
// Complete the insert operation.
TodoItem current = await InsertAsync(item);
return CreatedAtRoute("Tables", new {id = current.Id}, current);
}
// DELETE tables/TodoItem/55D11C86-6EA6-4C44-AA33-337FC9A27525
[AuthorizeLevel(AuthorizationLevel.Admin)]
public Task DeleteTodoItem(string id)
{
return DeleteAsync(id);
}
}
}
Here is the EntityData class
namespace Service.Ams.DataObjects
{
[Table("dbo.TodoItems")]
public class TodoItem : EntityData
{
public string ContainerName { get; set; }
public string ResourceNameLq { get; set; }
public string ResourceNameHq { get; set; }
public string SasQueryString { get; set; }
public string ImageLqUri { get; set; }
public string ImageHqUri { get; set; }
}
}
Is there any way you can get a dump of what the HTTP request looks like?
I don't have an android client handy here but we can have a look on Monday.
Henrik
TableController and client corresponding class must have the same name for example TodoController and TodoClass. I don't know if there is an attribute that modifies this rule and how to use, if at server side decorating TableController class or at client side decorating data class.

Variable Placeholder Ignored

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.

ServiceStack Not Caching Json

I'm using ServiceStack on ASP.NET 4.5. I'm having troubles with the ServiceStack InMemory caching. If I just call the URL directly from the browser it pulls back the cached version, but if I try to call it via getJSON in JQuery, it never pulls back the cached version and just refetches the data each time.
Here's the basic code bits...
public class AResponse : IHasResponseStatus
{
public ResponseStatus ResponseStatus { get; set; }
public Html Html { get; set; }
}
public object Get(A request)
{
var cacheKey = UrnId.Create<string>(request.UserKey + request.Id);
var expireInTimespan = new TimeSpan(1, 0, 0);
return RequestContext.ToOptimizedResultUsingCache(base.Cache, cacheKey, expireInTimespan, () =>
{
var ar = new AResponse();
var html = new Html();
html.Test = "test";
ar.Html = html;
return ar;
});
}
...Thanks for any ideas.
My understanding is that when you call the Service from the browser you are going to cache a Html version. So, ServiceStack will insert/retrieve by applying a .html suffix onto your key. When you call it from JQuery it will cache a Json version and apply a .json suffix onto your key. You could test this by calling into your Service from the browser using ?format=json on the url. This would cache a json version (instead of html) and then calling from JQuery to get the cached json.

Resources