This is a boilerplate framework that helps building fast, robust, and modern web applications. S.O.L.I.D principles are applied in this project. It implements NLayer Architecture (Domain, Application, Infrastructure and UI Layers) and also provides a good infrastructure to implement best practices such as Aspect Oriented Programming, Dependency Injection.
- Authentication
- Authorization
- OAuth 2.0
- Validation
- Localization
- Inversion of Control
- CRUD Operations
- Repository Pattern
- RESTful Services
- Single Page Application
- Aspect Oriented Programming
- Object Oriented Programming
- Asp.Net Web Api 2
- SQL Server
- FluentValidation
- Castle Windsor
- NHibernate
- Fluent NHibernate
- AngularJS
- Angular Material
- Material Design Data Table
- Gulp
- npm
- NUnit
- Moq
- Unsplash
There are dynamic handlers and service interceptors for adding additional behavior to the RESTful or Application Service layer without modifying service codes.
It aims to add behavior for RESTful Service (AbiokaApi.Host application). These behaviors can be logging Http Request and Response messages, checking authentication etc. If you want to do something for every request or response, you should use a dynamic handler.
Write a class that implements IDynamicHandler interface.
public class NhUnitOfWorkHandler : IDynamicHandler
{
private readonly IUnitOfWork unitOfWork;
public short Order => 10;
public NhUnitOfWorkHandler(IUnitOfWork unitOfWork) {
this.unitOfWork = unitOfWork;
}
public void BeforeSend(IRequestContext requestContext) {
if (!unitOfWork.IsInTransaction)
{
unitOfWork.BeginTransaction();
}
}
public void AfterSend(IResponseContext responseContext) {
if (unitOfWork.IsInTransaction)
{
unitOfWork.Commit();
}
}
public void OnException(IExceptionContext exceptionContext) {
if (unitOfWork.IsInTransaction)
{
unitOfWork.Rollback();
}
}
}
Register this class with IoC container. To learn additional information about Lifestyles please read this.
DependencyContainer.Container.Register<IDynamicHandler, NhUnitOfWorkHandler>(LifeStyle.PerWebRequest)
There are following dynamic handlers:
It checks Http Request header to find Json Web Token and throws an exception, if there is no valid token and the action is not allowed for anonymous login.
It catches every exception and wraps it and then returns Http Response with specific Status Code and additional Header value.
It opens a db transaction before calling service layer and commits this transaction after service layer response. If there is an exception, it rollbacks the transaction.
It aims to add behavior for the application services (AbiokaApi.ApplicationService application).
if the current user hasn't the role which is necessary for the application service method, it throws an exception.
internal class RoleValidationInterceptor : IServiceInterceptor
{
private readonly ICurrentContext currentContext;
public RoleValidationInterceptor(ICurrentContext currentContext) {
this.currentContext = currentContext;
}
public int Order => 0;
public void BeforeProceed(IInvocationContext context) {
var attributes = context.Method.GetCustomAttributes(typeof(AllowedRole), true);
if (attributes == null || attributes.Count() == 0)
return;
if(currentContext.Current.Principal.Roles == null)
throw new DenialException("AccessDenied");
var allowedRoles = (AllowedRole)attributes.First();
if (currentContext.Current.Principal.Roles.Any(r => allowedRoles.Roles.Contains(r)))
return;
throw new DenialException("AccessDenied");
}
}
Creating a class that inherits CustomValidator<parameter type
> is enough to validate the parameter type for every service method which has this parameter.
Example
public interface IUserService : IReadService<User>
{
User Add(AddUserRequest request);
}
public class AddUserRequestValidator : CustomValidator<AddUserRequest>
{
private readonly IUserSecurityRepository userSecurityRepository;
public AddUserRequestValidator(IUserSecurityRepository userSecurityRepository) {
this.userSecurityRepository = userSecurityRepository;
RuleFor(r => r.Email).NotEmpty().WithMessage("IsRequired").EmailAddress().WithMessage("ShouldBeCorrectEmail");
RuleFor(r => r.Password).NotEmpty().WithMessage("IsRequired");
}
protected override void DataValidate(AddUserRequest instance, ActionType actionType) {
var tmpUser = userSecurityRepository.GetByEmail(instance.Email);
if (tmpUser != null)
throw new DenialException("UserIsAlreadyRegistered", instance.Email);
}
}
JWT is used for Authentication. Once the user is logged in, then JWT is generated and returned as a response. Each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token. You may want to look at AuthenticationHandler
Allow Anonymous
Adding AllowAnonymous
attributte to a controller method disables token validation for that method.
[RoutePrefix("api/User")]
public class UserController : BaseReadController<User>
{
private readonly IUserService userService;
public UserController(IUserService userService)
: base(userService) {
this.userService = userService;
}
[AllowAnonymous]
[HttpPost]
[Route("Login")]
public HttpResponseMessage Login([FromBody]LoginRequest request) {
var token = userService.Login(request);
var response = Request.CreateResponse(HttpStatusCode.OK, token);
return response;
}
}
Facebook and Google accounts can also used for login.
Only defining the roles with AllowedRole
attributte for service method is sufficient for authorization. You may want to look at RoleValidationInterceptor
Example
public interface IUserService : IReadService<User>
{
[AllowedRole("Admin", "SuperUser")]
void Update(User entiy);
}
AngularJS provides SPA template for UI.
It's a compenent wraps md-data-table. It uses $resource object to interact with RESTful server-side data sources.
Example
With load-data option:
<abioka-data-table options="vm.options" load-data="vm.loadData">
</abioka-data-table>
/* @ngInject */
function UsersController($timeout, AdminResource) {
var vm = this;
vm.loadData = false;
vm.options = {
loadOnInit: false, // If you want to load data immediately, pass this as true.
rowSelection: false,
resource: AdminResource.users,
query: {},
columns: [{ name: "Email", text: "Email", order: true }],
dialogController: 'UserDialogController',
editTemplate: '/app/components/user/userDialog.html',
deleteTemplate: '/app/shared/deleteComponent/deleteComponent.html'
};
$timeout(function () {
vm.loadData = true;
}, 1000);
}
With cellTemplate option:
<abioka-data-table options="vm.options">
</abioka-data-table>
/* @ngInject */
function LoginAttemptsController($filter, AdminResource) {
var vm = this;
var resultTemplate = "<span class='ab-label' ng-class=\"{'label-warning': entity.LoginResult === 'WrongPassword', 'label-success': entity.LoginResult === 'Successful'}\">{{entity.LoginResult | translate}}</span>";
var fulldateFormat = $filter("translate")("FullDateFormat");
var dateTemplate = "<span>{{entity.Date | abDate:'" + fulldateFormat + "'}}</span>";
vm.options = {
loadOnInit: true,
rowSelection: false,
isReadOnly: true,
resource: AdminResource.loginAttempts,
query: { order: '-Date' }, // default order by Date descending while loading data.
columns: [{ name: "Date", text: "Date", order: true, cellTemplate: dateTemplate },
{ name: "User.Email", text: "Email" },
{ name: "LoginResult", text: "LoginResult", cellTemplate: resultTemplate },
{ name: "IP", text: "IP" }],
};
}
Language resources are stored under resources folder and used with translate
filter.
Example
resource_en.json:
{
"Password": "Password",
"PasswordAgain": "Password Again",
"IsAdmin": "Is Admin",
"LoginForm": "Login Form",
}
in html:
<label>{{'Password' | translate}}</label>
You can test this application at demo.abioka.com
User Name: demo@abioka.com
Password : demo
Clone this repository to your local machine.
git clone https://github.com/tugrulelmas/AbiokaApi.git
Run the following codes:
cd AbiokaApi/AbiokaApi.Host
npm install
gulp
Change connection string in web.config
Change Facebook and Google's client and secret key's value in web.config
Open the AbiokaApi.sln with Visual Studio and then run the project.
Open http://localhost:84 in your lovely browser.
Install full code:
Install-Package AbiokaApi.All
or install custom libraries:
Install-Package AbiokaApi.Domain
Install-Package AbiokaApi.Repository
Install-Package AbiokaApi.ApplicationService
Install-Package AbiokaApi.Infrastructure.Common
Install-Package AbiokaApi.Infrastructure.Framework
Install-Package AbiokaApi.Host
This project is licensed under the MIT License - see the LICENSE file for details