ServiceStack InMemoryVirtualPathProvider for Razor - GetViewPage null - servicestack

I tested this with the default out of the box implementation and GetViewPage retrieves the view from the file system without a problem.
I swapped out the RazorFormat's VirtualFileSource for the inmemory one:
Plugins.Add(new RazorFormat() {
VirtualFileSources = new InMemoryVirtualPathProvider(this),
});
In the service I'm writing a view if it doesn't exist:
var helloView = razor.GetViewPage(email.BlastId.ToString());
if (helloView==null)
{
((InMemoryVirtualPathProvider)razor.VirtualFileSources)
.WriteFile("~/views/"+email.BlastId + ".cshtml", email.Blast);
// .WriteFile(email.BlastId + ".cshtml", email.Blast); doesn't work
}
helloView = razor.GetViewPage(email.BlastId.ToString());
//helloView is always null
I've confirmed that the RazorFormat's VirtualFileSource has the file, the GetViewPage just doesn't retrieve it.
Screenshot of the file located in the VirtualFileSource: https://db.tt/8oirKd9Msi
Furthermore this returns true: razor.VirtualFileSources.FileExists("~/views/"+email.BlastId + ".cshtml") I've tried it without the views folder/etc. It doesn't seem to make an impact.

The RazorFormat loads compiled views on Startup, so the view needs to exist in the VirtualFileSources before RazorFormat is registered in order for it to be available with GetViewPage().
To add a file after RazorFormat has loaded, you need to call AddPage() after it's written to the Virtual File System, e.g:
razorFormat.VirtualFileSources.WriteFile(filePath, contents);
var razorView = razorFormat.AddPage(filePath);
If you only wanted to create a temporary Razor View you can call CreatePage() to create the view:
var razorView = razorFormat.CreatePage(razorHtml);
And render it with:
razorFormat.RenderToHtml(razorView, model);
Or if both the Razor Page and model is temporary, it can be condensed in the 1-liner:
var html = razorFormat.CreateAndRenderToHtml(razorHtml, model);
Working Example
const string template = "This is my sample view, Hello #Model.Name!";
RazorFormat.VirtualFileSources.WriteFile("/Views/simple.cshtml", template);
var addedView = RazorFormat.AddPage("/Views/simple.cshtml");
var viewPage = RazorFormat.GetViewPage("simple"); //addedView == viewPage
var html = RazorFormat.RenderToHtml(viewPage, new { Name = "World" });
html.Print(); //= "This is my sample view, Hello World!"

Related

SharePoint JS Link file reference globally

I would like to ask that I have one document library with more than 20 views. I have implemented one JS link file to render required changes in the view.
My query is that is there any way to declare this JS link file globally for all the Document library views instead of adding reference in each view.
Thanks in advance
You could Update JS Link of list views Using PowerShell. Put all view links into an array and then iterating the links.
Sample script:
$webpartPages = "/Lists/Tickets/AllItems.aspx","/document%20li/Forms/1.aspx","/document%20li/Forms/2.aspx","/document%20li/Forms/AllItems.aspx";
$web = Get-SPWeb http://url;
foreach($webpartPage in $webpartPages ){
$file = $web.GetFile($webPartPage)
$file.CheckOut()
$webPartManager = $web.GetLimitedWebPartManager($webPartPage, [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)
$webpart = $webPartManager.WebParts[0]
$webpart.JSLink = "~sitecollection/SiteAssets/test12.js"#If you have placed your JS inside SiteAssets library of some CHILD site, you should use ~site/SiteAssets/... url.If you have placed your JS inside SiteAssets library of root site of site collection, you should use ~sitecollection/SiteAssets/... url.
$webPartManager.SaveChanges($webpart)
$file.CheckIn("Awesomeness has been delivered")
}
Or you could upload the js file to the SharePoint library, then modify the master page in sharepoint designer, add <script type = "text/javascript" src = "http://url/SiteAssets/test12.js"> </script> to import the js file ,
You need to pay attention to determine whether current view is target view based on the URL in the js file, otherwise it may affect other pages.
Sample script :
if (window.location.href.toLowerCase().indexOf('document%20li') > 0) {
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
OnPostRender: function (ctx) {
var rows = ctx.ListData.Row;
for (var i = 0; i < rows.length; i++) {
var isApproved = rows[i]["testColumn"] == "Arvind Kushwaha1575353984864";
if (isApproved) {
var rowElementId = GenerateIIDForListItem(ctx, rows[i]);
var tr = document.getElementById(rowElementId);
tr.style.backgroundColor = "#ada";
}
}
}
});
}

SharePoint 2016 Custom Action to trigger a workflow

I am trying to create a custom action that can be chosen by right-clicking on a file in a any folder of a particular SharePoint Library. This custom action would copy the file into the same folder with the user's login name appended to the end of the file name.
I currently have an event receiver that will perform the custom action when a file is being updated but that's not when I want it to happen. I was able to add a custom action to the right-click file menu using SharePoint Designer but SharePoint Designer only lets the custom action trigger special SharePoint 2010 compatible workflows or load a web page. I need to make it so the event handler (or possibly a workflow) fires when the user chooses the custom action after right-clicking on the file. I'm not sure what approach or what kind of project or app I need to create in Visual Studio 2017 to get this functionality.
Your custom action should call javascript function or perform GET request to your SharePoint hosted WCF or ASMX WebService.
ASMX
Official MSDN Walktrought: Creating a Custom ASP.NET Web Service
For additional resources with more screenshots check this blog post: Walkthrough: Creating a Custom ASP.NET (ASMX) Web Service in SharePoint 2010
WCF
Official Technet Walkthrough: SharePoint 2013: Create a Custom WCF REST Service Hosted in SharePoint and Deployed in a WSP
Note: with GET request you will need web.AllowUnsafeUpdate = true
With javascript u need AJAX call ie jQuery.ajax()
/edit
To hook up web service and your custom action use SharePoint Desinger, delete or change your existing custom action, change type to Navigate to URL and in the textbox type:
javascript: (function() { console.log('Testing...' + {ItemId}); /* your web service call */ })();
Use {ItemId} alias to pass proper item id to your AJAX call.
On the other hand, on web service side use SPWorkflowManager class to start a workflow on item. Check the code belowe (link):
public void StartWorkflow(SPListItem listItem, SPSite spSite, string wfName) {
SPList parentList = listItem.ParentList;
SPWorkflowAssociationCollection associationCollection = parentList.WorkflowAssociations;
foreach (SPWorkflowAssociation association in associationCollection) {
if (association.Name == wfName){
association.AutoStartChange = true;
association.AutoStartCreate = false;
association.AssociationData = string.Empty;
spSite.WorkflowManager.StartWorkflow(listItem, association, association.AssociationData);
}
}
}
I found a way to do this using JavaScript, without SharePoint Designer. I put the following script in a Content Editor web part on the page where the listview webpart is and now I can right click on a file and get the option to "Get My Copy". If you have a Comments sub folder, the renamed copy will get put there.
<script type="text/javascript">
// adds the menu option to Get My Copy
function Custom_AddDocLibMenuItems(m, ctx)
{
var strDisplayText = "Get My Copy"; //Menu Item Text
var strAction = "copyFile()";
var strImagePath = ""; //Menu item Image path
CAMOpt(m, strDisplayText, strAction, strImagePath); // Add our new menu item
CAMSep(m); // add a separator to the menu
return false; // false means standard menu items should also be rendered
}
// append current user account to filename and copy to subfolder named Comments
function copyFile()
{
// get web and current user from context
var context = new SP.ClientContext.get_current();
var web = context.get_web();
this.currentUser = web.get_currentUser();
context.load(currentUser);
// load the folder
var currentFolder = decodeURIComponent(ctx.rootFolder);
var folderSrc = web.getFolderByServerRelativeUrl(currentFolder);
context.load(folderSrc,'Files');
context.executeQueryAsync(
function() {
// get the first (and hopefully only) file in the folder
var files = folderSrc.get_files();
var e = files.getEnumerator();
e.moveNext()
var file = e.get_current();
// get user account
var curUserAcct = currentUser.get_loginName();
curUserAcct = curUserAcct.substring(curUserAcct.indexOf("\\") + 1);
// get file without extension
var file_with_ext = file.get_name();
var name_without_ext = file_with_ext.substr(0, file_with_ext.lastIndexOf("."));
var destLibUrl = currentFolder + "/Comments/" + name_without_ext + " " + curUserAcct + ".docx";
file.copyTo(destLibUrl, true);
context.executeQueryAsync(
function() { alert("Success! File File successfully copied to: " + destLibUrl); },
function(sender, args) { alert("error: " + args.get_message()) }
);
},
function(sender, args){ alert("Something went wrong with getting current user or getting current folder '" + currentFolder + "'. " + args.get_message()); }
);
}
</script>

Change culture for an ASP.NET Core 2 Razor page

I created an ASP.NET Core 2 projects with razor pages and I would like to give the opportunity to the visitor to select a language. The first problem that I had was to change the web application url so that ti will include the current language code. I solved this problem by adding the following code in ConfigureServices.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Account/Manage");
options.Conventions.AuthorizePage("/Account/Logout");
options.Conventions.AddFolderRouteModelConvention("/", model =>
{
foreach (var selector in model.Selectors)
{
var attributeRouteModel = selector.AttributeRouteModel;
attributeRouteModel.Template = AttributeRouteModel.CombineTemplates("{language=el-GR}", attributeRouteModel.Template);
}
});
});
}
}
Now I could visit a page using the following URL:
http://domain/el-GR/MyPage
The last thing that I would like to do is to change the culture of each request. The best solution that I fount which I do not like is to put the following code in my page:
System.Globalization.CultureInfo.CurrentCulture = new System.Globalization.CultureInfo((string)RouteData.Values["language"]);
System.Globalization.CultureInfo.CurrentUICulture = new System.Globalization.CultureInfo((string)RouteData.Values["language"]);
This is not nice because I will have to add these lies in every razor page that I will create in my project.
Is there another way to set the culture for all the requests of my web application?
Refer to this article: https://joonasw.net/view/aspnet-core-localization-deep-dive
There are a few methods, I use the RequestCultureProviders.
NuGet: Microsoft.AspNetCore.Localization
in my Startup.Configure method.
IList<CultureInfo> sc = new List<CultureInfo>();
sc.Add(new CultureInfo("en-US"));
sc.Add(new CultureInfo("zh-TW"));
var lo = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = sc,
SupportedUICultures = sc
};
var cp = lo.RequestCultureProviders.OfType<CookieRequestCultureProvider>().First();
cp.CookieName = "UserCulture"; // Or whatever name that you like
app.UseRequestLocalization(lo);
Set your cookie "UserCulture" to "c=zh-TW|uic=zh-TW" once.
And it works magically.

Making jslink target specific list

Background
I got a page where I’m showing two list views from two separate lists which both have Custom List as their ListTemplate. They got their separate jslink file cause I don’t want them to look alike.
Problem
The js link file targets both listviews since they use the same Template.
Code
(function () {
var listContext = {};
listContext.Templates = {};
listContext.ListTemplateType = 100;
listContext.Templates.Header = "<div><ul>";
listContext.Templates.Footer = "</ul></div>";
listContext.Templates.Item = LinkTemplate;
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(listContext);
})();
Question
Is there any way to make the js only target a specific list?
Ended up going with Paul Hunts solution that he writes about on myfatblog.co.uk. http://www.myfatblog.co.uk/index.php/2013/09/listview-web-part-issues-with-jslink-and-display-templates-a-solution/
The script ended up looking like this and I pasted it into the jslink function where I define what listContext to override.
// Override the RenderListView once the ClientTemplates.JS has been called
ExecuteOrDelayUntilScriptLoaded(function(){
// Copy and override the existing RenderListView
var oldRenderListView = RenderListView;
RenderListView = function(ctx,webPartID)
{
// Check the title and set the BaseViewId
if (ctx.ListTitle == "List")
ctx.BaseViewID = "list";
//now call the original RenderListView
oldRenderListView(ctx,webPartID);
}
},"ClientTemplates.js");

Extension Library Name Picker gets local Names.nsf even though DB is on server

I have a Name Picker on an XPage with a dataProvider dominoNABNamePicker with the addressBookSel = all-public and people and groups = true. With the database on a Domino server using the Notes Client it displays my local Names.nsf. If I open the DB in a brouse it selects the correct names.nsf from the server.
Can't figure out if this is the result of settings in my client, the server or XPages. Does the same thing on two different PC's.
I would think that the all-public would force it to open only public NABs but it does not appear so.
I asked the same question myself.
The answer, in the control add
addressBookDb="SERVER!!names.nsf"
From here.
Can I have the extlib name picker running in xPINC lookup the directory on the server?
After a fair bit of frustration I have this working for the Notes Client and the Web Client. Perhaps this is obvious to most of you, but it sure wasn't to me.
First on the Name Picker I created a namePickerAggregator. Then I added a dominoNABNamePicker
in the addressBookDb I put the following SSJS:
var server:String = #Name("[CN]",session.getCurrentDatabase().getServer());
var allNABs:Array = session.getAddressBooks().iterator();
var pubNABs = new Array;
var privNABs = new Array;
while (allNABs.hasNext()) {
var db:NotesDatabase = allNABs.next();
if (db.isPublicAddressBook()){
pubNABs.push(db.getFileName())
} else {
privNABs.push(db.getFileName())
}
db.recycle()
}
if (pubNABs[0] == ""){
return privNames[0];
break;
} else {
return server + "!!" + pubNABs[0];
break
}
I then added a second dominoNABNamePicker with the same block of code except the return is
if (pubNABs[1] != "") {
return server + "!!" + pubNABs[1];
break;
} else {
return "";
}
This code works for both the Notes Client and the Web client so I'm now a happy camper, unless I find a gotcha somewhere.
Here is what I eventually did. I set a limit on the maximum number of address books (not great but it works) of 4 you can create as many as you want. So I created a couple of sessionScope variable that I created in the after Page Loads event on the XPage. I used this formula.
var allNABs:Array = session.getAddressBooks().iterator();
var pubNABs = new Array;
var privNABs = new Array;
while (allNABs.hasNext()) {
var db:NotesDatabase = allNABs.next();
if (db.isPublicAddressBook()){
pubNABs.push(db.getFilePath())
} else {
privNABs.push(db.getFilePath())
}
db.recycle()
}
sessionScope.put("ssPublicNABs", pubNABs);
sessionScope.put("ssPrivateNABs", privNABs);
because I use several different Name Pickers on the same page I did not want to repeat having to cycle through the Address books.
Then I created 4 NamePicker controls and added 1, 2 , 3 and 4 dominoNABNamePickers providers to each of the successive controls. Then set the rendered property based on the number of public Address books so they would not blow up on me. The db name property on each of the providers is :
var server:String = #Name("[CN]",session.getCurrentDatabase().getServer());
var pubNABs:Array = sessionScope.get("ssPublicNABs");
return server + "!!" + pubNABs[0];
where pubNABs[n] returns the correct filePath for the NAB. It works well in both Notes Client and the Web.
Then to make it not blow up on a local disconnected replica I created 4 more controls and did the same thing but used the privNABs with appropriate rendered properties so that there is no conflict. Seems like the long way around and I'm sure that there is an easier way, but it works.

Resources