Click here to Skip to main content
15,885,365 members
Articles / Web Development / ASP.NET / ASP.NET Core

ApiBoilerPlate: A Project Template for Building ASP.NET Core APIs in .NET Core 3.x

Rate me:
Please Sign up or sign in to vote.
4.95/5 (14 votes)
1 Oct 2019CPOL11 min read 15.9K   18   13
A simple yet organized project template for building ASP.NET Core APIs in .NET Core 3.x

Introduction

Microservices architecture got its reputation in software development since it was introduced and today, it is a very popular architectural style approach to build apps and is used by many organizations. When taking a microservices approach, you would normally break product features into services or APIs to perform specific tasks, and .NET Core is a perfect candidate to build these kinds of services.

Building APIs that can be deployed and hosted anywhere is pretty much easy nowadays when .NET Core came into life. However, if you will be building a lot of APIs to support various product features within a short timeframe, then that’s how things can get troublesome. This is because each developer/team will have their own way of building APIs and has their own standard base structure of the project. You may probably have to do a lot of copy and paste to have a consistent base structure of your API project and configure its core dependencies all over again which can eat a lot of development time. With these reasons, ApiBoilerPlate was created.

The Project Template

Today, I'm happy to announce that the ApiBoilerPlate is now officially released and can now be downloaded and installed in Visual Studio 2019 from Visual Studio Market place or via .NET CLI. I made this project open-source to allow fellow developers to contribute on it for improvements.

ApiBoilerPlate is a simple yet organized project template for building ASP.NET Core APIs using .NET Core 3.x (the latest/fastest version of .NET Core to date) with preconfigured tools and frameworks. The goal is to help you get up to speed when setting up the core structure of your app and its dependencies. This enables you to focus on implementing business specific requirements without you having to copy and paste the core structure of your project, and installing its dependencies all over again. This will speed up your development time when building new API project while enforcing standard project structure with its dependencies and configurations for all your apps.

If you are looking for a project template for ASP.NET Core API that you can reuse across your team, or if you are new to ASP.NET Core and would like to get up to speed on how it works without having you to configure most of the basic features that an API will have, then this is for you.

How to get it?

There are two ways to install the template:

Tools and Frameworks Used

Keep in mind that you can always replace and choose whatever framework you want to use for your API. After all, the template is just a skeleton for your project structure with default preconfigured middlewares. For example, you can always replace Dapper with EntityFramework CorePetaPoco, etc. and configure them yourself. You can also replace Serilog with whatever logging frameworks and providers you want that works with ASP.NET Core - the choice is yours.

Install the Template from .NET CLI

  1. Install the latest .NET Core SDK.
  2. Run dotnet new -i apiboilerplate.aspnetcore. This will install the template in your machine.
  3. Run dotnet new apiboilerplate --name "MyAPI"; -o samples. This will generate the project template named MyAPI within the samples directory.

Once installed, you should see this output below:

Quote:

The template "ASP.NET Core API Template for .NET Core 3.x" was created successfully.

Install the Template from the Visual Studio Marketplace

  1. Fire up Visual Studio 2019, click "Continue without code" link
  2. On the "Extensions" menu, click "Manage Extensions".
  3. Click "Online" and then search for "ApiBoilerPlate".

Image 1

  1. Click "Download". The extension is then scheduled for install. Close all instances of Visual Studio.
  2. The VISX (Extension) installer should popup. Click "Modify" to begin installing the template as shown in the figure below:

Image 2

  1. Click "Close" after modification is complete.

Alternatively, you can download and install the VSIX Extension directly at the following link: https://marketplace.visualstudio.com/items?itemName=vmsdurano.ApiProjVSExt

Create a new Project from ApiBoilerPlate Template

  1. Open Visual Studio 2019 and then select "Create a New Project" box
  2. The newly installed template should appear at the top. You can also type "ApiBoilerPlate" in the search bar:

Image 3

  1. Click the "ApiBoilerPlate" item and then click "Next".
  2. Name your project to whatever you like and then click "Create".
  3. Visual Studio should generate the files for you as shown in the figure below.

Image 4

The generated files contain the skeleton structure of the project with a very basic sample implementation for you get started working on your APIs.

Steps to run the Template

STEP 1: Create a Test local Database:

  1. Open Visual Studio 2019
  2. Go to View > SQL Server Object Explorer
  3. Drilldown to SQL Server > (localdb)\MSSQLLocalDB
  4. Right-click on the "Database" Folder
  5. Click "Add New Database"
  6. Name it as "TestDB" and click OK
  7. Under "TestDB", Right-click on the "Tables" folder and select "Add New Table". Or you could simply right-click on the "TestDB" database, select "New Query" and run the script below to generate the "Person" table.
  8. Name the table as "Person" with the following fields:
CREATE TABLE [dbo].[Person]
(
       [Id] INT NOT NULL PRIMARY KEY IDENTITY(1,1), 
        [FirstName] NVARCHAR(20) NOT NULL, 
        [LastName] NVARCHAR(20) NOT NULL, 
        [DateOfBirth] DATETIME NOT NULL
)

STEP 2: Update Database ConnectionString (optional)

If you follow step 1, then you can skip this step and run the application right away.

If you have a different database and table name then you need to change the connectionString in appsettings.json that is pointing to the newly created database. You can get the connectionString values in the properties window of the "TestDB" database in Visual Studio.

Testing the Default API Controller

After setting up the database, it’s time to test the API that was preconfigured for you. Run the application and navigate to: https://localhost:44321/swagger (See Properties > launchSettings.json to know how the application launching was configured)

Now, you should be presented with the Swagger UI documentation page as shown below:

Image 5

Swagger provides an advance documentation for your APIs where it allows you to look the details of your API endpoints and test them when necessary. The template is preconfigured to enable this feature by default using Swashbuckle.AspNetCore tool. This means that everytime you add API endpoints to your project, swagger will automatically generate this document without doing anything in your part. This document is very helpful as a reference especially when your APIs are publicly available and you expect many developers using it.

Another good thing about the Swagger UI, is it allows you to test your APIs. For example, you can POST a request to your API and get the response back. To see that in action let’s make a POST request with the following body parameters:

JSON
{
  "firstName": "Vianne Maverich",
  "lastName": "Durano",
  "dateOfBirth": "2019-09-27T23:27:50.076Z"
}

Clicking the Execute button gives you the following response:

Response Body:

JSON
{
  "message": "Created Successfully",
  "isError": false,
  "result": 1
}

Response Header:

JSON
content-type: application/json 
 date: Fri, 27 Sep 2019 23:31:35 GMT 
 server: Microsoft-IIS/10.0 
 status: 201 
 x-powered-by: ASP.NET

To verify if the data was inserted to the database, you can invoke the GET method to see the results. For this example, it should return you the following JSON payload:

JSON
{
    "message": "Request successful.",
    "isError": false,
    "result": [
        {
            "id": 1,
            "firstName": "Vianne Maverich",
            "lastName": "Durano",
            "dateOfBirth": "2019-09-27T23:27:50.077"
        }
    ]
}

If you notice, the HTTP response was automatically formatted for you. The template uses AutoWrapper middleware to wrap each HTTP responses for both success and failure without doing anything on your side. For more information, see: AutoWrapper: Prettify Your ASP.NET Core APIs with Meaningful Responses

TipPostman is also a good alternative to test APIs and I still use that.

Logging

Logging is also preconfigured for you. It uses Serilog.AspNetCore to log data. By default, the template is preconfigured to log data in Console and File. You can find the logs at the Logs folder. Here’s a sample of logs that was captured based on this example:

2019-09-27T18:08:48.0418932-05:00 [INF] () Starting web host  
2019-09-27T18:31:35.6179254-05:00 [INF] (AutoWrapper.AutoWrapperMiddleware) Request: POST https localhost:44321/api/v1/Persons  {"firstName":"Vianne Maverich","lastName":"Durano","dateOfBirth":"2019-09-27T23:27:50.076Z"} Responded with [201] in 968ms  
2019-09-27T18:34:38.6852072-05:00 [INF] (AutoWrapper.AutoWrapperMiddleware) Request: GET https localhost:44321/api/v1/Persons   Responded with [200] in 87ms

To see how the log is configured, see the appsettings.logs.json file from the project template.

Walkthrough

If you got this far, then I’m assuming you got the application up and running. At this point, you might be wondering where to start modifying the template to add your code and feel a bit confused. In this section, I’ll try my best to walk you through.

Data Folder

The first thing that you may want to look at in the template is the Data folder, which contains a class called "DBFactoryBase". This class is an abstract class that implements a few methods to perform basic database operations:

C#
namespace ApiBoilerPlate1.Data  
{
    public abstract class DBFactoryBase
    {
        private readonly IConfiguration _config;
        public DBFactoryBase(IConfiguration config)
        {
            _config = config;
        }

        internal IDbConnection DbConnection
        {
            get {
                return new SqlConnection(_config.GetConnectionString("SQLDBConnectionString"));
            }
        }

        public virtual async Task<IEnumerable<T>> DbQueryAsync<T>(string sql, object parameters = null)
        {
            using (IDbConnection dbCon = DbConnection)
            {
                dbCon.Open();
                if (parameters == null)
                    return await dbCon.QueryAsync<T>(sql);

                return await dbCon.QueryAsync<T>(sql, parameters);
            }
        }
        public virtual async Task<T> DbQuerySingleAsync<T>(string sql, object parameters)
        {
            using (IDbConnection dbCon = DbConnection)
            {
                dbCon.Open();
                return await dbCon.QueryFirstOrDefaultAsync<T>(sql, parameters);
            }
        }

        public virtual async Task<bool> DbExecuteAsync<T>(string sql, object parameters)
        {
            using (IDbConnection dbCon = DbConnection)
            {
                dbCon.Open();
                return await dbCon.ExecuteAsync(sql, parameters) > 0;
            }
        }

        public virtual async Task<bool> DbExecuteScalarAsync(string sql, object parameters)
        {
            using (IDbConnection dbCon = DbConnection)
            {
                dbCon.Open();
                return await dbCon.ExecuteScalarAsync<bool>(sql, parameters);
            }
        }
    }
}

It uses Dapper as a data access mechanism to communicate with the database. Again, you are free to modify it as you please. The main reason why I opt for Dapper is because of its speed and simplicity. If you value convenience over performance, then you can always replace your data access with other full-blown ORM such as Entity Framework Core.

Contracts Folder

The next place to look at is the Contracts folder. This is where you add all your interfaces that are needed for your application. By default, it contains three (3) Interfaces:

The IRepository Interface

C#
namespace ApiBoilerPlate1.Contracts  
{
    public interface IRepository<T>
    {
        Task<IEnumerable<T>> GetAllAsync();
        Task<T> GetByIdAsync(object id);
        Task<long> CreateAsync(T entity);
        Task<bool> UpdateAsync(T entity);
        Task<bool> DeleteAsync(object id);
        Task<bool> ExistAsync(object id);
    }
}

We don't want our API Controller to access directly the DBFactoryBase class, instead we will let other classes handle the communication between our DBFactoryBase and API Controllers

The code above defines a simple generic repository interface. An interface is just a skeleton of a method without the actual implementation.

The IPersonManager Interface

C#
using ApiBoilerPlate1.Domain.Entity;  
namespace ApiBoilerPlate1.Contracts  
{
    public interface IPersonManager : IRepository<Person>
    {
        //Add object specific methods here when necessary
    }
}

The code above is an interface for managing the Person entity as an example. Notice that it implements the IRepository interface so that this interface can inherit all the available methods from the generic repository. This interface will be injected into the API Controller so we will only need to talk to the interface rather than the actual implementation of the repository. One of the main benefits of using interface is to make our code reusable, testable, maintainable and taking advantage of Dependency Injection.

The IServiceRegistration Interface

C#
using Microsoft.Extensions.Configuration;  
using Microsoft.Extensions.DependencyInjection;

namespace ApiBoilerPlate1.Contracts  
{
    public interface IServiceRegistration
    {
        void RegisterAppServices(IServiceCollection services, IConfiguration configuration);
    }
}

The code above is an interface that will be used for services that needs to be added in IServiceCollection.

Domain Folder

The next place to look at is the Domain folder. This is where we implement the actual database operation and its associated logic. This folder has a sub-folder called "Entity" wherein you define all your models for your database. Here’s the Person entity model example:

C#
namespace ApiBoilerPlate1.Domain.Entity  
{
    public class Person
    {
        public long ID { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime DateOfBirth { get; set; }
    }
}

The code above is nothing but a plain class that houses a few properties. The class name represents the database table name, and the properties represents the table columns.

The PersonManager

C#
namespace ApiBoilerPlate1.Domain  
{
    public class PersonManager : DBFactoryBase, IPersonManager
    {
        public PersonManager(IConfiguration config) : base(config)
        {

        }
        public async Task<IEnumerable<Person>> GetAllAsync()
        {
            return await DbQueryAsync<Person>("SELECT * FROM Person");
        }

        public async Task<Person> GetByIdAsync(object id)
        {
            return await DbQuerySingleAsync<Person>("SELECT * FROM Person WHERE ID = @ID", new { id });
        }

        public async Task<long> CreateAsync(Person person)
        {
            string sqlQuery = $@"INSERT INTO Person (FirstName, LastName, DateOfBirth) 
                                     VALUES (@FirstName, @LastName, @DateOfBirth)
                                     SELECT CAST(SCOPE_IDENTITY() as bigint)";

            return await DbQuerySingleAsync<long>(sqlQuery, person);
        }
        public async Task<bool> UpdateAsync(Person person)
        {
            string sqlQuery = $@"IF EXISTS (SELECT 1 FROM Person WHERE ID = @ID) 
                                            UPDATE Person SET FirstName = @FirstName, LastName = @LastName, DateOfBirth = @DateOfBirth
                                            WHERE ID = @ID";

            return await DbExecuteAsync<bool>(sqlQuery, person);
        }
        public async Task<bool> DeleteAsync(object id)
        {
            string sqlQuery = $@"IF EXISTS (SELECT 1 FROM Person WHERE ID = @ID)
                                        DELETE Person WHERE ID = @ID";

            return await DbExecuteAsync<bool>(sqlQuery, new { id });
        }
        public async Task<bool> ExistAsync(object id)
        {
            return await DbExecuteScalarAsync("SELECT COUNT(1) FROM Person WHERE ID = @ID", new { id });
        }
    }
}

The code above is a concrete class that implements the DBFactorBase base class and IPersonManager interface that we have just created earlier. This is where you implement domain specific log and database operations.

DTO Folder

Data Transfer Object (a.k.a DTO) are classes that defines the model with predefined validation in place. DTOs will be passed as the parameters to your API endpoints or use it as a return object for results. The basic idea about having a DTO is to decouple the validation from the Entity models and control the data that you want to allow the client to see.

Here’s an example of the PersonDTO:

C#
using FluentValidation;  
using System;

namespace ApiBoilerPlate1.DTO  
{
    public class PersonDTO
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime DateOfBirth { get; set; }
    }

    public class PersonDTOValidator : AbstractValidator<PersonDTO>
    {
        public PersonDTOValidator()
        {
            RuleFor(o => o.FirstName).NotEmpty();
            RuleFor(o => o.LastName).NotEmpty();
            RuleFor(o => o.DateOfBirth).NotEmpty();
        }
    }
}

The class above defines validations for the model. It uses FluentValidation.AspNetCore to provide a clean, flexible and strongly-typed validation rules.

Helpers Folder

The next place to look at is the Helpers folder. This is where you add any class that performs common tasks. For example, you can add Utility or Extension classes in this folder. By default it contains an Extensions folder and a MapperProfile class.

The MappingProfile Class

C#
using AutoMapper;  
using ApiBoilerPlate1.Domain.Entity;  
using ApiBoilerPlate1.DTO;

namespace ApiBoilerPlate1.Helpers  
{
    public class MappingProfile : Profile
    {
        public MappingProfile()
        {
            CreateMap<Person, PersonDTO>().ReverseMap();
        }
    }
}

The class above is where you define the mappings between your DTOs and Entity models. It uses AutoMapper to map between objects. In this example, we created a mapping between a Person entity and a PersonDTO model.

Extensions Folder

This folder is where you add classes for your extension methods in your project. By default, it contains a ServiceRegistrationExtension class with an extension method AddServicesInAssembly() which is responsible for adding all available services within the assembly. This class will be called in the ConfigureServices() method of Startup.cs file.

Here’s the ServiceRegistrationExtension implementation:

C#
namespace ApiBoilerPlate1.Helpers.Extensions  
{
    public static class ServiceRegistrationExtension
    {
        public static void AddServicesInAssembly(this IServiceCollection services, IConfiguration configuration)
        {
            var appServices = typeof(Startup).Assembly.ExportedTypes
                            .Where(x => typeof(IServiceRegistration)
                            .IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract)
                            .Select(Activator.CreateInstance)
                            .Cast<IServiceRegistration>().ToList();

            appServices.ForEach(svc => svc.RegisterAppServices(services, configuration));
        }
    }
}

Installers Folder

This is where you add services that you would want to be configured on application start up. By default, it contains two classes:

The RegisterContractMappings Class

C#
namespace ApiBoilerPlate1.Installers  
{
    public class RegisterContractMappings : IServiceRegistration
    {
        public void RegisterAppServices(IServiceCollection services, IConfiguration configuration)
        {
            services.AddTransient<IPersonManager, PersonManager>();
        }
    }
}

The class above is responsible for registering all interface mappings between your contract repositories and concrete classes that implement the contracts.

The RegisterModelValidators Class

C#
namespace ApiBoilerPlate1.Installers  
{
    public class RegisterModelValidators : IServiceRegistration
    {
        public void RegisterAppServices(IServiceCollection services, IConfiguration configuration)
        {
            services.AddTransient<IValidator<PersonDTO>, PersonDTOValidator>();
        }
    }
}

The class above is responsible for registering all validators for your DTO models.
Notice that both classes above implement the IServiceRegistration interface so that they will be configured automatically upon calling the AddServicesInAssembly() extension method defined in ServiceRegistrationExtension class.

API Folder

Now that we have most of the pieces in place, the next place to look at is the API folder. This folder contains a sub-folder called "v1" to organize Controllers into versions. By default, the v1 folder contains the PersonsController with the following implementation:

C#
namespace ApiBoilerPlate1.API.v1  
{
    [Route("api/v1/[controller]")]
    [ApiController]
    public class PersonsController : ControllerBase
    {
        private readonly ILogger<PersonsController> _logger;
        private readonly IPersonManager _personManager;
        private readonly IMapper _mapper;
        public PersonsController(IPersonManager personManager, IMapper mapper, ILogger<PersonsController> logger)
        {
            _personManager = personManager;
            _mapper = mapper;
            _logger = logger;
        }

        [HttpGet]
        public async Task<IEnumerable<Person>> Get()
        {
            return await _personManager.GetAllAsync();
        }

        [Route("{id:long}")]
        [HttpGet]
        public async Task<Person> Get(long id)
        {
            var person = await _personManager.GetByIdAsync(id);
            if (person != null)
            {
                return person;
            }
            else
                throw new ApiException($"Record with id: {id} does not exist.", 204);
        }

        [HttpPost]
        public async Task<ApiResponse> Post([FromBody] PersonDTO dto)
        {

            if (ModelState.IsValid)
            {
                try
                {
                    var person = _mapper.Map<Person>(dto);
                    return new ApiResponse("Created Successfully", await _personManager.CreateAsync(person), 201);
                }
                catch (Exception ex)
                {
                    _logger.Log(LogLevel.Error, ex, "Error when trying to insert.");
                    throw;
                }
            }
            else
                throw new ApiException(ModelState.AllErrors());
        }

        [Route("{id:long}")]
        [HttpPut]
        public async Task<ApiResponse> Put(long id, [FromBody] PersonDTO dto)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    var person = _mapper.Map<Person>(dto);
                    person.ID = id;

                    if (await _personManager.UpdateAsync(person))
                        return new ApiResponse("Update successful.", true);
                    else
                        throw new ApiException($"Record with id: {id} does not exist.", 400);
                }
                catch (Exception ex)
                {
                    _logger.Log(LogLevel.Error, ex, "Error when trying to update with ID:{@ID}", id);
                    throw;
                }
            }
            else
                throw new ApiException(ModelState.AllErrors());
        }


        [Route("{id:long}")]
        [HttpDelete]
        public async Task<bool> Delete(long id)
        {
            try
            {
                var isDeleted = await _personManager.DeleteAsync(id);
                if (isDeleted)
                {
                    return isDeleted;
                }
                else
                    throw new ApiException($"Record with id: {id} does not exist.", 400);
            }
            catch (Exception ex)
            {
                _logger.Log(LogLevel.Error, ex, "Error when trying to delete with ID:{@ID}", id);
                throw;
            }

        }
    }
}

The class above defines the API endpoints and its routes with preconfigured implementation as an example. For more information about creating APIs ASP.NET Core, see: Create web APIs with ASP.NET Core

Workers Folder

As an additional feature, the template includes a default example on how to run a background tasks within ASP.NET Core. Here’s the simple implementation of a worker service:

C#
namespace ApiBoilerPlate1.Workers  
{
    public class MessageQueueProcessorService : BackgroundService
    {
        private readonly ILogger<MessageQueueProcessorService> _logger;

        public MessageQueueProcessorService(ILogger<MessageQueueProcessorService> logger)
        {
            _logger = logger;
        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogDebug($"MessageQueueProcessorService is starting.");

            stoppingToken.Register(() => _logger.LogDebug($" MessageQueueProcessorService background task is stopping."));

            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogDebug($"MessageQueueProcessorService task doing background work.");

                //TO DO:
                //PubSub/Message Queue subcription and process message
                //Save to DB

                await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
            }

            _logger.LogDebug($"MessageQueueProcessorService background task is stopping.");
        }
    }
}

The code above simulates a simple background task that keeps on running every 5 seconds. So, if there’s a need for you to implement a worker service, then this should provides you an idea on how to get started.

For more information about running a background tasks with hosted services in ASP.NET Core, See: Hosted Services in ASP.NET Core

Wrap Up

To see how each of the services in the template are configured, then take a look at the Startup.cs and Program.cs classes. To give you a quick overview, here’s how the ConfigureServices() method of Startup.cs file looks like :

C#
public void ConfigureServices(IServiceCollection services)  
{
    //Uncomment to Register Worker Service
    //services.AddSingleton<IHostedService, MessageQueueProcessorService>();

    //Register DTO Validators and Interface Mappings for Repositories
    services.AddServicesInAssembly(Configuration);

    //Disable Automatic Model State Validation built-in to ASP.NET Core
    services.Configure<ApiBehaviorOptions>(opt => { opt.SuppressModelStateInvalidFilter = true; });

    //Register MVC/Web API and add FluentValidation Support
    services.AddControllers()
            .AddFluentValidation(fv => {  fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false; });

    //Register Automapper
    services.AddAutoMapper(typeof(Helpers.MappingProfile));

    //Register Swagger
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "ASP.NET Core Template API", Version = "v1" });
    });
}

For more information on how the Startup and Program class works, read: ASP.NET Core Fundamentals

Summary

In this post, we’ve learned how to use the ApiBoilerPlate project template for building APIs in .NET Core 3.0. We've also learned about its structure and how it was implemented.

I’m pretty sure there are still lots of things to improve in this project, so feel free to try it out and let me know your thoughts. Comments and suggestions are welcome, please drop a message and I’d be happy to answer any queries as I can.

Source code can be found here: https://github.com/proudmonkey/ApiBoilerPlate

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
A code monkey who loves to drink beer, play guitar and listen to music.

My Tech Blog: https://vmsdurano.com/
My Youtube Channel: https://www.youtube.com/channel/UCuabaYm8QH4b1MAclaRp-3Q

I currently work as a Solutions Architect and we build "cool things" to help people improve their health.

With over 14 years of professional experience working as a Sr. Software Engineer specializing mainly on Web and Mobile apps using Microsoft technologies. My exploration into programming began at the age of 15;Turbo PASCAL, C, C++, JAVA, VB6, Action Scripts and a variety of other equally obscure acronyms, mainly as a hobby. After several detours, I am here today on the VB.NET to C# channel. I have worked on Web Apps + Client-side technologies + Mobile Apps + Micro-services + REST APIs + Event Communication + Databases + Cloud + Containers , which go together like coffee crumble ice cream.

I have been awarded Microsoft MVP each year since 2009, awarded C# Corner MVP for 2015, 2016,2017 and 2018, CodeProject MVP, MVA, MVE, Microsoft Influencer, Dzone MVB, Microsoft ASP.NET Site Hall of Famer with All-Star level and a regular contributor at various technical community websites such as CSharpCorner, CodeProject, ASP.NET and TechNet.

Books written:
" Book: Understanding Game Application Development with Xamarin.Forms and ASP.NET
" Book (Technical Reviewer): ASP.NET Core and Angular 2
" EBook: Dockerizing ASP.NET Core and Blazor Applications on Mac
" EBook: ASP.NET MVC 5- A Beginner's Guide
" EBook: ASP.NET GridView Control Pocket Guide

Comments and Discussions

 
PraiseMy vote of 5 Pin
harrybowles23-Oct-19 10:37
harrybowles23-Oct-19 10:37 
GeneralRe: My vote of 5 Pin
Vincent Maverick Durano24-Oct-19 18:08
professionalVincent Maverick Durano24-Oct-19 18:08 
GeneralRe: My vote of 5 Pin
Vincent Maverick Durano2-Dec-19 15:19
professionalVincent Maverick Durano2-Dec-19 15:19 
GeneralMy vote of 5 Pin
Red Feet3-Oct-19 22:56
Red Feet3-Oct-19 22:56 
Great template, with excellent explanation
GeneralRe: My vote of 5 Pin
Vincent Maverick Durano4-Oct-19 2:43
professionalVincent Maverick Durano4-Oct-19 2:43 
GeneralRe: My vote of 5 Pin
Vincent Maverick Durano2-Dec-19 15:20
professionalVincent Maverick Durano2-Dec-19 15:20 
QuestionI did this exact same thing and here's what I included Pin
DizzleDazzle3-Oct-19 4:46
DizzleDazzle3-Oct-19 4:46 
AnswerRe: I did this exact same thing and here's what I included Pin
Vincent Maverick Durano3-Oct-19 5:36
professionalVincent Maverick Durano3-Oct-19 5:36 
AnswerRe: I did this exact same thing and here's what I included Pin
SlackerDev3-Oct-19 14:44
SlackerDev3-Oct-19 14:44 
QuestionRe: I did this exact same thing and here's what I included Pin
Red Feet3-Oct-19 23:00
Red Feet3-Oct-19 23:00 
AnswerRe: I did this exact same thing and here's what I included Pin
Akhil Mittal14-Nov-19 2:54
professionalAkhil Mittal14-Nov-19 2:54 
QuestionGreat Job, but I think two important things are missing. Pin
Gerry Softman2-Oct-19 9:23
professionalGerry Softman2-Oct-19 9:23 
AnswerRe: Great Job, but I think two important things are missing. Pin
Vincent Maverick Durano2-Oct-19 15:38
professionalVincent Maverick Durano2-Oct-19 15:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.