01) use jquary class for module wise
example :-
module Ecart.User.ViewInfo{
var m = (()=>{
var apiCalls ={
get(e):{
return $.ajax({});
},
insert(e):{
return $.ajax({});
}
},
function get(e?){
if(!validateForm()){return;}
apiCalls.get(e).done((r)=>{console.log(r);}).fail((r)=>{alert('failed');});
}
function insert(e?){
if(!validateForm()){return;}
apiCalls.insert(e).done((r)=>{console.log(r);}).fail((r)=>{alert('fail');});
}
function validateForm(){
return true;
}
return{
int:()=>{
// int controlls for this module.
// call crud functions.
$('#btnClick').on('click',this,insert);
},
}
});
$(document).ready(m.init);
}
02) Think Web api method as Noun. not as the verb.
public class StudentController:ApiController{
[HttpGet]
[Route("Students")]
public IHttpResponse Get(){}
[HttpGet]
[Route("Students/{id}")]
public IHttpResponse Get(int id){}
[HttpPost]
[Route("Student}s")]
public IHttpResponse Post(Student obj){}
[HttpDelete]
[Route("Students/{id}")]
public IHttpResponse Post(int id){}
[HttpPut]
[Route("Students")]
public IHttpResponse Put(Student object){}
}
in that case use plural except singular for identity routing
03) User Automapper to Domain transfer to Different Domain.
using X.App_Start
public class AutomapperConfig(){
public void Register(){
// register mapping
}
}
// call to mapping from Globle.ascx
public class Globle{
public void Application_Start(){
// call to registration
new AutomapperConfig().Register();
}
}
04) Always return IHttpResponse type from Api method
public IHttpResult Get(){
try{
if(!Validate()){
return BadRequest('error list converted to json');
}
else{
return Ok<T>(result);
}
}catch(Exception e){
return Content<Exception>(HttpStatusCode.InternalServerError,e);
}
}
05) Be Always Asyncronas
public async Task <IHttpResult> Insert(T object){
await c.Insert(T);
return Ok();
}
06) Always do Server side validation.
public static bool IsModelValied(object x, out List<string> e)
{
ValidationContext context = new ValidationContext(x, null, null);
IList<ValidationResult> errors = new List<ValidationResult>();
var ex = new List<string>();
bool isValied = true;
if (!Validator.TryValidateObject(x, context, errors, true))
{
isValied = false;
foreach (ValidationResult result in errors)
{
ex.Add(result.ErrorMessage);
}
}
e = ex;
return isValied;
}
public Task<IHttpResponse> Insert(T obj){
var err = new List<string>();
if(!IsModelValied(T,out err)){
return BadRequest(JSON.Stringify(err));
}
return Ok();
}
07) Use Dependency injection
Controller
public class StudentService:ApiController{
StudentDbService service;
public A(UnitOfWork _uow){
serice = new StudentDbServive(_uow);
}
}
08) Use Lamda Expression what you need
public void Insert(){
var lst = new List<T>();
Action<string> come = (e)=>{ lst.Add(e); };
Predicate<string> =passwordLength= (e)=>{ return e.length > 10; }
Func<string,string> some2 = (e) =>{ return e.string();}
}
09) use Unit of work.
public interface IUnitOfWork
{
GenericRepository<Student> StudentRepository { get; }
GenericRepository<Subject> SubjectRepository { get; }
GenericRepository<StudentSubject> StudentSubjectRepository { get; }
GenericRepository<UserAuthontication> UserAuthonticationRepository { get; }
void Save();
Task SaveAsync();
DbContext Context { get; }
}
public class UnitOfWork : IUnitOfWork, IDisposable
{
public UnitOfWork(IContext cnt, Enums.ERunType type)
{
if (type == Enums.ERunType.Debug)
{
context = (SchoolContext)cnt;
context.Configuration.AutoDetectChangesEnabled=false;
}
else
{
context = (MockDbContext)cnt;
}
}
private DbContext context;
private GenericRepository<Student> studentRepository;
public DbContext Context
{
get
{
return context;
}
}
public GenericRepository<Student> StudentRepository
{
get
{
if (this.studentRepository == null)
{
this.studentRepository = new GenericRepository<Student>(context);
}
return studentRepository;
}
}
public void Save()
{
context.SaveChanges();
}
public async Task SaveAsync()
{
await context.SaveChangesAsync();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
10) Use Genaric repository
public class GenericRepository<TEntity> where TEntity : class
{
internal DbContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(DbContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public virtual TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
if (entityToDelete!= null)
{
Delete(entityToDelete);
}
}
public virtual void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.AddOrUpdate(entityToUpdate);
}
}
11. Be Interface Every ware
Api base interface
public interface IService<T>
{
Task<IHttpActionResult> Insert(T item);
Task<IHttpActionResult> Delete(int Id);
Task<IHttpActionResult> Update(T item);
Task<IHttpActionResult> Get();
Task<IHttpActionResult> GetSingle(int studentId);
}
public class StudentServiceController : BaseServiceController, IService<StudentViewModel>
{
PlutoContext
public interface IContext
{
DbSet<Student> Students { get; set; }
DbSet<Subject> Subjects { get; set; }
DbSet<StudentSubject> StudentSubjects { get; set; }
DbSet<UserAuthontication> UserAuthontications { get; set; }
}
public class SchoolContext : DbContext,IContext
{
Genatic repository
public interface IRepositoryRead<T> where T : class
{
IEnumerable<T> Get(Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
string includeProperties = "");
T GetByID(object id);
}
public interface IRepositoryUpdate<T> where T : class {
void Insert(T entity);
void Delete(object id);
void Delete(T entityToDelete);
void Update(T entityToUpdate);
}
public interface IRepositoryUpdateAsync<T> where T : class {
Task<int> InsertAsync(T entity);
Task DeleteAsync(object id);
Task UpdateAsync(T entityToUpdate);
}
public interface IStudentDbService : IRepositoryRead<StudentBo>, IRepositoryUpdateAsync<StudentBo>
{
IEnumerable<StudentBo> Get(Expression<Func<Student, bool>> filter = null, Func<IQueryable<Student>, IOrderedQueryable<Student>> orderBy = null, string includeProperties = "");
}
public class StudentDbService : IStudentDbService
{
.............................
12) Use cache everywhere
using System.Runtime.Caching;
public class MemoryCacher
{
public static bool Add(string key, object value, DateTimeOffset absExpiration)
{
MemoryCache memoryCache = MemoryCache.Default;
return memoryCache.Add(key, value, absExpiration);
}
public static object GetValue(string key)
{
MemoryCache memoryCache = MemoryCache.Default;
return memoryCache.Get(key);
}
public static void Delete(string key)
{
MemoryCache memoryCache = MemoryCache.Default;
if (memoryCache.Contains(key))
{
memoryCache.Remove(key);
}
}
}
in this case put all cache keys in one place like CacheKeysConfig.cs as const string.
13). Use Json Format
config.Formatters.JsonFormatter.SupportedMediaTypes
.Add(new MediaTypeHeaderValue("text/html"));
14) Use Following Folder structure
X (Api,Utility/)
Lib /
X.Domain
X.Bo -->Utility(Enums)
X.DbAccess --> Context / Migration
X.DbService --> Service / Interface / Infastucture
Test / X.UnitTest,X.AutomationTest,X.MockData
15) use attribute base routing for.
01) api versioning.
02) web api method as noun
[RouterPrefix('v1')]
public class StudentController:ApiController{
[Route("student")]
public async Task<IHttpActionResult> Get()
{}
[Route("student/{Id:int}")]
public async Task<IHttpActionResult> GetSingle(int Id)
{}
[Route("student")]
public async Task<IHttpActionResult> Insert(StudentViewModel item)
{}
[Route("student")]
public async Task<IHttpActionResult> Update(StudentViewModel item)
{}
[Route("student/{Id:int}")]
public async Task<IHttpActionResult> Delete(int Id)
{}
}
16) use observer to bind values to html.
17) use template for itoration date
18) Use GZip To compress result
https://damienbod.com/2014/07/16/web-api-using-gzip-compression/
https://github.com/azzlack/Microsoft.AspNet.WebApi.MessageHandlers.Compression
19) KISS (Keep It Simple and Stupid)
- do not expose more than expert.
- return thing in similar way.
public async Task<IHttpResponse> Response(){
return Ok<T>(value);
return BadRequest(string content);
return Component(ResponseCode.InternalServerError,Ex);
}
- use only common Response Code. not all. 404/200/401/500/403
20) Documentation
21) Consider about Versioning
[RouterPrefix('api/v2')]
public class StudentController : ApiController{}
22) Result Filtering / Sorting / Searching / Pagination
quay string
GET /tickets?fields=id,subject,customer_name,updated_at&state=open&sort=-updated_at&skip=10&take=100
23) Errors and Exception Handling
return json message format
{
"code" : 1024,
"message" : "Validation Failed",
"errors" : [
{
"code" : 5432,
"field" : "first_name",
"message" : "First name cannot have fancy characters"
},
{
"code" : 5622,
"field" : "password",
"message" : "Password cannot be blank"
}
]
}
24) use Customer action filter always
public XController:ApiController{
[Validation]
public async Task<IHttpResponse> Insert(Student s){
return null;
}
}
public ValidationActionFilter:ActionFilter{
public override void OnActionExecuting(HttpContext x){
//check context model state is valied
if(!x.ModelState.IsValied()){
//set context as bad request
x.Response = new HttpResponseMessage<Json>("some validation failded",badRequest);
}
}
}
// finally this action filter must register on Globle.asax
public static void Configure(config){
config.Filter.Add(new ValidationActionFilter());
}
25) User Following exception type for following reasons.
406 - notAcceptable (not trigger on database (primary key violation))
404 - not found
200 - OK
201 - created
403 - Forbidden
400 - bad request
26 Handle sql exception (Primary key | forign key) by following way
custom exception for db
public class DbPKViolationException:Exception
{
public DbPKViolationException() : base() { }
public DbPKViolationException(string message) : base(message) { }
}
public class DbFKViolationException : Exception
{
public DbFKViolationException() : base() { }
public DbFKViolationException(string message) : base(message) { }
}
public class DbUniqKeyViolationException : Exception
{
public DbUniqKeyViolationException() : base() { }
public DbUniqKeyViolationException(string message) : base(message) { }
}
genetic exception handler
protected Exception HandleException(Exception exception)
{
var x = exception;
while (x.InnerException != null)
{
x = exception.InnerException;
}
var sqlException = x as SqlException;
if (sqlException==null)
{
throw x;
}
if (sqlException.Number == 2627 || sqlException.Number == 2601)
{
throw new DbPKViolationException();
}
throw exception;
}
}
Service class : base service
public UserBo GetByID(object id)
{
try
{
return Mapper.Map<UserBo>(this.uow.UserRepository.GetByID(Convert.ToUInt32(id)));
}
catch (Exception ex)
{
throw HandleException(ex);
}
}
Controller class
[HttpPost]
[Route("user/login")]
public async Task<IHttpActionResult> Login(UserViewModel entity)
{
try
{
var o = this.service.Login(Mapper.Map<UserBo>(entity));
return Ok();
}
catch (ArgumentException)
{
return Content<string>(HttpStatusCode.NotAcceptable, "invalied user name or password");
}
catch (Exception ex)
{
return await LogErrors(ex);
}
}