I'm having a go with the razor functionality in service stack.
I have a razor cshtml view working for one of my response DTO's.
I need to access some values from the request DTO in the razor view that have been filled in from some fields from the REST route, so i can construct a url to put into the response html page and also label some form labels.
Is there anyway of doing this? I don't want to duplicate the property from the request DTO into the response DTO just for this html view. Because i'm trying to emulate an existing REST service of another product, i do not want to emit extra data just for the html view.
eg
http://localhost/rest/{Name}/details/{Id}
eg
#inherits ViewPage<DetailsResponse>
#{
ViewBag.Title = "todo title";
Layout = "HtmlReport";
}
this needs to come from the request dto NOT #Model
link to user
link to user details
If you want to access the Request DTO it needs to be referenced by either by adding the Request to the Response DTO (which you don't want to do), so the other option is to add it to the IHttpRequest.Items Dictionary which is the preferred way to pass data between your filters and services.
public class MyService : Service {
public object Any(MyRequest request) {
base.Request.Items["RequestDto"] = request;
return MyResponse { ... };
}
}
Then in your view:
#{
var myRequest = (MyRequest)base.Request.Items["RequestDto"];
}
Wrapping Re-usable functionality in Request Filters
If you find you need to access the Request DTO in your views a lot, then rather than manually assigning it in each service, you can create a Request Filter Attribute or if you want it assigned all the time in a Global Request Filter.
public class SetRequestDtoAttribute : RequestFilterAttribute {
public override void Execute(
IHttpRequest req, IHttpResponse res, object requestDto)
{
req.Items["RequestDto"] = requestDto;
}
}
Then you can add this behavior by decorating the [SetRequestDto] attribute on different levels of granularity on either an Action, Service, Request DTO or base class.
Related
I am trying to get the new AutoPopulate attribute to work but I am having some difficulty understanding the new AutoQuery functionality.
To test it out I am aiming to replace this service that is a standard AutoQuery endpoint but it also filters by the logged in users ID. I want to replace it so it works completely with just the model definition.
public class DevExtremeService : ServiceBase
{
public IAutoQueryDb AutoQuery { get; set; }
public QueryResponse<DeWatchedUrlResponse> Any(WatchedUrlDevExRequest request)
{
var q = AutoQuery.CreateDevXQuery(request, Request.GetRequestParams(), Request);
q.Where(x => x.UserAuthCustomId == GetUserId());
var response = AutoQuery.Execute(request, q, base.Request);
return response;
}
}
[Route("/de/watched-urls")]
public class WatchedUrlDevExRequest : QueryDb<WatchedUrlRecord, DeWatchedUrlResponse>
{
}
So I deleted the service and updated model to:
[ValidateIsAuthenticated]
[AutoPopulate(nameof(WatchedUrlDevExRequest.UserAuthCustomId), Eval = "userAuthId")]
[Route("/de/watched-urls")]
public class WatchedUrlDevExRequest : QueryDb<WatchedUrlRecord, DeWatchedUrlResponse>
{
public long UserAuthCustomId { get; set; }
}
My understanding from reading the release notes is that userAuthId is a variable declared in the AutoQuery #script context that is added by default.
I have tried a few different variations and I cannot get the property to populate. The docs seem focused on audit history and multitenancy but really I am just looking for a quick way to make endpoints.
I have 2 main questions:
Why is the auto populate not working on this property?
Where can I see the default #script definition so I can see how things like userAuthId are defined and better get an understanding how to add my own?
edit
I re-read docs and I gues this only works when writing data to db. I really like the concept of being able to apply #script to a request model via attribute. Is that possible?
AutoQuery CRUD's [AutoPopulate] attribute initially only populated AutoQuery CRUD's Data Model when performing CRUD operations, e.g. Inserting, Updating or Deleting entities.
For ensuring a query only returns a users records, it's recommended to use an AutoFilter instead, which behaves as expected ensuring the query is always applied to the Data Model, e.g:
[ValidateIsAuthenticated]
[Route("/de/watched-urls")]
[AutoFilter(QueryTerm.Ensure, nameof(WatchedUrlRecord.UserAuthCustomId),
Eval = "userAuthId")]
public class WatchedUrlDevExRequest : QueryDb<WatchedUrlRecord, DeWatchedUrlResponse>
{
}
However as I can see it's a useful feature I've also just added support for [AutoPopulate] & [AutoMap] attributes on Query DTOs in this commit where your AutoQuery DTO would work as expected where it populates the Request DTO property:
[ValidateIsAuthenticated]
[AutoPopulate(nameof(WatchedUrlDevExRequest.UserAuthCustomId), Eval = "userAuthId")]
[Route("/de/watched-urls")]
public class WatchedUrlDevExRequest : QueryDb<WatchedUrlRecord, DeWatchedUrlResponse>
{
public long UserAuthCustomId { get; set; }
}
This change is available from v5.10.3 that's now available on MyGet.
An alternative approach to populate AutoQuery's Request DTO you could have a custom AutoQuery implementation like you have, an Extensible Query Filter or custom base class or I'd personally go with a Global Request Filter that updates all Request DTOs with a shared interface, e.g:
GlobalRequestFilters.Add((req, res, dto) => {
if (dto is IHasUserAuthCustomId authDto)
{
var session = req.GetSession();
if (session.IsAuthenticated)
authDto.UserAuthCustomId = session.UserAuthId;
}
});
Or you could wrap this logic in a Request Filter Attribute and apply the behavior to Request DTOs that way.
Note: userAuthId is a ServiceStack #Script method that returns the currently authenticated User Id.
I am porting a Asp.Net MVC application to Razor Pages.
In some of the controllers of the MVC application it makes use of return View("someOtherView", someModelForOtherView);
How do I port this to Razor Pages?
What I need to do is to transfer the request over to another Razor Page and pass the prepared PageModel to it (the other page does not need to execute OnMethod() but simply render its html.
Or, in other words, I only need to swap the template file that should be rendered with another one.
I cannot use Redirect as there must not be another roundtrip via the browser.
I doubt this is (easily) possible. From the github request that Lerner linked above, it's noted Razor Pages weren't designed to do that.
The closest workaround I was able to achieve was to turn my destination Razor Page into a View. (Hence, no code-behind.) Obviously that will only be possible if your destination page is never directly accessed via URL. For example, if you want to redirect to /Pages/MyPage, and you still need to be able to access the url http://example.com/MyPage, this won't work.
But, say all you want is a generic error or status page. Those don't have to be directly-accessible through URL. This works well for that.
Here's a couple extension methods on PageModel to do it, one that accepts models and one that doesn't:
public static ViewResult View(this PageModel pageModel, string viewName) {
return new ViewResult() {
ViewName = viewName,
ViewData = pageModel.ViewData,
TempData = pageModel.TempData
};
}
public static ViewResult View<TModel>(this PageModel pageModel, string viewName, TModel model) {
var viewDataDictionary = new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(), new ModelStateDictionary()) {
Model = model
};
foreach (var kvp in pageModel.ViewData) viewDataDictionary.Add(kvp);
return new ViewResult {
ViewName = viewName,
ViewData = viewDataDictionary,
TempData = pageModel.TempData
};
}
FYI, the reason for having to recreate the view dictionary is because the one in your pageModel is going to have a model type specific to the current Page, not to the View you're directing to, and you can't change the Model within a ViewDataDictionary to a different type. MVC would complain and throw an exception.
Usage:
public IActionResult OnGet(string id) {
// check if id is good here
if (idIsNoGood) return this.View("InvalidId", new ErrorModel...);
else {
return Page();
}
}
The above will look for InvalidId.cshtml view, which can be in the same folder as your page, the root /Pages/ folder, or /Pages/Shared/. And it'll still use your Layout too, like any other page.
Just make sure your cshtml file doesn't have a #page directive at the top; this won't work for a Razor page, only a View.
Example InvalidId.cshtml:
#model MyProject.Models.ErrorModel
<h1>Invalid Request</h1>
<p>#Model.Message</p>
I have created a View using module, now in controller of this view i need to fetch some specific content type and return to view. Please can some one eleborate with code sample.
You will need to inject the IContentManager services in your controller constructor (see dependency injection) , but since you will need to populate a new shape, you could inject IOrchardServices which will include a few common OrchardServices in one instance.
IOrchardServices services;
public MyController(IOrchardServices services){
this.services = services;
}
Then in your action (if you want to show it on the front end you will have to mark it as themed), do something like this:
[Themed]
public ActionResult MyAction(){
//Notice that you can filter the contentItems here, this is just a basic example
var myContentItems = services.ContentManager.Query().ForType("MyContentItem").List();
//You probably need to create a new shape for showing the ContentTypes
var shape = services.New.YourCustomShape(); //Notice that you must create a view that matches this name
shape.YourContentItems = myContentItems;
return new ShapeResult(this, shape);
}
And that's it.
I am implementing a RESTful service which will be consumed by a Dojo framewwork's RestStore, which will permit binding the service to various widgets like an interactive grid or a type-ahead ajax select.
The RestStore wants to send and receive the HTTP Range and Content-Range headers in order to restrict the results of the queries to particular subrange.
What is the best practice pattern in ServiceStack (new API) for reacting to HTTP headers as part of the service? Normally, the service method like Get doesn't have access to the HTTP headers, unless I have missed an alternate API.
The current way that I see is to implement an attribute like
public class RangeSupporter : Attribute, IHasRequestFilter, IHasResponseFilter
which will parse the headers on request, and write the headers on response. The DTO would then be marked for this filter.
The filter would transfer the values, say 'First' and 'Last' in and out of the DTO. However, to know that the DTO even has such attributes, it would have to have some marker interface like
interface IHasRangeSupport {
int First { set; get; }
int Last { set; get; }
int Total { set; get; }
}
so that the filter can transfer the information into and out of the DTO the with code similar to:
var r = request as IHasRangeSupport;
if (r != null) {
/// Access the DTO attributes for the range parameters
}
This seems like a lot of ceremony, and a very awkward implementation.
Is there a better pattern for accessing the HTTP headers when implementing a REST service?
In my particular use case, supporting non-REST endpoints (like SOAP) are not required. Only the HTTP end-point is important.
What is the best practice pattern in ServiceStack (new API) for reacting to HTTP headers as part of the service?
I believe you can get the headers in your service class...
public class FooService : Service
{
public object Get(Foo reqeust)
{
//get header
var h1 = base.RequestContext.GetHeader("headerName");
//get header differently
var h2 = base.Request.Headers.Get("headerName");
}
}
How would I generate a URL to a specific service defined in ServiceStack?
I want to include full or relative URLs to other endpoints as part of the response DTO. RestServiceBase contains RequestContext.AbsoluteUri, but that is entirely dependent on the request.
Reverse Routing
The Reverse Routing section in the wiki shows how to use extension methods on a popualated Request DTO to generate relative and absolute URI's:
If you use [Route] metadata attributes (as opposed to the Fluent API) you will be able to generate strong-typed URI's using just the DTOs, letting you create urls outside of ServiceStack web framework as done with .NET Service Clients using the ToUrl(HttpMethod) and ToAbsoluteUri(HttpMethod), e.g:
[Route("/reqstars/search", "GET")]
[Route("/reqstars/aged/{Age}")]
public class SearchReqstars : IReturn<ReqstarsResponse>
{
public int? Age { get; set; }
}
var relativeUrl = new SearchReqstars { Age = 20 }.ToGetUrl();
var absoluteUrl = new SearchReqstars { Age = 20 }.ToAbsoluteUri();
relativeUrl.Print(); //= /reqstars/aged/20
absoluteUrl.Print(); //= http://www.myhost.com/reqstars/aged/20
The Email Contacts demo shows an example of using the above Reverse Routing extension methods to populate routes for HTML Forms and Links in Razor Views.
Other Reverse Routing Extension methods
new RequestDto().ToPostUrl();
new RequestDto().ToPutUrl();
new RequestDto().ToDeleteUrl();
new RequestDto().ToOneWayUrl();
new RequestDto().ToReplyUrl();
Accessing Http Request
You can also inspect the incoming underlying httpRequest with:
var httpReq = base.RequestContext.Get<IHttpRequest>();
As well as the underlying ASP.NET (or HttpListener) Request object with:
var aspNetReq = httpReq.OriginalRequest;
They should contain additional properties that should be more useful.