servicestack plugin to a Windows Service that will serve static files? - servicestack

I've ServiceStack (V5.1.0) as a Windows Service, serving REST APIs, no problems. I would like to create a plugin that will serve static files from a specific physical directory, for any routes that start with /GUI.
I've read "Q: ServiceStack Razor with Multiple SPAs" here ServiceStack Razor with Multiple SPAs
But this seems to only handle individual files like index.html., and I need to serve not just files in the root of the physical path, but files in the subdirs of the physical path as well. For example, the route /GUI/css/site.css should serve the site.css file found in the css subdirectory below the root.
I looked at "Mapping static file directories in ServiceStack" here
https://forums.servicestack.net/t/mapping-static-file-directories-in-servicestack/3377/1
and based on this, tried overriding GetVirtualFileSources
public class AppHost : AppSelfHostBase {
...
// override GetVirtualFileSources to support multiple FileSystemMapping.
// Allow plugins to add their own FileSystemMapping
public override List<IVirtualPathProvider> GetVirtualFileSources()
{
var existingProviders = base.GetVirtualFileSources();
// Hardcoded now, will use a IoC collection populated by plugins in the future. Paths will be either absolute, or relative to the location at which the Program assembly is located.
existingProviders.Add(new FileSystemMapping("GUI",#"C:\Obfuscated\netstandard2.0\blazor"));
return existingProviders;
}
....
}
and using a FallBackRoute in the plugins' model,
[FallbackRoute("/GUI/{PathInfo*}")]
public class FallbackForUnmatchedGUIRoutes : IReturn<IHttpResult>
{
public string PathInfo { get; set; }
}
But I can't figure out how to get the interface method to change the PathInfo into an object that implements IVirtualFile.
public HttpResult Get(FallbackForUnmatchedGUIRoutes request)
{
// If no file is requested, default to "index.html"" file name
var cleanPathInfo = request.PathInfo ?? "index.html";
// Somehow, need to convert the cleanPathInfo into an IVirtualFile, that specifies the correct VirtualPathProvider (indexed by "GUI"")
// insert here the magic code to convert cleanPathInfo into an object that implements IVirtualFile
// var cleanVirtualPathInfo = cleanPathInfo as IVirtualFile
// to make use of ServiceStack enhanced functionality, wrap the cleanVirtualPathInfo in a HttpResult,
HttpResult httpresult = new HttpResult(cleanPathInfo,false); // this doesn't compile, because no overload with 2 parameters takes a string as the first parameter, but there is an overload that will take an IVirtualFile object
return httpresult;
}
Any suggestions to make the interface code return the correct files? Or a better way to allow for multiple plugins, each to support a different SPA, based on the first portion of the route? Hints, links, explicit instructions - any and all are welcome!

You just need to register a Virtual File System, you don't need to create your own Service as ServiceStack's static file handler will automatically return the first file it finds in the list of registered Virtual File Sources.
If you want to be able to register File Mappings in a Plugin you can add it to the AppHost's AddVirtualFileSources list in the plugin constructor, e.g:
public class GuiPlugin : IPlugin, IPreInitPlugin
{
public void Configure(IAppHost appHost)
{
appHost.AddVirtualFileSources.Add(
new FileSystemMapping("GUI", appHost.MapProjectPath("~/blazor")));
}
public void Register(IAppHost appHost) {}
}
The appHost.MapProjectPath() lets you resolve a physical file from your AppHost's projects Content Path. Then you can register the Plugin in your AppHost with:
public override void Configure(Container container)
{
Plugins.Add(new GuiPlugin());
}
Where your files in /blazor should now be resolvable from your registered path mapping, e.g:
/GUI/file.html -> C:\project\path\file.html
Note you don't need any Services and ServiceStack will automatically return the static files of registered file mappings, so you'll want to remove any [FallbackRoute] you've added.

Related

Automapper isn't using extension methods for mapping

I have automapper setup like this
services.AddAutoMapper(typeof(MappingAssembly).Assembly, typeof(AssemblyWithExtensionMethods).Assembly);
And in one of my profiles
public class UserModuleMapper : Profile {
public UserModuleMapper() {
IncludeSourceExtensionMethods(typeof(UserGroup));
CreateMap<UserGroup, UserGroupDto>(MemberList.Destination);
}
}
And I have defined the extension method as
public static List<string> GetRoleNames(this UserGroup group) {
return group.UserGroupRoles.Select(x => x.Role.Name).ToList();
}
I have a property on DTO defined as
public List<string> RoleNames { get; set; }
As per the automapper documentation, I have made the following assumptions:
IncludeSourceExtensionMethods, which include extension methods while mapping
while mapping it will also look for methods with prefix Get
But when I validate the automapper extension I get error for unmapped property
Unmapped properties: RoleNames
What is missing in my configuration, automapper should detect the extension method.
I have tried (a) remove GET from the method name, but still does not work (b) moving CreateMap before or after the IncludeSourceExtensionMethods to see if sequence matters, but none of it helped.
With in few minutes after posting the question I got the answer by carefully looking at this issue on Github
The issue was with below statement
IncludeSourceExtensionMethods(typeof(UserGroup));
the type mentioned here should be of extension class
IncludeSourceExtensionMethods(typeof(UserGroupExtensions));
Not deleting the question, as it might help someone in future.

Liferay 7 Asset publisher new web content folder

I've two web content structures (foo and bar) in Liferay 7.0 and I want to store the web contents inside webcontents folders (webcontents/foo and webcontents/bar).
I added two asset publishers, one for each structure, and I also allow the user to create new webcontents through the asset publisher plus '+' icon. However, they are created in the web content root folder (webcontents/). There is any way to dynamicaly save the webcontent that are created through the '+' icon in the asset publisher to a specific folder (based on the template itself, tags, or any other field)?
I used a "ModelListener" for this exact scenario. https://dev.liferay.com/de/develop/tutorials/-/knowledge_base/7-0/model-listeners
If you extend Liferays BaseModelListener you can use the onBeforeCreate() Method for example.
First check the ddmStructure of the current journalArticle and get or create your Folder. Now set the Folder ID for your journalArticle and your done!
I don't think that this can be achieved without customization.
I'd create a service wrapper to determine the Folder e.g. by the Structure's name.
Posting the code as solution suggested by #Viergelenker
public class ArticleSetListenerPortlet extends BaseModelListener<JournalArticle> {
private static final Log LOGGER = LogFactoryUtil.getLog(ArticleSetListenerPortlet.class);
#Override
public void onBeforeCreate(JournalArticle model) throws ModelListenerException {
String structureName = model.getDDMStructure().getName(Locale.US);
long groupId = xxxxx;
List<JournalFolder> journalFolders = JournalFolderLocalServiceUtil.getFolders(groupId);
for(JournalFolder folder : journalFolders) {
if("Foo".equals(folder.getName())) {
model.setFolderId(folder.getFolderId());
LOGGER.info("Set folder as Foo");
}
}
super.onBeforeCreate(model);
}

How can I disable specific Visual Studio Commands from my Package?

I am creating a package which requires the text white space be in a specific format. Without arguing about the reason why lets just assume this is an okay requirement. I must then prevent visual studio from auto-updating the code.
This is fairly easy from an open document where I can add a command filter and prevent the command from being executed with the following code.
[Export(typeof(IVsTextViewCreationListener))]
internal sealed class MyListener : IVsTextViewCreationListener
{
public void VsTextViewCreated(IVsTextView textViewAdapter)
{
var filter = PackageManager.Kernel.Get<CommandFilter>();
if (ErrorHandler.Succeeded(textViewAdapter.AddCommandFilter(filter,out var next)))
filter.Next = next;
}
}
public class CommandFilter : IOleCommandTarget
{
public IOleCommandTarget Next { get; set; }
public const uint CmdEditFormat = 0x8F;
public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
{
switch (prgCmds[0].cmdID)
{
case CmdEditFormat:
return VSConstants.E_ABORT;
return Next.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
}
}
The Edit.FormatDocument command id is blocked as I require. I could also add Edit.FormatSelection or any other commands that may affect the white-space. This is all well and good for open documents which I mark with this special need.
However, when it comes to add-ins like Resharper which updated files in a multitude of ways without actually opening the files themselves there becomes trouble. I need to also block some of these commands, once I find which ones are volatile to my implementation.
So the question is can I setup some sort of CommandFilter application-wide so I can catch them in the act? This would allow me cleanup command of Resharper and then restore the files that contain formatting as needed.
Another possibility is if I can find where the Resharper options file is and updated it somehow to exclude said files. I know this is manually possible.

Where should I put code that I want to always run for each request?

Maybe this is more of an ASP.NET MVC question than an Orchard question but I'm relatively new to both and I don't know the answer in either case.
ASP.NET MVC applications don't technically have a single point of entry, so where am I supposed to put code that I want to always run each and every time someone visits any page, regardless of layer or origin or permissions? Is there a specific Orchard way of doing this?
If it makes a difference, what I'm specifically trying to do at the moment is to restrict the IP range that has access to my website. I want to look at every incoming request and check if the user is either authenticated or has an IP in the allowed range that I configured in my custom settings.
I can think of some quick and dirty ways to achieve this like putting the check in Layout and wrap a condition around all of my zones or implement IThemeSelector to switch to a different Theme but I'd like to do it properly.
All what should you do to achieve this, is implementing new IActionFilter or IAuthorizationFilter like the following:
public class CheckAccessFilter : FilterProvider, IActionFilter, IAuthorizationFilter {
public void OnActionExecuting(ActionExecutingContext filterContext) {
// here you can check the incoming request, and how the system will deal with it,
// before executing the action
}
public void OnActionExecuted(ActionExecutedContext filterContext) {
}
public void OnAuthorization(AuthorizationContext filterContext) {
// here you can authorize any request
}
}
But if you want only to authorize based on content items (like: Widgets, Pages, Projections), you can implement IAuthorizationServiceEventHandler:
public class IPAuthorizationEventHandler : IAuthorizationServiceEventHandler {
public void Checking(CheckAccessContext context) {
}
public void Adjust(CheckAccessContext context) {
}
public void Complete(CheckAccessContext context) {
}
}
The best sample you can follow to implement this approach is SecurableContentItemsAuthorizationEventHandler, you can find it in Orchard.ContentPermissions module.

ServiceStack: Writing an API without needing multiple DTOs?

Subject may be unclear, but I'd like to expose two API calls that are almost identical, like so:
Routes
.Add<GameConsole>("/consoles", "GET")
.Add<GameConsole>("/consoles/count", "GET");
What I have now is "/consoles" giving me a list of all GameConsole objects from my repository. What I'd like to add is "/consoles/count", which gives me a count of all the GameConsole objects from my repository.
But since the service can only map one DTO in the routes, I can only have:
public object Get(GameConsole request)
{
return mRepository.GetConsoles();
}
Not sure I truly understand the limitations of only having one route map to a DTO; is there a way around this? As a side note, it seems odd that I have to pass the DTO to my service method, even though it's not being used at all (mapping to the route is the only purpose?)
Since the 2 routes don't contain any mappings to any variables and are both registered with the same request, you wont be able to tell the matching route from just the Request DTO, e.g:
public object Get(GameConsole request)
{
return mRepository.GetConsoles();
}
i.e. You would need to introspect the base.Request and look at the .PathInfo, RawUrl or AbsoluteUri to distinguish the differences.
If it mapped to a variable, e.g:
Routes
.Add<GameConsole>("/consoles", "GET")
.Add<GameConsole>("/consoles/{Action}", "GET");
Then you can distinguish the requests by looking at the populated request.Action.
But if they have different behaviors and return different responses then they should just be 2 separate services, e.g:
Routes
.Add<GameConsole>("/consoles", "GET")
.Add<GameConsoleCount>("/consoles/count", "GET");
The other option is to only have a single coarse-grained service that returns the combined dataset of both services (i.e. that also contains the count) that way the same service can fulfill both requests.
In very similar situations, I have been creating a subclass DTO for each separate routing service, inheriting the shared elements.
It has been working very well.
So the pattern is
public class SharedRequestDto
{
public string CommonItem { set; get; }
public string CommonId { set; get; }
}
then
[Route("/api/mainservice")]
public class MainServiceRequest : SharedRequestedDto
{
}
[Route("/api/similarservice")]
public class SimilarServiceRquest : SharedRequestDto
{
public string AddedItem { set; get; }
}
This allows differing but similar DTOs to be routed to individual services to process them. There is no need to perform introspection.
You can still use common code when necessary behind the concrete services because they can assume that their request object parameter is a SharedRequestDto.
It probably is not the right solution for every use case, but it is effective, especially since many of my DTOs are in families that share a great deal of data.

Resources