├── .github
└── FUNDING.yml
├── Images
└── Diagrams
│ ├── DatasilkCoreMvc-View.fla
│ └── DatasilkCoreMvc-View.jpg
├── Test
├── test.html
├── App.xaml
├── App.xaml.cs
├── Test.csproj
├── AssemblyInfo.cs
├── MainWindow.xaml.cs
└── MainWindow.xaml
├── Core
├── Web
│ ├── IRequest.cs
│ ├── IService.cs
│ ├── Request.cs
│ ├── Routes.cs
│ ├── Response.cs
│ ├── Service.cs
│ ├── IController.cs
│ ├── Parameters.cs
│ ├── Controller.cs
│ └── View.cs
├── Attributes
│ └── RequestMethods.cs
├── Extensions
│ └── Mvc.cs
├── LICENSE
├── Core.csproj
└── Middleware
│ ├── MvcOptions.cs
│ └── Mvc.cs
├── LICENSE
├── Core.sln
├── README.nuget.md
├── .gitignore
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [datasilk]
--------------------------------------------------------------------------------
/Images/Diagrams/DatasilkCoreMvc-View.fla:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Datasilk/Core/HEAD/Images/Diagrams/DatasilkCoreMvc-View.fla
--------------------------------------------------------------------------------
/Images/Diagrams/DatasilkCoreMvc-View.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Datasilk/Core/HEAD/Images/Diagrams/DatasilkCoreMvc-View.jpg
--------------------------------------------------------------------------------
/Test/test.html:
--------------------------------------------------------------------------------
1 | This is a test!
2 |
{{title}}
3 | Written by {{author}}
4 |
5 | {{page-list path:"support", "length":"15"}}
--------------------------------------------------------------------------------
/Core/Web/IRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Http;
3 |
4 | namespace Datasilk.Core.Web
5 | {
6 | public interface IRequest : IDisposable
7 | {
8 | HttpContext Context { get; set; }
9 | string Path { get; set; }
10 | string[] PathParts { get; set; }
11 | Parameters Parameters { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Core/Web/IService.cs:
--------------------------------------------------------------------------------
1 | namespace Datasilk.Core.Web
2 | {
3 | public interface IService : IRequest
4 | {
5 | void Init();
6 | string Success();
7 | string Empty();
8 | string AccessDenied(string message = "Error 403");
9 | string Error(string message = "Error 500");
10 | string BadRequest(string message = "Bad Request 400");
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Test/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Core/Attributes/RequestMethods.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Datasilk.Core.Web
4 | {
5 | [AttributeUsage(AttributeTargets.Method)]
6 | public class GETAttribute : Attribute { }
7 | public class POSTAttribute : Attribute { }
8 | public class PUTAttribute : Attribute { }
9 | public class HEADAttribute : Attribute { }
10 | public class DELETEAttribute : Attribute { }
11 | }
12 |
--------------------------------------------------------------------------------
/Test/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace Test
10 | {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Core/Web/Request.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Http;
2 |
3 | namespace Datasilk.Core.Web
4 | {
5 | public abstract class Request: IRequest
6 | {
7 | public HttpContext Context { get; set; }
8 | public string Path { get; set; }
9 | public string[] PathParts { get; set; }
10 | public Parameters Parameters { get; set; }
11 | public virtual void Dispose() { }
12 | }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/Core/Extensions/Mvc.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 |
3 | namespace Datasilk.Core.Extensions
4 | {
5 | public static class Mvc
6 | {
7 | public static IApplicationBuilder UseDatasilkMvc(this IApplicationBuilder builder, MvcOptions options = default)
8 | {
9 | return builder.UseMiddleware(options);
10 | }
11 | }
12 |
13 | public class MvcOptions : Middleware.MvcOptions { }
14 | }
15 |
--------------------------------------------------------------------------------
/Core/Web/Routes.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Http;
2 |
3 | namespace Datasilk.Core.Web
4 | {
5 | public class Routes
6 | {
7 | public virtual IController FromControllerRoutes(HttpContext context, Parameters parameters, string name)
8 | {
9 | return null;
10 | }
11 |
12 | public virtual IService FromServiceRoutes(HttpContext context, Parameters parameters, string name)
13 | {
14 | return null;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Test/Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net6.0-windows
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | PreserveNewest
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/Test/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Datasilk
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Core/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Datasilk
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Core/Web/Response.cs:
--------------------------------------------------------------------------------
1 | namespace Datasilk.Core.Web
2 | {
3 | public enum responseType
4 | {
5 | replace = 0,
6 | append = 1,
7 | before = 2,
8 | after = 3,
9 | prepend = 4
10 | }
11 |
12 | //used to send a JSON object back to the client web browser
13 | public class Response
14 | {
15 | public responseType type { get; set; } = responseType.replace; //type of insert command
16 | public string selector { get; set; } = ""; //css selector to insert response HTML into
17 | public string html { get; set; } = ""; //HTML response
18 | public string javascript { get; set; } = ""; //optional javascript to insert onto the page dynamically
19 | public string css { get; set; } = ""; //optional CSS to insert onto the page dynamically
20 | public string json { get; set; } = "";
21 |
22 | public Response(string html = "", string javascript = "", string css = "", string json = "", string selector = "", responseType type = responseType.replace)
23 | {
24 | this.html = html;
25 | this.javascript = javascript;
26 | this.css = css;
27 | this.selector = selector;
28 | this.json = json;
29 | this.type = type;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Core/Web/Service.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using Microsoft.AspNetCore.Http;
3 |
4 | namespace Datasilk.Core.Web
5 | {
6 | public class Service : Request, IService
7 | {
8 | public virtual void Init() { }
9 |
10 | public string JsonResponse(dynamic obj)
11 | {
12 | Context.Response.ContentType = "text/json";
13 | return JsonSerializer.Serialize(obj);
14 | }
15 |
16 | public string AccessDenied(string message = "Error 403")
17 | {
18 | Context.Response.StatusCode = 403;
19 | Context.Response.WriteAsync(message);
20 | return message;
21 | }
22 |
23 | public string Error(string message = "Error 500")
24 | {
25 | Context.Response.StatusCode = 500;
26 | Context.Response.WriteAsync(message);
27 | return message;
28 | }
29 |
30 | public string BadRequest(string message = "Bad Request 400")
31 | {
32 | Context.Response.StatusCode = 400;
33 | return "Bad Request";
34 | }
35 |
36 | public string Success()
37 | {
38 | return "success";
39 | }
40 |
41 | public string Empty()
42 | {
43 | Context.Response.ContentType = "text/json";
44 | return "{}";
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Test/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 |
5 | namespace Test
6 | {
7 | ///
8 | /// Interaction logic for MainWindow.xaml
9 | ///
10 | public partial class MainWindow : Window
11 | {
12 | public MainWindow()
13 | {
14 | InitializeComponent();
15 | }
16 |
17 | private void Button_Click(object sender, RoutedEventArgs e)
18 | {
19 | var view = new View(new ViewOptions()
20 | {
21 | Html = txtInput.Text
22 | });
23 | view.Show("is-block");
24 | view["insert-adj"] = view.Elements.Where(a => a.Name == "insert-adj").First().Vars["text"];
25 | var header = view.Child("header");
26 | var i = header.Fields.Where(a => a.Key == "page-list").First().Value[0];
27 | header["page-list"] = "This is a page list filtered by path: " + view.Elements[i].Vars["path"];
28 | txtInput.Text = view.Render();
29 | txtInput.Text += "\n\n" + view.GetBlock(view.Elements.Where(a => a.Name == "is-block").First());
30 | btnRender.Visibility = Visibility.Hidden;
31 | }
32 |
33 | private void txtInput_TextChanged(object sender, TextChangedEventArgs e)
34 | {
35 |
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Core/Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | 1.0.8.2
6 | Datasilk.Core
7 | Datasilk.Core.Mvc
8 | Mark Entingh
9 | Datasilk
10 | Datasilk Core MVC
11 | An ultra-lightweight MVC framework for the web that handles page requests and RESTful web API calls.
12 | Datasilk 2023
13 | LICENSE
14 | https://mvc.datasilk.io/
15 | https://mvc.datasilk.io/images/favicon.png
16 | https://github.com/datasilk/core
17 | GitHub
18 | MVC, Web API, ultra-lightweight
19 | Fixed caching issue with View.Partial, fixed issue with partial includes mustache variable parameters disappearing, cleaned up View class by removing unused classes, methods, & vars, and optimized the Render function.
20 | true
21 | 1.0.8.2
22 | 1.0.8.2
23 | en
24 |
25 |
26 |
27 |
28 | True
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Test/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Core/Web/IController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 |
4 | namespace Datasilk.Core.Web
5 | {
6 | public interface IController: IRequest
7 | {
8 | StringBuilder Scripts { get; set; }
9 | StringBuilder Css { get; set; }
10 |
11 | void Init();
12 | string Render(string body = "");
13 | string Redirect(string url);
14 | void AddScript(string url, string id = "", string callback = "");
15 | void AddCSS(string url, string id = "");
16 | string Error() where T : IController;
17 | string Error(string message = "Error 500");
18 | string Error404() where T : IController;
19 | string Error404(string message = "Error 404");
20 | string AccessDenied() where T : IController;
21 |
22 | static T LoadController(IController parent) where T : IController
23 | {
24 | var controller = (T)Activator.CreateInstance(typeof(T));
25 | controller.Context = parent.Context;
26 | controller.PathParts = parent.PathParts;
27 | controller.Path = parent.Path;
28 | controller.Parameters = parent.Parameters;
29 | return controller;
30 |
31 | }
32 | static string AccessDenied(IController parent) where T : IController
33 | {
34 | var controller = LoadController(parent);
35 | return controller.Render();
36 | }
37 |
38 | static string Error(IController parent) where T : IController
39 | {
40 | var controller = LoadController(parent);
41 | return controller.Render();
42 | }
43 |
44 | static string Error404(IController parent) where T : IController
45 | {
46 | var controller = LoadController(parent);
47 | return controller.Render();
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Core.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.5.33414.496
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "Core\Core.csproj", "{ECD4B899-16D6-45BA-AE89-E3FA166AA31E}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "Test\Test.csproj", "{E0A60593-5E3C-4B87-B730-975EEA149263}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B1C24D06-3C0B-4129-A518-47426E3A3299}"
11 | ProjectSection(SolutionItems) = preProject
12 | README.md = README.md
13 | EndProjectSection
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Release|Any CPU = Release|Any CPU
19 | EndGlobalSection
20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
21 | {ECD4B899-16D6-45BA-AE89-E3FA166AA31E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {ECD4B899-16D6-45BA-AE89-E3FA166AA31E}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {ECD4B899-16D6-45BA-AE89-E3FA166AA31E}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {ECD4B899-16D6-45BA-AE89-E3FA166AA31E}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {E0A60593-5E3C-4B87-B730-975EEA149263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {E0A60593-5E3C-4B87-B730-975EEA149263}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {E0A60593-5E3C-4B87-B730-975EEA149263}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {E0A60593-5E3C-4B87-B730-975EEA149263}.Release|Any CPU.Build.0 = Release|Any CPU
29 | EndGlobalSection
30 | GlobalSection(SolutionProperties) = preSolution
31 | HideSolutionNode = FALSE
32 | EndGlobalSection
33 | GlobalSection(ExtensibilityGlobals) = postSolution
34 | SolutionGuid = {DC06A1E0-9FF7-4FA0-AEF9-C59FAE14D8BF}
35 | EndGlobalSection
36 | EndGlobal
37 |
--------------------------------------------------------------------------------
/Core/Middleware/MvcOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Datasilk.Core.Middleware
4 | {
5 | public class MvcOptions
6 | {
7 | ///
8 | /// If true, removes the request body size limit
9 | ///
10 | public bool IgnoreRequestBodySize { get; set; } = false;
11 |
12 | ///
13 | /// If true, outputs details about each request to the console window
14 | ///
15 | public bool WriteDebugInfoToConsole { get; set; } = false;
16 |
17 | ///
18 | /// If true, outputs details about each request to a logger
19 | ///
20 | public bool LogRequests { get; set; } = false;
21 |
22 | ///
23 | /// A list of paths that is used to access Web API services. The default is: new string[] { "api" }
24 | ///
25 | public string[] ServicePaths { get; set; } = new string[] { "api" };
26 |
27 | ///
28 | /// Reference to a class that can quickly create instances of Controller & Service classes based on request URI routes
29 | ///
30 | public Web.Routes Routes { get; set; } = new Web.Routes();
31 |
32 | ///
33 | /// Set the default controller to load if the request URI path is empty
34 | ///
35 | public string DefaultController { get; set; } = "home";
36 |
37 | ///
38 | /// Set the default service method to load if the request URI path is empty
39 | ///
40 | public string DefaultServiceMethod { get; set; } = "Get";
41 |
42 | ///
43 | /// Determines if the Middleware will invoke the next middleware in the pipeline
44 | ///
45 | public bool InvokeNext { get; set; } = true;
46 | public bool AwaitInvoke { get; set; } = true;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Core/Web/Parameters.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Collections.Generic;
3 |
4 | namespace Datasilk.Core.Web
5 | {
6 | ///
7 | /// Parameters extracted from the request body
8 | ///
9 | public class Parameters : Dictionary
10 | {
11 | ///
12 | /// Raw data extracted from the request body before deserialized into parameters
13 | ///
14 | public string RequestBody { get; set; } = "";
15 | public Dictionary Files = new Dictionary();
16 | private List _isArray = new List();
17 |
18 | ///
19 | /// Adds a value (comma-delimited) to an existing key/value pair
20 | ///
21 | ///
22 | ///
23 | public void AddTo(string key, string value)
24 | {
25 | _isArray.Add(key);
26 | var param = this[key];
27 | this[key] = param + "^,^" + value;
28 | }
29 |
30 | ///
31 | /// Return whether or not a specific key has a comma-delimited set of values
32 | ///
33 | ///
34 | ///
35 | public bool ContainsArray(string key)
36 | {
37 | return _isArray.Contains(key);
38 | }
39 |
40 | ///
41 | /// Gets a list of values for a specified key in the dictionary
42 | ///
43 | ///
44 | ///
45 | public string[] GetValues(string key)
46 | {
47 | return this[key].Split("^,^");
48 | }
49 | }
50 |
51 | public class FormFile: MemoryStream
52 | {
53 | public string Filename { get; set; }
54 | public string ContentType { get; set; }
55 | }
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/README.nuget.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Datasilk Core MVC
4 | #### An MVC Framework for ASP.NET Core
5 | Datasilk Core is an ultra-fast, light-weight alternative to ASP.NET Core MVC, it supports Views using HTML with mustache variables, hierarchical Controller rendering, and RESTful web services.
6 |
7 | ## Startup.cs
8 |
9 | Make sure to include the middleware within `Startup.cs`.
10 |
11 | ``` csharp
12 | app.UseDatasilkMvc(new MvcOptions()
13 | {
14 | IgnoreRequestBodySize = true,
15 | WriteDebugInfoToConsole = true,
16 | Routes = new Routes()
17 | });
18 | ```
19 |
20 | ## Page Requests
21 |
22 | All page request URLs are mapped to controllers that inherit the `Datasilk.Core.Web.IController` interface. For example, the URL `http://localhost:7770/products` would map to the class `MyProject.Controllers.Products`.
23 |
24 | **/Views/Home/home.html**
25 | ``` html
26 |
27 |
{{title}}
28 | {{description}}
29 |
30 | ```
31 |
32 | **/Controllers/Home.cs**
33 | ``` csharp
34 | namespace MyProject.Controllers
35 | {
36 | public class Home: Datasilk.Core.Web.Controller
37 | {
38 | public override string Render(string body = "")
39 | {
40 | //render page
41 | var view = new View("/Views/Home/home.html");
42 | view["title"] = "Welcome";
43 | view["description"] = "I like to write software";
44 | AddScript("/js/views/home/home.js");
45 | return view.Render();
46 | }
47 | }
48 | }
49 | ```
50 |
51 | ## Web Services
52 | The Datasilk Core MVC framework comes with the ability to call *RESTful* web APIs. All web API calls are executed from `Datasilk.Core.Web.IService` interfaces.
53 |
54 | #### Example
55 |
56 | ``` csharp
57 | namespace MyProject.Services
58 | {
59 | public class User: Datasilk.Core.Web.Service
60 | {
61 | [POST]
62 | public string Authenticate(string email, string password)
63 | {
64 | //authenticate user
65 | if(Authenticated(email, password))
66 | {
67 | return Success();
68 | }
69 | else
70 | {
71 | return AccessDenied("Incorrect email and/or password");
72 | }
73 | }
74 | }
75 | }
76 | ```
77 |
78 |
79 | Read more [Documentation](https://www.github.com/datasilk/core) on Github
--------------------------------------------------------------------------------
/Core/Web/Controller.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text;
3 |
4 | namespace Datasilk.Core.Web
5 | {
6 | public class Controller: Request, IController
7 | {
8 | public StringBuilder Scripts { get; set; } = new StringBuilder();
9 | public StringBuilder Css { get; set; } = new StringBuilder();
10 | private List Resources { get; set; } = new List();
11 |
12 |
13 | public virtual void Init() { }
14 | public virtual string Render(string body = "")
15 | {
16 | return body;
17 | }
18 |
19 | public string Error() where T : IController
20 | {
21 | Context.Response.StatusCode = 500;
22 | return IController.Error(this);
23 | }
24 |
25 | public string Error(string message = "Error 500")
26 | {
27 | Context.Response.StatusCode = 500;
28 | return message;
29 | }
30 |
31 | public string Error404() where T : IController
32 | {
33 | Context.Response.StatusCode = 404;
34 | return IController.Error404(this);
35 | }
36 |
37 | public string Error404(string message = "Error 404")
38 | {
39 | Context.Response.StatusCode = 404;
40 | return message;
41 | }
42 |
43 | public string AccessDenied() where T : IController
44 | {
45 | Context.Response.StatusCode = 403;
46 | return IController.AccessDenied(this);
47 | }
48 |
49 | public string Redirect(string url)
50 | {
51 | return "";
52 | }
53 |
54 | public void AddScript(string url, string id = "", string callback = "")
55 | {
56 | if (ContainsResource(url)) { return; }
57 | Scripts.Append("");
59 | }
60 |
61 | public void AddCSS(string url, string id = "")
62 | {
63 | if (ContainsResource(url)) { return; }
64 | Css.Append("");
65 | }
66 |
67 | protected bool ContainsResource(string url)
68 | {
69 | if (Resources.Contains(url)) { return true; }
70 | Resources.Add(url);
71 | return false;
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.sln.docstates
8 | *.bak
9 | *.jfm
10 | **/.vs
11 |
12 | # Build results
13 | [Dd]ebug/
14 | [Dd]ebugPublic/
15 | [Rr]elease/
16 | x64/
17 | build/
18 | bld/
19 | [Bb]in/
20 | [Oo]bj/
21 |
22 | # Roslyn cache directories
23 | *.ide/
24 |
25 | # MSTest test Results
26 | [Tt]est[Rr]esult*/
27 | [Bb]uild[Ll]og.*
28 |
29 | #NUNIT
30 | *.VisualState.xml
31 | TestResult.xml
32 |
33 | # Build Results of an ATL Project
34 | [Dd]ebugPS/
35 | [Rr]eleasePS/
36 | dlldata.c
37 |
38 | *_i.c
39 | *_p.c
40 | *_i.h
41 | *.ilk
42 | *.meta
43 | *.obj
44 | *.pch
45 | *.pdb
46 | *.pgc
47 | *.pgd
48 | *.rsp
49 | *.sbr
50 | *.tlb
51 | *.tli
52 | *.tlh
53 | *.tmp
54 | *.tmp_proj
55 | *.log
56 | *.vspscc
57 | *.vssscc
58 | .builds
59 | *.pidb
60 | *.svclog
61 | *.scc
62 |
63 | # Chutzpah Test files
64 | _Chutzpah*
65 |
66 | # Visual C++ cache files
67 | ipch/
68 | *.aps
69 | *.ncb
70 | *.opensdf
71 | *.sdf
72 | *.cachefile
73 |
74 | # Visual Studio profiler
75 | *.psess
76 | *.vsp
77 | *.vspx
78 |
79 | # TFS 2012 Local Workspace
80 | $tf/
81 |
82 | # Guidance Automation Toolkit
83 | *.gpState
84 |
85 | # ReSharper is a .NET coding add-in
86 | _ReSharper*/
87 | *.[Rr]e[Ss]harper
88 | *.DotSettings.user
89 |
90 | # JustCode is a .NET coding addin-in
91 | .JustCode
92 |
93 | # TeamCity is a build add-in
94 | _TeamCity*
95 |
96 | # DotCover is a Code Coverage Tool
97 | *.dotCover
98 |
99 | # NCrunch
100 | _NCrunch_*
101 | .*crunch*.local.xml
102 |
103 | # MightyMoose
104 | *.mm.*
105 | AutoTest.Net/
106 |
107 | # Web workbench (sass)
108 | .sass-cache/
109 |
110 | # Installshield output folder
111 | [Ee]xpress/
112 |
113 | # DocProject is a documentation generator add-in
114 | DocProject/buildhelp/
115 | DocProject/Help/*.HxT
116 | DocProject/Help/*.HxC
117 | DocProject/Help/*.hhc
118 | DocProject/Help/*.hhk
119 | DocProject/Help/*.hhp
120 | DocProject/Help/Html2
121 | DocProject/Help/html
122 |
123 | # Click-Once directory
124 | publish/
125 |
126 | # Publish Web Output
127 | *.[Pp]ublish.xml
128 | *.azurePubxml
129 | ## TODO: Comment the next line if you want to checkin your
130 | ## web deploy settings but do note that will include unencrypted
131 | ## passwords
132 | #*.pubxml
133 |
134 | # NuGet Packages Directory
135 | packages/*
136 | ## TODO: If the tool you use requires repositories.config
137 | ## uncomment the next line
138 | #!packages/repositories.config
139 |
140 | # Enable "build/" folder in the NuGet Packages folder since
141 | # NuGet packages use it for MSBuild targets.
142 | # This line needs to be after the ignore of the build folder
143 | # (and the packages folder if the line above has been uncommented)
144 | !packages/build/
145 |
146 | # Windows Azure Build Output
147 | csx/
148 | *.build.csdef
149 |
150 | # Windows Store app package directory
151 | AppPackages/
152 |
153 | # Others
154 | *.Cache
155 | ClientBin/
156 | [Ss]tyle[Cc]op.*
157 | ~$*
158 | *~
159 | *.dbmdl
160 | *.dbproj.schemaview
161 | *.pfx
162 | *.publishsettings
163 | node_modules/
164 |
165 | # RIA/Silverlight projects
166 | Generated_Code/
167 |
168 | # Backup & report files from converting an old project file
169 | # to a newer Visual Studio version. Backup files are not needed,
170 | # because we have git ;-)
171 | _UpgradeReport_Files/
172 | Backup*/
173 | UpgradeLog*.XML
174 | UpgradeLog*.htm
175 |
176 | # SQL Server files
177 | *.mdf
178 | *.ldf
179 |
180 | # Business Intelligence projects
181 | *.rdl.data
182 | *.bim.layout
183 | *.bim_*.settings
184 |
185 | # Microsoft Fakes
186 | FakesAssemblies/
187 |
188 | # LightSwitch generated files
189 | GeneratedArtifacts/
190 | _Pvt_Extensions/
191 | ModelManifest.xml
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Datasilk Core MVC
4 | #### An MVC Framework for ASP.NET Core
5 | Datasilk Core is an ultra-fast, light-weight alternative to ASP.NET Core MVC, it supports Views using HTML with mustache variables, hierarchical Controller rendering, and RESTful web services.
6 |
7 | ## Installation
8 |
9 | #### From Nuget
10 | 1. Include the Nuget Package `Datasilk.Core.Mvc` within your ASP.NET Core project.
11 |
12 | #### From Github
13 | 1. Add this repository to your ASP.NET Core project as a submodule:
14 |
15 | `git submodule add https://github.com/Datasilk/Core`
16 |
17 | ## Startup.cs
18 |
19 | Make sure to include the middleware within `Startup.cs`.
20 |
21 | ``` csharp
22 | public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env)
23 | {
24 | app.UseDatasilkMvc(new MvcOptions()
25 | {
26 | IgnoreRequestBodySize = true,
27 | WriteDebugInfoToConsole = true,
28 | LogRequests = false,
29 | Routes = new Routes(),
30 | ServicePaths = new string[] { 'api' },
31 | DefaultController = 'home',
32 | DefaultServiceMethod = 'Get',
33 | InvokeNext = true
34 | });
35 | }
36 | ```
37 |
38 | ## Page Requests
39 |
40 | All page request URLs are mapped to controllers that inherit the `Datasilk.Core.Web.IController` interface. For example, the URL `http://localhost:7770/products` would map to the class `MyProject.Controllers.Products`. Each controller contains one method, `Render`, which is used to serve a web page to the user's web browser.
41 |
42 | **/Views/Home/home.html**
43 | ``` csharp
44 |
45 |
{{title}}
46 | {{description}}
47 |
48 | ```
49 |
50 | **/Controllers/Home.cs**
51 | ``` csharp
52 | namespace MyProject.Controllers
53 | {
54 | public class Home: Datasilk.Core.Web.Controller
55 | {
56 | public override string Render(string body = "")
57 | {
58 | //render page
59 | var view = new View("/Views/Home/home.html");
60 | view["title"] = "Hello.";
61 | view["description"] = "I like to write software.";
62 | AddScript("/js/views/home/home.js");
63 | return view.Render();
64 | }
65 | }
66 | }
67 | ```
68 |
69 | In the example above, a user tries to access the URL `http://localhost:7770/`, which (by default) will render the contents of the `MyProject.Controllers.Home` class. This class loads `/Views/Home/home.html` into a `View` object and replaces the `{{title}}` & `{{description}}` variables located within the `home.html` file with the text, "Hello" & "I like to write software". Then, the page returns the contents of `view.Render()`.
70 |
71 | #### Controller Hierarchy
72 | You could create controllers that inherit other controllers, then `return base.Render(view.Render())` to create a cascade of controller rendering. This would be useful to have a base controller that renders the header & footer of a web page, while all other controllers render the body.
73 |
74 | **/Views/Shared/layout.html**
75 | ```
76 |
77 | ...
78 | {{content}}
79 |
80 | ```
81 |
82 | **/Controllers/Layout.cs**
83 | ``` csharp
84 | namespace MyProject.Controllers
85 | {
86 | public class Layout: Datasilk.Core.Web.Controller
87 | {
88 | public override string Render(string body = "")
89 | {
90 | //render layout
91 | var view = new View("/Views/Shared/layout.html");
92 | view["content"] = body;
93 | return view.Render();
94 | }
95 | }
96 | }
97 | ```
98 |
99 | **/Controllers/Home.cs**
100 | ``` csharp
101 | namespace MyProject.Controllers
102 | {
103 | public class Home: Layout
104 | {
105 | public override string Render(string body = "")
106 | {
107 | //render page
108 | var view = new View("/Views/Home/home.html");
109 | return base.Render(view.Render());
110 | }
111 | }
112 | }
113 | ```
114 |
115 | In the example above, the `Home` controller inherits from the `Layout` controller class, then calls `base.Render`, which will allow the `Layout` controller to inject the home page HTML content into the body of the HTML layout.
116 |
117 | > NOTE: `MyProject.Controllers.Home` is the default class that is instantiated if the URL contains a domain name with no path structure.
118 |
119 | ### Controller Routing
120 | To render Controllers based on complex URL paths, the MVC framework relies heavily on the first part of the request path to determine which class to instantiate. For example, if the user accesses the URL `http://localhost:7770/blog/2019/11/09/Progress-Report`, Datasilk Core MVC initializes the `MyProject.Controllers.Blog` class.
121 |
122 | The request path is split up into an array and passed into the `PathParts` field within the `Datasilk.Core.Web.Controller` class. The `PathParts` array is used to determine what type of content to load for the user. If we're loading a blog post like the above example, we can check the `PathParts` array to find year, month, and day, followed by the title of the blog post, and determine which blog post to load.
123 |
124 | ``` csharp
125 | namespace MyProject.Controllers
126 | {
127 | public class Blog: Datasilk.Core.Web.Controller
128 | {
129 | public override string Render(string body = "")
130 | {
131 | if(PathParts.length > 3){
132 | //get blog entry
133 | var date = new Date(PathParts[1] + "/" + PathParts[2] + "/" + PathParts[3]);
134 | var title = PathParts[3] || "";
135 | var view = new View("/Views/Blog/entry.html");
136 | //get blog entry from database using date & title
137 | var entry = Blog.GetEntry(date, title);
138 | view.Bind(entry);
139 | return view.Render();
140 | }
141 | else
142 | {
143 | //get blog home page
144 | var view = new View("/Views/Blog/blog.html");
145 | return view.Render();
146 | }
147 | }
148 | }
149 | ```
150 |
151 | ### Access Denied
152 | If your web page is protected behind security and must display an `Access Denied` page, you can use:
153 |
154 | ``` csharp
155 | return AccessDenied(this)
156 | ```
157 |
158 | from within your `Datasilk.Core.Web.Controller` class `Render` method, which will render a controller of your choosing with a response status code of `403`. The above example renders the Login controller.
159 |
160 | ### 500 Error
161 | If your controller experiences an error, you can show another controller:
162 |
163 | ``` csharp
164 | return Error(this)
165 | ```
166 |
167 | from within your `Datasilk.Core.Web.Controller` class `Render` method, which will render a controller of your choosing with a response status code of `500`. The above example renders the BrokenPage controller.
168 |
169 | ### 404 Error
170 | If your controller processes an unknown request path, you could show a 404 not found error:
171 |
172 | ``` csharp
173 | return Error404(this)
174 | ```
175 |
176 | from within your `Datasilk.Core.Web.Controller` class `Render` method, which will render a controller of your choosing with a response status code of `404`. The above example renders the NotFound controller.
177 |
178 | ## Web Services
179 | The Datasilk Core MVC framework comes with the ability to call *RESTful* web APIs. All web API calls are executed from `Datasilk.Core.Web.IService` interfaces.
180 |
181 | #### Example
182 |
183 | ``` csharp
184 | namespace MyProject.Services
185 | {
186 | public class User: Datasilk.Core.Web.Service
187 | {
188 | [POST]
189 | public string Authenticate(string email, string password)
190 | {
191 | //authenticate user
192 | //...
193 | if(authenticated)
194 | {
195 | return Success();
196 | }
197 | else
198 | {
199 | return AccessDenied("Incorrect email or password");
200 | }
201 | }
202 | }
203 | }
204 | ```
205 |
206 | In the example above, the user would send an `AJAX` `POST` via JavaScript to the Uri `/api/User/Authenticate` to authenticate their email & password. The data sent to the server over HTTPS would be formatted using the JavaScript function, `JSON.stringify({email:myemail, password:mypass})`, and the data properties would be mapped to C# method arguments.
207 |
208 | ### Web Service Response
209 | All `Datasilk.Core.Web.Service` methods should return a string. You can easily return a JSON object as well. Use the built-in method `JsonResponse(dynamic)` to ensure that the ContentType for your response is set to `text/json`.
210 |
211 | ``` csharp
212 | return JsonResponse(new {id = user.Id, firstname, lastname});
213 | ```
214 |
215 | ## Routes.cs
216 | If you would like to map requests to controllers & services directly, create a new class that inherits `Datasilk.Core.Web.Routes`. For example:
217 |
218 | ``` csharp
219 | using Microsoft.AspNetCore.Http;
220 | using Datasilk.Core.Web;
221 |
222 | public class Routes : Datasilk.Core.Web.Routes
223 | {
224 | public override IController FromControllerRoutes(HttpContext context, Parameters parameters, string name)
225 | {
226 | switch (name)
227 | {
228 | case "": case "home": return new Controllers.Home();
229 | case "login": return new Controllers.Login();
230 | case "dashboard": return new Controllers.Dashboard();
231 | }
232 | return null;
233 | }
234 |
235 | public override IService FromServiceRoutes(HttpContext context, Parameters parameters, string name)
236 | {
237 | switch (name)
238 | {
239 | case "": case "user": return new Services.User();
240 | case "dashboard": return new Services.Dashboard();
241 | }
242 | return null;
243 | }
244 | }
245 | ```
246 |
247 | #### Why Routing?
248 | By routing new class instances using the `new` keyword, you bypass the last resort for Datasilk Core MVC, which is to create an instance of your `Controller` or `Service` class using `Activator.CreateInstance`, taking 10 times the amount of CPU ticks to instatiate. You don't have to use routing, but it does speed up performance slightly.
249 |
--------------------------------------------------------------------------------
/Core/Web/View.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.IO;
6 | using System.Text;
7 | using System.Text.Json;
8 | using System.ComponentModel;
9 |
10 | public class ViewElement
11 | {
12 | //make sure all fields are read-only so user doesn't accidentally modify site-wide cached objects
13 | public string Name { get; }
14 | public string Html { get; }
15 | public Dictionary Vars { get; }
16 | public string Var { get; }
17 | public bool isBlock { get; }
18 | public int? blockEnd { get; }
19 |
20 | public ViewElement(string name, string html, Dictionary vars = null, string var = "", bool isblock = false, int? blockend = null)
21 | {
22 | Name = name;
23 | Html = html;
24 | Vars = vars;
25 | Var = var;
26 | isBlock = isblock;
27 | blockEnd = blockend;
28 | }
29 | }
30 |
31 | public static class ViewCache
32 | {
33 | public static Dictionary Cache { get; set; } = new Dictionary();
34 |
35 | public static void Remove(string file, string section = "")
36 | {
37 | if (Cache.ContainsKey(file + '/' + section) == true)
38 | {
39 | Cache.Remove(file + '/' + section);
40 | }
41 | }
42 |
43 | public static void Clear()
44 | {
45 | Cache.Clear();
46 | }
47 | }
48 |
49 | public class ViewChild
50 | {
51 | public ViewDictionary Data { get; set; }
52 | public Dictionary Fields = new Dictionary();
53 | public View Parent { get; set; }
54 |
55 | public ViewChild(View parent, string id)
56 | {
57 | Parent = parent;
58 | Data = new ViewDictionary(parent, id);
59 | //load related fields
60 | foreach (var item in parent.Fields)
61 | {
62 | if (item.Key.IndexOf(id + "-") == 0)
63 | {
64 | Fields.Add(item.Key.Replace(id + "-", ""), item.Value);
65 | }
66 | }
67 | }
68 |
69 | public string this[string key]
70 | {
71 | get
72 | {
73 | return Data[key];
74 | }
75 | set
76 | {
77 | Data[key] = value;
78 | }
79 | }
80 |
81 | public void Show(string blockKey)
82 | {
83 | Data[blockKey] = "True";
84 | }
85 |
86 | ///
87 | /// Binds an object to the view template. Use e.g. {{myprop}} or {{myobj.myprop}} to represent object fields & properties in template
88 | ///
89 | ///
90 | public void Bind(object obj, string root = "")
91 | {
92 | if (obj != null)
93 | {
94 | foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(obj))
95 | {
96 | object val = property.GetValue(obj);
97 | var name = (root != "" ? root + "." : "") + property.Name.ToLower();
98 | if (val == null)
99 | {
100 | Data[name] = "";
101 | }
102 | else if (val is string || val is int || val is long || val is double || val is decimal || val is short)
103 | {
104 | //add property value to dictionary
105 | Data[name] = val.ToString();
106 | }
107 | else if (val is bool)
108 | {
109 | Data[name] = (bool)val == true ? "1" : "0";
110 | }
111 | else if (val is DateTime)
112 | {
113 | Data[name] = ((DateTime)val).ToShortDateString() + " " + ((DateTime)val).ToShortTimeString();
114 | }
115 | else if (val is object)
116 | {
117 | //recurse child object for properties
118 | Bind(val, name);
119 | }
120 | }
121 | }
122 | }
123 | }
124 |
125 | public class ViewDictionary : Dictionary
126 | {
127 | private View _parent;
128 | private string _id;
129 | public ViewDictionary(View parent, string id)
130 | {
131 | _parent = parent;
132 | _id = id;
133 | }
134 |
135 | #pragma warning disable CS0108 // Member hides inherited member; missing new keyword
136 | public string this[string key]
137 | #pragma warning restore CS0108 // Member hides inherited member; missing new keyword
138 | {
139 | get
140 | {
141 | return _parent[_id + "-" + key];
142 | }
143 | set
144 | {
145 | _parent[_id + "-" + key] = value;
146 | }
147 | }
148 | }
149 |
150 | public class ViewPartial
151 | {
152 | public string Name { get; set; }
153 | public string Path { get; set; }
154 | public string Prefix { get; set; } //prefix used in html variable names after importing the partial
155 | }
156 |
157 | ///
158 | /// Allow developers to create pointers so that the mustache variable can use a
159 | /// pointer path instead of the actual relative path to a partial view file
160 | ///
161 | public static class ViewPartialPointers
162 | {
163 | public static List> Paths { get; set; } = new List>();
164 | }
165 |
166 | public class ViewData : IDictionary
167 | {
168 | private Dictionary _data = new Dictionary();
169 |
170 | public string this[string key]
171 | {
172 | get
173 | {
174 | return _data[key];
175 | }
176 | set
177 | {
178 | try
179 | {
180 | _data[key] = value;
181 | }
182 | catch (Exception)
183 | {
184 | _data.Add(key, value);
185 | }
186 |
187 | }
188 | }
189 |
190 | public bool this[string key, bool isBool]
191 | {
192 | get
193 | {
194 | if (_data[key] == "True")
195 | {
196 | return true;
197 | }
198 | return false;
199 | }
200 |
201 | set
202 | {
203 | if (value)
204 | {
205 | _data[key] = "True";
206 | }
207 | else
208 | {
209 | _data[key] = "False";
210 | }
211 | }
212 | }
213 |
214 | public ICollection Keys => _data.Keys;
215 |
216 | public ICollection Values => _data.Values;
217 |
218 | public int Count => _data.Count;
219 |
220 | public bool IsReadOnly => false;
221 |
222 | public void Add(string key, string value)
223 | {
224 | _data.Add(key, value);
225 | }
226 |
227 | public void Add(string key, bool value)
228 | {
229 | _data.Add(key, value.ToString());
230 | }
231 |
232 | public void Add(KeyValuePair item)
233 | {
234 | _data.Add(item.Key, item.Value);
235 | }
236 |
237 | public void Clear()
238 | {
239 | _data.Clear();
240 | }
241 |
242 | public bool Contains(KeyValuePair item)
243 | {
244 | return _data.Contains(item);
245 | }
246 |
247 | public bool ContainsKey(string key)
248 | {
249 | return _data.ContainsKey(key);
250 | }
251 |
252 | public void CopyTo(KeyValuePair[] array, int arrayIndex)
253 | {
254 | throw new NotImplementedException();
255 | }
256 |
257 | public IEnumerator> GetEnumerator()
258 | {
259 | return _data.GetEnumerator();
260 | }
261 |
262 | public bool Remove(string key)
263 | {
264 | return _data.Remove(key);
265 | }
266 |
267 | public bool Remove(KeyValuePair item)
268 | {
269 | if (_data.Contains(item))
270 | {
271 | return _data.Remove(item.Key);
272 | }
273 | return false;
274 | }
275 |
276 | public bool TryGetValue(string key, out string value)
277 | {
278 | return _data.TryGetValue(key, out value);
279 | }
280 |
281 | IEnumerator IEnumerable.GetEnumerator()
282 | {
283 | return _data.GetEnumerator();
284 | }
285 | }
286 |
287 | public class ViewOptions
288 | {
289 | public string Html { get; set; }
290 | public string File { get; set; }
291 | public string Section { get; set; }
292 | }
293 |
294 | public static class ViewConfig
295 | {
296 | public static string Path { get; set; }
297 | }
298 |
299 | public class View
300 | {
301 | ///
302 | /// Prep class that collects data and then assigns the read-only properties from the prep data
303 | ///
304 | private class ViewPrep
305 | {
306 | public List Elements { get; set; } = new List();
307 | public List Partials { get; set; } = new List();
308 | public Dictionary Fields { get; set; } = new Dictionary();
309 | public string Filename { get; set; } = "";
310 | public string HTML { get; set; } = "";
311 | public string Section { get; set; } = ""; //section of the template to use for rendering
312 | }
313 |
314 | private readonly List _Elements;
315 | private readonly List _Partials;
316 | private readonly Dictionary _Fields;
317 | private readonly string _Filename;
318 | private readonly string _HTML;
319 | private readonly string _Section; //section of the template to use for rendering
320 |
321 | public List Elements { get { return _Elements; } }
322 | public List Partials { get { return _Partials; } }
323 | public Dictionary Fields { get { return _Fields; } }
324 | public string Filename { get { return _Filename; } }
325 | public string HTML { get { return _HTML; } }
326 | public string Section { get { return _Section; } } //section of the template to use for rendering
327 |
328 | public ViewData Data;
329 | private Dictionary children = null;
330 |
331 | public ViewChild Child(string id)
332 | {
333 | if (children == null)
334 | {
335 | children = new Dictionary();
336 | }
337 | if (!children.ContainsKey(id))
338 | {
339 | children.Add(id, new ViewChild(this, id));
340 | }
341 | return children[id];
342 | }
343 |
344 | public View(ViewOptions options)
345 | {
346 | string file = "";
347 | string section = "";
348 | string html = "";
349 | if (options.Html != null && options.Html != "")
350 | {
351 | html = options.Html;
352 | }
353 | else if(options.File != "")
354 | {
355 | file = options.File;
356 | section = options.Section;
357 | }
358 | var prep = Parse(file, section, html);
359 | _Elements = prep.Elements;
360 | _Partials = prep.Partials;
361 | _Fields = prep.Fields;
362 | _Filename = prep.Filename;
363 | _HTML = prep.HTML;
364 | _Section = prep.Section;
365 | }
366 |
367 | ///
368 | /// Use a template file to bind data and replace mustache variables with data, e.g. {{my-name}} is replaced with value of View["my-name"]
369 | ///
370 | /// relative path to the template file
371 | /// Dictionary object used to save cached, parsed template to
372 | public View(string file, Dictionary cache = null)
373 | {
374 | var prep = Parse(file, "", "", cache);
375 | _Elements = prep.Elements;
376 | _Partials = prep.Partials;
377 | _Fields = prep.Fields;
378 | _Filename = prep.Filename;
379 | _HTML = prep.HTML;
380 | _Section = prep.Section;
381 | }
382 |
383 | ///
384 | /// Use a template file to bind data and replace mustache variables with data, e.g. {{my-name}} is replaced with value of View["my-name"]
385 | ///
386 | /// relative path to the template file
387 | /// section name within the template file to load, e.g. {{my-section}} ... {{/my-section}}
388 | /// Dictionary object used to save cached, parsed template to
389 | public View(string file, string section, Dictionary cache = null)
390 | {
391 | var prep = Parse(file, section.ToLower(), "", cache);
392 | _Elements = prep.Elements;
393 | _Partials = prep.Partials;
394 | _Fields = prep.Fields;
395 | _Filename = prep.Filename;
396 | _HTML = prep.HTML;
397 | _Section = prep.Section;
398 | }
399 |
400 | ///
401 | /// Most common way to load a view as it will access the ViewCache first, then generate a new View object if it is not cached
402 | ///
403 | /// relative path to your file
404 | /// The name of a mustache block found inside the file
405 | /// Either the cached View or a new View
406 | public static View Load(string file, string section = "")
407 | {
408 | return new View(file, section);
409 | }
410 |
411 | public string this[string key]
412 | {
413 | get
414 | {
415 | try {
416 | return Data[key.ToLower()];
417 | }
418 | catch (Exception)
419 | {
420 | Data.Add(key.ToLower(), "");
421 | return "";
422 | }
423 | }
424 | set
425 | {
426 | try
427 | {
428 | Data[key.ToLower()] = value;
429 | }
430 | catch (Exception)
431 | {
432 | Data.Add(key.ToLower(), value);
433 | }
434 |
435 | }
436 | }
437 |
438 | public bool ContainsKey(string key)
439 | {
440 | return Data.ContainsKey(key.ToLower());
441 | }
442 |
443 | public void Show(string blockKey)
444 | {
445 | Data[blockKey.ToLower(), true] = true;
446 | }
447 |
448 | ///
449 | /// Clears any bound data, which is useful when reusing the same View object in a loop
450 | ///
451 | public void Clear()
452 | {
453 | Data = new ViewData();
454 | }
455 |
456 | ///
457 | /// If the Element item at the given index is a mustache block, return contents of entire block
458 | ///
459 | ///
460 | /// Index of Elements item
461 | public string GetBlock(int ElementIndex)
462 | {
463 | if(ElementIndex < 0) { return ""; }
464 | var html = new StringBuilder();
465 | var elem = Elements[ElementIndex];
466 | if (!elem.isBlock) { return ""; }
467 | html.Append(elem.Html);
468 | var i = ElementIndex + 1;
469 | while (i < Elements.Count)
470 | {
471 | var el = Elements[i];
472 | if(el.Name == "/" + elem.Name) { break; }
473 | html.Append("{{" + el.Name + (el.Var != null && el.Var != "" ? " " + el.Var : "") + "}}" + el.Html);
474 | i++;
475 | }
476 | return html.ToString();
477 | }
478 |
479 | ///
480 | /// If the Element is a mustache block, return contents of entire block
481 | ///
482 | ///
483 | ///
484 | public string GetBlock(ViewElement Element)
485 | {
486 | var index = Elements.IndexOf(Element);
487 | if(index >= 0)
488 | {
489 | return GetBlock(index);
490 | }
491 | return "";
492 | }
493 |
494 | ///
495 | /// Binds an object to the view template. Use e.g. {{myprop}} or {{myobj.myprop}} to represent
496 | /// object fields & properties in template
497 | ///
498 | /// The object that you wish to bind to mustache variables within the View contents
499 | /// Specify the root object name found within your mustache variables (e.g. {{myobj.myprop}} would use "myobj" as the root)
500 | /// Date/Time string formatting to use for DateTime objects
501 | public void Bind(object obj, string name = "", string dtFormat = "M/dd/yyyy h:mm tt")
502 | {
503 | if (obj != null)
504 | {
505 | foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(obj))
506 | {
507 | object val = property.GetValue(obj);
508 | var varname = (name != "" ? name + "." : "") + property.Name.ToLower();
509 | if (val == null)
510 | {
511 | Data[varname] = "";
512 | }
513 | else if (val is string || val is int || val is long || val is double || val is decimal || val is short)
514 | {
515 | //add property value to dictionary
516 | Data[varname] = val.ToString();
517 | }
518 | else if (val is bool)
519 | {
520 | Data[varname] = (bool)val == true ? "1" : "0";
521 | }
522 | else if (val is DateTime)
523 | {
524 | Data[varname] = ((DateTime)val).ToString(dtFormat);
525 | }
526 | else if (val is object)
527 | {
528 | //recurse child object for properties
529 | Bind(val, varname);
530 | }
531 | }
532 | }
533 | }
534 |
535 | ///
536 | /// Get relative path by utilizing the ViewPartialPointers.Paths property to translate path into relative path
537 | ///
538 | ///
539 | ///
540 | public string GetPath(string path)
541 | {
542 | if (path[0] == '/') { path = path.Substring(1); }
543 |
544 | //replace pointer paths with relative paths
545 | var pointer = ViewPartialPointers.Paths.Where(a => path.IndexOf(a.Key) == 0).Select(p => new { p.Key, p.Value }).FirstOrDefault();
546 | if (pointer != null)
547 | {
548 | path = path.Replace(pointer.Key, pointer.Value);
549 | }
550 | return '/' + path;
551 | }
552 |
553 | private ViewPrep Parse(string file, string section = "", string html = "", Dictionary cache = null, bool loadPartials = true)
554 | {
555 | var prep = new ViewPrep();
556 | prep.Filename = file;
557 | prep.Section = section;
558 | Data = new ViewData();
559 | if (file != "")
560 | {
561 | if (cache == null && ViewCache.Cache != null)
562 | {
563 | cache = ViewCache.Cache;
564 | }
565 |
566 | if (cache != null)
567 | {
568 | if (cache.ContainsKey(file + '/' + section) == true)
569 | {
570 | var cached = cache[file + '/' + section];
571 | prep.Elements = cached.Elements;
572 | prep.Fields = cached.Fields;
573 | prep.Partials = cached.Partials;
574 | return prep;
575 | }
576 | }
577 | }
578 |
579 | //was NOT able to retrieve cached object
580 | prep.Elements = new List();
581 |
582 | //try loading file from disk
583 | if (file != "")
584 | {
585 | if (File.Exists(MapPath(file)))
586 | {
587 | prep.HTML = File.ReadAllText(MapPath(file));
588 | }
589 | }
590 | else
591 | {
592 | prep.HTML = html;
593 | }
594 | if (prep.HTML.Trim() == "") { return prep; }
595 |
596 | //next, find the group of code matching the view section name
597 | if (section != "")
598 | {
599 | //find starting tag (optionally with arguments)
600 | //for example: {{button (name:submit, style:outline)}}
601 | int[] e = new int[3];
602 | e[0] = prep.HTML.IndexOf("{{" + section);
603 | if (e[0] >= 0)
604 | {
605 | e[1] = prep.HTML.IndexOf("}", e[0]);
606 | if (e[1] - e[0] <= 256)
607 | {
608 | e[1] = prep.HTML.IndexOf("{{/" + section + "}}", e[1]);
609 | }
610 | else { e[0] = -1; }
611 |
612 | }
613 |
614 | if (e[0] >= 0 & e[1] > (e[0] + section.Length + 4))
615 | {
616 | e[2] = e[0] + 4 + section.Length;
617 | prep.HTML = prep.HTML.Substring(e[2], e[1] - e[2]);
618 | }
619 | }
620 |
621 | //get view from html code
622 | var dirty = true;
623 | string[] arr;
624 | var i = 0;
625 | var s = 0;
626 | var c = 0;
627 | var u = 0;
628 | var u2 = 0;
629 | string viewElem_Name;
630 | string viewElem_Html;
631 | Dictionary viewElem_Vars;
632 | string viewElem_Var;
633 | bool viewElem_isBlock;
634 | int? viewElem_blockEnd;
635 |
636 | while (dirty == true)
637 | {
638 | dirty = false;
639 | arr = prep.HTML.Split("{{");
640 | i = 0;
641 | s = 0;
642 | c = 0;
643 | u = 0;
644 | u2 = 0;
645 |
646 | //types of view elements
647 |
648 | // {{title}} = variable
649 | // {{address}} {{/address}} = block
650 | // {{button "/ui/button-medium"}} = HTML include
651 | // {{button "/ui/button" title:"save", onclick="do.this()"}} = HTML include with properties
652 | // {{page-list path:"blog", length:"5"}} = HTML variable with properties
653 |
654 | //first, load all HTML includes
655 | for (var x = 0; x < arr.Length; x++)
656 | {
657 | if (x == 0 && prep.HTML.IndexOf(arr[x]) == 0)
658 | {
659 | arr[x] = "{!}" + arr[x];
660 | }
661 | else if (arr[x].Trim() != "")
662 | {
663 | i = arr[x].IndexOf("}}");
664 | s = arr[x].IndexOf(':');
665 | u = arr[x].IndexOf('"');
666 | if (i > 0 && u > 0 && u < i - 2 && (s == -1 || s > u) && loadPartials == true)
667 | {
668 | //read partial include & load HTML from another file
669 | viewElem_Name = arr[x].Substring(0, u - 1).Trim().ToLower();
670 | u2 = arr[x].IndexOf('"', u + 2);
671 | var partial_path = arr[x].Substring(u + 1, u2 - u - 1);
672 | if (partial_path.Length > 0) {
673 | partial_path = GetPath(partial_path);
674 |
675 | //load the view HTML
676 | var newScaff = new View(partial_path, "", cache);
677 |
678 | //check for HTML include variables
679 | if (i - u2 > 0)
680 | {
681 | var vars = arr[x].Substring(u2 + 1, i - (u2 + 1)).Trim();
682 | if (vars.IndexOf(":") > 0)
683 | {
684 | //HTML include variables exist
685 | try
686 | {
687 | var kv = JsonSerializer.Deserialize>("{" + vars + "}");
688 | foreach (var kvp in kv)
689 | {
690 | newScaff[kvp.Key] = kvp.Value;
691 | }
692 | }
693 | catch (Exception)
694 | {
695 | }
696 | }
697 | }
698 |
699 | //rename child view variables, adding a prefix
700 | var ht = newScaff.Render(newScaff.Data, false);
701 | var y = 0;
702 | var prefix = viewElem_Name + "-";
703 | while (y >= 0)
704 | {
705 | y = ht.IndexOf("{{", y);
706 | if (y < 0) { break; }
707 | if (ht.Substring(y + 2, 1) == "/")
708 | {
709 | ht = ht.Substring(0, y + 3) + prefix + ht.Substring(y + 3);
710 | }
711 | else
712 | {
713 | ht = ht.Substring(0, y + 2) + prefix + ht.Substring(y + 2);
714 | }
715 | y += 2;
716 | }
717 |
718 | prep.Partials.Add(new ViewPartial() { Name = viewElem_Name, Path = partial_path, Prefix = prefix });
719 | prep.Partials.AddRange(newScaff.Partials.Select(a =>
720 | {
721 | var partial = a;
722 | partial.Prefix = prefix + partial.Prefix;
723 | return partial;
724 | })
725 | );
726 | arr[x] = "{!}" + ht + arr[x].Substring(i + 2);
727 | }
728 | else
729 | {
730 | //partial not found
731 | arr[x] = "{!}" + arr[x].Substring(i + 2);
732 | }
733 |
734 | prep.HTML = JoinHTML(arr);
735 | dirty = true; //HTML is dirty, restart loop
736 | break;
737 | }
738 | }
739 |
740 | }
741 | if (dirty == false)
742 | {
743 | //next, process variables & blocks
744 | for (var x = 0; x < arr.Length; x++)
745 | {
746 | if (x == 0 && prep.HTML.IndexOf(arr[0].Substring(3)) == 0)//skip "{!}" using substring
747 | {
748 | //first element is HTML only
749 | prep.Elements.Add(new ViewElement("", arr[x].Substring(3)));
750 | }
751 | else if (arr[x].Trim() != "")
752 | {
753 | i = arr[x].IndexOf("}}");
754 | s = arr[x].IndexOf(' ');
755 | c = arr[x].IndexOf(':');
756 | u = arr[x].IndexOf('"');
757 | viewElem_Name = "";
758 | viewElem_Html = "";
759 | viewElem_Vars = null;
760 | viewElem_Var = "";
761 | viewElem_isBlock = false;
762 | viewElem_blockEnd = null;
763 | if (i > 0)
764 | {
765 | viewElem_Html = arr[x].Substring(i + 2);
766 |
767 | //get variable name
768 | if (s < i && s > 0)
769 | {
770 | //found space
771 | viewElem_Name = arr[x].Substring(0, s).Trim().ToLower();
772 | }
773 | else
774 | {
775 | //found tag end
776 | viewElem_Name = arr[x].Substring(0, i).Trim().ToLower();
777 | }
778 | //since each variable could have the same name but different parameters,
779 | //save the full name & parameters as the name
780 | //viewElem.Name = arr[x].Substring(0, i);
781 |
782 | if (!viewElem_Name.Contains('/'))
783 | {
784 | if (prep.Fields.ContainsKey(viewElem_Name))
785 | {
786 | //add element index to existing field
787 | var field = prep.Fields[viewElem_Name];
788 | prep.Fields[viewElem_Name] = field.Append(prep.Elements.Count).ToArray();
789 | }
790 | else
791 | {
792 | //add field with element index
793 | prep.Fields.Add(viewElem_Name, new int[] { prep.Elements.Count });
794 | }
795 | //check if view element is a block
796 | for(var y = x + 1; y < arr.Length; y++)
797 | {
798 | if(arr[y].IndexOf("/" + viewElem_Name + "}}") == 0)
799 | {
800 | viewElem_isBlock = true;
801 | viewElem_blockEnd = y;
802 | break;
803 | }
804 | }
805 | }
806 | if (s < i && s > 0)
807 | {
808 | //get optional variables stored within tag
809 | var vars = arr[x].Substring(s + 1, i - s - 1);
810 | viewElem_Var = vars.Trim();
811 | //clean vars
812 | var vi = 0;
813 | var ve = 0;
814 | var inq = false;//inside quotes
815 | var vItems = new List();
816 | while(vi < vars.Length)
817 | {
818 | var a = vars.Substring(vi, 1);
819 | if(a == "\"") { inq = !inq ? true : false; }
820 | if((inq == false && (a == ":" || a == ",")) || vi == vars.Length - 1)
821 | {
822 | if(vi == vars.Length - 1) { vi = vars.Length; }
823 | var r = vars.Substring(ve, vi - ve).Trim();
824 | if(r.Substring(0, 1) != "\"") { r = "\"" + r + "\""; }
825 | vItems.Add(r);
826 | ve = vi + 1;
827 | }
828 | vi++;
829 | }
830 | inq = false;
831 | vars = "";
832 | foreach(var item in vItems)
833 | {
834 | vars += item + (!inq ? ":" : ",");
835 | inq = !inq ? true : false;
836 | }
837 | if(vars[^1] == ',') { vars = vars.Substring(0, vars.Length - 1); }
838 | try
839 | {
840 | viewElem_Vars = JsonSerializer.Deserialize>("{" + vars + "}");
841 | }
842 | catch (Exception){}
843 | }
844 | }
845 | else
846 | {
847 | viewElem_Name = "";
848 | viewElem_Html = arr[x];
849 | }
850 | prep.Elements.Add(new ViewElement(viewElem_Name, viewElem_Html, viewElem_Vars, viewElem_Var, viewElem_isBlock, viewElem_blockEnd));
851 | }
852 | }
853 | }
854 | }
855 | //cache the view data
856 | if (cache != null && !cache.ContainsKey(file + "/" + section))
857 | {
858 | cache.Add(file + '/' + section, this);
859 | }
860 |
861 | return prep;
862 | }
863 |
864 | private string JoinHTML(string[] html)
865 | {
866 | for (var x = 0; x < html.Length; x++)
867 | {
868 | if (html[x].Substring(0, 3) == "{!}")
869 | {
870 | html[x] = html[x].Substring(3);
871 | }
872 | else
873 | {
874 | html[x] = "{{" + html[x];
875 | }
876 | }
877 | return string.Join("", html);
878 | }
879 |
880 | public string Render()
881 | {
882 | return Render(Data);
883 | }
884 |
885 | public string Render(List excludeElements = null)
886 | {
887 | return Render(Data, true, excludeElements);
888 | }
889 |
890 | public string Render(ViewData nData, bool hideElements = true, List excludeElements = null)
891 | {
892 | if (Elements.Count == 0) { return ""; }
893 | var html = new StringBuilder();
894 | for (var x = 0; x < Elements.Count; x++)
895 | {
896 | //check for excluded elements
897 | if(excludeElements != null && excludeElements.Contains(Elements[x]))
898 | {
899 | html.Append(Elements[x].Html);
900 | continue;
901 | }
902 | //check if view item is an enclosing tag or just a variable
903 | if (Elements[x].isBlock == false && nData.ContainsKey(Elements[x].Name))
904 | {
905 | //inject string into view variable
906 | html.Append(nData[Elements[x].Name] + Elements[x].Html);
907 | }
908 | else
909 | {
910 | if (hideElements == true && Elements[x].blockEnd != null && !(Elements[x].isBlock && Elements[x].Name.IndexOf('/') < 0 && nData.ContainsKey(Elements[x].Name) && nData[Elements[x].Name] == "True"))
911 | {
912 | //skip elements if user decides not to show a block
913 | x = Elements[x].blockEnd.Value - 1;
914 | }
915 | else if(hideElements == false && Elements[x].Name != "")
916 | {
917 | //passively add htm, ignoring view variable
918 | html.Append("{{" + Elements[x].Name +
919 | (!string.IsNullOrEmpty(Elements[x].Var) ? " " + Elements[x].Var : "") +
920 | "}}" + Elements[x].Html);
921 | }
922 | else
923 | {
924 | html.Append(Elements[x].Html);
925 | }
926 | }
927 | }
928 | //render scaffolding as HTML string
929 | return html.ToString();
930 | }
931 |
932 | public string Get(string name)
933 | {
934 | var index = Elements.FindIndex(c => c.Name == name);
935 | if (index < 0) { return ""; }
936 | var part = Elements[index];
937 | var html = part.Html;
938 | for (var x = index + 1; x < Elements.Count; x++)
939 | {
940 | part = Elements[x];
941 | if (part.Name == "/" + name) { break; }
942 |
943 | //add inner view elements
944 | if (part.Name.IndexOf('/') < 0)
945 | {
946 | try
947 | {
948 | if (Data[part.Name, true] == true)
949 | {
950 | html += Get(part.Name);
951 | }
952 | }
953 | catch (Exception) { }
954 | }
955 | else
956 | {
957 | html += part.Html;
958 | }
959 |
960 | }
961 |
962 | return html;
963 | }
964 |
965 | private static string MapPath(string strPath = "")
966 | {
967 | if (string.IsNullOrEmpty(ViewConfig.Path))
968 | {
969 | ViewConfig.Path = Path.GetFullPath(".");
970 | }
971 | var path = strPath.Replace("\\", "/");
972 | if (path.Substring(0, 1) == "/") { path = path.Substring(1); }
973 | return Path.Combine(ViewConfig.Path, path);
974 | }
975 | }
--------------------------------------------------------------------------------
/Core/Middleware/Mvc.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Web;
3 | using System.Net;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Text.Json;
8 | using System.Text.RegularExpressions;
9 | using System.Reflection;
10 | using System.Collections.Generic;
11 | using System.Threading.Tasks;
12 | using Microsoft.AspNetCore.Http;
13 | using Microsoft.AspNetCore.Http.Features;
14 | using Microsoft.Extensions.Logging;
15 | using Microsoft.AspNetCore.Mvc;
16 |
17 | namespace Datasilk.Core.Middleware
18 | {
19 | public class Mvc
20 | {
21 | private RequestDelegate _next { get; set; }
22 | private readonly ILogger Logger;
23 | private readonly MvcOptions options;
24 | private int requestCount = 0;
25 | private Web.Routes routes;
26 | private Dictionary controllers = new Dictionary();
27 | private Dictionary services = new Dictionary();
28 | private Dictionary controllerNamespaces = new Dictionary();
29 | private Dictionary serviceNamespaces = new Dictionary();
30 | private Dictionary serviceMethods = new Dictionary();
31 |
32 | private string[] phishingPaths = new string[] {
33 | "phpmyadmin/", "webfig/", ".env", "config/getuser", "app", "shell", "boaform/admin/formLogin",
34 | "api/jsonws/invoke", "solr/", "CFIDE/administrator/", "latest/meta-data/", "solr/admin/info/system"
35 | };
36 |
37 | private string[] phishingParamKeys = new string[]
38 | {
39 | "XDEBUG_SESSION_START"
40 | };
41 |
42 | public Mvc(RequestDelegate next, MvcOptions options, ILoggerFactory loggerFactory)
43 | {
44 | _next = next;
45 | Logger = loggerFactory.CreateLogger();
46 | this.options = options;
47 | routes = this.options.Routes;
48 | var assemblies = new List { Assembly.GetCallingAssembly() };
49 | if (!assemblies.Contains(Assembly.GetExecutingAssembly()))
50 | {
51 | assemblies.Add(Assembly.GetExecutingAssembly());
52 | }
53 | if (!assemblies.Contains(Assembly.GetEntryAssembly()))
54 | {
55 | assemblies.Add(Assembly.GetEntryAssembly());
56 | }
57 |
58 | foreach (var assembly in assemblies)
59 | {
60 | //get a list of controllers from the assembly
61 | var types = assembly.GetTypes()
62 | .Where(type => typeof(Web.IController).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract && type.Name != "Controller").ToList();
63 | foreach (var type in types)
64 | {
65 | if (!type.Equals(typeof(Web.IController)))
66 | {
67 | controllers.Add((type.FullName).ToLower(), type);
68 | }
69 | }
70 |
71 | types = assembly.GetTypes()
72 | .Where(type => typeof(Web.IService).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract && type.Name != "Service").ToList();
73 | foreach (var type in types)
74 | {
75 | if (!type.Equals(typeof(Web.IService)))
76 | {
77 | services.Add((type.FullName).ToLower(), type);
78 | }
79 | }
80 | }
81 |
82 | if (options.LogRequests)
83 | {
84 | Logger.LogInformation("Datasilk Core MVC started ({0} controllers, {1} services)",
85 | controllers.Count, services.Count);
86 | }
87 | if (options.WriteDebugInfoToConsole)
88 | {
89 | Console.WriteLine("Datasilk Core MVC started ({0} controllers, {1} services)",
90 | controllers.Count, services.Count);
91 | }
92 | }
93 |
94 | public async Task Invoke(HttpContext context)
95 | {
96 | if (options.IgnoreRequestBodySize == true)
97 | {
98 | context.Features.Get().MaxRequestBodySize = null;
99 | }
100 |
101 | var requestStart = DateTime.Now;
102 | var path = CleanPath(context.Request.Path.ToString());
103 |
104 | //trap phishing requests that contain specific path values
105 | if (phishingPaths.Contains(path))
106 | {
107 | context.Response.StatusCode = 500;
108 | if (options.InvokeNext) { await _next.Invoke(context); }
109 | return;
110 | }
111 |
112 | var paths = path.Split('/').Where(a => a != "").ToArray();
113 | requestCount++;
114 |
115 | if (paths.Length > 0 && paths[^1].IndexOf(".") > 0)
116 | {
117 | //do not process files, but instead return a 404 error
118 | context.Response.StatusCode = 404;
119 | if (options.InvokeNext) { await _next.Invoke(context); }
120 | return;
121 | }
122 | //get parameters from request body
123 | var parameters = await GetParameters(context);
124 |
125 | //trap phishing requests that contain specific parameter keys
126 | if (phishingParamKeys.Any(a => parameters.Any(b => b.Key == a)))
127 | {
128 | context.Response.StatusCode = 500;
129 | if (options.InvokeNext) { await _next.Invoke(context); }
130 | return;
131 | }
132 |
133 | if (options.LogRequests)
134 | {
135 | Logger.LogDebug("{0} [{7}] {1} {2} ({3}), {4} kb, # {5}, params: {6}",
136 | DateTime.Now.ToString("hh:mm:ss"),
137 | context.Request.Method,
138 | string.IsNullOrEmpty(path) ? "/" : path,
139 | Math.Round(((DateTime.Now - requestStart)).TotalMilliseconds) + " ms",
140 | ((parameters.RequestBody.Length * sizeof(char)) / 1024.0).ToString("N1"),
141 | requestCount,
142 | string.Join('&', parameters.Select(a => a.Key + "=" + a.Value).ToArray()),
143 | context.Connection.RemoteIpAddress);
144 | }
145 | if (options.WriteDebugInfoToConsole)
146 | {
147 | Console.WriteLine("{0} [{7}] {1} {2} ({3}), {4} kb, # {5}, params: {6}",
148 | DateTime.Now.ToString("hh:mm:ss"),
149 | context.Request.Method,
150 | string.IsNullOrEmpty(path) ? "/" : path,
151 | Math.Round(((DateTime.Now - requestStart)).TotalMilliseconds) + " ms",
152 | ((parameters.RequestBody.Length * sizeof(char)) / 1024.0).ToString("N1"),
153 | requestCount,
154 | string.Join('&', parameters.Select(a => a.Key + "=" + a.Value).ToArray()),
155 | context.Connection.RemoteIpAddress);
156 | }
157 |
158 | if (paths.Length > 1 && options.ServicePaths.Contains(paths[0]) == true)
159 | {
160 | //handle web API requests
161 | ProcessService(context, path, paths, parameters);
162 | }
163 | else
164 | {
165 | //handle controller requests
166 | ProcessController(context, path, paths, parameters);
167 | }
168 | await context.Response.CompleteAsync();
169 | if (options.InvokeNext && context.Response.HasStarted == false) {
170 | if (options.AwaitInvoke)
171 | {
172 | await _next.Invoke(context);
173 | }
174 | else
175 | {
176 | Task task = _next.Invoke(context);
177 | }
178 | }
179 | }
180 |
181 | private void ProcessController(HttpContext context, string path, string[] pathParts, Web.Parameters parameters)
182 | {
183 | var html = "";
184 | var newpaths = path.Split('?', 2)[0].Split('/');
185 | var page = routes.FromControllerRoutes(context, parameters, newpaths[0].ToLower());
186 |
187 | if (page == null)
188 | {
189 | //page is not part of any known routes, try getting page class manually
190 | var className = (newpaths[0] == "" ? options.DefaultController : newpaths[0].Replace("-", " ")).Replace(" ", "").ToLower();
191 |
192 | //get namespace from className
193 | var classNamespace = "";
194 | if (!controllerNamespaces.ContainsKey(className))
195 | {
196 | //find namespace from compiled list of service namespaces
197 | classNamespace = controllers.Keys.FirstOrDefault(a => a.Contains(className));
198 | if (classNamespace != "")
199 | {
200 | controllerNamespaces.Add(className, classNamespace);
201 | }
202 | else
203 | {
204 | //could not find controller
205 | page = new Web.Controller()
206 | {
207 | Context = context,
208 | Parameters = parameters,
209 | Path = path,
210 | PathParts = pathParts
211 | };
212 | page.Init();
213 | html = page.Error404();
214 | return;
215 | }
216 | }
217 | else
218 | {
219 | classNamespace = controllerNamespaces[className];
220 | }
221 | //found controller
222 | page = (Web.IController)Activator.CreateInstance(controllers[classNamespace]);
223 | }
224 |
225 | if (page != null)
226 | {
227 | //render page
228 | page.Context = context;
229 | page.Parameters = parameters;
230 | page.Path = path;
231 | page.PathParts = pathParts;
232 | page.Init();
233 | html = page.Render();
234 | }
235 | else
236 | {
237 | //show 404 error
238 | page = new Web.Controller()
239 | {
240 | Context = context,
241 | Parameters = parameters,
242 | Path = path,
243 | PathParts = pathParts
244 | };
245 | page.Init();
246 | html = page.Error404();
247 | }
248 |
249 | //unload Datasilk Core
250 | page.Dispose();
251 | page = null;
252 |
253 | //send response back to client
254 | if (context.Response.ContentType == null ||
255 | context.Response.ContentType == "")
256 | {
257 | context.Response.ContentType = "text/html";
258 | }
259 | if (context.Response.HasStarted == false)
260 | {
261 | //context.Response.ContentLength = html.Length;
262 | context.Response.WriteAsync(html);
263 | }
264 | }
265 |
266 | private void ProcessService(HttpContext context, string path, string[] pathParts, Web.Parameters parameters)
267 | {
268 | //load service class from URL path
269 | string className = CleanReflectionName(pathParts[1].Replace("-", "")).ToLower();
270 | string methodName = pathParts.Length > 2 ? pathParts[2] : options.DefaultServiceMethod;
271 | if (pathParts.Length >= 4)
272 | {
273 | //path also contains extra namespace path(s)
274 | for (var x = 2; x < pathParts.Length - 1; x++)
275 | {
276 | //add extra namespaces
277 | className += "." + CleanReflectionName(pathParts[x].Replace("-", "")).ToLower();
278 | }
279 | //get method name at end of path
280 | methodName = CleanReflectionName(pathParts[^1].Replace("-", ""));
281 | }
282 |
283 | //get service type
284 | Type type = null;
285 |
286 | //get instance of service class
287 | var service = routes.FromServiceRoutes(context, parameters, className);
288 | if (service == null)
289 | {
290 | //get namespace from className
291 | var classNamespace = "";
292 | if (!serviceNamespaces.ContainsKey(className))
293 | {
294 | //find namespace from compiled list of service namespaces
295 | classNamespace = services.Keys.FirstOrDefault(a => a.Contains(className));
296 | if (classNamespace != "")
297 | {
298 | serviceNamespaces.Add(className, classNamespace);
299 | }
300 | else
301 | {
302 | context.Response.StatusCode = 404;
303 | context.Response.WriteAsync("service does not exist");
304 | return;
305 | }
306 | }
307 | else
308 | {
309 | classNamespace = serviceNamespaces[className];
310 | }
311 | service = (Web.IService)Activator.CreateInstance(services[classNamespace]);
312 | }
313 |
314 | //check if service class was found
315 | type = service.GetType();
316 | if (type == null)
317 | {
318 | context.Response.StatusCode = 404;
319 | context.Response.WriteAsync("service does not exist");
320 | return;
321 | }
322 |
323 | //update service fields
324 | service.Context = context;
325 | service.Parameters = parameters;
326 | service.Path = path;
327 | service.PathParts = pathParts;
328 | service.Init();
329 | if (context.Response.StatusCode >= 400)
330 | {
331 | //service init returned an error status code
332 | return;
333 | }
334 |
335 | //get class method from service type
336 | var serviceMethodName = className + "/" + methodName;
337 | MethodInfo method;
338 | if (serviceMethods.ContainsKey(serviceMethodName))
339 | {
340 | method = serviceMethods[serviceMethodName];
341 | }
342 | else
343 | {
344 | method = type.GetMethod(methodName);
345 | serviceMethods.Add(serviceMethodName, method);
346 | }
347 |
348 | //check if method exists
349 | if (method == null)
350 | {
351 | context.Response.StatusCode = 404;
352 | context.Response.WriteAsync("Web service method " + methodName + " does not exist");
353 | return;
354 | }
355 |
356 | //check request method
357 | if (!CanUseRequestMethod(context, method))
358 | {
359 | context.Response.StatusCode = 400;
360 | context.Response.WriteAsync("Web service method " + methodName + " does not accept the '" + context.Request.Method + "' request method");
361 | return;
362 | }
363 |
364 | //try to cast params to correct types
365 | var paramVals = MapParameters(method.GetParameters(), parameters, method);
366 |
367 | //execute service method
368 | string result = (string)method.Invoke(service, paramVals);
369 | service.Dispose();
370 |
371 | if (context.Response.HasStarted == false)
372 | {
373 | if (context.Response.ContentType == null)
374 | {
375 | if (result.IndexOf("{") < 0)
376 | {
377 | context.Response.ContentType = "text/plain";
378 | }
379 | else if (result.IndexOf("{") >= 0 && result.IndexOf("}") > 0)
380 | {
381 | context.Response.ContentType = "text/json";
382 | }
383 | }
384 | if (result != null)
385 | {
386 | //context.Response.ContentLength = result.Length;
387 | context.Response.WriteAsync(result);
388 | }
389 | else
390 | {
391 | context.Response.WriteAsync("{}");
392 | }
393 | }
394 | }
395 |
396 | #region "Helpers"
397 | private string CleanPath(string path)
398 | {
399 | //check for malicious path input
400 | if (path == "") { return path; }
401 | if (path[0] == '/') { path = path.Substring(1); }
402 | if (path.Replace("/", "").Replace("-", "").Replace("+", "").All(char.IsLetterOrDigit))
403 | {
404 | //path is clean
405 | return path;
406 | }
407 |
408 | //path needs to be cleaned
409 | return path
410 | .Replace("{", "")
411 | .Replace("}", "")
412 | .Replace("'", "")
413 | .Replace("\"", "")
414 | .Replace(":", "")
415 | .Replace("$", "")
416 | .Replace("!", "")
417 | .Replace("*", "");
418 | }
419 |
420 | private async Task GetParameters(HttpContext context)
421 | {
422 | var contentType = context.Request.ContentType;
423 | var parameters = new Web.Parameters();
424 | string data = "";
425 | if (contentType != null && contentType.IndexOf("multipart/form-data") >= 0)
426 | {
427 | GetMultipartParameters(context, parameters, Encoding.UTF8);
428 | }
429 | else if (context.Request.ContentType == "application/octet-stream")
430 | {
431 | //file uploaded via HTML 5 ajax or similar method
432 | var filename = context.Request.Headers["X-File-Name"].ToString();
433 | var formFile = new Web.FormFile()
434 | {
435 | Filename = filename,
436 | ContentType = contentType
437 | };
438 | await context.Request.Body.CopyToAsync(formFile);
439 | formFile.Seek(0, SeekOrigin.Begin);
440 | parameters.Files.Add(filename, formFile);
441 | }
442 | else if (contentType != null && contentType.IndexOf("multipart/form-data") < 0 && context.Request.Body.CanRead)
443 | {
444 | //get POST data from request
445 | byte[] bytes = new byte[0];
446 | using (MemoryStream ms = new MemoryStream())
447 | {
448 | await context.Request.Body.CopyToAsync(ms);
449 | bytes = ms.ToArray();
450 | }
451 | data = Encoding.UTF8.GetString(bytes, 0, bytes.Length).Trim();
452 | }
453 |
454 |
455 | if (data.Length > 0)
456 | {
457 | parameters.RequestBody = data;
458 | if (data.IndexOf("Content-Disposition") < 0 && (
459 | (data.IndexOf("{") == 0 || data.IndexOf("[") == 0) && data.IndexOf(":") > 0)
460 | )
461 | {
462 | //get method parameters from POST
463 | Dictionary attr = JsonSerializer.Deserialize>(data);
464 | foreach (KeyValuePair item in attr)
465 | {
466 | var key = WebUtility.UrlDecode(item.Key.ToLower());
467 | if (parameters.ContainsKey(key))
468 | {
469 | parameters.Remove(parameters[key]);
470 | }
471 | if (item.Value != null)
472 | {
473 | parameters.Add(key, item.Value.ToString());
474 | }
475 | else
476 | {
477 | parameters.Add(key, "");
478 | }
479 | }
480 | }
481 | else if (contentType != null && contentType.IndexOf("application/x-www-form-urlencoded") >= 0)
482 | {
483 | var kvps = data.Split("&");
484 | foreach (var kv in kvps)
485 | {
486 | var kvp = kv.Split("=");
487 | parameters.Add(kvp[0], kvp.Length > 1 ? HttpUtility.UrlDecode(kvp[1]) : "");
488 | }
489 | }
490 | }
491 |
492 | //get method parameters from query string
493 | foreach (var key in context.Request.Query.Keys)
494 | {
495 | var value = context.Request.Query[key].ToString();
496 | if (!parameters.ContainsKey(key))
497 | {
498 | parameters.Add(key, HttpUtility.UrlDecode(value));
499 | }
500 | else
501 | {
502 | parameters.AddTo(key, HttpUtility.UrlDecode(value));
503 | }
504 | }
505 | return parameters;
506 | }
507 |
508 | private void GetMultipartParameters(HttpContext context, Web.Parameters parameters, Encoding encoding)
509 | {
510 | var data = ToByteArray(context.Request.BodyReader.AsStream());
511 | var content = encoding.GetString(data);
512 | int delimiterEndIndex = content.IndexOf("\r\n");
513 |
514 | if (delimiterEndIndex > -1)
515 | {
516 | var delimiter = content.Substring(0, content.IndexOf("\r\n"));
517 | var sections = content.Split(new string[] { delimiter }, StringSplitOptions.RemoveEmptyEntries);
518 | var delimiterBytes = encoding.GetBytes("\r\n" + delimiter);
519 | var totalLength = delimiter.Length;
520 |
521 | foreach (string s in sections)
522 | {
523 | // if we find "Content-Disposition", this is a valid multi-part section
524 | if (s.Contains("Content-Disposition"))
525 | {
526 | // look for the "name" parameter
527 | Match nameMatch = new Regex(@"(?<=name\=\"")(.*?)(?=\"")").Match(s);
528 | if (nameMatch.Success == false)
529 | {
530 | nameMatch = new Regex(@"(?<=name\=)(.*?)(?=\r\n)").Match(s);
531 | }
532 | string name = nameMatch.Value.Trim().ToLower();
533 | if (name.IndexOf(";") > 0)
534 | {
535 | nameMatch = new Regex(@"(?<=name\=)(.*?)(?=;)").Match(s);
536 | name = nameMatch.Value.Trim().ToLower();
537 | }
538 |
539 | // look for Content-Type
540 | Match contentTypeMatch = new Regex(@"(?<=Content\-Type:)(.*?)(?=\r\n\r\n)").Match(s);
541 | if (contentTypeMatch.Success == false)
542 | {
543 | contentTypeMatch = new Regex(@"(?<=Content\-Type:)(.*?)(?=;)").Match(s);
544 | }
545 | if (contentTypeMatch.Success == false)
546 | {
547 | contentTypeMatch = new Regex(@"(?<=Content\-Type:)(.*?)(?=\r\n)").Match(s);
548 | }
549 |
550 | // look for Content-Disposition
551 | Match contentDispositionMatch = new Regex(@"(?<=Content\-Type:)(.*?)(?=\r\n\r\n)").Match(s);
552 | if (contentDispositionMatch.Success == false)
553 | {
554 | contentDispositionMatch = new Regex(@"(?<=Content\-Type:)(.*?)(?=;)").Match(s);
555 | }
556 | if (contentDispositionMatch.Success == false)
557 | {
558 | contentDispositionMatch = new Regex(@"(?<=Content\-Type:)(.*?)(?=\r\n)").Match(s);
559 | }
560 |
561 | // look for filename
562 | Match filenameMatch = new Regex(@"(?<=filename\=\"")(.*?)(?=\"")").Match(s);
563 | if (filenameMatch.Success == false)
564 | {
565 | filenameMatch = new Regex(@"(?<=filename\=)(.*?)(?=;)").Match(s);
566 | }
567 |
568 | // look for filename*
569 | Match filenameWildMatch = new Regex(@"(?<=filename\*\=\"")(.*?)(?=\"")").Match(s);
570 | if (filenameWildMatch.Success == false)
571 | {
572 | filenameWildMatch = new Regex(@"(?<=filename\*\=)(.*?)(?=;)").Match(s);
573 | }
574 |
575 | // did we find the required values?
576 | if (contentTypeMatch.Success && filenameMatch.Success)
577 | {
578 | // get the start & end indexes of the file contents
579 | var maxIndex = Math.Max(Math.Max(Math.Max(
580 | contentTypeMatch.Index,
581 | contentDispositionMatch.Index),
582 | filenameMatch.Index),
583 | filenameWildMatch.Index);
584 | Match propsEnd = new Regex(@"(?=\r\n\r\n)").Match(s, maxIndex);
585 | Match propsEnd2 = new Regex(@"(?=\r\n)").Match(s, maxIndex);
586 | var propsEndIndex = 0;
587 | if (propsEnd2.Index == propsEnd.Index)
588 | {
589 | propsEndIndex = propsEnd.Index + 4;
590 | }
591 | else
592 | {
593 | propsEndIndex = propsEnd2.Index + 2;
594 | }
595 | var startIndex = totalLength + propsEndIndex;
596 | var endIndex = IndexOf(data, delimiterBytes, startIndex);
597 | var contentLength = endIndex - startIndex;
598 |
599 | // extract the file contents from the byte array
600 | var fileData = new byte[contentLength];
601 | Buffer.BlockCopy(data, startIndex, fileData, 0, contentLength);
602 |
603 | //create form file
604 | var formFile = new Web.FormFile()
605 | {
606 | Filename = filenameMatch.Value.Trim(),
607 | ContentType = contentTypeMatch.Value.Trim()
608 | };
609 | formFile.Write(fileData, 0, contentLength);
610 | formFile.Seek(0, SeekOrigin.Begin);
611 |
612 | //add form file to Files list
613 | parameters.Files.Add(name, formFile);
614 | totalLength = endIndex + delimiterBytes.Length;
615 | }
616 | else if (!string.IsNullOrWhiteSpace(name))
617 | {
618 | // Get the start & end indexes of the file contents
619 | int startIndex = nameMatch.Index + nameMatch.Length + 4; // "\r\n\r\n".Length;
620 | parameters.Add(name, HttpUtility.UrlDecode(s.Substring(startIndex).TrimEnd(new char[] { '\r', '\n' }).Trim()));
621 | totalLength += s.Length + delimiter.Length;
622 | }
623 | }
624 | }
625 |
626 | }
627 | }
628 |
629 | private object[] MapParameters(ParameterInfo[] methodParams, Web.Parameters parameters, MethodInfo method)
630 | {
631 | var paramVals = new object[methodParams.Length];
632 | for (var x = 0; x < methodParams.Length; x++)
633 | {
634 | //find correct key/value pair
635 | var param = "";
636 | var methodParamName = methodParams[x].Name.ToLower();
637 | var paramType = methodParams[x].ParameterType;
638 |
639 | foreach (var item in parameters)
640 | {
641 | if (item.Key == methodParamName)
642 | {
643 | param = item.Value;
644 | break;
645 | }
646 | }
647 |
648 | if (param == "")
649 | {
650 | //set default value for empty parameter
651 | if (paramType == typeof(Int32))
652 | {
653 | param = "0";
654 | }
655 | }
656 |
657 | //cast params to correct (supported) types
658 | if (paramType.Name != "String")
659 | {
660 | if (int.TryParse(param, out int i) == true)
661 | {
662 | if (paramType.IsEnum == true)
663 | {
664 | //convert param value to enum
665 | paramVals[x] = Enum.Parse(paramType, param);
666 | }
667 | else
668 | {
669 | //convert param value to matching method parameter number type
670 | paramVals[x] = Convert.ChangeType(i, paramType);
671 | }
672 |
673 | }
674 | else if (paramType.FullName.Contains("DateTime"))
675 | {
676 | //convert param value to DateTime
677 | if (param == "")
678 | {
679 | paramVals[x] = null;
680 | }
681 | else
682 | {
683 | try
684 | {
685 | paramVals[x] = DateTime.Parse(param);
686 | }
687 | catch (Exception) { }
688 | }
689 | }
690 | else if (paramType.IsArray)
691 | {
692 | //convert param value to array (of T)
693 | var arr = param.Replace("[", "").Replace("]", "").Replace("\r", "").Replace("\n", "").Split(",").Select(a => { return a.Trim(); }).ToList();
694 | if (paramType.FullName == "System.Int32[]")
695 | {
696 | //convert param values to int array
697 | paramVals[x] = arr.Select(a => { return int.Parse(a); }).ToArray();
698 | }
699 | else
700 | {
701 | //convert param values to array (of matching method parameter type)
702 | paramVals[x] = Convert.ChangeType(arr, paramType);
703 | }
704 |
705 |
706 | }
707 | else if (paramType.Name.IndexOf("Dictionary") == 0)
708 | {
709 | //convert param value (JSON) to Dictionary
710 | if (param != "" && param != "[]" && param != "{}")
711 | {
712 | try
713 | {
714 | paramVals[x] = JsonSerializer.Deserialize>(param);
715 | }
716 | catch (Exception)
717 | {
718 | if (options.WriteDebugInfoToConsole)
719 | {
720 | Console.WriteLine("Could not convert JSON string into Dictionary for parameter \"" + methodParamName + "\" in method \"" + method.Name + "\"");
721 | }
722 | }
723 | }
724 |
725 | if (paramVals[x] == null)
726 | {
727 | paramVals[x] = new Dictionary();
728 | }
729 |
730 | }
731 | else if (paramType.Name == "Boolean")
732 | {
733 | paramVals[x] = param.ToLower() == "true";
734 | }
735 | else
736 | {
737 | //convert param value to matching method parameter type
738 | try
739 | {
740 | paramVals[x] = JsonSerializer.Deserialize(param, paramType);
741 | }
742 | catch (Exception ex)
743 | {
744 | if (options.WriteDebugInfoToConsole)
745 | {
746 | Console.WriteLine(ex.Message + "\n" +
747 | "Parameter: " + param
748 | );
749 | }
750 | }
751 | }
752 | }
753 | else
754 | {
755 | //matching method parameter type is string
756 | paramVals[x] = param;
757 | }
758 | }
759 | return paramVals;
760 | }
761 |
762 | private string CleanReflectionName(string myStr)
763 | {
764 | string newStr = myStr.ToString();
765 | int x = 0;
766 | while (x < newStr.Length)
767 | {
768 | if (
769 | (Encoding.ASCII.GetBytes(newStr.Substring(x, 1))[0] >= Encoding.ASCII.GetBytes("a")[0] && Encoding.ASCII.GetBytes(newStr.Substring(x, 1))[0] <= Encoding.ASCII.GetBytes("z")[0]) ||
770 | (Encoding.ASCII.GetBytes(newStr.Substring(x, 1))[0] >= Encoding.ASCII.GetBytes("A")[0] & Encoding.ASCII.GetBytes(newStr.Substring(x, 1))[0] <= Encoding.ASCII.GetBytes("Z")[0]) ||
771 | (Encoding.ASCII.GetBytes(newStr.Substring(x, 1))[0] >= Encoding.ASCII.GetBytes("0")[0] & Encoding.ASCII.GetBytes(newStr.Substring(x, 1))[0] <= Encoding.ASCII.GetBytes("9")[0])
772 | )
773 | {
774 | x++;
775 | }
776 | else
777 | {
778 | //remove character
779 | newStr = newStr.Substring(0, x - 1) + newStr.Substring(x + 1);
780 | }
781 | }
782 | return newStr;
783 | }
784 |
785 | private bool CanUseRequestMethod(HttpContext context, MethodInfo method)
786 | {
787 | var reqMethod = context.Request.Method.ToLower();
788 | var hasReqAttr = false;
789 | switch (reqMethod)
790 | {
791 | case "get": hasReqAttr = method.GetCustomAttributes(typeof(Web.GETAttribute), false).Any(); break;
792 | case "post": hasReqAttr = method.GetCustomAttributes(typeof(Web.POSTAttribute), false).Any(); break;
793 | case "put": hasReqAttr = method.GetCustomAttributes(typeof(Web.PUTAttribute), false).Any(); break;
794 | case "head": hasReqAttr = method.GetCustomAttributes(typeof(Web.HEADAttribute), false).Any(); break;
795 | case "delete": hasReqAttr = method.GetCustomAttributes(typeof(Web.DELETEAttribute), false).Any(); break;
796 | }
797 | if (hasReqAttr == false)
798 | {
799 | //check if method contains other request method attributes
800 | if ((method.GetCustomAttributes(typeof(Web.GETAttribute), false).Any() && reqMethod != "get") ||
801 | (method.GetCustomAttributes(typeof(Web.POSTAttribute), false).Any() && reqMethod != "post") ||
802 | (method.GetCustomAttributes(typeof(Web.PUTAttribute), false).Any() && reqMethod != "put") ||
803 | (method.GetCustomAttributes(typeof(Web.HEADAttribute), false).Any() && reqMethod != "head") ||
804 | (method.GetCustomAttributes(typeof(Web.DELETEAttribute), false).Any() && reqMethod != "delete"))
805 | {
806 | //display an error
807 | return false;
808 | }
809 | }
810 | return true;
811 | }
812 |
813 | private int IndexOf(byte[] searchWithin, byte[] serachFor, int startIndex)
814 | {
815 | int index = 0;
816 | int startPos = Array.IndexOf(searchWithin, serachFor[0], startIndex);
817 |
818 | if (startPos != -1)
819 | {
820 | while ((startPos + index) < searchWithin.Length)
821 | {
822 | if (searchWithin[startPos + index] == serachFor[index])
823 | {
824 | index++;
825 | if (index == serachFor.Length)
826 | {
827 | return startPos;
828 | }
829 | }
830 | else
831 | {
832 | startPos = Array.IndexOf(searchWithin, serachFor[0], startPos + index);
833 | if (startPos == -1)
834 | {
835 | return -1;
836 | }
837 | index = 0;
838 | }
839 | }
840 | }
841 |
842 | return -1;
843 | }
844 |
845 | private byte[] ToByteArray(Stream stream)
846 | {
847 | byte[] buffer = new byte[32768];
848 | using (MemoryStream ms = new MemoryStream())
849 | {
850 | while (true)
851 | {
852 | int read = stream.Read(buffer, 0, buffer.Length);
853 | if (read <= 0)
854 | return ms.ToArray();
855 | ms.Write(buffer, 0, read);
856 | }
857 | }
858 | }
859 | #endregion
860 | }
861 | }
862 |
--------------------------------------------------------------------------------