Skip to content

Commit a07a51c

Browse files
Publishing release with new JSON APIs (#44)
* Added video demo * adding JSON Mock API * adding instructions * Added HTTP Results calls to fix #7 * Added configuration for JSON APIs * Tagging for a preview release * Added Release Notes file * Moved release notes to top level Co-authored-by: softchris <[email protected]>
1 parent 9ec49d4 commit a07a51c

15 files changed

+485
-12
lines changed

Fritz.InstantAPIs.sln

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
1818
CONTRIBUTING.md = CONTRIBUTING.md
1919
EndProjectSection
2020
EndProject
21-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{CD123B01-1B52-4E80-84F7-4D10E01EE10F}"
21+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "Test\Test.csproj", "{CD123B01-1B52-4E80-84F7-4D10E01EE10F}"
22+
EndProject
23+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestJson", "TestJson\TestJson.csproj", "{99D818F8-63A2-4004-87AB-CA61E1B125CC}"
2224
EndProject
2325
Global
2426
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -38,6 +40,10 @@ Global
3840
{CD123B01-1B52-4E80-84F7-4D10E01EE10F}.Debug|Any CPU.Build.0 = Debug|Any CPU
3941
{CD123B01-1B52-4E80-84F7-4D10E01EE10F}.Release|Any CPU.ActiveCfg = Release|Any CPU
4042
{CD123B01-1B52-4E80-84F7-4D10E01EE10F}.Release|Any CPU.Build.0 = Release|Any CPU
43+
{99D818F8-63A2-4004-87AB-CA61E1B125CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44+
{99D818F8-63A2-4004-87AB-CA61E1B125CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
45+
{99D818F8-63A2-4004-87AB-CA61E1B125CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
46+
{99D818F8-63A2-4004-87AB-CA61E1B125CC}.Release|Any CPU.Build.0 = Release|Any CPU
4147
EndGlobalSection
4248
GlobalSection(SolutionProperties) = preSolution
4349
HideSolutionNode = FALSE

Fritz.InstantAPIs/Fritz.InstantAPIs.csproj

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,19 @@
1515
<PublishRepositoryUrl>true</PublishRepositoryUrl>
1616
<EmbedUntrackedSources>true</EmbedUntrackedSources>
1717
<DebugType>embedded</DebugType>
18-
<Version>0.2.0-preview-1</Version>
19-
<PackageReleaseNotes>Introduced a fluent configuration API for defining which tables to include and exclude from the Entity Framework Context.</PackageReleaseNotes>
18+
<Version>0.2.0-preview-2</Version>
2019
</PropertyGroup>
2120

21+
22+
<Target Name="PreparePackageReleaseNotesFromFile" BeforeTargets="GenerateNuspec">
23+
<ReadLinesFromFile File="../RELEASE_NOTES.txt" >
24+
<Output TaskParameter="Lines" ItemName="ReleaseNoteLines"/>
25+
</ReadLinesFromFile>
26+
<PropertyGroup>
27+
<PackageReleaseNotes>@(ReleaseNoteLines, '%0a')</PackageReleaseNotes>
28+
</PropertyGroup>
29+
</Target>
30+
2231
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
2332
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
2433
</PropertyGroup>

Fritz.InstantAPIs/JsonAPIsConfig.cs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
using System.Text.Json.Nodes;
2+
3+
namespace Microsoft.AspNetCore.Builder;
4+
5+
internal class JsonAPIsConfig
6+
{
7+
8+
internal HashSet<WebApplicationExtensions.TypeTable> Tables { get; } = new HashSet<WebApplicationExtensions.TypeTable>();
9+
10+
internal string JsonFilename = "mock.json";
11+
12+
}
13+
14+
15+
public class JsonAPIsConfigBuilder
16+
{
17+
18+
private JsonAPIsConfig _Config = new();
19+
private string _FileName;
20+
private readonly HashSet<TableApiMapping> _IncludedTables = new();
21+
private readonly List<string> _ExcludedTables = new();
22+
23+
public JsonAPIsConfigBuilder SetFilename(string fileName)
24+
{
25+
_FileName = fileName;
26+
return this;
27+
}
28+
29+
#region Table Inclusion/Exclusion
30+
31+
/// <summary>
32+
/// Specify individual entities to include in the API generation with the methods requested
33+
/// </summary>
34+
/// <param name="entityName">Name of the JSON entity collection to include</param>
35+
/// <param name="methodsToGenerate">A flags enumerable indicating the methods to generate. By default ALL are generated</param>
36+
/// <returns>Configuration builder with this configuration applied</returns>
37+
public JsonAPIsConfigBuilder IncludeEntity(string entityName, ApiMethodsToGenerate methodsToGenerate = ApiMethodsToGenerate.All)
38+
{
39+
40+
var tableApiMapping = new TableApiMapping(entityName, methodsToGenerate);
41+
_IncludedTables.Add(tableApiMapping);
42+
43+
if (_ExcludedTables.Contains(entityName)) _ExcludedTables.Remove(tableApiMapping.TableName);
44+
45+
return this;
46+
47+
}
48+
49+
/// <summary>
50+
/// Exclude individual entities from the API generation. Exclusion takes priority over inclusion
51+
/// </summary>
52+
/// <param name="entitySelector">Name of the JSON entity collection to exclude</param>
53+
/// <returns>Configuration builder with this configuraiton applied</returns>
54+
public JsonAPIsConfigBuilder ExcludeTable(string entityName)
55+
{
56+
57+
if (_IncludedTables.Select(t => t.TableName).Contains(entityName)) _IncludedTables.Remove(_IncludedTables.First(t => t.TableName == entityName));
58+
_ExcludedTables.Add(entityName);
59+
60+
return this;
61+
62+
}
63+
64+
private HashSet<string> IdentifyEntities()
65+
{
66+
var writableDoc = JsonNode.Parse(File.ReadAllText(_FileName));
67+
68+
// print API
69+
return writableDoc?.Root.AsObject()
70+
.AsEnumerable().Select(x => x.Key)
71+
.ToHashSet();
72+
73+
}
74+
75+
private void BuildTables()
76+
{
77+
78+
var tables = IdentifyEntities();
79+
80+
if (!_IncludedTables.Any() && !_ExcludedTables.Any())
81+
{
82+
_Config.Tables.UnionWith(tables.Select(t => new WebApplicationExtensions.TypeTable
83+
{
84+
Name = t,
85+
ApiMethodsToGenerate = ApiMethodsToGenerate.All
86+
}));
87+
return;
88+
}
89+
90+
// Add the Included tables
91+
var outTables = _IncludedTables
92+
.Select(t => new WebApplicationExtensions.TypeTable
93+
{
94+
Name = t.TableName,
95+
ApiMethodsToGenerate = t.MethodsToGenerate
96+
}).ToArray();
97+
98+
// If no tables were added, added them all
99+
if (outTables.Length == 0)
100+
{
101+
outTables = tables.Select(t => new WebApplicationExtensions.TypeTable
102+
{
103+
Name = t,
104+
ApiMethodsToGenerate = ApiMethodsToGenerate.All
105+
}).ToArray();
106+
}
107+
108+
// Remove the Excluded tables
109+
outTables = outTables.Where(t => !_ExcludedTables.Any(e => t.Name.Equals(e, StringComparison.InvariantCultureIgnoreCase))).ToArray();
110+
111+
if (outTables == null || !outTables.Any()) throw new ArgumentException("All tables were excluded from this configuration");
112+
113+
_Config.Tables.UnionWith(outTables);
114+
115+
}
116+
117+
#endregion
118+
119+
internal JsonAPIsConfig Build()
120+
{
121+
122+
if (string.IsNullOrEmpty(_FileName)) throw new ArgumentNullException("Missing Json Filename for configuration");
123+
if (!File.Exists(_FileName)) throw new ArgumentException($"Unable to locate the JSON file for APIs at {_FileName}");
124+
_Config.JsonFilename = _FileName;
125+
126+
BuildTables();
127+
128+
return _Config;
129+
}
130+
131+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Nodes;
3+
using Microsoft.AspNetCore.Builder;
4+
using Microsoft.AspNetCore.Http;
5+
6+
namespace Mock;
7+
8+
public static class JsonApiExtensions
9+
{
10+
11+
static JsonAPIsConfig _Config;
12+
13+
public static WebApplication UseJsonRoutes(this WebApplication app, Action<JsonAPIsConfigBuilder> options = null)
14+
{
15+
16+
var builder = new JsonAPIsConfigBuilder();
17+
_Config = new JsonAPIsConfig();
18+
if (options != null)
19+
{
20+
options(builder);
21+
_Config = builder.Build();
22+
}
23+
24+
var writableDoc = JsonNode.Parse(File.ReadAllText(_Config.JsonFilename));
25+
26+
// print API
27+
foreach (var elem in writableDoc?.Root.AsObject().AsEnumerable())
28+
{
29+
30+
var thisEntity = _Config.Tables.FirstOrDefault(t => t.Name.Equals(elem.Key, StringComparison.InvariantCultureIgnoreCase));
31+
if (thisEntity == null) continue;
32+
33+
if ((thisEntity.ApiMethodsToGenerate & ApiMethodsToGenerate.Get) == ApiMethodsToGenerate.Get)
34+
Console.WriteLine(string.Format("GET /{0}", elem.Key.ToLower()));
35+
36+
if ((thisEntity.ApiMethodsToGenerate & ApiMethodsToGenerate.GetById) == ApiMethodsToGenerate.GetById)
37+
Console.WriteLine(string.Format("GET /{0}", elem.Key.ToLower()) + "/id");
38+
39+
if ((thisEntity.ApiMethodsToGenerate & ApiMethodsToGenerate.Insert) == ApiMethodsToGenerate.Insert)
40+
Console.WriteLine(string.Format("POST /{0}", elem.Key.ToLower()));
41+
42+
if ((thisEntity.ApiMethodsToGenerate & ApiMethodsToGenerate.Delete) == ApiMethodsToGenerate.Delete)
43+
Console.WriteLine(string.Format("DELETE /{0}", elem.Key.ToLower()) + "/id");
44+
45+
Console.WriteLine(" ");
46+
}
47+
48+
// setup routes
49+
foreach (var elem in writableDoc?.Root.AsObject().AsEnumerable())
50+
{
51+
52+
var thisEntity = _Config.Tables.FirstOrDefault(t => t.Name.Equals(elem.Key, StringComparison.InvariantCultureIgnoreCase));
53+
if (thisEntity == null) continue;
54+
55+
var arr = elem.Value.AsArray();
56+
57+
if ((thisEntity.ApiMethodsToGenerate & ApiMethodsToGenerate.Get) == ApiMethodsToGenerate.Get)
58+
app.MapGet(string.Format("/{0}", elem.Key), () => elem.Value.ToString());
59+
60+
if ((thisEntity.ApiMethodsToGenerate & ApiMethodsToGenerate.GetById) == ApiMethodsToGenerate.GetById)
61+
app.MapGet(string.Format("/{0}", elem.Key) + "/{id}", (int id) =>
62+
{
63+
var matchedItem = arr.SingleOrDefault(row => row
64+
.AsObject()
65+
.Any(o => o.Key.ToLower() == "id" && int.Parse(o.Value.ToString()) == id)
66+
);
67+
return matchedItem;
68+
});
69+
70+
if ((thisEntity.ApiMethodsToGenerate & ApiMethodsToGenerate.Insert) == ApiMethodsToGenerate.Insert)
71+
app.MapPost(string.Format("/{0}", elem.Key), async (HttpRequest request) =>
72+
{
73+
string content = string.Empty;
74+
using (StreamReader reader = new StreamReader(request.Body))
75+
{
76+
content = await reader.ReadToEndAsync();
77+
}
78+
var newNode = JsonNode.Parse(content);
79+
var array = elem.Value.AsArray();
80+
newNode.AsObject().Add("Id", array.Count() + 1);
81+
array.Add(newNode);
82+
83+
File.WriteAllText(_Config.JsonFilename, writableDoc.ToString());
84+
return content;
85+
});
86+
87+
if ((thisEntity.ApiMethodsToGenerate & ApiMethodsToGenerate.Update) == ApiMethodsToGenerate.Update)
88+
app.MapPut(string.Format("/{0}", elem.Key), () =>
89+
{
90+
return "TODO";
91+
});
92+
93+
if ((thisEntity.ApiMethodsToGenerate & ApiMethodsToGenerate.Delete) == ApiMethodsToGenerate.Delete)
94+
app.MapDelete(string.Format("/{0}", elem.Key) + "/{id}", (int id) =>
95+
{
96+
97+
var matchedItem = arr
98+
.Select((value, index) => new { value, index })
99+
.SingleOrDefault(row => row.value
100+
.AsObject()
101+
.Any(o => o.Key.ToLower() == "id" && int.Parse(o.Value.ToString()) == id)
102+
);
103+
if (matchedItem != null)
104+
{
105+
arr.RemoveAt(matchedItem.index);
106+
File.WriteAllText(_Config.JsonFilename, writableDoc.ToString());
107+
}
108+
109+
return "OK";
110+
});
111+
112+
};
113+
114+
return app;
115+
}
116+
}

0 commit comments

Comments
 (0)