├── src
├── WebApiBolierplate
│ ├── API
│ │ ├── appsettings.json
│ │ ├── Operations
│ │ │ ├── Interfaces
│ │ │ │ ├── IUserLib.cs
│ │ │ │ ├── IValueLib.cs
│ │ │ │ └── IAuthLib.cs
│ │ │ ├── ValueLib.cs
│ │ │ ├── UserLib.cs
│ │ │ └── AuthLib.cs
│ │ ├── Models
│ │ │ ├── User.cs
│ │ │ ├── LoginDTO.cs
│ │ │ └── APIResponse.cs
│ │ ├── Controllers
│ │ │ ├── CustomBaseController.cs
│ │ │ ├── LoginController.cs
│ │ │ └── ValuesController.cs
│ │ ├── appsettings.production.json
│ │ ├── appsettings.Development.json
│ │ ├── Configurations
│ │ │ ├── Validations.cs
│ │ │ ├── DependencyInjection.cs
│ │ │ └── Authentication.cs
│ │ ├── Helpers
│ │ │ ├── ValidationHelper.cs
│ │ │ ├── Interfaces
│ │ │ │ └── IWebAPIHelper.cs
│ │ │ └── WebAPIHelper.cs
│ │ ├── API.csproj
│ │ ├── web.config
│ │ ├── Properties
│ │ │ └── launchSettings.json
│ │ └── Program.cs
│ ├── Core.Constants
│ │ ├── Core.Constants.csproj
│ │ ├── CustomClaims.cs
│ │ ├── Enums.cs
│ │ ├── Errors.cs
│ │ └── Roles.cs
│ ├── Core.Lib
│ │ ├── Utilities
│ │ │ ├── Interfaces
│ │ │ │ ├── ITextUtils.cs
│ │ │ │ ├── IDateUtils.cs
│ │ │ │ ├── IRandomUtils.cs
│ │ │ │ ├── IGzipUtils.cs
│ │ │ │ ├── ISecurityUtils.cs
│ │ │ │ └── IJwtUtils.cs
│ │ │ ├── DateUtils.cs
│ │ │ ├── TextUtils.cs
│ │ │ ├── GzipUtils.cs
│ │ │ ├── RandomUtils.cs
│ │ │ ├── SecurityUtils.cs
│ │ │ └── JwtUtils.cs
│ │ ├── Core.Lib.csproj
│ │ ├── Attributes
│ │ │ ├── MinValueAttribute.cs
│ │ │ └── Date
│ │ │ │ ├── PastOnlyAttribute.cs
│ │ │ │ ├── FutureOnlyAttribute.cs
│ │ │ │ ├── PastAndPresentAttribute.cs
│ │ │ │ └── PresentAndFutureAttribute.cs
│ │ └── Adapters
│ │ │ └── DBAdapter.cs
│ ├── Core.Test
│ │ ├── Utilities
│ │ │ ├── TextUtilsTest.cs
│ │ │ ├── DateUtilsTest.cs
│ │ │ ├── GzipUtilsTest.cs
│ │ │ ├── RandomUtilsTest.cs
│ │ │ └── SecurityUtilsTest.cs
│ │ └── Core.Test.csproj
│ ├── WebApiBolierplate.sln
│ └── .editorconfig
└── postman
│ └── Web API Boilerplate.postman_collection.json
├── .vscode
└── spellright.dict
├── .github
├── workflows
│ ├── CI.yml
│ ├── CD.yml
│ └── codeql-analysis.yml
└── dependabot.yml
├── LICENSE
├── README.md
├── CHANGELOG.md
└── .gitignore
/src/WebApiBolierplate/API/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "AllowedHosts": "*"
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/spellright.dict:
--------------------------------------------------------------------------------
1 | startup
2 | appconfig
3 | appsettings.json
4 | src
5 | launchsettings.json
6 | editorconfig
7 | db
8 | Serilog
9 | Bcrypt
10 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Constants/Core.Constants.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Operations/Interfaces/IUserLib.cs:
--------------------------------------------------------------------------------
1 | using API.Models;
2 |
3 | namespace API.Operations.Interfaces;
4 |
5 | public interface IUserLib
6 | {
7 | public User GetUser(string userName);
8 | }
9 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Operations/Interfaces/IValueLib.cs:
--------------------------------------------------------------------------------
1 | namespace API.Operations.Interfaces;
2 |
3 | public interface IValueLib
4 | {
5 | public string[] GetValueList();
6 | public string GetValueById(int id);
7 | public int AddValue(string value);
8 | public int UpdateValue(string value, int id);
9 | public int DeleteValue(int id);
10 | }
11 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Models/User.cs:
--------------------------------------------------------------------------------
1 | namespace API.Models;
2 |
3 | public sealed class User
4 | {
5 | public User()
6 | {
7 |
8 | }
9 |
10 | public string Email { get; set; }
11 | public string Name { get; set; }
12 | public string CompanyId { get; set; }
13 | public string Roles { get; set; }
14 | public string Id { get; set; }
15 | }
16 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Utilities/Interfaces/ITextUtils.cs:
--------------------------------------------------------------------------------
1 | namespace Core.Lib.Utilities.Interfaces;
2 | public interface ITextUtils
3 | {
4 | ///
5 | /// Removes all line endings such as Line ending, paragraph endings, etc...
6 | /// Ref: https://stackoverflow.com/a/6765676/5407188
7 | ///
8 | public string RemoveLineEndings(string text);
9 | }
10 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Utilities/DateUtils.cs:
--------------------------------------------------------------------------------
1 | using Core.Lib.Utilities.Interfaces;
2 | using System;
3 |
4 | namespace Core.Lib.Utilities;
5 |
6 | public sealed class DateUtils: IDateUtils
7 | {
8 | public DateTime CombaineDateAndTime(DateTime date, DateTime time)
9 | {
10 | return new DateTime(date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Constants/CustomClaims.cs:
--------------------------------------------------------------------------------
1 | namespace Core.Constants;
2 |
3 | ///
4 | /// Contains commonly added custom claims for JWT tokens
5 | ///
6 | public static class CustomClaims
7 | {
8 | // the value of the strings are all small case with underscores instead of spaces
9 |
10 | public const string UserIdentifier = "user_identifier";
11 | public const string CompanyIdentifier = "company_identifier";
12 | }
13 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Models/LoginDTO.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace API.Models;
4 |
5 | public sealed class LoginDTO
6 | {
7 | public LoginDTO()
8 | {
9 |
10 | }
11 |
12 | [Required(ErrorMessage = "No name. No game.", AllowEmptyStrings = false)]
13 | public string UserName { get; set; }
14 |
15 | [Required(ErrorMessage = "No password?! Are you kidding?", AllowEmptyStrings = false)]
16 | public string Password { get; set; }
17 | }
18 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Constants/Enums.cs:
--------------------------------------------------------------------------------
1 | namespace Core.Constants;
2 |
3 | public static class Enums
4 | {
5 | ///
6 | /// Enum for character set
7 | ///
8 | public enum CharSet
9 | {
10 | Alphabets, // UPPERCASE, lowercase
11 | Numbers,
12 | AlphaNumeric, // UPPERCASE, lowercase and number
13 | UppercaseOnly,
14 | LowercaseOnly,
15 | UppercaseWithNumbers,
16 | LowercaseWithNumbers
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Utilities/Interfaces/IDateUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Core.Lib.Utilities.Interfaces;
4 | public interface IDateUtils
5 | {
6 | ///
7 | /// Combaines date and time from separate DateTime objects into one
8 | ///
9 | /// DateTime object containing date
10 | /// DateTime object containing time
11 | public DateTime CombaineDateAndTime(DateTime date, DateTime time);
12 | }
13 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Controllers/CustomBaseController.cs:
--------------------------------------------------------------------------------
1 | using Core.Constants;
2 | using Microsoft.AspNetCore.Mvc;
3 |
4 | namespace API.Controllers;
5 |
6 | [Route("api/[controller]")]
7 | [ApiController]
8 | public class CustomBaseController : ControllerBase
9 | {
10 | protected string loginId => User.FindFirst(CustomClaims.UserIdentifier)?.Value;
11 | protected string companyId => User.FindFirst(CustomClaims.CompanyIdentifier).Value;
12 |
13 | public CustomBaseController()
14 | {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/appsettings.production.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning"
5 | }
6 | },
7 | "AppConfig": {
8 | "DataBase": {
9 | "ConnectionString": "sample connection string"
10 | },
11 | "JWT": {
12 | "Audience": "https://www.example.com",
13 | "Issuer": "WebApiBoilerplate",
14 | "Key": "JWT_keys_with_random_chars---9sav3zkqd1gmgur2r7sdxqwtwvsxong36lylhdnwca3sfr5n6kqg7qpdr1nbhf1q",
15 | "HoursValid": 48
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Core.Lib.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | },
9 | "AppConfig": {
10 | "DataBase": {
11 | "ConnectionString": "sample connection string"
12 | },
13 | "JWT": {
14 | "Audience": "https://www.example.com",
15 | "Issuer": "WebApiBoilerplate",
16 | "Key": "JWT_keys_with_random_chars---m4sjcv46v06etirgflvcgdwixb9tg5wk5hy1mew85zcznjshivssia9mls539rsq",
17 | "HoursValid": 48
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Attributes/MinValueAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 |
4 | namespace Core.Lib.Attributes;
5 |
6 | ///
7 | /// Used to set minimum value validation for int, long and double values
8 | ///
9 | public class MinValueAttribute : ValidationAttribute
10 | {
11 | private long Minimum { get; set; }
12 |
13 | public MinValueAttribute(long minimum)
14 | {
15 | Minimum = minimum;
16 | }
17 |
18 | public override bool IsValid(object value)
19 | {
20 | return Convert.ToInt64(value) >= Minimum;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.github/workflows/CI.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | branches: [ main ]
6 |
7 | jobs:
8 | build-linux:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v6.0.0
13 | with:
14 | show-progress: false
15 | - name: Install dependencies
16 | run: |
17 | cd ./src/WebApiBolierplate
18 | dotnet restore
19 | - name: Build
20 | run: |
21 | cd ./src/WebApiBolierplate
22 | dotnet build --configuration Release --no-restore
23 | - name: Test
24 | run: |
25 | cd ./src/WebApiBolierplate
26 | dotnet test ./Core.Test/Core.Test.csproj
27 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Constants/Errors.cs:
--------------------------------------------------------------------------------
1 | namespace Core.Constants;
2 |
3 | ///
4 | /// Constains all the error messages across the application
5 | ///
6 | public class Errors
7 | {
8 | public const string InternalServerError = "Oops! An Internal Error Occured.";
9 |
10 | #region [Authorization]
11 |
12 | public const string InvalidCredentials = "The credentials are invalid.";
13 | public const string AccountNotFound = "No acount exists for the specified username. Are you trying to Signup?";
14 | public const string AccountDeleted = "The user account has been deleted. Contact administrator.";
15 |
16 | #endregion [Authorization]
17 | }
18 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Constants/Roles.cs:
--------------------------------------------------------------------------------
1 | namespace Core.Constants;
2 |
3 | ///
4 | /// Contains all the roles of a user
5 | ///
6 | public static class Roles
7 | {
8 | public const string Admin = "admin";
9 | public const string User = "user";
10 | }
11 |
12 | ///
13 | /// Contains roles strings used for auth purporses including combination of roles
14 | ///
15 | public static class AuthRoles
16 | {
17 | // adding combained roles for auth
18 | public const string All = "admin, user";
19 |
20 | // repeating single roles
21 | public const string Admin = Roles.Admin;
22 | public const string User = Roles.User;
23 | }
24 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Utilities/Interfaces/IRandomUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using static Core.Constants.Enums;
3 |
4 | namespace Core.Lib.Utilities.Interfaces;
5 | public interface IRandomUtils
6 | {
7 | ///
8 | /// Generates a random string using cryptography of the specified length
9 | ///
10 | /// Length of the random string
11 | /// The enum variable describing which chracters to be used
12 | ///
13 | ///
14 | public string GenRandomChar(int length, CharSet charSet);
15 | }
16 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Configurations/Validations.cs:
--------------------------------------------------------------------------------
1 | using API.Helpers;
2 | using Microsoft.AspNetCore.Mvc;
3 |
4 | namespace API.Configurations;
5 |
6 | public static class Validations
7 | {
8 | public static IServiceCollection AddProjectValidations(this IServiceCollection services)
9 | {
10 | // To handle model validation errors and change it to
11 | // Ref: https://stackoverflow.com/a/51159755/5407188
12 |
13 | var validationHelper = new ValidationHelper();
14 |
15 | services.Configure(o => {
16 | o.InvalidModelStateResponseFactory = actionContext => validationHelper.GetDataValidationError(actionContext.ModelState);
17 | });
18 |
19 | return services;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Operations/ValueLib.cs:
--------------------------------------------------------------------------------
1 | using API.Operations.Interfaces;
2 |
3 | namespace API.Operations;
4 |
5 | public sealed class ValueLib : IValueLib
6 | {
7 | private string[] _values = { "Jon Snow", "Rob Stark", "Sansa Stark", "Arya Stark", "Bran Stark", "Rickon Stark" };
8 |
9 | public ValueLib(IConfiguration configuration)
10 | {
11 | }
12 |
13 | public string[] GetValueList() => _values;
14 |
15 | public string GetValueById(int id) => _values.Length <= id ? string.Empty : _values[id];
16 |
17 | public int AddValue(string value) => 1;
18 |
19 | public int UpdateValue(string value, int id) => _values.Length <= id ? -1 : 1;
20 |
21 | public int DeleteValue(int id) => _values.Length <= id ? -1 : 1;
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Test/Utilities/TextUtilsTest.cs:
--------------------------------------------------------------------------------
1 | using Core.Lib.Utilities;
2 | using Core.Lib.Utilities.Interfaces;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 |
5 | namespace Core.Test.Utilities;
6 |
7 | [TestClass]
8 | public sealed class TextUtilsTest
9 | {
10 | private readonly ITextUtils _textUtils;
11 |
12 | public TextUtilsTest()
13 | {
14 | _textUtils = new TextUtils();
15 | }
16 |
17 | [TestMethod]
18 | public void TestLineEndingRemoval()
19 | {
20 | const string inputStr = "Line 1\nLine 2\r";
21 | const string expectedOutput = "Line 1Line 2";
22 | var processedStr = _textUtils.RemoveLineEndings(inputStr);
23 | Assert.AreEqual(expectedOutput, processedStr);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Operations/Interfaces/IAuthLib.cs:
--------------------------------------------------------------------------------
1 | using API.Models;
2 |
3 | namespace API.Operations.Interfaces;
4 |
5 | public interface IAuthLib
6 | {
7 | public int ValidateLogin(LoginDTO login);
8 |
9 | ///
10 | /// Generates a signed token for the given user Id and role
11 | ///
12 | /// Unique Identifier of the user
13 | /// Role of the user
14 | /// Name of the user
15 | /// Company Identfier
16 | ///
17 | /// User Id is a must
18 | public string GenerateToken(string userId, string userRole = null, string userName = null, string companyId = null);
19 | }
20 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Attributes/Date/PastOnlyAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 |
4 | namespace Core.Lib.Attributes.Date;
5 |
6 | ///
7 | /// Sets validation to ensure that date value is in past
8 | ///
9 | public class PastOnlyAttribute : ValidationAttribute
10 | {
11 | public PastOnlyAttribute()
12 | {
13 |
14 | }
15 |
16 | public override bool IsValid(object value)
17 | {
18 | if (value == null)
19 | {
20 | return true;
21 | }
22 |
23 | var val = Convert.ToDateTime(value);
24 | var dateValue = new DateTime(val.Year, val.Month, val.Day, 0, 0, 0);
25 | var now = DateTime.UtcNow;
26 | return dateValue < new DateTime(now.Year, now.Month, now.Day, 0, 0, 0);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Attributes/Date/FutureOnlyAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 |
4 | namespace Core.Lib.Attributes.Date;
5 |
6 | ///
7 | /// Sets validation to ensure that date value is in future
8 | ///
9 | public class FutureOnlyAttribute : ValidationAttribute
10 | {
11 | public FutureOnlyAttribute()
12 | {
13 | }
14 |
15 | public override bool IsValid(object value)
16 | {
17 | if (value == null)
18 | {
19 | return true;
20 | }
21 | var val = Convert.ToDateTime(value);
22 |
23 | var dateValue = new DateTime(val.Year, val.Month, val.Day, 0, 0, 0);
24 | var now = DateTime.UtcNow;
25 | return dateValue > new DateTime(now.Year, now.Month, now.Day, 0, 0, 0);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Configurations/DependencyInjection.cs:
--------------------------------------------------------------------------------
1 | using API.Helpers.Interfaces;
2 | using API.Helpers;
3 | using API.Operations.Interfaces;
4 | using API.Operations;
5 | using Core.Lib.Utilities.Interfaces;
6 | using Core.Lib.Utilities;
7 |
8 | namespace API.Configurations;
9 |
10 | public static class DependencyInjection
11 | {
12 | public static IServiceCollection AddProjectServices(this IServiceCollection services)
13 | {
14 | #region operations
15 |
16 | services.AddScoped();
17 | services.AddScoped();
18 | services.AddScoped();
19 |
20 | #endregion operations
21 |
22 | services.AddScoped();
23 | services.AddScoped();
24 |
25 | return services;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Attributes/Date/PastAndPresentAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 |
4 | namespace Core.Lib.Attributes.Date;
5 |
6 | ///
7 | /// Sets validation to ensure that date value is in either past or today
8 | ///
9 | public class PastAndPresentAttribute : ValidationAttribute
10 | {
11 | public PastAndPresentAttribute()
12 | {
13 | }
14 |
15 | public override bool IsValid(object value)
16 | {
17 | if (value == null)
18 | {
19 | return true;
20 | }
21 |
22 | var val = Convert.ToDateTime(value);
23 | var dateValue = new DateTime(val.Year, val.Month, val.Day, 0, 0, 0);
24 | var now = DateTime.UtcNow;
25 | return dateValue <= new DateTime(now.Year, now.Month, now.Day, 0, 0, 0);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Attributes/Date/PresentAndFutureAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 |
4 | namespace Core.Lib.Attributes.Date;
5 |
6 | ///
7 | /// Sets validation to ensure that date value is in future or today
8 | ///
9 | public class PresentAndFutureAttribute : ValidationAttribute
10 | {
11 | public PresentAndFutureAttribute()
12 | {
13 | }
14 |
15 | public override bool IsValid(object value)
16 | {
17 | if (value == null)
18 | {
19 | return true;
20 | }
21 |
22 | var val = Convert.ToDateTime(value);
23 | var dateValue = new DateTime(val.Year, val.Month, val.Day, 0, 0, 0);
24 | var now = DateTime.UtcNow;
25 | return dateValue >= new DateTime(now.Year, now.Month, now.Day, 0, 0, 0);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Test/Utilities/DateUtilsTest.cs:
--------------------------------------------------------------------------------
1 | using Core.Lib.Utilities;
2 | using Core.Lib.Utilities.Interfaces;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using System;
5 |
6 | namespace Core.Test.Utilities;
7 |
8 | [TestClass]
9 | public sealed class DateUtilsTest
10 | {
11 |
12 | private readonly IDateUtils _dateUtils;
13 |
14 | public DateUtilsTest()
15 | {
16 | _dateUtils = new DateUtils();
17 | }
18 |
19 | [TestMethod]
20 | public void TestDateTimeCombining()
21 | {
22 | var date = new DateTime(2021, 1, 1);
23 | var time = new DateTime(2000, 12, 12, 1, 2, 3);
24 | var expectedOutput = new DateTime(2021, 1, 1, 1, 2, 3);
25 | var combinedDate = _dateUtils.CombaineDateAndTime(date, time);
26 | Assert.AreEqual(expectedOutput, combinedDate);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Test/Utilities/GzipUtilsTest.cs:
--------------------------------------------------------------------------------
1 | using Core.Lib.Utilities;
2 | using Core.Lib.Utilities.Interfaces;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using System.Text;
5 |
6 | namespace Core.Test.Utilities;
7 |
8 | [TestClass]
9 | public sealed class GzipUtilsTest
10 | {
11 | private readonly IGzipUtils _gzipUtils;
12 | private const string _sampleString = "This is a sample string";
13 |
14 | public GzipUtilsTest()
15 | {
16 | _gzipUtils = new GzipUtils();
17 | }
18 |
19 | [TestMethod]
20 | public void TestGZip()
21 | {
22 | var compressedString = _gzipUtils.CompressToString(_sampleString, Encoding.UTF8);
23 | var decompressedString = _gzipUtils.DecompressString(compressedString, Encoding.UTF8);
24 | Assert.AreEqual(_sampleString, decompressedString);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Test/Core.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | all
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Utilities/Interfaces/IGzipUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Core.Lib.Utilities.Interfaces;
8 | public interface IGzipUtils
9 | {
10 | ///
11 | /// Compress the given string into a gzip string
12 | ///
13 | /// Uncompressed string
14 | /// Encoding of the string
15 | ///
16 | public string CompressToString(string s, Encoding encoding);
17 |
18 | ///
19 | /// Decompress a gzip response.
20 | ///
21 | /// Compressed string
22 | /// Encoding of the string
23 | ///
24 | public string DecompressString(string s, Encoding encoding);
25 | }
26 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Test/Utilities/RandomUtilsTest.cs:
--------------------------------------------------------------------------------
1 | using Core.Lib.Utilities;
2 | using Core.Lib.Utilities.Interfaces;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using System;
5 |
6 | namespace Core.Test.Utilities;
7 |
8 | [TestClass]
9 | public sealed class RandomUtilsTest
10 | {
11 | private readonly IRandomUtils _randomUtils;
12 |
13 | public RandomUtilsTest()
14 | {
15 | _randomUtils = new RandomUtils();
16 | }
17 |
18 | [TestMethod]
19 | public void TestingRandomChar()
20 | {
21 | const int length = 10;
22 | var randomChars = _randomUtils.GenRandomChar(length, Constants.Enums.CharSet.Alphabets);
23 | Assert.AreEqual(randomChars.Length, length);
24 |
25 | Assert.ThrowsException(() => {
26 | _randomUtils.GenRandomChar(0, Constants.Enums.CharSet.Alphabets);
27 | });
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Helpers/ValidationHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Microsoft.AspNetCore.Mvc.ModelBinding;
3 |
4 | namespace API.Helpers;
5 |
6 | public sealed class ValidationHelper
7 | {
8 | ///
9 | /// Obtains all model validation errors and combaines them into a single CSV error text
10 | ///
11 | ///
12 | ///
13 | public BadRequestObjectResult GetDataValidationError(ModelStateDictionary model)
14 | {
15 | var errorText = string.Empty;
16 | foreach (var value in model.Values)
17 | {
18 | for (var i = 0; i < value.Errors.Count; i++)
19 | {
20 | errorText = errorText + ", " + value.Errors[i].ErrorMessage;
21 | }
22 | }
23 | errorText = errorText[1..].Trim();
24 | return new WebAPIHelper().CreateBadRequest(errorText);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/API.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | true
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Utilities/TextUtils.cs:
--------------------------------------------------------------------------------
1 | using Core.Lib.Utilities.Interfaces;
2 |
3 | namespace Core.Lib.Utilities;
4 |
5 | public sealed class TextUtils: ITextUtils
6 | {
7 | #region [Private variables]
8 |
9 | private readonly string _lineSeparator = ((char)0x2028).ToString();
10 | private readonly string _paragraphSeparator = ((char)0x2029).ToString();
11 |
12 | #endregion [Private variables]
13 |
14 | #region [Public functions]
15 |
16 | public string RemoveLineEndings(string text)
17 | {
18 | if (string.IsNullOrEmpty(text))
19 | {
20 | return text;
21 | }
22 |
23 | return text.Replace("\r\n", string.Empty)
24 | .Replace("\n", string.Empty)
25 | .Replace("\r", string.Empty)
26 | .Replace(_lineSeparator, string.Empty)
27 | .Replace(_paragraphSeparator, string.Empty);
28 | }
29 |
30 | #endregion [Public functions]
31 | }
32 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Operations/UserLib.cs:
--------------------------------------------------------------------------------
1 | using Core.Constants;
2 | using API.Models;
3 | using API.Operations.Interfaces;
4 |
5 | namespace API.Operations;
6 |
7 | public sealed class UserLib : IUserLib
8 | {
9 | private const string _eddardStark = "Eddard Stark";
10 | private readonly ILogger _logger;
11 |
12 | public UserLib(IConfiguration configuration, ILogger logger)
13 | {
14 | _logger = logger;
15 | }
16 |
17 | public User GetUser(string userName)
18 | {
19 | try
20 | {
21 | var user = new User()
22 | {
23 | Name = userName,
24 | Email = $"{userName}@gameofthrones.com",
25 | CompanyId = "GOT",
26 | Roles = userName == _eddardStark ? Roles.Admin : Roles.User,
27 | Id = Guid.NewGuid().ToString("D")
28 | };
29 | return user;
30 | }
31 | catch (Exception ex)
32 | {
33 | _logger.LogError("GetUser - {message}", ex.Message);
34 | return null;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Kolappan Nathan
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 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Helpers/Interfaces/IWebAPIHelper.cs:
--------------------------------------------------------------------------------
1 | using API.Models;
2 | using Microsoft.AspNetCore.Mvc;
3 |
4 | namespace API.Helpers.Interfaces;
5 |
6 | public interface IWebAPIHelper
7 | {
8 | ///
9 | /// Creates a error or successful response based on the data to be sent
10 | ///
11 | ///
12 | ///
13 | public JsonResult CreateResponse(object data);
14 |
15 | ///
16 | /// Creates a Json response with 400 status code(Bad Request)
17 | ///
18 | /// Error message to be sent
19 | /// The http response
20 | public BadRequestObjectResult CreateBadRequest(string errorText);
21 |
22 | ///
23 | /// Creates an error response with a 500 status code(Internal Server error)
24 | ///
25 | /// Error message to be sent
26 | /// Data to be sent
27 | ///
28 | public APIResponse CreateErrorResponse(string errorText, dynamic data = null);
29 | }
30 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Configurations/Authentication.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authentication.JwtBearer;
2 | using Microsoft.IdentityModel.Tokens;
3 | using System.Text;
4 |
5 | namespace API.Configurations;
6 |
7 | public static class Authentication
8 | {
9 | public static IServiceCollection AddProjectAuthServices(this IServiceCollection services, IConfiguration configuration)
10 | {
11 | services
12 | .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
13 | .AddJwtBearer(options => {
14 | options.TokenValidationParameters = new TokenValidationParameters
15 | {
16 | ValidateIssuer = true,
17 | ValidateAudience = true,
18 | ValidateLifetime = true,
19 | ValidateIssuerSigningKey = true,
20 | ValidIssuer = configuration["AppConfig:JWT:Issuer"],
21 | ValidAudience = configuration["AppConfig:JWT:Audience"],
22 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["AppConfig:JWT:Key"]))
23 | };
24 | });
25 | return services;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Utilities/GzipUtils.cs:
--------------------------------------------------------------------------------
1 | using Core.Lib.Utilities.Interfaces;
2 | using System;
3 | using System.IO;
4 | using System.IO.Compression;
5 | using System.Text;
6 |
7 | namespace Core.Lib.Utilities;
8 |
9 | public sealed class GzipUtils : IGzipUtils
10 | {
11 | public string CompressToString(string s, Encoding encoding)
12 | {
13 | var bytes = encoding.GetBytes(s);
14 | using (var msi = new MemoryStream(bytes))
15 | using (var mso = new MemoryStream())
16 | {
17 | using (var gs = new GZipStream(mso, CompressionMode.Compress))
18 | {
19 | msi.CopyTo(gs);
20 | }
21 | return Convert.ToBase64String(mso.ToArray());
22 | }
23 | }
24 |
25 | public string DecompressString(string s, Encoding encoding)
26 | {
27 | var bytes = Convert.FromBase64String(s);
28 | using (var msi = new MemoryStream(bytes))
29 | using (var mso = new MemoryStream())
30 | {
31 | using (var gs = new GZipStream(msi, CompressionMode.Decompress))
32 | {
33 | gs.CopyTo(mso);
34 | }
35 | return encoding.GetString(mso.ToArray());
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:60289",
8 | "sslPort": 44332
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": true,
15 | "launchUrl": "swagger/index.html",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "API": {
21 | "commandName": "Project",
22 | "launchBrowser": true,
23 | "launchUrl": "swagger/index.html",
24 | "environmentVariables": {
25 | "ASPNETCORE_ENVIRONMENT": "Development"
26 | },
27 | "applicationUrl": "https://localhost:7030;http://localhost:5030"
28 | },
29 | "WSL": {
30 | "commandName": "WSL2",
31 | "launchBrowser": true,
32 | "launchUrl": "https://localhost:7030/swagger/index.html",
33 | "environmentVariables": {
34 | "ASPNETCORE_ENVIRONMENT": "Development",
35 | "ASPNETCORE_URLS": "https://localhost:7030;http://localhost:5030"
36 | },
37 | "distributionName": ""
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Test/Utilities/SecurityUtilsTest.cs:
--------------------------------------------------------------------------------
1 | using Core.Lib.Utilities;
2 | using Core.Lib.Utilities.Interfaces;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 |
5 | namespace Core.Test.Utilities;
6 |
7 | [TestClass]
8 | public sealed class SecurityUtilsTest
9 | {
10 | private readonly ISecurityUtils _securityUtils;
11 | private const string _sampleString = "This is a sample string";
12 | private const string _key = "THk5emRHRmphMjkyWlhKbWJHOTNMbU52YlM5eGRXVnpkR2x2Ym5Ndk16azJORGs1TnpZdmFYTXRh";
13 | private const string _salt = "umssxcahqnumssxcahqnumssxcahqn";
14 |
15 | public SecurityUtilsTest()
16 | {
17 | _securityUtils = new SecurityUtils();
18 | }
19 |
20 | [TestMethod]
21 | public void TestHash()
22 | {
23 |
24 | var hashedValue = _securityUtils.HashBCrypt(_sampleString, _salt);
25 | var hashComparission = _securityUtils.VerifyBCrypt(_sampleString, _salt, hashedValue);
26 | Assert.IsTrue(hashComparission);
27 | }
28 |
29 | [TestMethod]
30 | public void TestEncryption()
31 | {
32 | var encryptedString = _securityUtils.EncryptString(_sampleString, _key);
33 | var decryptedString = _securityUtils.DecryptString(encryptedString, _key);
34 | Assert.AreEqual(_sampleString, decryptedString);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Models/APIResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 |
3 | namespace API.Models;
4 |
5 | ///
6 | /// A class for standard formatting of Data
7 | ///
8 | public sealed class APIResponse
9 | {
10 | public APIResponse(dynamic data, string message, bool isError, HttpStatusCode statusCode)
11 | {
12 | Data = data;
13 | Message = message;
14 | IsError = isError;
15 | StatusCode = statusCode;
16 | }
17 |
18 | public APIResponse(dynamic data, string message, bool isError)
19 | {
20 | Data = data;
21 | Message = message;
22 | IsError = isError;
23 | StatusCode = isError? HttpStatusCode.InternalServerError : HttpStatusCode.OK;
24 | }
25 |
26 | ///
27 | /// Contains error meaasage and the likess
28 | ///
29 | public string Message { get; private set; }
30 |
31 | ///
32 | /// The actual data sent for the request
33 | ///
34 | public dynamic Data { get; private set; }
35 |
36 | ///
37 | /// Indicates if it is an error or not
38 | ///
39 | public bool IsError { get; private set; }
40 |
41 | ///
42 | /// HTTP status code
43 | ///
44 | public HttpStatusCode StatusCode { get; private set; }
45 | }
46 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Utilities/Interfaces/ISecurityUtils.cs:
--------------------------------------------------------------------------------
1 | namespace Core.Lib.Utilities.Interfaces;
2 | public interface ISecurityUtils
3 | {
4 | ///
5 | /// Encrypts the given string and return ciphertext
6 | ///
7 | public string EncryptString(string clearText, string encryptionKey);
8 |
9 | ///
10 | /// Decrypts the given string and returns plain text
11 | ///
12 | public string DecryptString(string cipherText, string encryptionKey);
13 |
14 | ///
15 | /// Hashes the plaintext password using BCrypt and returns the hash
16 | ///
17 | ///
18 | ///
19 | ///
20 | ///
21 | ///
22 | public string HashBCrypt(string plainText, string salt);
23 |
24 | ///
25 | /// Verfies the hash and password with Brcypt
26 | ///
27 | ///
28 | ///
29 | ///
30 | ///
31 | ///
32 | ///
33 | public bool VerifyBCrypt(string plainText, string salt, string hash);
34 | }
35 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Controllers/LoginController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 | using Microsoft.AspNetCore.Mvc;
3 | using API.Models;
4 | using API.Operations.Interfaces;
5 | using API.Helpers.Interfaces;
6 |
7 | namespace API.Controllers;
8 |
9 | [Route("api/[controller]")]
10 | [ApiController]
11 | public class LoginController : CustomBaseController
12 | {
13 | private readonly IUserLib _userLib;
14 | private readonly IAuthLib _authLib;
15 | private readonly IWebAPIHelper _webAPIHelper;
16 |
17 | public LoginController(IUserLib userLib, IAuthLib authLib, IWebAPIHelper webAPIHelper)
18 | {
19 | _userLib = userLib;
20 | _authLib = authLib;
21 | _webAPIHelper = webAPIHelper;
22 | }
23 |
24 | [AllowAnonymous]
25 | [HttpPost]
26 | public IActionResult Login([FromBody]LoginDTO loginDTO)
27 | {
28 | var result = _authLib.ValidateLogin(loginDTO);
29 | if (result == 1)
30 | {
31 | var user = _userLib.GetUser(loginDTO.UserName);
32 | var token = _authLib.GenerateToken(userId: user.Id,
33 | userRole: user.Roles,
34 | userName: user.Name,
35 | companyId: user.CompanyId);
36 | return _webAPIHelper.CreateResponse(token);
37 | }
38 | return _webAPIHelper.CreateResponse(result);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/.github/workflows/CD.yml:
--------------------------------------------------------------------------------
1 | name: CD
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 |
7 | jobs:
8 | build-windows:
9 | runs-on: windows-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v6.0.0
13 | with:
14 | show-progress: false
15 | - uses: actions/cache@v4
16 | with:
17 | path: ~/.nuget/packages
18 | key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
19 | restore-keys: |
20 | ${{ runner.os }}-nuget-
21 | - name: Setup .NET Core SDK
22 | uses: actions/setup-dotnet@v5.0.1
23 | with:
24 | dotnet-version: 8.0.x
25 | - name: Install dependencies
26 | run: |
27 | cd ./src/WebApiBolierplate
28 | dotnet restore
29 | - name: Build
30 | run: |
31 | cd ./src/WebApiBolierplate
32 | dotnet build --configuration Release --no-restore
33 | - name: Test
34 | run: |
35 | cd ./src/WebApiBolierplate
36 | dotnet test ./Core.Test/Core.Test.csproj
37 | build-linux:
38 | runs-on: ubuntu-latest
39 | steps:
40 | - name: Checkout
41 | uses: actions/checkout@v6.0.0
42 | with:
43 | show-progress: false
44 | - name: Install dependencies
45 | run: |
46 | cd ./src/WebApiBolierplate
47 | dotnet restore
48 | - name: Build
49 | run: |
50 | cd ./src/WebApiBolierplate
51 | dotnet build --configuration Release --no-restore
52 | - name: Test
53 | run: |
54 | cd ./src/WebApiBolierplate
55 | dotnet test ./Core.Test/Core.Test.csproj
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # .Net Web API boilerplate
2 |
3 | [](https://github.com/kolappannathan/dotnet-web-api-boilerplate/generate)
4 | [](https://github.com/kolappannathan/dotnet-web-api-boilerplate/actions?query=workflow%3ACD)
5 | [](https://github.com/kolappannathan/dotnet-web-api-boilerplate/releases)
6 |
7 | A boilerplate / template for a WebAPI server based on ASP.Net.
8 |
9 | ## Scope
10 |
11 | This API boilerplate includes the following:
12 |
13 | - Role based JWT authentication.
14 | - Web API Helper which standardizes responses, maps errors, etc...
15 | - An implementation of Serilog for logging.
16 | - A core library with the following
17 | - Custom data model attributes
18 | - Database adapter
19 | - Helper functions for hashing, encrypting, compression, random number & character generation, etc...
20 | - A constants library for commonly used constants.
21 |
22 | ## Notice
23 |
24 | **Before using the code in production**
25 |
26 | ###### Change the following values
27 |
28 | - In appsettings.json
29 | 1. Database connection string.
30 | 2. JWT secret, issuer and audience
31 | - Update the login controller, user lib & auth lib.
32 | - In program.cs
33 | 1. Update CORS websites list
34 |
35 | ###### Remove the following
36 | - Values controller, values lib & corresponding interfaces
37 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 |
9 | # github actions
10 | - package-ecosystem: "github-actions"
11 | # Workflow files stored in the
12 | # default location of `.github/workflows`
13 | directory: "/"
14 | schedule:
15 | interval: "monthly"
16 |
17 | # nuget packages
18 | - package-ecosystem: "nuget" # See documentation for possible values
19 | directory: "/src/WebApiBolierplate/API/" # Location of package manifests
20 | schedule:
21 | interval: "monthly"
22 | groups:
23 | nuget-dependencies:
24 | patterns:
25 | - "*"
26 | - package-ecosystem: "nuget" # See documentation for possible values
27 | directory: "/src/WebApiBolierplate/Core.Constants/" # Location of package manifests
28 | schedule:
29 | interval: "monthly"
30 | groups:
31 | nuget-dependencies:
32 | patterns:
33 | - "*"
34 | - package-ecosystem: "nuget" # See documentation for possible values
35 | directory: "/src/WebApiBolierplate/Core.Lib/" # Location of package manifests
36 | schedule:
37 | interval: "monthly"
38 | groups:
39 | nuget-dependencies:
40 | patterns:
41 | - "*"
42 | - package-ecosystem: "nuget" # See documentation for possible values
43 | directory: "/src/WebApiBolierplate/Core.Test/" # Location of package manifests
44 | schedule:
45 | interval: "monthly"
46 | groups:
47 | nuget-dependencies:
48 | patterns:
49 | - "*"
50 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Controllers/ValuesController.cs:
--------------------------------------------------------------------------------
1 | using API.Helpers.Interfaces;
2 | using API.Operations.Interfaces;
3 | using Core.Constants;
4 | using Microsoft.AspNetCore.Authorization;
5 | using Microsoft.AspNetCore.Mvc;
6 |
7 | namespace API.Controllers;
8 |
9 | [Route("api/[controller]")]
10 | [ApiController]
11 | public class ValuesController : CustomBaseController
12 | {
13 | private readonly IValueLib _valueLib;
14 | private readonly IWebAPIHelper _webAPIHelper;
15 |
16 | public ValuesController(IValueLib valueLib, IWebAPIHelper webAPIHelper)
17 | {
18 | _valueLib = valueLib;
19 | _webAPIHelper = webAPIHelper;
20 | }
21 |
22 | [HttpGet("")]
23 | [Authorize(Roles = AuthRoles.All)]
24 | public IActionResult Get()
25 | {
26 | var result = _valueLib.GetValueList();
27 | return _webAPIHelper.CreateResponse(result);
28 | }
29 |
30 | [HttpGet("{id}")]
31 | [Authorize(Roles = AuthRoles.All)]
32 | public IActionResult Get([FromRoute]int id)
33 | {
34 | var result = _valueLib.GetValueById(id);
35 | if (result == string.Empty)
36 | {
37 | return new NotFoundResult();
38 | }
39 | return _webAPIHelper.CreateResponse(result);
40 | }
41 |
42 | [HttpPost("")]
43 | [Authorize(Roles = AuthRoles.Admin)]
44 | public IActionResult Post([FromBody] string value)
45 | {
46 | var result = _valueLib.AddValue(value);
47 | return _webAPIHelper.CreateResponse(result);
48 | }
49 |
50 | [HttpPut("{id}")]
51 | [Authorize(Roles = AuthRoles.Admin)]
52 | public IActionResult Put([FromRoute]int id, [FromBody] string value)
53 | {
54 | var result = _valueLib.UpdateValue(value, id);
55 | return _webAPIHelper.CreateResponse(result);
56 | }
57 |
58 | [HttpDelete("{id}")]
59 | [Authorize(Roles = AuthRoles.Admin)]
60 | public IActionResult Delete([FromRoute]int id)
61 | {
62 | var result = _valueLib.DeleteValue(id);
63 | return _webAPIHelper.CreateResponse(result);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Utilities/RandomUtils.cs:
--------------------------------------------------------------------------------
1 | using Core.Lib.Utilities.Interfaces;
2 | using System;
3 | using System.Security.Cryptography;
4 | using static Core.Constants.Enums;
5 |
6 | namespace Core.Lib.Utilities;
7 |
8 | public sealed class RandomUtils : IRandomUtils
9 | {
10 | #region [Declarations]
11 |
12 | private readonly string _uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
13 | private readonly string _lowercase = "abcdefghijklmnopqrstuvwxyz";
14 | private readonly string _numbers = "0123456789";
15 |
16 | #endregion Declarations
17 |
18 | #region [Private Functions]
19 |
20 | ///
21 | /// Returns the characters as an array for the Characer set specified
22 | ///
23 | ///
24 | ///
25 | private char[] GetChars(CharSet charSet)
26 | {
27 | var s1 = string.Empty;
28 | switch (charSet)
29 | {
30 | case CharSet.Alphabets:
31 | s1 = _uppercase + _lowercase;
32 | break;
33 | case CharSet.Numbers:
34 | s1 = _numbers;
35 | break;
36 | case CharSet.AlphaNumeric:
37 | s1 = _uppercase + _lowercase + _numbers;
38 | break;
39 | case CharSet.UppercaseOnly:
40 | s1 = _uppercase;
41 | break;
42 | case CharSet.LowercaseOnly:
43 | s1 = _lowercase;
44 | break;
45 | case CharSet.UppercaseWithNumbers:
46 | s1 = _uppercase + _numbers;
47 | break;
48 | case CharSet.LowercaseWithNumbers:
49 | s1 = _lowercase + _numbers;
50 | break;
51 | }
52 | return s1.ToCharArray();
53 | }
54 |
55 | #endregion [Private Functions]
56 |
57 | #region [Public Functions]
58 |
59 | public string GenRandomChar(int length, CharSet charSet)
60 | {
61 | ArgumentOutOfRangeException.ThrowIfLessThan(length, 1);
62 |
63 | var chars = GetChars(charSet);
64 | var stringChars = RandomNumberGenerator.GetString(chars, length);
65 | return new string(stringChars);
66 | }
67 |
68 | #endregion [Public Functions]
69 | }
70 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Utilities/Interfaces/IJwtUtils.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Core.Lib.Utilities.Interfaces;
4 |
5 | public interface IJwtUtils
6 | {
7 | public string GenerateToken();
8 |
9 | #region [Adding values]
10 |
11 | ///
12 | /// Adds the security key used for signing JWT token
13 | ///
14 | public IJwtUtils AddSecurityKey(string securityKey);
15 |
16 | ///
17 | /// Adds expiry time in hours to JWT token
18 | ///
19 | public IJwtUtils AddExpiry(int expiryInHours);
20 |
21 | #endregion [Adding values]
22 |
23 | #region [Adding Claims]
24 |
25 | ///
26 | /// Adds issuer to JWT token
27 | ///
28 | public IJwtUtils AddIssuer(string issuer);
29 |
30 | ///
31 | /// Adds audience value to JWT token
32 | ///
33 | public IJwtUtils AddAudience(string audience);
34 |
35 | ///
36 | /// Adds subject as a claim to the token
37 | ///
38 | public IJwtUtils AddSubject(string subject);
39 |
40 | ///
41 | /// Adds user name as claim to the token
42 | ///
43 | public IJwtUtils AddName(string username);
44 |
45 | ///
46 | /// Adds email as claim to JWT token
47 | ///
48 | public IJwtUtils AddEmail(string email);
49 |
50 | ///
51 | /// Adds user role as claim to the token
52 | ///
53 | public IJwtUtils AddRole(string value);
54 |
55 | #region [Custom Claims]
56 |
57 | public IJwtUtils AddUserId(string value);
58 |
59 | public IJwtUtils AddCompanyId(string value);
60 |
61 | #endregion [Custom Claims]
62 |
63 | #region [Generic Claims]
64 |
65 | ///
66 | /// Adds a given value to the given claim
67 | ///
68 | /// claim type
69 | /// value for the claim
70 | ///
71 | public IJwtUtils AddClaim(string type, string value);
72 |
73 | ///
74 | /// Bulk add claims to the JWT tokens
75 | ///
76 | /// list of claim types and its corresponding values
77 | ///
78 | public IJwtUtils AddClaims(Dictionary claimList);
79 |
80 | #endregion [Generic Claims]
81 |
82 | #endregion [Adding Claims]
83 | }
84 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Operations/AuthLib.cs:
--------------------------------------------------------------------------------
1 | using API.Models;
2 | using API.Operations.Interfaces;
3 | using Core.Lib.Utilities.Interfaces;
4 |
5 | namespace API.Operations;
6 |
7 | public class AuthLib : IAuthLib
8 | {
9 | #region [Declarations]
10 |
11 | private const string _brandonStark = "Brandon Stark";
12 | private const string _eddardStark = "Eddard Stark";
13 | private readonly IConfiguration _configuration;
14 | private IJwtUtils _jwtTokenBuilder;
15 | private readonly ILogger _logger;
16 |
17 | #endregion [Declarations]
18 |
19 | public AuthLib(IConfiguration configuration, IJwtUtils jwtTokenBuilder, ILogger logger)
20 | {
21 | _configuration = configuration;
22 | _jwtTokenBuilder = jwtTokenBuilder;
23 | _logger = logger;
24 | }
25 |
26 | public int ValidateLogin(LoginDTO login)
27 | {
28 | try
29 | {
30 | _logger.LogInformation("Login validation started");
31 |
32 | if (login.UserName == _brandonStark && login.Password == "My direwolf is Summer")
33 | {
34 | return 1;
35 | }
36 | else if (login.UserName == _eddardStark && login.Password == "I'm the lord of WinterFell")
37 | {
38 | return 1;
39 | }
40 | else if (login.UserName == _brandonStark && login.Password == "My direwolf is Lady")
41 | {
42 | return -103; // invalid credential
43 | }
44 | else if (login.UserName == _brandonStark && login.Password == "Eddard's elder brother")
45 | {
46 | return -104; // account deleted
47 | }
48 | else if (login.UserName == "No one" && login.Password == "A man wants an exception")
49 | {
50 | throw new Exception("This is an excpetion");
51 | }
52 | return -102; // account not found
53 | }
54 | catch (Exception ex)
55 | {
56 | _logger.LogError("ValidateLogin - {message}", ex.Message);
57 | return -1;
58 | }
59 | }
60 |
61 | public string GenerateToken(string userId, string userRole = null, string userName = null, string companyId = null)
62 | {
63 | ArgumentException.ThrowIfNullOrEmpty(userId);
64 |
65 | _jwtTokenBuilder
66 | .AddSecurityKey(_configuration["AppConfig:JWT:Key"])
67 | .AddIssuer(_configuration["AppConfig:JWT:Issuer"])
68 | .AddAudience(_configuration["AppConfig:JWT:Audience"])
69 | .AddExpiry(Convert.ToInt32(_configuration["AppConfig:JWT:HoursValid"]))
70 | .AddRole(userRole)
71 | .AddName(userName)
72 | .AddUserId(userId)
73 | .AddCompanyId(companyId);
74 |
75 | return _jwtTokenBuilder.GenerateToken();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "main" ]
17 | paths-ignore:
18 | - '.github/**'
19 | schedule:
20 | - cron: '5 9 12 * *'
21 |
22 | jobs:
23 | analyze:
24 | name: Analyze
25 | runs-on: ubuntu-latest
26 | permissions:
27 | actions: read
28 | contents: read
29 | security-events: write
30 |
31 | strategy:
32 | fail-fast: false
33 | matrix:
34 | language: [ 'csharp' ]
35 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
36 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
37 |
38 | steps:
39 | - name: Checkout repository
40 | uses: actions/checkout@v6.0.0
41 |
42 | # Initializes the CodeQL tools for scanning.
43 | - name: Initialize CodeQL
44 | uses: github/codeql-action/init@v4
45 | with:
46 | languages: ${{ matrix.language }}
47 | # If you wish to specify custom queries, you can do so here or in a config file.
48 | # By default, queries listed here will override any specified in a config file.
49 | # Prefix the list here with "+" to use these queries and those in the config file.
50 |
51 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
52 | # queries: security-extended,security-and-quality
53 |
54 |
55 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
56 | # If this step fails, then you should remove it and run the build manually (see below)
57 | - name: Autobuild
58 | uses: github/codeql-action/autobuild@v4
59 |
60 | # ℹ️ Command-line programs to run using the OS shell.
61 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
62 |
63 | # If the Autobuild fails above, remove it and uncomment the following three lines.
64 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
65 |
66 | # - run: |
67 | # echo "Run, Build Application using script"
68 | # ./location_of_script_within_repo/buildscript.sh
69 |
70 | - name: Perform CodeQL Analysis
71 | uses: github/codeql-action/analyze@v4
72 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Helpers/WebAPIHelper.cs:
--------------------------------------------------------------------------------
1 | using Core.Constants;
2 | using Microsoft.AspNetCore.Mvc;
3 | using API.Models;
4 | using System.Net;
5 | using API.Helpers.Interfaces;
6 |
7 | namespace API.Helpers;
8 |
9 | public sealed class WebAPIHelper: IWebAPIHelper
10 | {
11 | public JsonResult CreateResponse(object data)
12 | {
13 | var apiResponse = data switch
14 | {
15 | bool resData when resData == false => CreateErrorResponse(Errors.InternalServerError),
16 | int resData when resData < 0 => CreateErrorResponse(resData),
17 | APIResponse resData => resData,
18 | _ => new APIResponse(data, string.Empty, false),
19 | };
20 | return new JsonResult(apiResponse)
21 | {
22 | StatusCode = (int)apiResponse.StatusCode
23 | };
24 | }
25 |
26 | public BadRequestObjectResult CreateBadRequest(string errorText)
27 | {
28 | var apiResponse = new APIResponse(string.Empty, errorText, true, HttpStatusCode.BadRequest);
29 | return new BadRequestObjectResult(apiResponse);
30 | }
31 |
32 | public APIResponse CreateErrorResponse(string errorText, dynamic data = null)
33 | {
34 | if (data is int code && code < 0)
35 | {
36 | return CreateErrorResponse(code, data);
37 | }
38 | return new APIResponse(data, errorText, true, HttpStatusCode.InternalServerError);
39 | }
40 |
41 | #region Private Functions
42 |
43 | private APIResponse CreateErrorResponse(int errorCode, dynamic data = null)
44 | {
45 | (var resText, var resCode) = GetNotification(Convert.ToInt32(errorCode));
46 | return new APIResponse(data, resText, true, resCode);
47 | }
48 |
49 | ///
50 | /// Contains mapping for error codes to the Error messages and status
51 | ///
52 | private static readonly Dictionary notifications = new Dictionary()
53 | {
54 | { -1, (Errors.InternalServerError, HttpStatusCode.InternalServerError) },
55 | { -102, (Errors.AccountNotFound, HttpStatusCode.Unauthorized) },
56 | { -103, (Errors.InvalidCredentials, HttpStatusCode.Unauthorized) },
57 | { -104, (Errors.AccountDeleted, HttpStatusCode.Unauthorized) }
58 | };
59 |
60 | ///
61 | /// Returns the error message, and for the specified error code
62 | ///
63 | /// The error code obtained from business layer or other functions
64 | ///
65 | private static (string, HttpStatusCode) GetNotification(int code)
66 | {
67 | (string, HttpStatusCode) value;
68 | bool hasValue = notifications.TryGetValue(code, out value);
69 | if (!hasValue)
70 | {
71 | value = (Errors.InternalServerError, HttpStatusCode.InternalServerError);
72 | }
73 | return value;
74 | }
75 |
76 | #endregion Private Functions
77 | }
78 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Utilities/SecurityUtils.cs:
--------------------------------------------------------------------------------
1 | using Core.Constants;
2 | using Core.Lib.Utilities.Interfaces;
3 | using System;
4 | using System.IO;
5 | using System.Security.Cryptography;
6 | using System.Text;
7 |
8 | namespace Core.Lib.Utilities;
9 |
10 | ///
11 | /// This class offer simple encryption and decryption
12 | /// Ref: https://stackoverflow.com/a/27484425/5407188
13 | ///
14 | public sealed class SecurityUtils : ISecurityUtils
15 | {
16 | #region [Private Functions]
17 | private Aes BuildAesEncryptor(string encryptionKey)
18 | {
19 | var aesEncryptor = Aes.Create();
20 | var pdb = new Rfc2898DeriveBytes(
21 | password: encryptionKey,
22 | salt: "335f0298-9eae-4285-890e-ef7243c974f0"u8.ToArray(),
23 | iterations: 5033,
24 | hashAlgorithm: HashAlgorithmName.SHA512);
25 | aesEncryptor.Key = pdb.GetBytes(32);
26 | aesEncryptor.IV = pdb.GetBytes(16);
27 | return aesEncryptor;
28 | }
29 |
30 | #endregion [Private Functions]
31 |
32 | public string EncryptString(string clearText, string encryptionKey)
33 | {
34 | var aesEncryptor = BuildAesEncryptor(encryptionKey);
35 | var clearBytes = Encoding.Unicode.GetBytes(clearText);
36 | using (var ms = new MemoryStream())
37 | {
38 | using (var cs = new CryptoStream(ms, aesEncryptor.CreateEncryptor(), CryptoStreamMode.Write))
39 | {
40 | cs.Write(clearBytes, 0, clearBytes.Length);
41 | }
42 | var encryptedText = Convert.ToBase64String(ms.ToArray());
43 | return encryptedText;
44 | }
45 | }
46 |
47 | public string DecryptString(string cipherText, string encryptionKey)
48 | {
49 | var aesEncryptor = BuildAesEncryptor(encryptionKey);
50 | cipherText = cipherText.Replace(" ", "+");
51 | var cipherBytes = Convert.FromBase64String(cipherText);
52 | using (var ms = new MemoryStream())
53 | {
54 | using (var cs = new CryptoStream(ms, aesEncryptor.CreateDecryptor(), CryptoStreamMode.Write))
55 | {
56 | cs.Write(cipherBytes, 0, cipherBytes.Length);
57 | }
58 | var clearText = Encoding.Unicode.GetString(ms.ToArray());
59 | return clearText;
60 | }
61 | }
62 |
63 | public string HashBCrypt(string plainText, string salt)
64 | {
65 | ArgumentException.ThrowIfNullOrEmpty(plainText);
66 | ArgumentException.ThrowIfNullOrEmpty(salt);
67 |
68 | var hash = BCrypt.Net.BCrypt.HashPassword(plainText + salt, workFactor: 10);
69 | return hash;
70 | }
71 |
72 | public bool VerifyBCrypt(string plainText, string salt, string hash)
73 | {
74 | ArgumentException.ThrowIfNullOrEmpty(plainText);
75 | ArgumentException.ThrowIfNullOrEmpty(salt);
76 | ArgumentException.ThrowIfNullOrEmpty(hash);
77 |
78 | var isMatch = BCrypt.Net.BCrypt.Verify(plainText + salt, hash);
79 | return isMatch;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/API/Program.cs:
--------------------------------------------------------------------------------
1 | using API.Helpers;
2 | using API.Operations;
3 | using Microsoft.AspNetCore.Authentication.JwtBearer;
4 | using Microsoft.AspNetCore.Mvc;
5 | using Microsoft.Extensions.Configuration;
6 | using Microsoft.IdentityModel.Tokens;
7 | using Microsoft.OpenApi.Models;
8 | using System.Text;
9 | using Serilog;
10 | using Serilog.Events;
11 | using API.Operations.Interfaces;
12 | using API.Helpers.Interfaces;
13 | using Core.Lib.Adapters;
14 | using Core.Lib.Utilities.Interfaces;
15 | using Core.Lib.Utilities;
16 | using API.Configurations;
17 |
18 | var builder = WebApplication.CreateBuilder(args);
19 |
20 | // If needed, Clear default providers
21 | builder.Logging.ClearProviders();
22 |
23 | // Use Serilog
24 | builder.Host.UseSerilog((hostContext, services, loggerConfig) => {
25 | loggerConfig
26 | .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
27 | .WriteTo.File( "Logs/api-.log", rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true);
28 | });
29 |
30 | // To prevent .NET and server info from being added to header if Kestrel is used
31 | builder.WebHost.ConfigureKestrel(serverOptions => {
32 | serverOptions.AddServerHeader = false;
33 | });
34 |
35 | builder.Services.AddHsts(options => {
36 | options.Preload = true;
37 | options.MaxAge = TimeSpan.FromDays(30);
38 | });
39 |
40 | #region Configuring Services
41 |
42 | builder.Services.AddControllers();
43 |
44 | builder.Services
45 | .AddProjectServices()
46 | .AddProjectValidations()
47 | .AddProjectAuthServices(builder.Configuration);
48 |
49 | #region Swagger
50 |
51 | builder.Services.AddEndpointsApiExplorer();
52 | builder.Services.AddSwaggerGen( c => {
53 | c.SwaggerDoc("v1", new OpenApiInfo { Title="API", Version="1.0.0" });
54 | c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
55 | {
56 | Name = "Authorization",
57 | Type = SecuritySchemeType.ApiKey,
58 | Scheme = "Bearer",
59 | BearerFormat = "JWT",
60 | In = ParameterLocation.Header,
61 | Description = "Enter the Bearer Authorization string as following: `Bearer Generated-JWT-Token`"
62 | });
63 | c.AddSecurityRequirement(new OpenApiSecurityRequirement {
64 | {
65 | new OpenApiSecurityScheme {
66 | Reference = new OpenApiReference {
67 | Type = ReferenceType.SecurityScheme,
68 | Id = "Bearer"
69 | }
70 | },
71 | new string[] {}
72 | }
73 | });
74 | });
75 |
76 | #endregion Swagger
77 |
78 | #endregion Configuring Services
79 |
80 | var app = builder.Build();
81 | if (app.Environment.IsDevelopment())
82 | {
83 | app.UseSwagger();
84 | app.UseSwaggerUI();
85 | }
86 | else
87 | {
88 | app.UseHsts();
89 | }
90 |
91 | app.UseCors(options =>
92 | options
93 | .AllowAnyHeader()
94 | .AllowAnyMethod()
95 | .WithOrigins(new[] { "https://localhost:7030/" })
96 | );
97 |
98 | app.UseHttpsRedirection();
99 |
100 | app.UseSerilogRequestLogging();
101 |
102 | app.UseRouting();
103 | app.UseAuthentication();
104 | app.UseAuthorization();
105 | app.MapControllers();
106 |
107 | app.Run();
108 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/WebApiBolierplate.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.1.32421.90
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "API", "API\API.csproj", "{63F1C3A8-3F0D-47F1-8BC9-2E2645C6ABF3}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{DF265B0E-249D-4395-BCE8-03B031708DB5}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Lib", "Core.Lib\Core.Lib.csproj", "{3C9B3915-9D3D-4D3B-ADB4-1F976DF2B60B}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Constants", "Core.Constants\Core.Constants.csproj", "{B97C8E88-5711-44D9-A362-43F91A6F2C0C}"
13 | EndProject
14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{010FAFE4-6656-42DE-8A0F-3A1E5EAB314A}"
15 | ProjectSection(SolutionItems) = preProject
16 | .editorconfig = .editorconfig
17 | EndProjectSection
18 | EndProject
19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Test", "Core.Test\Core.Test.csproj", "{BC6F2A83-F94C-4145-A00C-D1F98DE88E38}"
20 | EndProject
21 | Global
22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
23 | Debug|Any CPU = Debug|Any CPU
24 | Release|Any CPU = Release|Any CPU
25 | EndGlobalSection
26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
27 | {63F1C3A8-3F0D-47F1-8BC9-2E2645C6ABF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {63F1C3A8-3F0D-47F1-8BC9-2E2645C6ABF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {63F1C3A8-3F0D-47F1-8BC9-2E2645C6ABF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {63F1C3A8-3F0D-47F1-8BC9-2E2645C6ABF3}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {3C9B3915-9D3D-4D3B-ADB4-1F976DF2B60B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {3C9B3915-9D3D-4D3B-ADB4-1F976DF2B60B}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {3C9B3915-9D3D-4D3B-ADB4-1F976DF2B60B}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {3C9B3915-9D3D-4D3B-ADB4-1F976DF2B60B}.Release|Any CPU.Build.0 = Release|Any CPU
35 | {B97C8E88-5711-44D9-A362-43F91A6F2C0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {B97C8E88-5711-44D9-A362-43F91A6F2C0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {B97C8E88-5711-44D9-A362-43F91A6F2C0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {B97C8E88-5711-44D9-A362-43F91A6F2C0C}.Release|Any CPU.Build.0 = Release|Any CPU
39 | {BC6F2A83-F94C-4145-A00C-D1F98DE88E38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40 | {BC6F2A83-F94C-4145-A00C-D1F98DE88E38}.Debug|Any CPU.Build.0 = Debug|Any CPU
41 | {BC6F2A83-F94C-4145-A00C-D1F98DE88E38}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {BC6F2A83-F94C-4145-A00C-D1F98DE88E38}.Release|Any CPU.Build.0 = Release|Any CPU
43 | EndGlobalSection
44 | GlobalSection(SolutionProperties) = preSolution
45 | HideSolutionNode = FALSE
46 | EndGlobalSection
47 | GlobalSection(NestedProjects) = preSolution
48 | {3C9B3915-9D3D-4D3B-ADB4-1F976DF2B60B} = {DF265B0E-249D-4395-BCE8-03B031708DB5}
49 | {B97C8E88-5711-44D9-A362-43F91A6F2C0C} = {DF265B0E-249D-4395-BCE8-03B031708DB5}
50 | {BC6F2A83-F94C-4145-A00C-D1F98DE88E38} = {DF265B0E-249D-4395-BCE8-03B031708DB5}
51 | EndGlobalSection
52 | GlobalSection(ExtensibilityGlobals) = postSolution
53 | SolutionGuid = {5E2BFE0C-9475-430E-95F1-3D76F7FD2BBE}
54 | EndGlobalSection
55 | EndGlobal
56 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Adapters/DBAdapter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data;
3 | using Microsoft.Data.SqlClient;
4 |
5 | namespace Core.Lib.Adapters;
6 |
7 | public sealed class DbAdapter
8 | {
9 | #region [Declarations]
10 |
11 | private readonly SqlConnection _sqlConnection;
12 |
13 | #endregion [Declarations]
14 |
15 | ///
16 | /// Initialises the dbAdapter and opens the dbconnection
17 | ///
18 | /// The database connection string
19 | ///
20 | ///
21 | public DbAdapter(string connectionString)
22 | {
23 | ArgumentException.ThrowIfNullOrEmpty(connectionString);
24 |
25 | _sqlConnection = new SqlConnection(connectionString);
26 | _sqlConnection.Open();
27 | }
28 |
29 | ///
30 | /// Closes the connection when the object is destroyed
31 | ///
32 | ~DbAdapter()
33 | {
34 | if (_sqlConnection != null)
35 | {
36 | // Don't close the connection if the conection is already closed or broken
37 | if (_sqlConnection.State != ConnectionState.Closed || _sqlConnection.State != ConnectionState.Broken)
38 | {
39 | try
40 | {
41 | _sqlConnection.Close();
42 | }
43 | catch
44 | {
45 | }
46 | }
47 | }
48 | }
49 |
50 | ///
51 | /// Returns the dbcommand for stored procedure
52 | ///
53 | /// Name of the stored procedure
54 | ///
55 | ///
56 | ///
57 | public SqlCommand GetStoredProcedure(string name)
58 | {
59 | ArgumentException.ThrowIfNullOrEmpty(name);
60 | if (_sqlConnection.State != ConnectionState.Open)
61 | {
62 | _sqlConnection.Open();
63 | }
64 |
65 | var dbCommand = new SqlCommand(name, _sqlConnection)
66 | {
67 | CommandType = CommandType.StoredProcedure
68 | };
69 | return dbCommand;
70 | }
71 |
72 | ///
73 | /// Execute the query in scalar and closes the connection
74 | ///
75 | /// SQL Command after adding parameters
76 | ///
77 | ///
78 | public object ExecuteScalar(SqlCommand dbCommand)
79 | {
80 | ArgumentNullException.ThrowIfNull(dbCommand);
81 |
82 | var result = dbCommand.ExecuteScalar();
83 | return result;
84 | }
85 |
86 | ///
87 | /// Execute the query and returns
88 | ///
89 | /// SQL Command after adding parameters
90 | ///
91 | ///
92 | public IDataReader ExecuteReader(SqlCommand dbCommand)
93 | {
94 | ArgumentNullException.ThrowIfNull(dbCommand);
95 |
96 | var result = dbCommand.ExecuteReader();
97 | return result;
98 | }
99 |
100 | ///
101 | /// Execute the command with ExecuteNonQuery
102 | ///
103 | /// SQL Command after adding parameters
104 | ///
105 | ///
106 | public int ExecuteNonQuery(SqlCommand dbCommand)
107 | {
108 | ArgumentNullException.ThrowIfNull(dbCommand);
109 |
110 | var result = dbCommand.ExecuteNonQuery();
111 | return result;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/Core.Lib/Utilities/JwtUtils.cs:
--------------------------------------------------------------------------------
1 | using Core.Lib.Utilities.Interfaces;
2 | using Core.Constants;
3 | using Microsoft.IdentityModel.Tokens;
4 | using System.Collections.Generic;
5 | using System.IdentityModel.Tokens.Jwt;
6 | using System.Security.Claims;
7 | using System.Text;
8 | using System;
9 |
10 | namespace Core.Lib.Utilities;
11 |
12 | public sealed class JwtUtils: IJwtUtils
13 | {
14 | #region [Declarations]
15 |
16 | private SecurityKey _securityKey = null;
17 | private int _expiryInHours = 0;
18 | private List _claims = new();
19 |
20 | #endregion [Declarations]
21 |
22 | public string GenerateToken()
23 | {
24 | EnsureArguments();
25 | var token = BuildJwtSecurityToken();
26 | return new JwtSecurityTokenHandler().WriteToken(token);
27 | }
28 |
29 | #region [Private Functions]
30 |
31 | ///
32 | /// Validates important arguments
33 | ///
34 | private void EnsureArguments()
35 | {
36 | ArgumentNullException.ThrowIfNull(_securityKey);
37 | ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(_expiryInHours, 0);
38 | }
39 |
40 | ///
41 | /// Adds the final claim for issued at, jwt id and builds JwtSecurityToken object
42 | ///
43 | ///
44 | private JwtSecurityToken BuildJwtSecurityToken()
45 | {
46 | AddClaim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString("D"));
47 |
48 | var currentUnixTime = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds().ToString();
49 | _claims.Add(new Claim(JwtRegisteredClaimNames.Nbf, currentUnixTime, ClaimValueTypes.Integer64));
50 | _claims.Add(new Claim(JwtRegisteredClaimNames.Iat, currentUnixTime, ClaimValueTypes.Integer64));
51 |
52 | return new JwtSecurityToken(
53 | claims: _claims,
54 | expires: DateTime.UtcNow.AddHours(_expiryInHours),
55 | signingCredentials: new SigningCredentials(_securityKey, SecurityAlgorithms.HmacSha256));
56 | }
57 |
58 | #endregion [Private Functions]
59 |
60 | #region [Adding values]
61 |
62 | public IJwtUtils AddSecurityKey(string securityKey)
63 | {
64 | _securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));
65 | return this;
66 | }
67 |
68 | public IJwtUtils AddExpiry(int expiryInHours)
69 | {
70 | _expiryInHours = expiryInHours;
71 | return this;
72 | }
73 |
74 | #endregion [Adding values]
75 |
76 | #region [Adding Claims]
77 |
78 | public IJwtUtils AddIssuer(string issuer)
79 | {
80 | return AddClaim(JwtRegisteredClaimNames.Iss, issuer);
81 | }
82 |
83 | public IJwtUtils AddAudience(string audience)
84 | {
85 | return AddClaim(JwtRegisteredClaimNames.Aud, audience);
86 | }
87 |
88 | public IJwtUtils AddSubject(string subject)
89 | {
90 | return AddClaim(JwtRegisteredClaimNames.Sub, subject);
91 | }
92 |
93 | public IJwtUtils AddName(string username)
94 | {
95 | return AddClaim(JwtRegisteredClaimNames.NameId, username);
96 | }
97 |
98 | public IJwtUtils AddEmail(string email)
99 | {
100 | return AddClaim(JwtRegisteredClaimNames.Email, email);
101 | }
102 |
103 | public IJwtUtils AddRole(string value)
104 | {
105 | // Changing this to JwtRegisteredClaimNames will break role based authentication
106 | return AddClaim(ClaimTypes.Role, value);
107 | }
108 |
109 | #region [Custom Claims]
110 |
111 | public IJwtUtils AddUserId(string value)
112 | {
113 | return AddClaim(CustomClaims.UserIdentifier, value);
114 | }
115 |
116 | public IJwtUtils AddCompanyId(string value)
117 | {
118 | return AddClaim(CustomClaims.CompanyIdentifier, value);
119 | }
120 |
121 | #endregion [Custom Claims]
122 |
123 | #region [Generic Claims]
124 |
125 | public IJwtUtils AddClaim(string type, string value)
126 | {
127 | if (value != null && type != null)
128 | {
129 | _claims.Add(new Claim(type, value));
130 | }
131 | return this;
132 | }
133 |
134 | public IJwtUtils AddClaims(Dictionary claimList)
135 | {
136 | foreach (var claim in claimList)
137 | {
138 | if (claim.Value != null && claim.Key != null)
139 | {
140 | _claims.Add(new Claim(claim.Key, claim.Value));
141 | }
142 | }
143 | return this;
144 | }
145 |
146 | #endregion [Generic Claims]
147 |
148 | #endregion [Adding Claims]
149 | }
150 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
4 |
5 | ## [Unreleased]
6 | ### Changed
7 | - Updated HSTS header
8 | - Updated dependencies
9 |
10 | ## [8.0.0] - 2023-12-02
11 | ### Added
12 | - Salt added as separate param for Bcrypt in Security lib. Previously it was indented to be already added and sent with the plain text password.
13 | - New test cases in postman
14 |
15 | ### Changed
16 | - Updated .NET version to 8
17 | - Moved Configurators into a separate folder to simplify Program.cs
18 | - Updated Random String Generator to use `System.Security.Cryptography.RandomNumberGenerator`.
19 | - Using updated checks for Argument exceptions
20 | - Updated dependencies
21 |
22 | ### Removed
23 | - Removed the random number generating function. The built-in function with RandomNumberGenerator to be used instead.
24 |
25 | ## [7.1.0] - 2023-03-02
26 | ### Added
27 | - Added interfaces for library classes, helper functions.
28 | - New AuthLib class for checking login and generating token.
29 |
30 | ### Changed
31 | - Helper functions now use dependency injection.
32 | - Made all non-derived classes sealed for security and performance.
33 | - Optimizations to JWT helpers, moved JWT token builder in core project.
34 | - Private variables now start with underscore.
35 | - JWT config now has Hours valid instead of days.
36 |
37 | ### Removed
38 | - Base class for operations is removed
39 | - Removed unused constants
40 | - Removed JWT Helper function from API project
41 |
42 | ## [7.0.0] - 2023-02-27
43 | ### Added
44 | - CORS settings
45 |
46 | ### Changed
47 | - Updated .NET version to 7
48 | - Using Serilog.AspNetCore for logging instead of serilog extensions as recommended by Serilog
49 | - Log files are now separated by date
50 | - Using new syntax for Argument null checks
51 | - Updated dependencies
52 |
53 | ## [6.1.0] - 2022-08-15
54 | ### Added
55 | - Added dependency Injection
56 | - Added Serilog
57 |
58 | ### Changed
59 | - Updated the default port
60 | - Updated API Helper
61 | - Encryption key is no longer part of code
62 | - Consolidated Business & Models into API project
63 | - Random Number generation now uses RandomNumberGenerator instead of RNGCryptoServiceProvider
64 |
65 | ### Removed
66 | - Removed config static class
67 | - Removed StartupLib and Tasks Controller
68 | - Removed csv logger
69 |
70 | ## [6.0.0] - 2022-07-16
71 | ### Added
72 | - Added Swagger UI
73 |
74 | ### Changed
75 | - Updated .NET to version 6
76 | - Unified Program.cs & startup.cs
77 | - Removing code intent for namespaces
78 | - Bad Request now return isError as true in addition to the HTTP status code
79 | - Postman now updated bearer token automatically after a successful login API call
80 | - Updated dependencies to latest versions
81 |
82 | ## [5.1.0] - 2021-09-19
83 | ### Added
84 | - Added unit testing for Helper functions. Removed the APIs used for manually testing them.
85 | - Added editor config
86 |
87 | ### Changes
88 | - Removed server headers for security
89 | - Connection string in appsettings.json is no longer encrypted. Ref [#74](https://github.com/kolappannathan/dotnet-web-api-boilerplate/issues/74) for details
90 |
91 | ## [5.0.0] - 2021-05-23
92 | ### Changed
93 | - Updated .Net to version 5
94 | - Updated packages
95 | - Moved CI from Azure DevOps to GitHub Actions
96 | - Moved postman collection into the src folder
97 |
98 | ## [4.1.0] - 2020-05-08
99 | ### Changed
100 | - Updating API to .Net core 3.1
101 | - Updating Libraries to .Net standard 2.1
102 | - Updating dependencies
103 | - Fixing some local and package references
104 | - JWT key is now stored as variable in postman collection
105 | - Minor code optimizations such as making some variables read only, using range operator, etc...
106 |
107 | ## [4.0.0] - 2019-09-20
108 | ### Added
109 | - Additional logger config in app settings
110 | - New helper function to remove line endings
111 | - New helper function that combines date and time from two different Date objects
112 | - New Date attributes for PastOnly and PastAndPresent
113 |
114 | ### Changed
115 | - StartupLib now refers to helpers functions in core lib instead of business lib.
116 | - Updating dependencies
117 | - Moved logger config assignment directly to logger
118 | - Renaming and relocating new Date attributes
119 |
120 | ## [3.4.0] - 2019-05-20
121 | ### Changed
122 | - Updated to .Net core 2.2
123 |
124 | ## [3.3.0] - 2019-05-16
125 | ### Added
126 | - New appconfig for development and production
127 |
128 | ### Changed
129 | - Reorganised postman request in the order they appear in code.
130 | - Updated NuGet packages
131 |
132 |
133 | ## [3.2.0] - 2019-05-09
134 | ### Added
135 | - Documentation for encryption helper
136 |
137 | ### Changed
138 | - Renamed CSV Logger
139 | - Optimised code in helpersLib
140 |
141 | ## [3.1.0] - 2019-05-08
142 | ### Changed
143 | - Added sub classes to helper
144 | - Moved Enums to constants
145 | - Minor code optimizations
146 |
147 | ## [3.0.0] - 2019-05-07
148 | ### Added
149 | - New function for encrypting and decrypting strings
150 | - New APIs to test Helper library functions
151 |
152 | ### Changed
153 | - Role claim in JWT changed to fix a bug
154 | - Optimized code in JWT token builder
155 | - Database connection string in appconfig is now encrypted
156 |
157 | ## [2.0.1] - 2019-05-03
158 | ### Added
159 | - New custom claims
160 |
161 | ### Changed
162 | - Code optimisations in JWT token builder
163 | - Few bug fixes in JWT token
164 |
165 |
166 | ## [2.0.0] - 2019-04-30
167 | ### Added
168 | - New startup lib for operations performed during startup
169 | - New API that reassigns config on the fly
170 | - New class for info texts
171 |
172 | ### Changed
173 | - JWT token validity is now taken from config
174 | - Moved Base class in business lib into Core namespace
175 | - Converted Logger functions into expressions
176 |
177 | ## [1.0.1] - 2019-03-25
178 | ### Added
179 | - Added documentation for many functions and classes
180 | - New claims for JWT token
181 |
182 | ### Changed
183 | - Updated NuGet package
184 | - Replace individual claim adding code using a common function
185 |
186 | ## [1.0.0] - 2019-01-11
187 | ### Added
188 | - Added JWT authorization.
189 | - Added new library function for Value controller.
190 | - Added model validation.
191 |
192 | ### Changed
193 | - All responses now use APIResponse model.
194 | - Updated Postman file with value controller and description.
195 | - Moved all config to appsettings.json
196 |
197 | ## [0.0.2] - 2019-01-10
198 | ### Added
199 | - Completed JWT token generation.
200 | - Created business library.
201 | - Added postman collection file.
202 |
203 | ### Changed
204 | - Removed Tuple declarations.
205 | - Constants are now added in startup.cs.
206 | - Other minor code optimizations.
207 |
208 | ## [0.0.1] - 2019-01-02
209 | ### Added
210 | - Added changelog.
211 | - Added core library.
212 | - Added API Helper functions.
213 |
214 | ### Changed
215 | - Format of model validation errors.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.tlog
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Nuget personal access tokens and Credentials
210 | nuget.config
211 |
212 | # Microsoft Azure Build Output
213 | csx/
214 | *.build.csdef
215 |
216 | # Microsoft Azure Emulator
217 | ecf/
218 | rcf/
219 |
220 | # Windows Store app package directories and files
221 | AppPackages/
222 | BundleArtifacts/
223 | Package.StoreAssociation.xml
224 | _pkginfo.txt
225 | *.appx
226 | *.appxbundle
227 | *.appxupload
228 |
229 | # Visual Studio cache files
230 | # files ending in .cache can be ignored
231 | *.[Cc]ache
232 | # but keep track of directories ending in .cache
233 | !?*.[Cc]ache/
234 |
235 | # Others
236 | ClientBin/
237 | ~$*
238 | *~
239 | *.dbmdl
240 | *.dbproj.schemaview
241 | *.jfm
242 | *.pfx
243 | *.publishsettings
244 | orleans.codegen.cs
245 |
246 | # Including strong name files can present a security risk
247 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
248 | #*.snk
249 |
250 | # Since there are multiple workflows, uncomment next line to ignore bower_components
251 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
252 | #bower_components/
253 |
254 | # RIA/Silverlight projects
255 | Generated_Code/
256 |
257 | # Backup & report files from converting an old project file
258 | # to a newer Visual Studio version. Backup files are not needed,
259 | # because we have git ;-)
260 | _UpgradeReport_Files/
261 | Backup*/
262 | UpgradeLog*.XML
263 | UpgradeLog*.htm
264 | ServiceFabricBackup/
265 | *.rptproj.bak
266 |
267 | # SQL Server files
268 | *.mdf
269 | *.ldf
270 | *.ndf
271 |
272 | # Business Intelligence projects
273 | *.rdl.data
274 | *.bim.layout
275 | *.bim_*.settings
276 | *.rptproj.rsuser
277 | *- [Bb]ackup.rdl
278 | *- [Bb]ackup ([0-9]).rdl
279 | *- [Bb]ackup ([0-9][0-9]).rdl
280 |
281 | # Microsoft Fakes
282 | FakesAssemblies/
283 |
284 | # GhostDoc plugin setting file
285 | *.GhostDoc.xml
286 |
287 | # Node.js Tools for Visual Studio
288 | .ntvs_analysis.dat
289 | node_modules/
290 |
291 | # Visual Studio 6 build log
292 | *.plg
293 |
294 | # Visual Studio 6 workspace options file
295 | *.opt
296 |
297 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
298 | *.vbw
299 |
300 | # Visual Studio LightSwitch build output
301 | **/*.HTMLClient/GeneratedArtifacts
302 | **/*.DesktopClient/GeneratedArtifacts
303 | **/*.DesktopClient/ModelManifest.xml
304 | **/*.Server/GeneratedArtifacts
305 | **/*.Server/ModelManifest.xml
306 | _Pvt_Extensions
307 |
308 | # Paket dependency manager
309 | .paket/paket.exe
310 | paket-files/
311 |
312 | # FAKE - F# Make
313 | .fake/
314 |
315 | # CodeRush personal settings
316 | .cr/personal
317 |
318 | # Python Tools for Visual Studio (PTVS)
319 | __pycache__/
320 | *.pyc
321 |
322 | # Cake - Uncomment if you are using it
323 | # tools/**
324 | # !tools/packages.config
325 |
326 | # Tabs Studio
327 | *.tss
328 |
329 | # Telerik's JustMock configuration file
330 | *.jmconfig
331 |
332 | # BizTalk build output
333 | *.btp.cs
334 | *.btm.cs
335 | *.odx.cs
336 | *.xsd.cs
337 |
338 | # OpenCover UI analysis results
339 | OpenCover/
340 |
341 | # Azure Stream Analytics local run output
342 | ASALocalRun/
343 |
344 | # MSBuild Binary and Structured Log
345 | *.binlog
346 |
347 | # NVidia Nsight GPU debugger configuration file
348 | *.nvuser
349 |
350 | # MFractors (Xamarin productivity tool) working folder
351 | .mfractor/
352 |
353 | # Local History for Visual Studio
354 | .localhistory/
355 |
356 | # BeatPulse healthcheck temp database
357 | healthchecksdb
358 |
359 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
360 | MigrationBackup/
361 |
362 | # Ionide (cross platform F# VS Code tools) working folder
363 | .ionide/
364 |
365 | # Fody - auto-generated XML schema
366 | FodyWeavers.xsd
367 |
368 | # VS Code files for those working on multiple tools
369 | !.vscode/settings.json
370 | !.vscode/tasks.json
371 | !.vscode/launch.json
372 | !.vscode/extensions.json
373 | *.code-workspace
374 |
375 | # Local History for Visual Studio Code
376 | .history/
377 |
378 | # Windows Installer files from build outputs
379 | *.cab
380 | *.msi
381 | *.msix
382 | *.msm
383 | *.msp
384 |
385 | # JetBrains Rider
386 | .idea/
387 | *.sln.iml
388 | *.deb
389 |
--------------------------------------------------------------------------------
/src/WebApiBolierplate/.editorconfig:
--------------------------------------------------------------------------------
1 | # Rules in this file were initially inferred by Visual Studio IntelliCode from the G:\Repo\Personal\github\dotnet-web-api-boilerplate\src\WebApiBolierplate codebase based on best match to current usage at 01-08-2021
2 | # You can modify the rules from these initially generated values to suit your own policies
3 | # You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
4 | [*.cs]
5 |
6 |
7 | #Core editorconfig formatting - indentation
8 |
9 | #use soft tabs (spaces) for indentation
10 | indent_style = space
11 |
12 | #Formatting - indentation options
13 |
14 | #indent switch case contents.
15 | csharp_indent_case_contents = true
16 | #indent switch labels
17 | csharp_indent_switch_labels = true
18 |
19 | #Formatting - new line options
20 |
21 | #place catch statements on a new line
22 | csharp_new_line_before_catch = true
23 | #place else statements on a new line
24 | csharp_new_line_before_else = true
25 | #require members of anonymous types to be on the same line
26 | csharp_new_line_before_members_in_anonymous_types = false
27 | #require members of object initializers to be on the same line
28 | csharp_new_line_before_members_in_object_initializers = false
29 | #require braces to be on a new line for methods, object_collection_array_initializers, control_blocks, and types (also known as "Allman" style)
30 | csharp_new_line_before_open_brace = methods, object_collection_array_initializers, control_blocks, types
31 |
32 | #Formatting - organize using options
33 |
34 | #do not place System.* using directives before other using directives
35 | dotnet_sort_system_directives_first = false
36 |
37 | #Formatting - spacing options
38 |
39 | #require NO space between a cast and the value
40 | csharp_space_after_cast = false
41 | #require a space before the colon for bases or interfaces in a type declaration
42 | csharp_space_after_colon_in_inheritance_clause = true
43 | #require a space after a keyword in a control flow statement such as a for loop
44 | csharp_space_after_keywords_in_control_flow_statements = true
45 | #require a space before the colon for bases or interfaces in a type declaration
46 | csharp_space_before_colon_in_inheritance_clause = true
47 | #remove space within empty argument list parentheses
48 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
49 | #remove space between method call name and opening parenthesis
50 | csharp_space_between_method_call_name_and_opening_parenthesis = false
51 | #do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
52 | csharp_space_between_method_call_parameter_list_parentheses = false
53 | #remove space within empty parameter list parentheses for a method declaration
54 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
55 | #place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
56 | csharp_space_between_method_declaration_parameter_list_parentheses = false
57 |
58 | #Formatting - wrapping options
59 |
60 | #leave code block on single line
61 | csharp_preserve_single_line_blocks = true
62 |
63 | #Style - Code block preferences
64 |
65 | #prefer curly braces even for one line of code
66 | csharp_prefer_braces = true:suggestion
67 |
68 | #Style - expression bodied member options
69 |
70 | #prefer block bodies for constructors
71 | csharp_style_expression_bodied_constructors = false:suggestion
72 | #prefer expression-bodied members for methods
73 | csharp_style_expression_bodied_methods = true:suggestion
74 |
75 | #Style - expression level options
76 |
77 | #prefer out variables to be declared before the method call
78 | csharp_style_inlined_variable_declaration = false:suggestion
79 | #prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
80 | dotnet_style_predefined_type_for_member_access = true:suggestion
81 |
82 | #Style - Expression-level preferences
83 |
84 | #prefer objects to be initialized using object initializers when possible
85 | dotnet_style_object_initializer = true:suggestion
86 | #prefer inferred anonymous type member names
87 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion
88 | #prefer inferred tuple element names
89 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
90 |
91 | #Style - implicit and explicit types
92 |
93 | #prefer var over explicit type in all cases, unless overridden by another code style rule
94 | csharp_style_var_elsewhere = true:suggestion
95 | #prefer var is used to declare variables with built-in system types such as int
96 | csharp_style_var_for_built_in_types = true:suggestion
97 | #prefer var when the type is already mentioned on the right-hand side of a declaration expression
98 | csharp_style_var_when_type_is_apparent = true:suggestion
99 |
100 | #Style - language keyword and framework type options
101 |
102 | #prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
103 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
104 |
105 | #Style - modifier options
106 |
107 | #prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
108 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
109 |
110 | #Style - Modifier preferences
111 |
112 | #when this rule is set to a list of modifiers, prefer the specified ordering.
113 | csharp_preferred_modifier_order = public,private,static,readonly:suggestion
114 |
115 | #Style - qualification options
116 |
117 | #prefer fields not to be prefaced with this. or Me. in Visual Basic
118 | dotnet_style_qualification_for_field = false:suggestion
119 | #prefer methods not to be prefaced with this. or Me. in Visual Basic
120 | dotnet_style_qualification_for_method = false:suggestion
121 | #prefer properties not to be prefaced with this. or Me. in Visual Basic
122 | dotnet_style_qualification_for_property = false:suggestion
123 | csharp_indent_labels = one_less_than_current
124 | csharp_using_directive_placement = outside_namespace:silent
125 | csharp_prefer_simple_using_statement = true:suggestion
126 | csharp_style_namespace_declarations = file_scoped:silent
127 | csharp_style_expression_bodied_operators = false:silent
128 | csharp_style_expression_bodied_properties = true:silent
129 | csharp_style_expression_bodied_indexers = true:silent
130 | csharp_style_expression_bodied_accessors = true:silent
131 | csharp_style_expression_bodied_lambdas = true:silent
132 | csharp_style_expression_bodied_local_functions = false:silent
133 | csharp_style_throw_expression = true:suggestion
134 | csharp_style_prefer_null_check_over_type_check = true:suggestion
135 | csharp_style_prefer_local_over_anonymous_function = true:suggestion
136 | csharp_prefer_simple_default_expression = true:suggestion
137 | csharp_style_prefer_index_operator = true:suggestion
138 | csharp_style_prefer_range_operator = true:suggestion
139 | csharp_style_prefer_tuple_swap = true:suggestion
140 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
141 | csharp_style_deconstructed_variable_declaration = true:suggestion
142 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent
143 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion
144 | csharp_prefer_static_local_function = true:suggestion
145 | csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
146 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
147 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
148 | csharp_style_conditional_delegate_call = true:suggestion
149 | csharp_style_prefer_switch_expression = true:suggestion
150 | csharp_style_prefer_pattern_matching = true:silent
151 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
152 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
153 | csharp_style_prefer_not_pattern = true:suggestion
154 | csharp_style_prefer_extended_property_pattern = true:suggestion
155 | csharp_space_around_binary_operators = before_and_after
156 |
157 | [*.{cs,vb}]
158 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
159 | tab_width = 4
160 | indent_size = 4
161 | end_of_line = crlf
162 | dotnet_style_coalesce_expression = true:suggestion
163 | dotnet_style_null_propagation = true:suggestion
164 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
165 | dotnet_style_prefer_auto_properties = true:silent
166 | dotnet_style_object_initializer = true:suggestion
167 | dotnet_style_collection_initializer = true:suggestion
168 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
169 | [*.{cs,vb}]
170 | #### Naming styles ####
171 |
172 | # Naming rules
173 |
174 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
175 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
176 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
177 |
178 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
179 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types
180 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
181 |
182 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
183 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
184 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
185 |
186 | # Symbol specifications
187 |
188 | dotnet_naming_symbols.interface.applicable_kinds = interface
189 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
190 | dotnet_naming_symbols.interface.required_modifiers =
191 |
192 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
193 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
194 | dotnet_naming_symbols.types.required_modifiers =
195 |
196 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
197 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
198 | dotnet_naming_symbols.non_field_members.required_modifiers =
199 |
200 | # Naming styles
201 |
202 | dotnet_naming_style.begins_with_i.required_prefix = I
203 | dotnet_naming_style.begins_with_i.required_suffix =
204 | dotnet_naming_style.begins_with_i.word_separator =
205 | dotnet_naming_style.begins_with_i.capitalization = pascal_case
206 |
207 | dotnet_naming_style.pascal_case.required_prefix =
208 | dotnet_naming_style.pascal_case.required_suffix =
209 | dotnet_naming_style.pascal_case.word_separator =
210 | dotnet_naming_style.pascal_case.capitalization = pascal_case
211 |
212 | dotnet_naming_style.pascal_case.required_prefix =
213 | dotnet_naming_style.pascal_case.required_suffix =
214 | dotnet_naming_style.pascal_case.word_separator =
215 | dotnet_naming_style.pascal_case.capitalization = pascal_case
216 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent
217 | dotnet_style_explicit_tuple_names = true:suggestion
218 | dotnet_style_prefer_conditional_expression_over_return = true:silent
219 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion
220 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
221 | dotnet_style_prefer_simplified_interpolation = true:suggestion
222 | dotnet_style_prefer_compound_assignment = true:suggestion
223 | dotnet_style_namespace_match_folder = true:suggestion
224 | dotnet_style_readonly_field = true:suggestion
225 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
226 | dotnet_style_predefined_type_for_member_access = true:suggestion
227 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
228 | dotnet_style_allow_multiple_blank_lines_experimental = true:silent
229 | dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
230 | dotnet_code_quality_unused_parameters = all:suggestion
231 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
232 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
233 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
234 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
235 | dotnet_style_qualification_for_field = false:suggestion
236 | dotnet_style_qualification_for_property = false:suggestion
237 | dotnet_style_qualification_for_method = false:suggestion
238 | dotnet_style_qualification_for_event = false:silent
239 |
--------------------------------------------------------------------------------
/src/postman/Web API Boilerplate.postman_collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "info": {
3 | "_postman_id": "e9a4c362-4912-4385-b212-60bfcd922d54",
4 | "name": "Web API Boilerplate",
5 | "description": "Postman collection for [https://github.com/kolappannathan/dotnet-web-api-boilerplate](https://github.com/kolappannathan/dotnet-web-api-boilerplate)\n\n- Set the api-url collection variable to point to the correct URL\n- Calling the login APIs will automatically update the bearer tokens",
6 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
7 | "_exporter_id": "11196299"
8 | },
9 | "item": [
10 | {
11 | "name": "Login Controller",
12 | "item": [
13 | {
14 | "name": "Login as User",
15 | "event": [
16 | {
17 | "listen": "test",
18 | "script": {
19 | "exec": [
20 | "pm.test(\"Status code is 200\", function () {\r",
21 | " pm.response.to.have.status(200);\r",
22 | "});\r",
23 | "\r",
24 | "pm.test(\"Set auth-token\", function () {\r",
25 | " var jsonData = pm.response.json();\r",
26 | " pm.collectionVariables.set(\"auth-token\", jsonData.data);\r",
27 | "});"
28 | ],
29 | "type": "text/javascript"
30 | }
31 | }
32 | ],
33 | "request": {
34 | "method": "POST",
35 | "header": [
36 | {
37 | "key": "Content-Type",
38 | "name": "Content-Type",
39 | "value": "application/json",
40 | "type": "text"
41 | }
42 | ],
43 | "body": {
44 | "mode": "raw",
45 | "raw": "{\n\t\"UserName\": \"Brandon Stark\",\n\t\"Password\": \"My direwolf is Summer\"\n}"
46 | },
47 | "url": {
48 | "raw": "{{api-url}}login",
49 | "host": [
50 | "{{api-url}}login"
51 | ]
52 | },
53 | "description": "Sucessfully login as user"
54 | },
55 | "response": []
56 | },
57 | {
58 | "name": "Login as Admin",
59 | "event": [
60 | {
61 | "listen": "test",
62 | "script": {
63 | "exec": [
64 | "pm.test(\"Status code is 200\", function () {\r",
65 | " pm.response.to.have.status(200);\r",
66 | "});\r",
67 | "\r",
68 | "pm.test(\"Set auth-token\", function () {\r",
69 | " var jsonData = pm.response.json();\r",
70 | " pm.collectionVariables.set(\"auth-token\", jsonData.data);\r",
71 | "});\r",
72 | ""
73 | ],
74 | "type": "text/javascript"
75 | }
76 | }
77 | ],
78 | "request": {
79 | "method": "POST",
80 | "header": [
81 | {
82 | "key": "Content-Type",
83 | "name": "Content-Type",
84 | "type": "text",
85 | "value": "application/json"
86 | }
87 | ],
88 | "body": {
89 | "mode": "raw",
90 | "raw": "{\n\t\"UserName\": \"Eddard Stark\",\n\t\"Password\": \"I'm the lord of WinterFell\"\n}"
91 | },
92 | "url": {
93 | "raw": "{{api-url}}login",
94 | "host": [
95 | "{{api-url}}login"
96 | ]
97 | },
98 | "description": "Sucessfully login as Admin"
99 | },
100 | "response": []
101 | },
102 | {
103 | "name": "Invalid Credential",
104 | "event": [
105 | {
106 | "listen": "test",
107 | "script": {
108 | "exec": [
109 | "pm.test(\"Status code is 401\", function () {\r",
110 | " pm.response.to.have.status(401);\r",
111 | "});\r",
112 | "\r",
113 | "pm.test(\"Check error message\", function () {\r",
114 | " var jsonData = pm.response.json();\r",
115 | " pm.expect(jsonData.message).to.eql(\"The credentials are invalid.\");\r",
116 | "});"
117 | ],
118 | "type": "text/javascript"
119 | }
120 | }
121 | ],
122 | "request": {
123 | "method": "POST",
124 | "header": [
125 | {
126 | "key": "Content-Type",
127 | "name": "Content-Type",
128 | "type": "text",
129 | "value": "application/json"
130 | }
131 | ],
132 | "body": {
133 | "mode": "raw",
134 | "raw": "{\n\t\"UserName\": \"Brandon Stark\",\n\t\"Password\": \"My direwolf is Lady\"\n}"
135 | },
136 | "url": {
137 | "raw": "{{api-url}}login",
138 | "host": [
139 | "{{api-url}}login"
140 | ]
141 | },
142 | "description": "Failed login as credentials are invalid"
143 | },
144 | "response": []
145 | },
146 | {
147 | "name": "Deleted account",
148 | "event": [
149 | {
150 | "listen": "test",
151 | "script": {
152 | "exec": [
153 | "pm.test(\"Status code is 401\", function () {\r",
154 | " pm.response.to.have.status(401);\r",
155 | "});\r",
156 | "\r",
157 | "pm.test(\"Check deleted account prompt\", function () {\r",
158 | " var jsonData = pm.response.json();\r",
159 | " pm.expect(jsonData.message).to.eql('The user account has been deleted. Contact administrator.');\r",
160 | "});"
161 | ],
162 | "type": "text/javascript"
163 | }
164 | }
165 | ],
166 | "request": {
167 | "method": "POST",
168 | "header": [
169 | {
170 | "key": "Content-Type",
171 | "name": "Content-Type",
172 | "type": "text",
173 | "value": "application/json"
174 | }
175 | ],
176 | "body": {
177 | "mode": "raw",
178 | "raw": "{\n\t\"UserName\": \"Brandon Stark\",\n\t\"Password\": \"Eddard's elder brother\"\n}"
179 | },
180 | "url": {
181 | "raw": "{{api-url}}login",
182 | "host": [
183 | "{{api-url}}login"
184 | ]
185 | },
186 | "description": "Oops! This API has credentials for a deleted account"
187 | },
188 | "response": []
189 | },
190 | {
191 | "name": "User 404",
192 | "event": [
193 | {
194 | "listen": "test",
195 | "script": {
196 | "exec": [
197 | "pm.test(\"Status code is 401\", function () {\r",
198 | " pm.response.to.have.status(401);\r",
199 | "});\r",
200 | "\r",
201 | "pm.test(\"Check Signup prompt\", function () {\r",
202 | " var jsonData = pm.response.json();\r",
203 | " pm.expect(jsonData.message).to.eql('No acount exists for the specified username. Are you trying to Signup?');\r",
204 | "});"
205 | ],
206 | "type": "text/javascript"
207 | }
208 | }
209 | ],
210 | "request": {
211 | "method": "POST",
212 | "header": [
213 | {
214 | "key": "Content-Type",
215 | "name": "Content-Type",
216 | "type": "text",
217 | "value": "application/json"
218 | }
219 | ],
220 | "body": {
221 | "mode": "raw",
222 | "raw": "{\n\t\"UserName\": \"Harry Potter\",\n\t\"Password\": \"I'm not in GOT\"\n}"
223 | },
224 | "url": {
225 | "raw": "{{api-url}}login",
226 | "host": [
227 | "{{api-url}}login"
228 | ]
229 | },
230 | "description": "There is no such user!"
231 | },
232 | "response": []
233 | },
234 | {
235 | "name": "Throw me an Exception",
236 | "event": [
237 | {
238 | "listen": "test",
239 | "script": {
240 | "exec": [
241 | "pm.test(\"Status code is 500\", function () {\r",
242 | " pm.response.to.have.status(500);\r",
243 | "});\r",
244 | "\r",
245 | "pm.test(\"Check Error msg\", function () {\r",
246 | " var jsonData = pm.response.json();\r",
247 | " pm.expect(jsonData.message).to.eql(\"Oops! An Internal Error Occured.\");\r",
248 | "});"
249 | ],
250 | "type": "text/javascript"
251 | }
252 | }
253 | ],
254 | "request": {
255 | "method": "POST",
256 | "header": [
257 | {
258 | "key": "Content-Type",
259 | "name": "Content-Type",
260 | "type": "text",
261 | "value": "application/json"
262 | }
263 | ],
264 | "body": {
265 | "mode": "raw",
266 | "raw": "{\n\t\"UserName\": \"No one\",\n\t\"Password\": \"A man wants an exception\"\n}"
267 | },
268 | "url": {
269 | "raw": "{{api-url}}login",
270 | "host": [
271 | "{{api-url}}login"
272 | ]
273 | },
274 | "description": "A perfectly self thrown exeption."
275 | },
276 | "response": []
277 | },
278 | {
279 | "name": "I want validation error",
280 | "event": [
281 | {
282 | "listen": "test",
283 | "script": {
284 | "exec": [
285 | "pm.test(\"Status code is 400\", function () {\r",
286 | " pm.response.to.have.status(400);\r",
287 | "});\r",
288 | "\r",
289 | "pm.test(\"Check error message\", function () {\r",
290 | " var jsonData = pm.response.json();\r",
291 | " pm.expect(jsonData.message).to.eql(\"No name. No game.\");\r",
292 | "});"
293 | ],
294 | "type": "text/javascript"
295 | }
296 | }
297 | ],
298 | "request": {
299 | "method": "POST",
300 | "header": [
301 | {
302 | "key": "Content-Type",
303 | "name": "Content-Type",
304 | "type": "text",
305 | "value": "application/json"
306 | }
307 | ],
308 | "body": {
309 | "mode": "raw",
310 | "raw": "{\n\t\"UserName\": null,\n\t\"Password\": \"A man wants an exception\"\n}"
311 | },
312 | "url": {
313 | "raw": "{{api-url}}login",
314 | "host": [
315 | "{{api-url}}login"
316 | ]
317 | },
318 | "description": "A perfectly self thrown exeption."
319 | },
320 | "response": []
321 | }
322 | ],
323 | "auth": {
324 | "type": "noauth"
325 | },
326 | "event": [
327 | {
328 | "listen": "prerequest",
329 | "script": {
330 | "type": "text/javascript",
331 | "exec": [
332 | ""
333 | ]
334 | }
335 | },
336 | {
337 | "listen": "test",
338 | "script": {
339 | "type": "text/javascript",
340 | "exec": [
341 | ""
342 | ]
343 | }
344 | }
345 | ]
346 | },
347 | {
348 | "name": "Values Controller",
349 | "item": [
350 | {
351 | "name": "Values List",
352 | "event": [
353 | {
354 | "listen": "test",
355 | "script": {
356 | "exec": [
357 | "pm.test(\"Status code is 200\", function () {\r",
358 | " pm.response.to.have.status(200);\r",
359 | "});\r",
360 | "\r",
361 | "pm.test(\"Check Value List\", function () {\r",
362 | " var jsonData = pm.response.json();\r",
363 | " pm.expect(jsonData.data).to.eql([\r",
364 | " \"Jon Snow\",\r",
365 | " \"Rob Stark\",\r",
366 | " \"Sansa Stark\",\r",
367 | " \"Arya Stark\",\r",
368 | " \"Bran Stark\",\r",
369 | " \"Rickon Stark\"\r",
370 | " ]);\r",
371 | "});"
372 | ],
373 | "type": "text/javascript"
374 | }
375 | }
376 | ],
377 | "request": {
378 | "method": "GET",
379 | "header": [],
380 | "url": {
381 | "raw": "{{api-url}}values",
382 | "host": [
383 | "{{api-url}}values"
384 | ]
385 | },
386 | "description": "A list of all values. Requires Authentication."
387 | },
388 | "response": []
389 | },
390 | {
391 | "name": "Value Detail",
392 | "event": [
393 | {
394 | "listen": "test",
395 | "script": {
396 | "exec": [
397 | "pm.test(\"Status code is 200\", function () {\r",
398 | " pm.response.to.have.status(200);\r",
399 | "});\r",
400 | "\r",
401 | "pm.test(\"Check value\", function () {\r",
402 | " var jsonData = pm.response.json();\r",
403 | " pm.expect(jsonData.data).to.eql('Jon Snow');\r",
404 | "});"
405 | ],
406 | "type": "text/javascript"
407 | }
408 | }
409 | ],
410 | "request": {
411 | "method": "GET",
412 | "header": [],
413 | "url": {
414 | "raw": "{{api-url}}values/0",
415 | "host": [
416 | "{{api-url}}values"
417 | ],
418 | "path": [
419 | "0"
420 | ]
421 | },
422 | "description": "A single value. Requires Authentication."
423 | },
424 | "response": []
425 | },
426 | {
427 | "name": "Value Detail NotFound",
428 | "event": [
429 | {
430 | "listen": "test",
431 | "script": {
432 | "exec": [
433 | "pm.test(\"Status code is 404\", function () {\r",
434 | " pm.response.to.have.status(404);\r",
435 | "});"
436 | ],
437 | "type": "text/javascript"
438 | }
439 | }
440 | ],
441 | "request": {
442 | "method": "GET",
443 | "header": [],
444 | "url": {
445 | "raw": "{{api-url}}values/10",
446 | "host": [
447 | "{{api-url}}values"
448 | ],
449 | "path": [
450 | "10"
451 | ]
452 | },
453 | "description": "Get detail for a value that is not there. Requires Authentication."
454 | },
455 | "response": []
456 | },
457 | {
458 | "name": "Validate Create",
459 | "event": [
460 | {
461 | "listen": "test",
462 | "script": {
463 | "exec": [
464 | "pm.test(\"Status code is 200\", function () {\r",
465 | " pm.response.to.have.status(200);\r",
466 | "});"
467 | ],
468 | "type": "text/javascript"
469 | }
470 | }
471 | ],
472 | "request": {
473 | "method": "POST",
474 | "header": [
475 | {
476 | "key": "Content-Type",
477 | "name": "Content-Type",
478 | "value": "application/json",
479 | "type": "text"
480 | }
481 | ],
482 | "body": {
483 | "mode": "raw",
484 | "raw": "\"Tyrion Lanister\""
485 | },
486 | "url": {
487 | "raw": "{{api-url}}values",
488 | "host": [
489 | "{{api-url}}values"
490 | ]
491 | },
492 | "description": "API request for creating new value. Requires Admin auth."
493 | },
494 | "response": []
495 | },
496 | {
497 | "name": "Validate Save",
498 | "event": [
499 | {
500 | "listen": "test",
501 | "script": {
502 | "exec": [
503 | "pm.test(\"Status code is 200\", function () {\r",
504 | " pm.response.to.have.status(200);\r",
505 | "});"
506 | ],
507 | "type": "text/javascript"
508 | }
509 | }
510 | ],
511 | "request": {
512 | "method": "PUT",
513 | "header": [
514 | {
515 | "key": "Content-Type",
516 | "name": "Content-Type",
517 | "type": "text",
518 | "value": "application/json"
519 | }
520 | ],
521 | "body": {
522 | "mode": "raw",
523 | "raw": "\"Tyrion Lanister\""
524 | },
525 | "url": {
526 | "raw": "{{api-url}}values/1",
527 | "host": [
528 | "{{api-url}}values"
529 | ],
530 | "path": [
531 | "1"
532 | ]
533 | },
534 | "description": "API request for updating existing value. Requires Admin auth."
535 | },
536 | "response": []
537 | },
538 | {
539 | "name": "Value Delete",
540 | "event": [
541 | {
542 | "listen": "test",
543 | "script": {
544 | "exec": [
545 | "pm.test(\"Status code is 200\", function () {\r",
546 | " pm.response.to.have.status(200);\r",
547 | "});"
548 | ],
549 | "type": "text/javascript"
550 | }
551 | }
552 | ],
553 | "request": {
554 | "method": "DELETE",
555 | "header": [],
556 | "body": {
557 | "mode": "raw",
558 | "raw": ""
559 | },
560 | "url": {
561 | "raw": "{{api-url}}values/1",
562 | "host": [
563 | "{{api-url}}values"
564 | ],
565 | "path": [
566 | "1"
567 | ]
568 | },
569 | "description": "A delete request to the value controller. Requires Admin auth."
570 | },
571 | "response": []
572 | }
573 | ]
574 | }
575 | ],
576 | "auth": {
577 | "type": "bearer",
578 | "bearer": [
579 | {
580 | "key": "token",
581 | "value": "{{auth-token}}",
582 | "type": "string"
583 | }
584 | ]
585 | },
586 | "event": [
587 | {
588 | "listen": "prerequest",
589 | "script": {
590 | "type": "text/javascript",
591 | "exec": [
592 | ""
593 | ]
594 | }
595 | },
596 | {
597 | "listen": "test",
598 | "script": {
599 | "type": "text/javascript",
600 | "exec": [
601 | ""
602 | ]
603 | }
604 | }
605 | ],
606 | "variable": [
607 | {
608 | "key": "api-url",
609 | "value": "https://localhost:44332/api/"
610 | },
611 | {
612 | "key": "auth-token",
613 | "value": ""
614 | }
615 | ]
616 | }
--------------------------------------------------------------------------------