EpiServer - Restrict what blocks can go in what folders - episerver

In EpiServer 11, I want to enforce what blocks get can added to which folders under blocks. Following this article:
https://talk.alfnilsson.se/2015/12/07/creating-a-content-folder-that-only-allows-specific-content-types/
This works, but the folders can only be one level deep. That is I cant add any custom folders that reside inside other custom folders. Is there a way to control this?

This was due to a lack of understanding about configuration. This was my folder:
[ContentType(DisplayName = "Settings Folder", GUID = "1422f7b1-a6aa-485f-a7f3-4049c9343f06", Description = "")]
[AvailableContentTypes(Availability.Specific, Include = new [] { typeof(SiteSettingsBlock), typeof(HeaderSettingsBlock), typeof(FooterSettingsBlock)})]
public class SettingsFolder : ContentFolder
{
}
All I needed to do was add Content Folder as an available type:
[ContentType(DisplayName = "Settings Folder", GUID = "1422f7b1-a6aa-485f-a7f3-4049c9343f06", Description = "")]
[AvailableContentTypes(Availability.Specific, Include = new [] { typeof(SiteSettingsBlock), typeof(HeaderSettingsBlock), typeof(FooterSettingsBlock), typeof(ContentFolder)})]
public class SettingsFolder : ContentFolder
{
}

Related

How do I create different sets of settings at run time?

My WPF application has a bunch of settings the user can adjust, I am using the built-in application settings (Properties.Settings... and a .settings file) to do this and it all works fine. Now, the application is supposed to allow the user to define different presets of settings for different purposes (different samples to be exact, it's a measurement system software) so they don't need to go over every setting again when they switch.
So, I would need to be able to create copies of the application settings at runtime and save them all separately in their own file, then restore them when the application starts up. I can create new settings files at design time but that's outside of the user's control and not what I am looking for. I also can create new Settings instances in code but when I save them it just overwrites the same user.config file the default instance used and the Save() method takes no arguments to save it somewhere else.
Any ideas?
You would have to create a separate class for your preset settings. Then you can save this as a list in Settings.
So lets say you have a preset class that holds your settings values:
public class Preset
{
public int MaxPower { get; set; }
public int AllowedRotations { get; set; }
}
You could in one place get all of these settings like so:
var presets = JsonConvert.DeserializeObject<List<Preset>>(Properties.Settings.Default.Presets);
And you would save the settings like so:
List<Preset> presets = null;
if (Properties.Settings.Default.Presets == null)
presets = new List<Preset>();
else
presets = JsonConvert.DeserializeObject<List<Preset>>(Properties.Settings.Default.Presets);
presets.Add(new Preset() { AllowedRotations = 1000, MaxPower = 200});
Properties.Settings.Default["Presets"] = JsonConvert.SerializeObject(presets);
Properties.Settings.Default.Save();
I guess you can also have some kind of unique ID for these objects so you can differentiate one preset from the other.
NOTE: I am using Json converter here and saving the list of objects as JSON.

Adding multiple maps when using dapper extensions

I am using dapper extensions and have a question about the class mapper. Unfortunately most of my tables need some mapping done to them such as different schemas etc.
So I find I am typically doing a lot of swapping out the DefaultMapper as per below:
public Hierarchies HierarchyGetByName(string aName)
{
Hierarchies result;
using (SqlConnection cn = GetSqlConnection())
{
cn.Open();
Type currModelMapper = DapperExtensions.DapperExtensions.DefaultMapper;
try
{
DapperExtensions.DapperExtensions.DefaultMapper = typeof(HierarchiesMapper);
IFieldPredicate predicate = Predicates.Field<Hierarchies>(f => f.Name, Operator.Eq, aName);
result = cn.GetList<Hierarchies>(predicate).FirstOrDefault();
}
finally
{
DapperExtensions.DapperExtensions.DefaultMapper = currModelMapper;
}
cn.Close();
}
return result;
}
If I access 2 tables then I have to do this twice for instance.
Is there a way to add all the mapper classes at once to say a collection and depending on the table being accessed the correct one is chosen?
You could add a set of classes within your app that apply custom remapping to your entities. For example these 3 empty classes apply the PrefixDapperTableMapper to the Profile and FileNotificationAdhocRecipient classes while the AnotherDifferentTypeOfDapperClassMapper is applied to NotificationProfile.
public class ProfileMapper : PrefixDapperTableMapper<Domain.Entities.Profile>
{
}
public class FileNotificationAdhocRecipientMapper : PrefixDapperTableMapper<Domain.Entities.FileNotificationAdhocRecipient>
{
}
public class NotificationProfileMapper : AnotherDifferentTypeOfDapperClassMapper<Domain.Entities.NotificationProfile>
{
}
and your actual mapping code exists in separate mappers (I've not shown AnotherDifferentTypeOfDapperClassMapper but that would be similar to below)
public class PrefixDapperTableMapper<T> : ClassMapper<T> where T : class
{
public PrefixDapperTableMapper()
{
AutoMap();
}
//name or schema manipulations in some overrides here.
}
As long as they're in the same assembly, DapperExtensions will find and use them or you can set the mapping assembly with code similar to:
DapperExtensions.DapperExtensions.SetMappingAssemblies({ typeof(ProfileMapper ).Assembly })

How to setup client side metadata for SPA using breeze

Hello all I am working on a small report library SPA using angular and breeze that allows a user to manage reports they create. I know you can use breeze along with the EF context to build the model by referencing the API.. But how this is being implemented I want to leave EF out of it. The API (WebAPI 2) is basically calling other repositories to do the work almost like an interface. The result coming back is just a json object. I have looked over the Edmunds sample from the Breeze website and I can see how I can build a client model as well as handle the mapping on the return. The current issue I have is I am not certain that I have the mapping in the jsonResultsAdapter correct. Or that I may be missing something on how the mapping is supposed to work.
I'm testing this currently with some mock data I have stubbed into the API until I can get this to work. Once this is bound and mapping I can go against the actual data. Here is what I have so far:
The mock data is a report object that contains an internal collection called labels (tags basically) a report can have multiple tag and from the label it can have multiple reports.
//report dto
public class ReportDto
{
public Int64 ReportId { get; set; }
public string ReportName { get; set; }
public string ReportDescription { get; set; }
public string ReportDateCreated { get; set; }
public string ReportOwner { get; set; }
public IEnumerable<ReportLabelDto> ReportLabels { get; set; }
}
public class ReportLabelDto
{
public Int64 LabelId { get; set; }
public string LabelName { get; set; }
public bool IsPrivate { get; set; }
public bool IsFavorite { get; set; }
public IEnumerable<ReportDto> Reports { get; set; }//placeholder?
}
Here is the code currently being used within the webapi controller which at this time is just for testing:
[Route ("reportlibrary/myreports/{userid}")]
public IEnumerable<ReportDto> GetAllReports(string userId)
{
List<ReportDto> result = new List<ReportDto>();
List<ReportLabelDto> label = new List<ReportLabelDto>();
//create 5 reports
for (int i = 0; i < 5; i++)
{
ReportDto r = new ReportDto();
ReportLabelDto l = new ReportLabelDto();
r.ReportId = i;
r.ReportOwner = "John Smith";
r.ReportDateCreated = DateTime.Now.ToString();
r.ReportDescription = "Report Description # " + i.ToString();
r.ReportName = "Report Description # " + i.ToString();
//generate labels
l.LabelId = i;
l.LabelName = "Special Label" + i.ToString();
l.IsPrivate = true;
l.IsFavorite = false;
label.Add(l);
r.ReportLabels = label;
result.Add(r);
}
return result;
}
The object that is currently coming back looks like this:
[{"ReportId":0,"ReportName":"Report Description # 0","ReportDescription":"Report Description # 0","ReportDateCreated":"12/22/2014 6:32:05 PM","ReportOwner":"John Smith","ReportLabels":[{"LabelId":0,"LabelName":"Special Label0","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":1,"LabelName":"Special Label1","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":2,"LabelName":"Special Label2","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":3,"LabelName":"Special Label3","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":4,"LabelName":"Special Label4","IsPrivate":true,"IsFavorite":false,"Reports":null}]},{"ReportId":1,"ReportName":"Report Description # 1","ReportDescription":"Report Description # 1","ReportDateCreated":"12/22/2014 6:32:05 PM","ReportOwner":"John Smith","ReportLabels":[{"LabelId":0,"LabelName":"Special Label0","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":1,"LabelName":"Special Label1","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":2,"LabelName":"Special Label2","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":3,"LabelName":"Special Label3","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":4,"LabelName":"Special Label4","IsPrivate":true,"IsFavorite":false,"Reports":null}]},...]
I have the services and controllers all talking and I can hit the api and get an object returned so I am going to omit that code for right now.
For the js model I defined the report object as follows:
app.factory('model', function () {
var DT = breeze.DataType;
return {
initialize: initialize
}
function initialize(metadataStore) {
metadataStore.addEntityType({
shortName: "Report",
namespace: "Inform",
dataProperties: {
reportid: { dataType: DT.Int64, isPartOfKey: true },
reportname: { dataType: DT.String },
reportdescription: { dataType: DT.String },
reportdatecreated: { dataType: DT.String },
reportowner: { dataType: DT.String },
mappedlabels: { dataType: DT.Undefined },
ishared: { dataType: DT.Bool },
isfavorite: { dataType: DT.Bool }
},
navigationProperties: {
labels: {
entityTypeName: "Label:#Inform", isScalar: false,
associationName: "Report_Labels"
}
}
});
metadataStore.addEntityType({
shortName: "Label",
namespace: "Inform",
dataProperties: {
labelid: { dataType: DT.Int64, isPartOfKey: true },
reportid: { dataType: DT.Int64 },
labelname: { dataType: DT.String },
ispublic: { dataType: DT.Bool },
mappedreports: { dataType: DT.Undefined }
},
navigationProperties: {
labels: {
entityTypeName: "Report:#Inform", isScalar: false,
associationName: "Report_Labels", foreignKeyNames: ["reportid"]
}
}
});
}
})
This is where I think the issue is I don't understand this adapter enough to ensure that I am receiving what I think I am as well as if it is handling the mapping correctly:
/* jsonResultsAdapter: parses report data into entities */
app.value('jsonResultsAdapter',
new breeze.JsonResultsAdapter({
name: "inform",
extractResults: function (data) {
var results = data.results;
if (!results) throw new Error("Unable to resolve 'results' property");
// Parse only the make and model types
return results && (results.reportHolder || results.labelHolder);
},
visitNode: function (node, parseContext, nodeContext) {
//Report parser
if (node.reportid && node.labels) {
node.mappedlabels = node.labels;
node.labels = [];
return { entityType: "Report" }
}
// Label parser
else if (node.labelid && node.reports) {
node.mappedreports = node.reports;
node.mappedreports = [];
return { entityType: "Label" };
}
}
}));
When I step through the code in chrome I can see that an object is returned. with 5 reports and each report has 5 labels ( I know the labels are showing null reports currently). When I set breakpoints within the jsonResultsAdapter I can see the result with 5 objects, but what gets passed back to the service is as a result is null. Can anyone help me verify if the model and mapping is correct or if you see anything out of place in the jsonResultsAdapter. I'd also appreciate any suggestions on things I may want to do different. I feel very black-box right now as I don't see/understand a good way to troubleshoot this mapping piece.
-cheers
Here I'll pick up on some of PW Kad's observations and add a few of my own.
Let's first understand the different roles of metadata and the JsonResultsAdapter.
Metadata is where you define the schema and validation rules for the client-side entity model. It describes the entity objects that Breeze keeps in cache and makes available to your program.
But the metadata have nothing to say about the JSON payload arriving from the server. That's a completely separate and lower level concern. That's the concern of the JsonResultsAdapter.
The JsonResultsAdapter sits in the pipeline between the JSON data arriving from the server as a result of an AJAX call ... and the entities in cache. The JSON data don't have to be shaped like the entities. They don't have to conform to the metadata you wrote. The metadata describe the entities as you would like to consume them. The JSON are the sad reality that the service gives you. The JsonResultsAdapter is there to bridge the gap.
Whether the entity schema conforms to the shape of the JSON payload is anyone's guess. Often the JSON data needs a little tweaking. It's the JsonResultsAdapter's job to manipulate the JSON "nodes" into something that Breeze can map into your entities. The job is easier if the JSON payload closely approximates the entity shape described by your metadata. Let's hope your JSON aligns well with your entities.
Metadata and materialization
Now Breeze does use the metadata when mapping the JSON into the entities. The MetadataStore has a NamingConvention that prescribes how to translate between the client entity property names and the service property names. The "materialization" process expects the JSON emerging from the JsonResultsAdapter to have the expected service property names. That's why I was adamant that the node property names (if you need them) be spelled in PascalCase ... assuming that you are using the standard Breeze camelCase convention and that your service does, in fact, spell property names in PascalCase.
Most C# and Java servers do. Rails and Node servers generally don't; they use camelCase on the server too ... which means you'd want NamingConvention.none if you're consuming feeds from these kinds of servers.
Ideally the JsonResultsAdapter has to do very little. The JSON property names typically map easily and obviously to the entity property names and you can handle whatever translation is needed with a NamingConvention. Such appears to be the case for you (see below).
For sure you're not accomplishing a thing with the code you showed us:
node.ReportId = node.ReportId;
node.ReportName = node.ReportName;
node.ReportDescription = node.ReportDescription;
That is the most elaborate "no op" code I've seen in a very long time. I wonder what you had in mind.
JsonResultsAdapter is often needed when identifying the EntityType corresponding to a JSON node. If you're not sourcing the data from .NET using the Json.Net serializer, your server may not be sending the type name down with the JSON data. Your JSON nodes are missing the $type property that Breeze is looking for by default.
If that's your situation (and it seems it is), your JsonResultsAdapter has to supply the type name.
Apparently, you can do that for your data by examining each node's key property. It seems that key property name contains within itself the distinguishing part of the type name.
Perhaps your JsonResultsAdapter.visitNode method could look like this:
visitNode: function (node, parseContext, nodeContext) {
//Report parser
if (node.ReportId) {
return { entityType: "Report:#Inform" }
}
// Label entity
else if (node.ReportLabels) {
return { entityType: "Label:#Inform" };
}
}
Notice that I included the namespace (:#Inform) in the entityType name property. The namespace is part of each EntityType's full name ... and you must supply it.
Notice that I did NOT do any property name mapping. I didn't see any reason for it. The node property names look just like the entity metadata names ... except for the PascalCasing ... and we take care of that with the NamingConvention.camelCase.
Bad Metadata?
Well actually the node property names do NOT look like the entity property names in your metadata, not even after accounting for the Pascal-to-camel-case conversion. I think this is what PW Kad was pointing out.
The problem is that the entity property names in your metadata are all lower case. Not camelCase; lower case. For example:
reportid: { dataType: DT.Int64, isPartOfKey: true },
reportname: { dataType: DT.String },
reportdescription: ...
Shouldn't they be:
reportId: { dataType: DT.Int64, isPartOfKey: true },
reportName: { dataType: DT.String },
reportDescription: ...
That would correspond nicely to your JSON property names
ReportId
ReportName
ReportDescription
Why would you want all lowercase property names on the client.
You could go all lowercase and write a really wacky custom NamingConvention to navigate between client entity names and service names. That's a lot of work to no purpose in my book.
Why is there no $type in your JSON?
I just scrolled to the top of this question and realized that your server is written in C# and it looks like you're using the Web API.
Why did you not annotate your Web API controller with the [BreezeController] attribute? Doing so would have configured your controller to serialize the data in the manner a Breeze client understands by default. You might not need a JsonResultsAdapter at all.
Don't change the type name!
Looking again I see yet another problem looming ahead. Your server-side class names have the suffix "Dto" but you don't want that suffix on your client type names. You are also changing the type name completely in one case: "ReportLabelDto" to "Label".
Breeze has a naming convention for morphing property names. It doesn't have a naming convention for "entity type" names.
It will be a royal pain if you insist on having different type names on the client and the server. I'm not sure it can be done.
Yes you can morph the entity name in the JsonResultsAdapter. That covers communications on the way in. But you also have to worry about the communications on the way out. The server is not going to be happy when you ask it to save an entity of class "Label" ... which it knows nothing about.
As I write I can't think of an easy way around this. At the moment, Breeze requires that the server-side type name be the same as the client EntityType name. If the type name on the server is "ReportLabelDto", you'll have to name the corresponding EntityType "ReportLabelDto". There is no easy way around that.
Fortunately, unlike property names which show up everywhere, you don't often refer to the EntityType name on the client so calling it "ReportLabelDto" shouldn't be a big deal.
It looks like all of your properties you are defining are not properly camelCased nor PascalCased. Breeze.js will look for properties that match - but unless I am missing something you have defined it will not toLowerCase them.
You need to set your model properties up like this -
ReportName: { dataType: DT.String },
and then in your results adapter you need to check for the property names properly like this -
if (node.ReportId && node.Labels) {
Thanks to PW Kad for pointing me in the right direction. I had forgotten the case sensitivity portion. In addition I went back and reassessed what I was trying to do in the jsonResultsAdapter.js file. I finally realized this file is working similar to automapper and I was under the impression that breeze would internally resolve the mappings. (maybe it does but with the EF context) but when creating client side meta I had to explicitly set the mappings. The updated code now shows:
app.value('jsonResultsAdapter',
new breeze.JsonResultsAdapter({
name: "inform",
extractResults: function (data) {
var results = data.results;
if (!results) throw new Error("Unable to resolve 'results' property");
// Parse only the make and model types
return results;
},
visitNode: function (node, parseContext, nodeContext) {
//Report parser
if (node) {
node.ReportId = node.ReportId;
node.ReportName = node.ReportName;
node.ReportDescription = node.ReportDescription;
node.ReportDateCreated = node.ReportDateCreated;
node.ReportOwner = node.ReportOwner;
node.ReportLabels = node.ReportLabels;
node.ReportLabels = [];
node.IsShared = node.IsShared;
node.IsFavorite = node.IsFavorite;
return { entityType: "Report" }
}
// Label parser
else if (node.ReportLabels) {
node.LabelId = node.LabelId;
node.LabelName = nodel.LabelName;
node.IsPrivate = node.IsPrivate;
node.IsFavorite = node.IsFavorite;
node.Reports = node.Reports;
node.Reports = [];
return { entityType: "Label" };
}
}
}));
I know I still have some tweaking to do on the mappings more than likely or how it is parsed from the API but making this change properly mapped the mock data and allowed it to bind/display within the UI.
Hope this helps

Error when using a custom view location convention with Nancy's ConfigurableBootstrapper

I am using a subclass of ConfigurableBootstrapper to test my Nancy modules. This is a workaround to get the ConfigurableBootstrapper to pick up the custom view location convention I use in the 'real' bootstrapper:
public class ConfigurableBootstrapperWithCustomConvention : ConfigurableBootstrapper
{
public ConfigurableBootstrapperWithCustomConvention(Action<ConfigurableBootstrapperConfigurator> configuration)
: base(configuration)
{
}
protected override void ConfigureConventions(NancyConventions nancyConventions)
{
nancyConventions.ViewLocationConventions.Add((viewName, model, context) =>
string.Concat(context.ModuleName, "/Views/", viewName));
base.ConfigureConventions(nancyConventions);
}
}
In my tests I first set the root path to that of my nancy project using the FakeRootPathProvider. This contains the necessary folder structure ("/Home/Views/Index.cshtml") for the view used in HomeModule to be located using this custom convention. The (simplified) test code:
[Test]
public void when_the_default_page_is_loaded_it_should_show_links_to_submit_form()
{
FakeRootPathProvider.RootPath = "../../../MyApp.Web";
var bootstrapper = new ConfigurableBootstrapperWithCustomConvention(with =>
{
with.RootPathProvider(new FakeRootPathProvider());
with.ViewEngine<RazorViewEngine>();
with.Module<HomeModule>();
});
var browser = new Browser(bootstrapper);
var response = browser.Get("/");
Assert.That(response.Body.AsString(), Is.StringContaining("<a href=\"SubmitSelf\">"));
}
When I run this test I get the following exception (at the call to response.Body):
System.ArgumentNullException : Value cannot be null.
Parameter name: httpContext
at System.Web.HttpContextWrapper..ctor(HttpContext httpContext)
at System.Web.Optimization.Styles.RenderFormat(String tagFormat, String[] paths)
at RazorOutput.RazorView.Execute()
at Nancy.ViewEngines.Razor.NancyRazorViewBase`1.ExecuteView(String body, IDictionary`2 sectionContents)
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid3<T0,T1,T2>(CallSite site, T0 arg0, T1 arg1, T2 arg2)
at Nancy.ViewEngines.Razor.RazorViewEngine.<>c__DisplayClass27.<RenderView>b__26(Stream stream)
at Nancy.Testing.BrowserResponse.get_Body()
at MyApp.Tests.Integration.Web.Home.HomeModuleTests.when_the_default_page_is_loaded_it_should_show_links_to_submit_form() in HomeModuleTests.cs: line 44
If I set the Fake root path to be "../../../MyApp.Web/Home/Views" instead, the test runs OK. (It appears that the custom view location convention is being ignored when testing using Nancy's "Browser" class).
Is there some issue with setting a NancyConvention in this way?
If so is there a different way to set a view location convention with ConfigurableBootstrapper?

Can't access ViewDataDictionary object properly on StringTemplate

I'm playing with StringTemplate version 3.1.4.6002 as a view engine for an MVC project. I can't seem to access an object being passed to the template and have it render on a webpage. So basically, I have the the following in the controller (in C#):
var layer1 = new { count = "1", label = "Description" };
var layer2 = new { count = "2", label = "Transcription" };
ViewData["layers"] = new object[] {layer1,layer2};
On the view file test.st, I have:
$layers:{
$it.count$
$it.label$
}$
The template does not display anything on the webpage, but if I do this on the test.st:
$layers$
it displays the object as:
{ count=1, label=Description } { count=2, label=Transcription }
which looks like to me a string. So how do I access just the count and the label independently? I've googled a lot already and can't seem to find anything that would fix my problem.
By the way, I'm using Visual Studio V10 and .NET Framework Version4. On the project I have References to Antlr3.Runtime, Antlr3.Runtime.Debug and Antlr3.StringTemplate dlls. Also, I have no problems accessing simple string objects. I've used the tutorial project in here. I can supply more details if required.
Are the count and label fields public or, if they are getters, are they public?

Resources