Initial Commit
commit
446247a549
|
@ -0,0 +1,46 @@
|
||||||
|
# Autosave files
|
||||||
|
*~
|
||||||
|
|
||||||
|
# build
|
||||||
|
[Oo]bj/
|
||||||
|
[Bb]in/
|
||||||
|
packages/
|
||||||
|
TestResults/
|
||||||
|
|
||||||
|
# globs
|
||||||
|
Makefile.in
|
||||||
|
*.DS_Store
|
||||||
|
*.sln.cache
|
||||||
|
*.suo
|
||||||
|
*.cache
|
||||||
|
*.pidb
|
||||||
|
*.userprefs
|
||||||
|
*.usertasks
|
||||||
|
config.log
|
||||||
|
config.make
|
||||||
|
config.status
|
||||||
|
aclocal.m4
|
||||||
|
install-sh
|
||||||
|
autom4te.cache/
|
||||||
|
*.user
|
||||||
|
*.tar.gz
|
||||||
|
tarballs/
|
||||||
|
test-results/
|
||||||
|
Thumbs.db
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
# Mac bundle stuff
|
||||||
|
*.dmg
|
||||||
|
*.app
|
||||||
|
|
||||||
|
# resharper
|
||||||
|
*_Resharper.*
|
||||||
|
*.Resharper
|
||||||
|
|
||||||
|
# dotCover
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
*.log
|
||||||
|
*.log.old
|
||||||
|
.vscode
|
||||||
|
.build
|
|
@ -0,0 +1,71 @@
|
||||||
|
using System.Reflection.Metadata;
|
||||||
|
using ln.http.api.attributes;
|
||||||
|
using ln.http.router;
|
||||||
|
using ln.json.mapping;
|
||||||
|
using ln.type;
|
||||||
|
|
||||||
|
namespace ln.http.api.demo
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
DemoApi demoApi = new DemoApi();
|
||||||
|
|
||||||
|
SimpleRouter webRouter = new SimpleRouter();
|
||||||
|
|
||||||
|
webRouter.AddSimpleRoute("/api/v1/*", demoApi);
|
||||||
|
|
||||||
|
HTTPServer httpServer = new HTTPServer(new LoggingRouter(webRouter));
|
||||||
|
httpServer.AddEndpoint(new Endpoint(IPv6.ANY, 3434));
|
||||||
|
|
||||||
|
httpServer.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DemoApi : WebApiController
|
||||||
|
{
|
||||||
|
|
||||||
|
[POST("/helloworld/:n")]
|
||||||
|
[POST("/helloworld")]
|
||||||
|
public string GetHelloWorld([FromContent( SourceName = "m")]int n, [FromParameter( SourceName = "n")]int i = -1)
|
||||||
|
{
|
||||||
|
return string.Format("Hello World. You gave me a n={0} / i={1}!", n, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[GET("/users/:id")]
|
||||||
|
public User GetUser(int id)
|
||||||
|
{
|
||||||
|
return new User();
|
||||||
|
}
|
||||||
|
|
||||||
|
[POST("/users")]
|
||||||
|
public User CreateUser([FromContent]User user)
|
||||||
|
{
|
||||||
|
user.PrimaryGroup += " / or another one";
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GET("/users")]
|
||||||
|
public User[] GetUsers()
|
||||||
|
{
|
||||||
|
return new User[]{
|
||||||
|
new User(),
|
||||||
|
new User(),
|
||||||
|
new User()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
public string Username { get; set;} = "niclasundharald";
|
||||||
|
public string PrimaryGroup { get; set; } = "admins";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ln.http.api\ln.http.api.csproj" />
|
||||||
|
|
||||||
|
<!--PackageReference Include="ln.http" Version="0.2.1-test" /-->
|
||||||
|
<ProjectReference Include="..\..\ln.http\ln.http\ln.http.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,48 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 15
|
||||||
|
VisualStudioVersion = 15.0.26124.0
|
||||||
|
MinimumVisualStudioVersion = 15.0.26124.0
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.http.api", "ln.http.api\ln.http.api.csproj", "{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.http.api.demo", "ln.http.api.demo\ln.http.api.demo.csproj", "{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{A8CE1889-2BFC-4EB1-A0E8-52A9CA30C3A2}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{DA9FFEBD-1422-4062-94FC-88AEF2CEC3C4}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
|
||||||
|
namespace ln.http.api
|
||||||
|
{
|
||||||
|
|
||||||
|
public enum HttpMethod {
|
||||||
|
HEAD, GET, POST, PUT, PATCH, DELETE, CONNECT, OPTIONS, TRACE
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
using ln.json;
|
||||||
|
|
||||||
|
namespace ln.http.api
|
||||||
|
{
|
||||||
|
|
||||||
|
public static class HttpResponseExtensions
|
||||||
|
{
|
||||||
|
|
||||||
|
public static HttpResponse Content(this HttpResponse response, JSONValue json)
|
||||||
|
{
|
||||||
|
response.SetHeader("Content-type", "application/json");
|
||||||
|
response.ContentWriter.Write(json.ToString());
|
||||||
|
response.ContentWriter.Flush();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using ln.http.api.attributes;
|
||||||
|
using ln.http.exceptions;
|
||||||
|
using ln.http.router;
|
||||||
|
using ln.json;
|
||||||
|
using ln.json.mapping;
|
||||||
|
|
||||||
|
namespace ln.http.api
|
||||||
|
{
|
||||||
|
|
||||||
|
public abstract class WebApiController : SimpleRouter
|
||||||
|
{
|
||||||
|
Dictionary<string,EndpointMethodCache> methodCaches = new Dictionary<string, EndpointMethodCache>();
|
||||||
|
|
||||||
|
public WebApiController()
|
||||||
|
{
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Initialize()
|
||||||
|
{
|
||||||
|
foreach (MethodInfo methodInfo in GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||||
|
{
|
||||||
|
foreach (EndpointAttribute endpointAttribute in methodInfo.GetCustomAttributes<EndpointAttribute>())
|
||||||
|
{
|
||||||
|
if (endpointAttribute != null)
|
||||||
|
{
|
||||||
|
EndpointMethodCache methodCache = GetOrCreateMethodCache(endpointAttribute.Route);
|
||||||
|
methodCache.Add(endpointAttribute.Method, methodInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse MapRequest(HttpRequest request, MethodInfo methodInfo)
|
||||||
|
{
|
||||||
|
MapArguments(request, methodInfo, out object[] arguments);
|
||||||
|
|
||||||
|
object result = methodInfo.Invoke(this, arguments);
|
||||||
|
|
||||||
|
if (!methodInfo.ReturnType.Equals(typeof(HttpResponse)))
|
||||||
|
{
|
||||||
|
if (!typeof(JSONValue).IsAssignableFrom(methodInfo.ReturnType))
|
||||||
|
{
|
||||||
|
if (!JSONMapper.DefaultMapper.Serialize(result, out JSONValue json))
|
||||||
|
return HttpResponse.InternalServerError().Content("Method result could not be serialized");
|
||||||
|
|
||||||
|
result = json;
|
||||||
|
}
|
||||||
|
return HttpResponse.OK().Content((JSONValue)result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse.OK().Content(result.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MapArguments(HttpRequest request, MethodInfo methodInfo,out object[] arguments)
|
||||||
|
{
|
||||||
|
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
|
||||||
|
arguments = new object[parameterInfos.Length];
|
||||||
|
|
||||||
|
JSONValue jsonContent = null;
|
||||||
|
|
||||||
|
if (request.GetRequestHeader("content-type", "").Equals("application/json"))
|
||||||
|
{
|
||||||
|
jsonContent = JSONParser.Parse(request.ContentReader.ReadToEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int n=0;n<arguments.Length;n++)
|
||||||
|
{
|
||||||
|
ParameterInfo parameterInfo = parameterInfos[n];
|
||||||
|
ArgumentSourceAttribute sourceAttribute = parameterInfo.GetCustomAttribute<ArgumentSourceAttribute>() ?? ArgumentSourceAttribute.Default;
|
||||||
|
|
||||||
|
if (!FindArgumentByName(sourceAttribute, request, jsonContent, parameterInfo.Name, out object value))
|
||||||
|
{
|
||||||
|
if (parameterInfo.HasDefaultValue)
|
||||||
|
{
|
||||||
|
arguments[n] = parameterInfo.DefaultValue;
|
||||||
|
} else
|
||||||
|
throw new BadRequestException();
|
||||||
|
} else {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (value is JSONValue jsonValue)
|
||||||
|
{
|
||||||
|
if (!JSONMapper.DefaultMapper.Deserialize(jsonValue, parameterInfo.ParameterType, out arguments[n]))
|
||||||
|
throw new BadRequestException();
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
arguments[n] = Convert.ChangeType(value, parameterInfo.ParameterType);
|
||||||
|
}
|
||||||
|
} catch (FormatException)
|
||||||
|
{
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FindArgumentByName(ArgumentSourceAttribute sourceAttribute, HttpRequest request, JSONValue jsonContent, string parameterName, out object value)
|
||||||
|
{
|
||||||
|
parameterName = sourceAttribute.SourceName ?? parameterName;
|
||||||
|
|
||||||
|
if (((sourceAttribute.Source & ArgumentSource.PARAMETER) == ArgumentSource.PARAMETER) && request.ContainsParameter(parameterName))
|
||||||
|
{
|
||||||
|
value = request.GetParameter(parameterName);
|
||||||
|
return true;
|
||||||
|
} else if (((sourceAttribute.Source & ArgumentSource.CONTENT) == ArgumentSource.CONTENT) && (jsonContent is JSONObject jsonObject) && (jsonObject.ContainsKey(parameterName)))
|
||||||
|
{
|
||||||
|
value = jsonObject[parameterName];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
EndpointMethodCache GetOrCreateMethodCache(string route)
|
||||||
|
{
|
||||||
|
if (!methodCaches.TryGetValue(route, out EndpointMethodCache methodCache))
|
||||||
|
{
|
||||||
|
methodCache = new EndpointMethodCache();
|
||||||
|
methodCaches.Add(route, methodCache);
|
||||||
|
AddSimpleRoute(route, (request) => {
|
||||||
|
MethodInfo methodInfo = methodCache[Enum.Parse<HttpMethod>(request.Method)];
|
||||||
|
return MapRequest(request, methodInfo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return methodCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class EndpointMethodCache
|
||||||
|
{
|
||||||
|
Dictionary<HttpMethod,MethodInfo> cache = new Dictionary<HttpMethod, MethodInfo>();
|
||||||
|
public EndpointMethodCache()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodInfo this[HttpMethod httpMethod]
|
||||||
|
{
|
||||||
|
get => cache[httpMethod];
|
||||||
|
set => cache[httpMethod] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(HttpMethod httpMethod, MethodInfo methodInfo) => cache.Add(httpMethod, methodInfo);
|
||||||
|
public void Remove(HttpMethod httpMethod, MethodInfo methodInfo) => cache.Remove(httpMethod);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ln.http.api.attributes
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum ArgumentSource : int{
|
||||||
|
AUTO = -1,
|
||||||
|
CONTENT = (1<<0),
|
||||||
|
PARAMETER = (1<<1)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ln.http.api.attributes
|
||||||
|
{
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||||
|
public abstract class EndpointAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string Route { get; set; }
|
||||||
|
public HttpMethod Method { get; set; }
|
||||||
|
|
||||||
|
public EndpointAttribute(HttpMethod method, string route)
|
||||||
|
{
|
||||||
|
Method = method;
|
||||||
|
Route = route;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HEADAttribute : EndpointAttribute
|
||||||
|
{
|
||||||
|
public HEADAttribute(string route) : base(HttpMethod.HEAD, route){}
|
||||||
|
}
|
||||||
|
public class GETAttribute : EndpointAttribute
|
||||||
|
{
|
||||||
|
public GETAttribute(string route) : base(HttpMethod.GET, route){}
|
||||||
|
}
|
||||||
|
public class POSTAttribute : EndpointAttribute
|
||||||
|
{
|
||||||
|
public POSTAttribute(string route) : base(HttpMethod.POST, route){}
|
||||||
|
}
|
||||||
|
public class PUTAttribute : EndpointAttribute
|
||||||
|
{
|
||||||
|
public PUTAttribute(string route) : base(HttpMethod.PUT, route){}
|
||||||
|
}
|
||||||
|
public class PATCHAttribute : EndpointAttribute
|
||||||
|
{
|
||||||
|
public PATCHAttribute(string route) : base(HttpMethod.PATCH, route){}
|
||||||
|
}
|
||||||
|
public class DELETEAttribute : EndpointAttribute
|
||||||
|
{
|
||||||
|
public DELETEAttribute(string route) : base(HttpMethod.DELETE, route){}
|
||||||
|
}
|
||||||
|
public class CONNECTAttribute : EndpointAttribute
|
||||||
|
{
|
||||||
|
public CONNECTAttribute(string route) : base(HttpMethod.CONNECT, route){}
|
||||||
|
}
|
||||||
|
public class OPTIONSAttribute : EndpointAttribute
|
||||||
|
{
|
||||||
|
public OPTIONSAttribute(string route) : base(HttpMethod.OPTIONS, route){}
|
||||||
|
}
|
||||||
|
public class TRACEAttribute : EndpointAttribute
|
||||||
|
{
|
||||||
|
public TRACEAttribute(string route) : base(HttpMethod.TRACE, route){}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class ArgumentSourceAttribute : Attribute
|
||||||
|
{
|
||||||
|
public static readonly ArgumentSourceAttribute Default = new DefaultArgumentSourceAttribute();
|
||||||
|
public string SourceName { get; set; }
|
||||||
|
public ArgumentSource Source { get; }
|
||||||
|
|
||||||
|
public ArgumentSourceAttribute(ArgumentSource source)
|
||||||
|
{
|
||||||
|
Source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultArgumentSourceAttribute : ArgumentSourceAttribute
|
||||||
|
{
|
||||||
|
public DefaultArgumentSourceAttribute() :base(ArgumentSource.AUTO) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter)]
|
||||||
|
public class FromParameterAttribute : ArgumentSourceAttribute
|
||||||
|
{
|
||||||
|
public FromParameterAttribute() :base(ArgumentSource.PARAMETER) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter)]
|
||||||
|
public class FromContentAttribute : ArgumentSourceAttribute
|
||||||
|
{
|
||||||
|
public FromContentAttribute() :base(ArgumentSource.CONTENT) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<Version>0.0.1-test</Version>
|
||||||
|
<Authors>Harald Wolff-Thobaben</Authors>
|
||||||
|
<Company>l--n.de</Company>
|
||||||
|
<Description>Framework to create REST like APIs</Description>
|
||||||
|
<Copyright>(c) 2020 Harald Wolff-Thobaben</Copyright>
|
||||||
|
<PackageTags></PackageTags>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!--PackageReference Include="ln.http" Version="0.2.1-test" /-->
|
||||||
|
<PackageReference Include="ln.json" Version="1.0.0" />
|
||||||
|
<ProjectReference Include="..\..\ln.http\ln.http\ln.http.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
Loading…
Reference in New Issue