Creating webapi entity-framework automapper autofac dependency-injection
- Sending Feedback
- About Web API
- Sample application with each labs
- Creating Web API application and configuration
- Creating Get API
- Modifying Data
- Functional API
- API Versioning
- Association with API
For feedback can drop mail to my email address amit.naik8103@gmail.com or you can create issue
- ASP.NET Web API is a framework for building HTTP services that can be accessed from any client including browsers and mobile devices. It is an ideal platform for building RESTful applications on the .NET Framework
- Create Web API application (Framework 4.7.2)
- Create Entity framework
- Refer to Entityframework repo
- Register Autofac configuration
- Create Repository pattern
Code | Description | Code | Description |
---|---|---|---|
200 | OK | 400 | Bad request |
201 | Created | 401 | Not authorized |
202 | Accepted | 403 | Forbidden |
404 | Not found | ||
302 | Found | 405 | Method not allowed |
304 | Not modified | 409 | Conflict |
307 | Temp redirect | ||
308 | Perm redirect | 500 | Internal error |
- Bold status code is widely used
using status code
public IHttpActionResult get()
{
var x = true;
if (x)
{
return Ok(new { Name = "Amit", Occupation = "CEO" });
}
else
{
return BadRequest("Bad request");
}
}
Using GET collections
public class CampsController : ApiController
{
private readonly ICampRepository repository;
public CampsController(ICampRepository campRepository)
{
repository = campRepository;
}
public async Task<IHttpActionResult> Get()
{
try
{
var result = await repository.GetAllCampsAsync();
return Ok(result);
}
catch (Exception ex)
{
// TODO Add logging
return InternalServerError(ex);
}
}
}
Giving limited property to end user
Add Models/CampModel.cs file
public class CampModel
{
public string Name { get; set; }
public string Moniker { get; set; }
public DateTime EventDate { get; set; } = DateTime.MinValue;
public int Length { get; set; } = 1;
}
- Install AutoMapper via nuget
- Add Models/CampMappingProfile.cs file
public class CampMappingProfile : Profile
{
public CampMappingProfile()
{
CreateMap<Camp, CampModel>();
}
}
Register automapper in autofac file
private static void RegisterServices(ContainerBuilder bldr)
{
var config = new MapperConfiguration(c =>
{
c.AddProfile(new CampMappingProfile());
});
bldr.RegisterInstance(config.CreateMapper())
.As<IMapper>()
.SingleInstance();
// ...
}
Implement automapper to centrailise configuration
public class CampsController : ApiController
{
private readonly ICampRepository _repository;
private readonly IMapper _mapper;
public CampsController(ICampRepository campRepository, IMapper mapper)
{
_repository = campRepository;
_mapper = mapper;
}
public async Task<IHttpActionResult> Get()
{
try
{
// Decoupling dal
var result = await _repository.GetAllCampsAsync();
// Centralising configuration
var mappedResult = _mapper.Map<IEnumerable<CampModel>>(result);
return Ok(mappedResult);
}
catch (Exception ex)
{
// TODO Add logging
return InternalServerError(ex);
}
}
}
In case, if we need camel case json then add below code to webapiconfig file
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
AutofacConfig.Register();
// Change case of JSON
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
// ...
}
}
Comment route configuration in webapiconfig.cs file
public static void Register(HttpConfiguration config)
{
// ...
//config.Routes.MapHttpRoute(
// name: "DefaultApi",
// routeTemplate: "api/{controller}/{id}",
// defaults: new { id = RouteParameter.Optional }
//);
}```
```c#
[RoutePrefix("api/camps")]
public class CampsController : ApiController
{
// ...
[Route("{moniker}")]
public async Task<IHttpActionResult> Get(string moniker)
{
try
{
// Decoupling dal
var result = await _repository.GetCampAsync(moniker);
if (result == null)
{
return NotFound();
}
return Ok(_mapper.Map<CampModel>(result));
}
catch (Exception ex)
{
// TODO Add logging
return InternalServerError(ex);
}
}
}
In case, we need location Add Location prefix to location property in CampModel
public class CampModel
{
public string Name { get; set; }
public string Moniker { get; set; }
public DateTime EventDate { get; set; } = DateTime.MinValue;
public int Length { get; set; } = 1;
// In case if your property name is not added with prefix
public string VenueName { get; set; }
public string LocationAddress1 { get; set; }
public string LocationAddress2 { get; set; }
public string LocationAddress3 { get; set; }
public string LocationCityTown { get; set; }
public string LocationStateProvince { get; set; }
public string LocationPostalCode { get; set; }
public string LocationCountry { get; set; }
}
Create speaker and talk model class, because to avoid data class to expose in api side
public class SpeakerModel
{
public int SpeakerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public string Company { get; set; }
public string CompanyUrl { get; set; }
public string BlogUrl { get; set; }
public string Twitter { get; set; }
public string GitHub { get; set; }
}
public class TalkModel
{
public int TalkId { get; set; }
public string Title { get; set; }
public string Abstract { get; set; }
public int Level { get; set; }
public SpeakerModel Speaker { get; set; }
}
Register speaker and talk in automapper
public class CampMappingProfile : Profile
{
public CampMappingProfile()
{
CreateMap<Camp, CampModel>()
.ForMember(c => c.Venue,
opt => opt.MapFrom(m => m.Location.VenueName));
CreateMap<Speaker, SpeakerModel>();
CreateMap<Talk, TalkModel>();
}
}
Add query string, if we need to add talk data then to pass it as query string
[RoutePrefix("api/camps")]
public class CampsController : ApiController
{
// ...
[Route()]
public async Task<IHttpActionResult> Get(bool includeTalks = false)
{
try
{
// Decoupling dal
var result = await _repository.GetAllCampsAsync(includeTalks);
// ...
}
[Route("{moniker}")]
public async Task<IHttpActionResult> Get(string moniker, bool includeTalks = false)
{
try
{
// Decoupling dal
var result = await _repository.GetCampAsync(moniker, includeTalks);
// ...
}
}
passing querystring in postman
http://localhost:56556/api/camps?includeTalks=true
http://localhost:56556/api/camps/ATL2018?includeTalks=true
Searching API
- As there is more the 2 get method, we have to use verbs (i.e. HttpGet)
[Route("searchByDate/{eventDate:datetime}")]
[HttpGet]
public async Task<IHttpActionResult> SearchByEventDate(DateTime eventDate, bool includeTalks = false)
{
try
{
// Decoupling dal
var result = await _repository.GetAllCampsByEventDate(eventDate, includeTalks);
return Ok(_mapper.Map<CampModel[]>(result));
}
catch (Exception ex)
{
// TODO Add logging
return InternalServerError(ex);
}
}
passing querystring in postman
http://localhost:56556/api/camps/searchByDate/2018-10-18
Resource return type | GET | POST | PUT | DELETE |
---|---|---|---|---|
/customers | List | New item | Status code only | Status code only(Error) |
/customers/123 | Item | Status code only(Error) | Updated item | Status code only |
[Route()]
public async Task<IHttpActionResult> Post(CampModel model)
{
return null;
}
[Route()]
public async Task<IHttpActionResult> Post(CampModel model)
{
try
{
if (ModelState.IsValid)
{
// Mapping CampModel to Camp
var camp = _mapper.Map<Camp>(model);
// Insert to DB
_repository.AddCamp(camp);
// Commit to DB
if (await _repository.SaveChangesAsync())
{
// Get the inserted CampModel
var newModel = _mapper.Map<CampModel>(camp);
// Pass to Route with new value
return CreatedAtRoute("GetCamp",
new { moniker = newModel.Moniker }, newModel);
}
}
}
catch (Exception ex)
{
// TODO Add logging
return InternalServerError(ex);
}
return BadRequest();
}
Add Name to route, incase if we need to redirect
[Route("{moniker}", Name = "GetCamp")]
public async Task<IHttpActionResult> Get(string moniker, bool includeTalks = false)
{
// ...
}
Add ReverseMap in case of binding CampModel to Camp
public CampMappingProfile()
{
CreateMap<Camp, CampModel>()
.ForMember(c => c.Venue, opt => opt.MapFrom(m => m.Location.VenueName))
.ReverseMap();
// ...
}
public class CampModel
{
[Required]
public string Name { get; set; }
[Required]
public string Moniker { get; set; }
[Required]
public DateTime EventDate { get; set; } = DateTime.MinValue;
[Range(1,30)]
public int Length { get; set; } = 1;
// ...
}
[Route()]
public async Task<IHttpActionResult> Post(CampModel model)
{
try
{
if (await _repository.GetCampAsync(model.Moniker) != null)
{
ModelState.AddModelError("Moniker", "Moniker in use");
}
if (ModelState.IsValid)
{
// ...
}
}
return BadRequest(ModelState);
}
[Route("{moniker}")]
public async Task<IHttpActionResult> Put(string moniker, CampModel model)
{
try
{
// check moniker in DB
Camp camp = await _repository.GetCampAsync(moniker);
// if it is not found, send 404
if (camp == null)
{
return NotFound();
}
// automapper map campModel to camp EF model
_mapper.Map(model, camp);
if (await _repository.SaveChangesAsync())
{
return Ok(_mapper.Map<CampModel>(camp));
}
else
{
return InternalServerError();
}
}
catch (Exception ex)
{
// TODO Add logging
return InternalServerError(ex);
}
}
calling from postman
http://localhost:56556/api/camps/ATL2019
// with JSON object as
{
"name": "New Code Camp",
"moniker": "ATL2019",
"eventDate": "2019-10-18T00:00:00",
"length": 1,
"talks": [],
"venue": "New address2",
"locationAddress1": null,
"locationAddress2": null,
"locationAddress3": null,
"locationCityTown": null,
"locationStateProvince": null,
"locationPostalCode": null,
"locationCountry": "IN"
}
[Route("{moniker}")]
public async Task<IHttpActionResult> Delete(string moniker, CampModel model)
{
try
{
// check moniker in DB
Camp camp = await _repository.GetCampAsync(moniker);
// if it is not found, send 404
if (camp == null)
{
return NotFound();
}
// Delete camp
_repository.DeleteCamp(camp);
if (await _repository.SaveChangesAsync())
{
return Ok();
}
else
{
return InternalServerError();
}
}
catch (Exception ex)
{
// TODO Add logging
return InternalServerError(ex);
}
}
calling from postman
http://localhost:56556/api/camps/ATL2020
For calling association data, we will use below URL design
/api/camps/alt2020/talks
/api/camps/alt2020/talks/1
[RoutePrefix("api/camps/{moniker}/talks")]
public class TalksController : ApiController
{
private readonly ICampRepository _repository;
private readonly IMapper _mapper;
public TalksController(ICampRepository repository, IMapper mapper)
{
_repository = repository;
_mapper = mapper;
}
[Route("{id:int}")]
public async Task<IHttpActionResult> Get(string moniker, int id)
{
try
{
var result = await _repository.GetTalkByMonikerAsync(moniker, id);
if (result == null)
{
return NotFound();
}
return Ok(_mapper.Map<TalkModel>(result));
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
}
calling from postman
http://localhost:56556/api/camps/ATL2018/talks/1
In case we need to some below operation, then we can use Functional API's
- print job, or
- sending email
- starting some operation or service
- clearing cache then we can use functional api
We have to avoid functional api for Reporting because functional api should not have returning data
public class OpertaionsController : ApiController
{
[HttpOptions]
[Route("api/sendemail")]
public IHttpActionResult SendEmail()
{
try
{
// TODO: Sending email logic
return Ok();
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
}
Below are some examples we can implement API versioning
-
URI Path (recommended)
https://foo.org/api/v2/customers -
Query string
https://foo.org/api/customers?v=2.0 -
Versioning with headers
GET /api/camps
HTTP/1.1
Host: localhost:4433
Content-type: application/json x-version: 2.0 -
Versioning with Accept header
GET /api/camps
HTTP/1.1
Host: localhost:4433
Content-type: application/json Accept: application/json;version=2. 0 -
Versioning with Content type (recommended)
GET /api/camps
HTTP/1.1
Host: localhost:4433
Content-type: application/vnd.yourapp.camp.v1+json Accept: application/vnd.yourapp.camp.v1+json
Add Microsoft.AspNet.WebApi.Versioning via nuget
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
AutofacConfig.Register();
config.AddApiVersioning(cfg =>
{
// Versioning 1.1
cfg.DefaultApiVersion = new Microsoft.Web.Http.ApiVersion(1, 1);
// No need to pass in query string
cfg.AssumeDefaultVersionWhenUnspecified = true;
// Version can be seen in headers
cfg.ReportApiVersions = true;
});
// ...
}
Removing versioning line for config file
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
AutofacConfig.Register();
config.AddApiVersioning(cfg =>
{
// Versioning 1.1
// cfg.DefaultApiVersion = new Microsoft.Web.Http.ApiVersion(1, 1);
// No need to pass in query string
cfg.AssumeDefaultVersionWhenUnspecified = true;
// Version can be seen in headers
cfg.ReportApiVersions = true;
});
// ...
}
[ApiVersion("1.1")]
[RoutePrefix("api/camps")]
public class CampsController : ApiController
{
// ...
}
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
AutofacConfig.Register();
config.AddApiVersioning(cfg =>
{
// No need to pass in query string
cfg.AssumeDefaultVersionWhenUnspecified = true;
// Version can be seen in headers
cfg.ReportApiVersions = true;
// URL vesion
cfg.ApiVersionReader = new UrlSegmentApiVersionReader();
});
// Change case of JSON
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof(ApiVersionRouteConstraint)
}
};
// Web API routes
config.MapHttpAttributeRoutes(constraintResolver);
}
[ApiVersion("2.0")]
[RoutePrefix("api/v{version:apiVersion}/camps")]
public class Camps2Controller : ApiController
{
// ...
}
http://localhost:56556/api/v2/camps