Am trying to get Nancy to retain property names as they are. For example FirstName to remain so instead of firstName in Response.AsJson.
I have seen where it is mentioned to set JsonSettings.RetainCasing = true.
I couldn't find it in Nancy.Json or an example where to set this configuration. Any hint where to find this setting and where to place it?
By default Nancy uses SimpleJson.
To configure case retaining just override the configure method like this :
public class MyBootstrapper : DefaultNancyBootstrapper
{
public override void Configure(INancyEnvironment environment)
{
environment.Json(retainCasing: true);
base.Configure(environment);
}
}
I could not make sense of the other answer, but found this solution. Add the following line to use in your startup code. It will prevent Nancy from converting cases on objects.
Nancy.Json.JsonSettings.RetainCasing = true;
I put it right before my host.Start() call:
NancyHost host = new NancyHost(uri, new DefaultNancyBootstrapper(), hostConfigs);
host.Start();
Related
Core2 has a hook for validating options read from appsettings.json:
services.PostConfigure<MyConfig>(options => {
// do some validation
// maybe throw exception if appsettings.json has invalid data
});
This validation code triggers on first use of MyConfig, and every time after that. So I get multiple runtime errors.
However it is more sensible to run validation during startup - if config validation fails I want the app to fail immediately. The docs imply that is how it works, but that is not what happens.
So am I doing it right? If so and this is by design, then how can I change what I'm doing so it works the way I want?
(Also, what is the difference between PostConfigure and PostConfigureAll? There is no difference in this case, so when should I use either one?)
This has been discussed in this dotnet/runtime issue since 2018.
In .NET 6, a ValidateOnStart extension method has been added to Microsoft.Extensions.Hosting
You can use it this way:
services.AddOptions<MyOptions>()
.ValidateDataAnnotations()
.ValidateOnStart(); // Support eager validation
However, ValidateDataAnnotations still does not validate nested properties and this won't be fixed soon (Microsoft issue).
This NuGet package provides a ConfigureAndValidate<TOptions> extension method which validates options at startup.
It is based on Microsoft.Extensions.Options.DataAnnotations. But unlike Microsoft's package, it can even validate nested properties.
It is compatible with .NET Standard 2.0, .NET Core 3.1, .NET 5, .NET 6 and .NET 7.
Documentation & source code (GitHub)
TL;DR
Create your options class(es)
Decorate your options with data annotations
Call ConfigureAndValidate<T>(Action<T> configureOptions) on your IServiceCollection
ConfigureAndValidate will configure your options (calling the base Configure method), but will also check that the built configuration respects the data annotations, otherwise an OptionsValidationException (with details) is thrown as soon as the application is started. No misconfiguration surprise at runtime!
Use
ServiceCollection extension
services.ConfigureAndValidate<TOptions>(configureOptions)
Is syntactic sugar for
services
.AddOptions<TOptions>()
.Configure(configureOptions) // Microsoft
.ValidateDataAnnotationsRecursively() // based on Microsoft's ValidateDataAnnotations, but supports nested properties
.ValidateOnStart() // or ValidateEagerly() in previous versions
.Services
OptionsBuilder extensions
ValidateDataAnnotationsRecursively
This method register this options instance for validation of its DataAnnotations at the first dependency injection. Nested objects are supported.
ValidateOnStart (or ValidateEagerly in previous versions)
This method validates this options instance at application startup rather than at the first dependency injection.
Custom validation
You can combine with your own option validations:
services
.AddOptions<TOptions>()
.Configure(configureOptions)
//...
.Validate(options => { /* custom */ }, message)
.Validate<TDependency1, TDependency2>((options, dependency1, dependency2) =>
{
// custom validation
},
"Custom error message")
//...
.ValidateDataAnnotationsRecursively()
.ValidateOnStart()
Microsoft options validation documentation
There is no real way to run a configuration validation during startup. As you already noticed, post configure actions run, just like normal configure actions, lazily when the options object is being requested. This completely by design, and allows for many important features, for example reloading configuration during run-time or also options cache invalidation.
What the post configuration action is usually being used for is not a validation in terms of “if there’s something wrong, then throw an exception”, but rather “if there’s something wrong, fall back to sane defaults and make it work”.
For example, there’s a post configuration step in the authentication stack, that makes sure that there’s always a SignInScheme set for remote authentication handlers:
options.SignInScheme = options.SignInScheme ?? _authOptions.DefaultSignInScheme ?? _authOptions.DefaultScheme;
As you can see, this will not fail but rather just provides multiple fallbacks.
In this sense, it’s also important to remember that options and configuration are actually two separate things. It’s just that the configuration is a commonly used source for configuring options. So one might argue that it is not actually the job of the options to validate that the configuration is correct.
As such it might make more sense to actually check the configuration in the Startup, before configuring the options. Something like this:
var myOptionsConfiguration = Configuration.GetSection("MyOptions");
if (string.IsNullOrEmpty(myOptionsConfiguration["Url"]))
throw new Exception("MyOptions:Url is a required configuration");
services.Configure<MyOptions>(myOptionsConfiguration);
Of course this easily becomes very excessive, and will likely force you to bind/parse many properties manually. It will also ignore the configuration chaining that the options pattern supports (i.e. configuring a single options object with multiple sources/actions).
So what you could do here is keep your post configuration action for validation, and simply trigger the validation during startup by actually requesting the options object. For example, you could simply add IOptions<MyOptions> as a dependency to the Startup.Configure method:
public void Configure(IApplicationBuilder app, IOptions<MyOptions> myOptions)
{
// all configuration and post configuration actions automatically run
// …
}
If you have multiple of these options, you could even move this into a separate type:
public class OptionsValidator
{
public OptionsValidator(IOptions<MyOptions> myOptions, IOptions<OtherOptions> otherOptions)
{ }
}
At that time, you could also move the logic from the post configuration action into that OptionsValidator. So you could trigger the validation explicitly as part of the application startup:
public void Configure(IApplicationBuilder app, OptionsValidator optionsValidator)
{
optionsValidator.Validate();
// …
}
As you can see, there’s no single answer for this. You should think about your requirements and see what makes the most sense for your case. And of course, this whole validation only makes sense for certain configurations. In particular, you will have difficulties when working configurations that will change during run-time (you could make this work with a custom options monitor, but it’s probably not worth the hassle). But as most own applications usually just use cached IOptions<T>, you likely don’t need that.
As for PostConfigure and PostConfigureAll, they both register an IPostConfigure<TOptions>. The difference is simply that the former will only match a single named option (by default the unnamed option—if you don’t care about option names), while PostConfigureAll will run for all names.
Named options are for example used for the authentication stack, where each authentication method is identified by its scheme name. So you could for example add multiple OAuth handlers and use PostConfigure("oauth-a", …) to configure one and PostConfigure("oauth-b", …) to configure the other, or use PostConfigureAll(…) to configure them both.
On an ASP.NET Core 2.2 project I got this working doing eager validation by following these steps...
Given an Options class like this one:
public class CredCycleOptions
{
[Range(1753, int.MaxValue, ErrorMessage = "Please enter a valid integer Number.")]
public int VerifiedMinYear { get; set; }
[Range(1753, int.MaxValue, ErrorMessage = "Please enter a valid integer Number.")]
public int SignedMinYear { get; set; }
[Range(1753, int.MaxValue, ErrorMessage = "Please enter a valid integer Number.")]
public int SentMinYear { get; set; }
[Range(1753, int.MaxValue, ErrorMessage = "Please enter a valid integer Number.")]
public int ConfirmedMinYear { get; set; }
}
In Startup.cs add these lines to ConfigureServices method:
services.AddOptions();
// This will validate Eagerly...
services.ConfigureAndValidate<CredCycleOptions>("CredCycle", Configuration);
ConfigureAndValidate is an extension method from here.
public static class OptionsExtensions
{
private static void ValidateByDataAnnotation(object instance, string sectionName)
{
var validationResults = new List<ValidationResult>();
var context = new ValidationContext(instance);
var valid = Validator.TryValidateObject(instance, context, validationResults);
if (valid)
return;
var msg = string.Join("\n", validationResults.Select(r => r.ErrorMessage));
throw new Exception($"Invalid configuration for section '{sectionName}':\n{msg}");
}
public static OptionsBuilder<TOptions> ValidateByDataAnnotation<TOptions>(
this OptionsBuilder<TOptions> builder,
string sectionName)
where TOptions : class
{
return builder.PostConfigure(x => ValidateByDataAnnotation(x, sectionName));
}
public static IServiceCollection ConfigureAndValidate<TOptions>(
this IServiceCollection services,
string sectionName,
IConfiguration configuration)
where TOptions : class
{
var section = configuration.GetSection(sectionName);
services
.AddOptions<TOptions>()
.Bind(section)
.ValidateByDataAnnotation(sectionName)
.ValidateEagerly();
return services;
}
public static OptionsBuilder<TOptions> ValidateEagerly<TOptions>(this OptionsBuilder<TOptions> optionsBuilder) where TOptions : class
{
optionsBuilder.Services.AddTransient<IStartupFilter, StartupOptionsValidation<TOptions>>();
return optionsBuilder;
}
}
I plumbed ValidateEargerly extension method right inside ConfigureAndValidate. It makes use of this other class from here:
public class StartupOptionsValidation<T> : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return builder =>
{
var options = builder.ApplicationServices.GetService(typeof(IOptions<>).MakeGenericType(typeof(T)));
if (options != null)
{
// Retrieve the value to trigger validation
var optionsValue = ((IOptions<object>)options).Value;
}
next(builder);
};
}
}
This allows us to add data annotations to the CredCycleOptions and get nice error feedback right at the moment the app starts making it an ideal solution.
If an option is missing or have a wrong value, we don't want users to catch these errors at runtime. That would be a bad experience.
There are easy way to validating with using IStartupFilter and IValidateOptions.
You can just put below code your ASP.NET Core project.
public static class OptionsBuilderExtensions
{
public static OptionsBuilder<TOptions> ValidateOnStartupTime<TOptions>(this OptionsBuilder<TOptions> builder)
where TOptions : class
{
builder.Services.AddTransient<IStartupFilter, OptionsValidateFilter<TOptions>>();
return builder;
}
public class OptionsValidateFilter<TOptions> : IStartupFilter where TOptions : class
{
private readonly IOptions<TOptions> _options;
public OptionsValidateFilter(IOptions<TOptions> options)
{
_options = options;
}
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
_ = _options.Value; // Trigger for validating options.
return next;
}
}
}
And just chain the ValidateOnStartup method on OptionsBuilder<TOptions>.
services.AddOptions<SampleOption>()
.Bind(Configuration)
.ValidateDataAnnotations()
.ValidateOnStartupTime();
If you want to create custom validator for options class, checkout this article.
This has been implemented in .NET 6. Now you can just write the following:
services.AddOptions<SampleOption>()
.Bind(Configuration)
.ValidateDataAnnotations()
.ValidateOnStart(); // works in .NET 6
No need for external NuGet Packages or extra code.
See OptionsBuilderExtensions.ValidateOnStart<TOptions>
Below is a generic ConfigureAndValidate method to validate immediately and "fail fast".
To summarize the steps:
Call serviceCollection.Configure for your options
Do serviceCollection.BuildServiceProvider().CreateScope()
Get the options instance with scope.ServiceProvider.GetRequiredService<IOptions<T>> (remember to take the .Value)
Validate it using Validator.TryValidateObject
public static class ConfigExtensions
{
public static void ConfigureAndValidate<T>(this IServiceCollection serviceCollection, Action<T> configureOptions) where T : class, new()
{
// Inspired by https://blog.bredvid.no/validating-configuration-in-asp-net-core-e9825bd15f10
serviceCollection.Configure(configureOptions);
using (var scope = serviceCollection.BuildServiceProvider().CreateScope())
{
var options = scope.ServiceProvider.GetRequiredService<IOptions<T>>();
var optionsValue = options.Value;
var configErrors = ValidationErrors(optionsValue).ToArray();
if (!configErrors.Any())
{
return;
}
var aggregatedErrors = string.Join(",", configErrors);
var count = configErrors.Length;
var configType = typeof(T).FullName;
throw new ApplicationException($"{configType} configuration has {count} error(s): {aggregatedErrors}");
}
}
private static IEnumerable<string> ValidationErrors(object obj)
{
var context = new ValidationContext(obj, serviceProvider: null, items: null);
var results = new List<ValidationResult>();
Validator.TryValidateObject(obj, context, results, true);
foreach (var validationResult in results)
{
yield return validationResult.ErrorMessage;
}
}
}
Recently i was using IOptions interface to read configuration in Asp.net core project and i found that my code doesn't show exception page until i call "validate" method explicitly with required property as you can see in below code.
appsettings.json
"DashboardSettings": {
"Header": {
"Title": "Seguro De Coche"//,
//"SearchBoxEnabled": true
}
},
DashboardSetting.cs
public class DashboardSettings
{
public HeaderSettings Header { get; set; }
}
public class HeaderSettings
{
public string Title { get; set; }
[Required]
public bool SearchEnabled { get; set; }
}
Startup.cs
services.AddOptions<DashboardSettings>().
Bind(configuration.GetSection("DashboardSettings")).
ValidateDataAnnotations();
In above case, "SearchEnabled" property required validation doesn't fire. and when i call validate methods explicitly with property, it fires. (see code below with validation method)
services.AddOptions<DashboardSettings>().
Bind(configuration.GetSection("DashboardSettings")).
ValidateDataAnnotations().
Validate(v =>
{
return v.Header.SearchEnabled;
});
so my question is, if my strongly type would have multiple configuration properties, then would i use all properties of class for validating them? If it is, it doesn't seem a good idea to me. Any suggestion on this please?
I don't know if this was an option back in .net core 3 but in .net core 6 you have to call
.ValidateOnStart()
on the optionsbuilder returned from AddOptions<>()
Otherwise, validation is called when the object is retrieved for the first time.
ValidateDataAnnotations method are not called until the IOptions is actually resolved, meaning you request the Value property of it. It is not enough even for DI to resolve the IOption for you. This validation is simply 'very lazy'
If you want this validation to happen earlier and you use ASP.NET Core 6, you can now add ValidateOnStart method.
If you need to use one of the previous version or want more control over the error handling, you can simply ensure that your IOptions are resolved earlier in the process, e.g. during Service Configuration.
To do that create a class, that depends on those options, and make it request their values. Add its instance in Startup.cs:
public class ConfigurationValidator ( IOptions<MyConfig> MyConfig )
{
//no validation trigger yet
MyConfig = myConfig;
}
public IOptions<MyConfig> MyConfig { get; }
internal void Validate()
{
//validation happens in this line:
_ = MyConfig?.Value;
}
And now in Startup.cs
public void Configure(IApplicationBuilder app, ConfigurationValidator configValidator)
{
try
{
configValidator.Validate();
}
catch (Exception ex)
{
//log your errors
}
//process continues here, or not...
}
This approach leaves full control to you so you can decide how to handle your error. But in fact you don't need the Validate method, as the whole logic can happen already in constructor and then Configure method will immediately stop the process and rethrow exception
Ahh, and of course you need to register your ConfigurationValidator as a service. Singleton or whatever.
On most of my APIs, I simply do authorization like this:
[Authorize(Policy = "Foo")]
public MyApi()
I get this policy from a NuGet though and can't modify it.
For some of my APIs, I don't always want to have this policy. This needs to get figured out at runtime based on some config. I'd like some way to run this in-line, and ensure all the handlers that are setup run.
After a lot of searching i've found that I create an IAuthorizationService, and use that to call AuthorizeAsync. This seems like it's what I want, but the issue i'm running into now is that all the handlers rely on an AuthorizationFilterContext as the resource on the context. This seems to happen automatically when the Authorization is done through the attribute, but not through the call to AuthorizeAsync. It needs to be passed in manually in this case. My code right now looks like this:
public MyApi()
{
var allowed = await _authorizationService.AuthorizeAsync(User, null, "Foo").ConfigureAwait(false);
}
This seems to go through all my handlers correctly, but they don't work due to missing the AuthorizationFilterContext.
1) Is this the correct approach to begin with, or is there some other way to do this in-line? I'm guessing there's probably some way to create my own policy that wraps this one and I can check the config there, but if there's a simple in-line approach i'd prefer that.
2) If this way is valid, is there a good way to get the AuthorizationFilterContext? I've tried creating it manually, but i'm afraid this isn't actually correct without passing in more data from the context, but I can't find any good examples/doc:
new AuthorizationFilterContext(new ActionContext(HttpContext, HttpContext.GetRouteData(), new ActionDescriptor()), new IFilterMetadata[] { });
There will be no AuthorizationFilterContext when you are outside of the authorization pipeline. You should therefore not handle the authentication inline with IAuthorizationService.
This seems to go through all my handlers correctly, but they don't work due to missing the AuthorizationFilterContext.
Sounds like you have the control over the authentication handlers. Have you tried short-circuit authentication inside the handler if it is not required?
The handler can get services via the DI so you can put your required runtime config via IOptions or IHttpContextAccessor and what so ever.
Can't you create you own Authorize attribute which would inherit current one and resolve policy internally? Or even better try using IAuthorizationPolicyProvider
class MyPolicyProvider : IAuthorizationPolicyProvider
{
private DefaultAuthorizationPolicyProvider BackupPolicyProvider { get; }
public MyPolicyProvider()
{
BackupPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (policyName.Equals("Foo"))
{
bool myConditionToAvoidPolicy = true;
if (myConditionToAvoidPolicy)
{
return Task.FromResult<AuthorizationPolicy>(null);
}
}
return BackupPolicyProvider.GetPolicyAsync(policyName);
}
}
This is not tested, but you can find more about it here.
Your check condition looks like happening at later point which I dont think it is a good idea. Your api method is being vulnerable and still open as your check is done at later point. But by using attribute you can capture it at earlier level and still can apply the custom logic. At the end of the day, all it decides is either "yes, have an access", or "no, no access for you!!" Below is not tested but should get you going:
public class CustomAuthorize : AuthorizeAttribute
{
private readonly PermissionAction[] permissionActions;
public CustomAuthorize(PermissionItem item, params PermissionAction[] permissionActions)
{
this.permissionActions = permissionActions;
}
public override void OnAuthorization(HttpActionContext actionContext)
{
var currentIdentity = System.Threading.Thread.CurrentPrincipal.Identity;
if (!currentIdentity.IsAuthenticated) {
// no access
}
bool myCondition = "money" == "happiness";
if(myCondition){
// do your magic here...
}
else{
// another magic...
}
}
}
I have my own inherited App.Controller from Mvc.Controller which then all of my controllers inherit from. I wrote a provider utilizing an interface and implemented it as MyService and the constructor takes the Server property of Mvc.Controller which is of HttpServerUtilityBase.
However, I instantiate MyService in App.Controller's constructor. The problem is that the Server property of the Controller is null when constructing MyService. I have used public Controller () : base() { } to get the base to be constructed. However, Server remains null.
I would like to avoid Web.HttpContext.Current.Server if possible.
Has any one have a work around for this problem?
Edit: Well, I have implemented tvanfosson's suggestion, and when my app constructs MyService in the property get method, Server is still null.
Edit 2: Nevermind, I was a goof. I had another Controller using Server aswell and did not change that. Case closed.
Use delayed initialization to construct your service.
private MyService service;
public MyService Service
{
get
{
if (this.service == null)
{
this.service = new MyService(this.Server);
}
return this.service;
}
}
Then, your service isn't actually instantiated until it is used in the controller action and by that time the Server property has been set.
I instantiate MyService in App.Controller's constructor.
There's your problem. You need to pass an instance of MyService which has already been constructed into your App.Controller's constructor. Take a look at the Inversion of Control / Dependency Injection patterns, and take a look at some of the libraries which make these patterns easy (see this list).
Why do you need the Server reference? Are you doing stuff like url/html encoding? If so, you could use HttpUtility instead and get rid of the context reference entirely.
This is a very old question, but the subject is still relevant. So, one hint in 2017 that was possibly not available in 2009:
It is true that in the Controller constructor Server is null. But you can use the OnActionExecuting event:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
CurrentServer = Server; // CurrentServer is some instance variable I need later.
}
This works fine for me.
According to this site, if you have this Controller:
public class MyController : Controller
{
private string folderPath;
public MyController()
{
// Throws an error because Server is null
folderPath = Server.MapPath("~/uploads");
// Throws an error because this.ControllerContext is null
folderPath = this.ControllerContext.HttpContext.Server.MapPath("~/uploads");
}
}
Then you want to initialize it this way:
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
// now Server has been initialized
folderPath = Server.MapPath("~/uploads");
}
I am a noob to Nancy. I have been using it as a framework to produce a REST API. I am familiar with Json.NET so I've been playing with the Nancy.Serialization.JsonNet package.
My goal: to customize the behavior (i.e. change the settings) of the JsonNetSerializer and JsonNetBodyDeserializer.
Specifically I'd like to incorporate the following settings...
var settings = new JsonSerializerSettings { Formatting = Formatting.Indented };
settings.Converters.Add( new StringEnumConverter { AllowIntegerValues = false, CamelCaseText = true } );
I wanted to perform this customization using the builtin TinyIoC container to avoid the inheritance chain and limit potential issues arising from any changes in the Nancy.Serialization.JsonNet package.
NOTE: As a temporary workaround, I have leveraged inheritance to create CustomJsonNetSerializer and CustomJsonNetBodyDeserializer.
I have tried several approaches to incorporate this configuration at least for the JsonNetSerializer. I've not tried configuring the JsonNetBodyDeserializer using the TinyIoC yet. I imagine it will be done similarly. All the work I've tried is in my CustomNancyBootstrapper (which inherits from DefaultNancyBootstrapper).
Most successful approach so far: override ConfigureApplicationContainer
protected override void ConfigureApplicationContainer( TinyIoCContainer container )
{
base.ConfigureApplicationContainer( container );
// probably don't need both registrations, and I've tried only keeping one or the other
var settings = new JsonSerializerSettings { Formatting = Formatting.Indented };
settings.Converters.Add( new StringEnumConverter { AllowIntegerValues = false, CamelCaseText = true } );
container.Register( new JsonNetSerializer( JsonSerializer.CreateDefault( settings ) ) );
container.Register<ISerializer>( new JsonNetSerializer( JsonSerializer.CreateDefault( settings ) ) );
}
I have traced the code and watched the JsonNetSerializer(JsonSerializer serializer) constructor in the JsonNet package.
Potential problem: I noticed the constructor is called twice. I did not expect this behavior.
The first time everything is just right - my customization is added and registered properly. But, then the second time happens and the types are re-registered, without the settings customization. The re-registration appears to replace the original registration losing my settings customization.
The call stack the second time the constructor is called shows that it is called during GetEngine and GetEngineInternal which seems to try to build a NancyEngine (I am using the self-host package so this happens in program.cs -- using(var host = new NancyHost(uri)) ).
Seems like I either need to tell Nancy not to do something or I need to hook in to a later part in the chain.
Any help would be appreciated.
Typically the way to solve this in Nancy is to implement your own JSON Serializer like so:
public sealed class CustomJsonSerializer : JsonSerializer
{
public CustomJsonSerializer()
{
ContractResolver = new CamelCasePropertyNamesContractResolver();
Converters.Add(new StringEnumConverter
{
AllowIntegerValues = false,
CamelCaseText = true
});
Formatting = Formatting.Indented;
}
}
Here you can override all the settings.
Then you can register it, I do this by using IRegistrations
public class JsonRegistration : IRegistrations
{
public IEnumerable<TypeRegistration> TypeRegistrations
{
get
{
yield return new TypeRegistration(typeof(JsonSerializer), typeof(CustomJsonSerializer));
}
}
public IEnumerable<CollectionTypeRegistration> CollectionTypeRegistrations { get; protected set; }
public IEnumerable<InstanceRegistration> InstanceRegistrations { get; protected set; }
}
Q: How does this approach differ from creating CustomJsonNetSerializer inheriting from JsonNetSerializer and then registering it in ConfigureApplicationContainer (container.Register( typeof(JsonNetSerializer), typeof(CustomJsonNetSerializer) )?
A: The JsonSerializer is the implementation if json.net for Nancy, this is the recommended method we define on the readme on github:
https://github.com/NancyFx/Nancy.Serialization.JsonNet#customization
The class you mention is the serialization of an object to JSON, there is another which handles deserialization, both of which utilize JsonSerializer internally:
https://github.com/NancyFx/Nancy.Serialization.JsonNet/blob/master/src/Nancy.Serialization.JsonNet/JsonNetSerializer.cs#L10
Using this method makes the implementation settings consistent anywhere that the JsonSerializer is used.
Q: can I correctly infer from the approach you've outlined that I would no longer need to explicitly register CustomJsonSerializer in the ConfigureApplicationContainer override in my CustomNancyBootstrapper?
A: The method I've done for registering is just a cleaner abstraction for registering dependencies, rather than making one giant Bootstrapper, you can create a few smaller specific classes.
Yes using my method means you do not need to register in the bootstrapper.