Skip to content

Commit

Permalink
🚀 Better JSON handling and nicer defaults. (#30)
Browse files Browse the repository at this point in the history
* 🚀 Better JSON handling and nicer defaults.
- Json DateTime format can optionally be set.
- Some of the Configure/ConfigureServices stuff have more defaults.
- Fix nuget package details.
- Update README.md with better examples.

* Fixed tests.

* More tests.

* PR feedback.
  • Loading branch information
PureKrome authored Aug 18, 2020
1 parent d2cda9b commit 0ea0292
Show file tree
Hide file tree
Showing 17 changed files with 469 additions and 61 deletions.
165 changes: 165 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# EditorConfig is awesome:
http://EditorConfig.org


#### How to manually clean up code in an IDE?

#### - Visual Studio: CTRL + K + D
#### - VSCode: SHIFT + ALT + F



####################################################################################################

### VS Settings Reference: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference




# top-most EditorConfig file
root = true

# Default settings:
# A newline ending every file
# Use 4 spaces as indentation
[*]
insert_final_newline = true
indent_style = space
indent_size = 4
end_of_line = crlf
charset = utf-8

# C# files
[*.cs]
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true

# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = one_less_than_current

# avoid this. unless absolutely necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion

# only use var when it's obvious what the variable type is
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion

# use language keywords instead of BCL types
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion

# name all constant fields using PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style

dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const

dotnet_naming_style.pascal_case_style.capitalization = pascal_case

# static fields should have s_ prefix
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style

dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static

dotnet_naming_style.static_prefix_style.required_prefix = s_
dotnet_naming_style.static_prefix_style.capitalization = camel_case

# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style

dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal

dotnet_naming_style.camel_case_underscore_style.required_prefix = _
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case

# Code style defaults
dotnet_sort_system_directives_first = true
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false

# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion

# Expression-bodied members
csharp_style_expression_bodied_methods = false:none
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = false:none
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none

# Pattern matching
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion

# Null checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion

# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false

[*.{asm,inc}]
indent_size = 8

# Xml project files
[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
indent_size = 2

# Xml config files
[*.{props,targets,config,nuspec}]
indent_size = 2

[CMakeLists.txt]
indent_size = 2

[*.cmd]
indent_size = 2
Binary file added Icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
86 changes: 82 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ This library contains a collection of helpers, models and extension methods that

### Quick Start, do everything, simple and quick.

This adds all the items, using Default settings (as listed below). Simple, quick and awesome.
Add both of these.

```
public void ConfigureServices(IServiceCollection services)
Expand All @@ -30,10 +32,66 @@ public void ConfigureServices(IServiceCollection services)
// - Default json options: camel casing, no indention, nulls ignored and enums as strings.
// - OpenApi adeed.
services.AddControllers()
.AddDefaultWebApiSettings(); // Can also be totally customized.
.AddDefaultWebApiSettings(); // Can also be totally customized.
}
public void Configure(IApplicationBuilder app)
{
// - Problem details
// - OpenAPI
// - Authorisation
// - Endpoints (with MapControllers)
app.UseDefaultWebApiSettings();
}
```

or a more customized version:

```
public void ConfigureServices(IServiceCollection services)
{
// - Home controller but no banner.
// - Default json options: camel casing, no indention, nulls ignored and enums as strings.
// - OpenApi adeed.
var banner = " -- ascii art --";
var isStackTraceDisplayed = _webHostEnvironment.IsDevelopment();
var isJsonIndented = _webHostEnvironment.IsDevelopment();
var jsonDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ" // No milliseconds.
var openAPITitle = "My API";
var openAPIVersion = "v1";
var openAPICustomOperationIdSelector = new Func<ApiDescription, string>((apiDescrption, string) => { .. // refer to sample code in website } );
services.AddControllers()
.AddDefaultWebApiSettings(banner,
isStackTraceDisplayed,
isJsonIndented,
jsonDateTimeFormat,
openAPITitle,
openAPIVersion,
openAPICustomOperationIdSelector)
}
public void Configure(IApplicationBuilder app)
{
// - Problem details
// - OpenAPI (Customised)
// - Authorisation
// - Endpoints (with MapControllers)
var useAuthorizatoin = true;
var openAPITitle = "My API";
var openAPIVersion = "v1";
var openAPIRoutePrefix = "swagger";
app.UseDefaultWebApiSettings(useAuthorization,
openAPITitle,
openAPIVersion,
openAPIRoutePrefix);
}
```


Otherwise, you can pick and choose which items you want.


Expand All @@ -47,8 +105,10 @@ Great for API's, this will create the default "root/home" route => `HTTP GET /`
```
public void ConfigureServices(IServiceCollection services)
{
var someASCIIArt = "... blah .."; // Optional.
services.AddControllers()
.AddAHomeController(services, SomeASCIIArt);
.AddAHomeController(services, someASCIIArt);
}
```
E.g. output
Expand Down Expand Up @@ -85,13 +145,28 @@ All responses are JSON and formatted using the common JSON settings:
:white_check_mark: Indented formatting.<br/>
:white_check_mark: Ignore null properties which have values.<br/>
:white_check_mark: Enums are rendered as `string`'s ... not their backing number-value.<br/>
:heart: Can specify a custom DateTime format template.

```
// Simplest : i
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddDefaultJsonOptions();
}
or
// All options...
public void ConfigureServices(IServiceCollection services)
{
var isIndented = true;
string dateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ" // No milliseconds.
services.AddControllers()
.AddDefaultJsonOptions(isIndented, dateTimeFormat);
}
```

Sample Model/Domain object:
Expand All @@ -116,6 +191,9 @@ Result JSON text:
}
```

:pen: Note on the default .NET Core DateTime formatting.<br/>
By default, it uses the [ISO 8601-1:2019 format](https://docs.microsoft.com/en-us/dotnet/standard/datetime/system-text-json-support). This means _if_ there is some microseconds, then they are rendered. If there's none -> no micrseconds are rendered. This might make it hard for some consumers of this data (e.g. iOS Swift apps consuming a .NET Core API) so you can hardcode a specific format template to get a consistent output.

### <a name="sample6">Custom OpenAPI (aka. Swagger) wired up</a>

OpenAPI (using the [Swashbuckle library](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)) has been wired up and allows for you to define the `title` and `version` of this API and also a custom `route prefix` (which is good for a gateway API with multiple OpenAPI endpoints because each microservice has their own OpenAPI doc).
Expand All @@ -124,12 +202,12 @@ OpenAPI (using the [Swashbuckle library](https://github.com/domaindrivendev/Swas
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddCustomSwagger("Test API", "v2");
.AddCustomOpenApi("Test API", "v2");
}
public void Configure(IApplicationBuilder app)
{
app.UseCustomSwagger("accounts/swagger", "Test API", "v2")
app.UseCustomOpenApi("accounts/swagger", "Test API", "v2")
.UseRouting()
.UseEndpoints(endpoints => endpoints.MapControllers());
}
Expand Down
39 changes: 39 additions & 0 deletions src/Homely.AspNetCore.Mvc.Helpers/DateTimeConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Homely.AspNetCore.Mvc.Helpers
{
// based on REF: https://stackoverflow.com/a/58103218/30674
// Also info on default behavior for DateTimes with System.Text.Json: https://docs.microsoft.com/en-us/dotnet/standard/datetime/system-text-json-support
public class DateTimeConverter : JsonConverter<DateTime>
{
private readonly string _dateTimeFormat;

public DateTimeConverter(string dateTimeFormat)
{
if (string.IsNullOrWhiteSpace(dateTimeFormat))
{
throw new ArgumentNullException($"'{nameof(dateTimeFormat)}' cannot be null or whitespace.", nameof(dateTimeFormat));
}

_dateTimeFormat = dateTimeFormat;
}

/// <inheritdoc/>
public override DateTime Read(ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
return DateTime.Parse(reader.GetString());
}

/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer,
DateTime value,
JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(_dateTimeFormat));
}
}
}
Loading

0 comments on commit 0ea0292

Please sign in to comment.