How to Create an ASP.NET Core WebAPI

Jul 29, 2016 reading time 9 minutes

How to Create an ASP.NET Core WebAPI

In this blog post, I want to show you how to create an ASP.NET Core WebAPI. We will use a custom mapper, you could use AutoMapper for this instead, a repository which is a singleton in this blog here, and we will use the normal CRUD operations to Create, Read, Update and Delete in the "database" here.

Code here https://github.com/FabianGosebrink/ASPNETCore-WebAPI-Sample

Visual Studio

AspNetCoreWebApi0 AspNetCoreWebApi02

We will start with the ASP.NET Startup-File

ASP.NET Core Startup

public class Startup
{
 public Startup(IHostingEnvironment env)
 {
 var builder = new ConfigurationBuilder()
 .SetBasePath(env.ContentRootPath)
 .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
 .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
 builder.AddEnvironmentVariables();
 Configuration = builder.Build();
 }
 public IConfigurationRoot Configuration { get; set; }
 // This method gets called by the runtime. Use this method to add services to the container.
 public void ConfigureServices(IServiceCollection services)
 {
 // Add framework services.
 services.AddMvc();
 }
 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
 {
 loggerFactory.AddConsole(Configuration.GetSection("Logging"));
 loggerFactory.AddDebug();
 app.UseDefaultFiles();
 app.UseStaticFiles();
 app.UseMvc();
 }
}

No magic here so far. We are creating a configuration in the first step (constructor) of the FIle and then add and use MVC with its defaultfiles (e.g. index.html) and add the ability to server static files in general (app.useStaticFiles).

The models

First lets build some entities we want to send to the client and back. We will create a DTO and an Entity and map everything before it gets send to the client.

public class HouseDto
{
 public int Id { get; set; }
 [Required, MinLength(3)]
 public string Street { get; set; }
 [Required, MinLength(3)]
 public string City { get; set; }
 [Required]
 [DataType(DataType.PostalCode)]
 public int ZipCode { get; set; }
}
public class HouseEntity
 {
 public int Id { get; set; }
 public string Street { get; set; }
 public string City { get; set; }
 public int ZipCode { get; set; }
}

So the DTO is an exact duplicate from the entity in this case.

Lets create the Mapper next (which is really obvious):

The mapper

public interface IHouseMapper
 {
 HouseDto MapToDto(HouseEntity houseEntity);
 HouseEntity MapToEntity(HouseDto houseDto);
}
public class HouseMapper : IHouseMapper
 {
 public HouseDto MapToDto(HouseEntity houseEntity)
 {
 return new HouseDto()
 {
 Id = houseEntity.Id,
 ZipCode = houseEntity.ZipCode,
 City = houseEntity.City,
 Street = houseEntity.Street
 };
 }
 public HouseEntity MapToEntity(HouseDto houseDto)
 {
 return new HouseEntity()
 {
 Id = houseDto.Id,
 ZipCode = houseDto.ZipCode,
 City = houseDto.City,
 Street = houseDto.Street
 };
 }
}

So here we are mapping from one to another. This can get more complex but it should do it for this time.

After we created the mapper we want to have this mapper instantiated every time a request comes in. For this we use the build in DI-container in ASP.NET Core.

So go to Startup.cs and add the line

services.AddTransient<IHouseMapper, HouseMapper>();

in the "ConfigureServices"-Method. It should look like this then:

public void ConfigureServices(IServiceCollection services)
{
 services.AddTransient<IHouseMapper, HouseMapper>();
 // Add framework services.
 services.AddMvc();
}

The Repository

The repository is responsible for providing and saving the data. Normally you would use da database here.

Like:

https://github.com/FabianGosebrink/ASPNETCore-Entity-Framework-6

https://github.com/FabianGosebrink/ASPNETCore-Entity-Framework-Core

But for this time we will use like a static list where objects are added and removed.

public interface IHouseRepository
 {
 List<HouseEntity> GetAll();
 HouseEntity GetSingle(int id);
 HouseEntity Add(HouseEntity toAdd);
 HouseEntity Update(HouseEntity toUpdate);
 void Delete(int id);
}
public class HouseRepository : IHouseRepository
 {
 readonly Dictionary<int, HouseEntity> _houses = new Dictionary<int, HouseEntity>();
 public HouseRepository()
 {
 _houses.Add(1, new HouseEntity() { City = "Town1", Id = 1, Street = "Street1", ZipCode = 1234 });
 _houses.Add(2, new HouseEntity() { City = "Town2", Id = 2, Street = "Street2", ZipCode = 1234 });
 _houses.Add(3, new HouseEntity() { City = "Town3", Id = 3, Street = "Street3", ZipCode = 1234 });
 _houses.Add(4, new HouseEntity() { City = "Town4", Id = 4, Street = "Street4", ZipCode = 1234 });
 }
 public List<HouseEntity> GetAll()
 {
 return _houses.Select(x => x.Value).ToList();
 }
 public HouseEntity GetSingle(int id)
 {
 return _houses.FirstOrDefault(x => x.Key == id).Value;
 }
 public HouseEntity Add(HouseEntity toAdd)
 {
 int newId = !GetAll().Any() ? 1 : GetAll().Max(x => x.Id) + 1;
 toAdd.Id = newId;
 _houses.Add(newId, toAdd);
 return toAdd;
 }
 public HouseEntity Update(HouseEntity toUpdate)
 {
 HouseEntity single = GetSingle(toUpdate.Id);
 if (single == null)
 {
 return null;
 }
 _houses[single.Id] = toUpdate;
 return toUpdate;
 }
 public void Delete(int id)
 {
 _houses.Remove(id);
 }
}

A normal interface using Entity Framwork could look like this btw:

public interface IExampleRepository
 {
 IEnumerable<MyModel> GetAll();
 MyModel GetSingle(int id);
 MyModel Add(MyModel toAdd);
 MyModel Update(MyModel toUpdate);
 void Delete(MyModel toDelete);
 int Save();
}

taken from https://github.com/FabianGosebrink/ASPNETCore-Entity-Framework-Core/blob/master/src/AspnetCoreEFCoreExample/Repositories/IExampleRepository.cs

See the "Save()”-Method here. But for this time it should be good with the list. We are not focussing on the Database here.

So we do have the repository to save the data. Let’s make it available through DI in the Startup. This time we do NOT want to have a new repo every request, so we will add a singleton this time.

In a normal case with a real DB you would not do that

Example for EF 6 is here

https://github.com/FabianGosebrink/ASPNETCore-Entity-Framework-6/blob/master/src/AspnetCoreEF6Example/Startup.cs#L32

https://github.com/FabianGosebrink/ASPNETCore-Entity-Framework-Core/blob/master/src/AspnetCoreEFCoreExample/Startup.cs#L32

But this time we will use a singleton. Shame on me so far.

services.AddSingleton<IHouseRepository, HouseRepository>();

So the whole Startup.cs is now like:

public void ConfigureServices(IServiceCollection services)
{
 services.AddSingleton<IHouseRepository, HouseRepository>();
 services.AddTransient<IHouseMapper, HouseMapper>();
 // Add framework services.
 services.AddMvc();
}

With this we can start using the whole construct in a controller like this:

[Route("api/[controller]")]
public class HouseController : Controller
{
 private readonly IHouseMapper _houseMapper;
 private readonly IHouseRepository _houseRepository;
 public HouseController(IHouseMapper houseMapper, IHouseRepository houseRepository)
 {
 _houseMapper = houseMapper;
 _houseRepository = houseRepository;
 }
 [HttpGet]
 public IActionResult Get()
 {
 try
 {
 return Ok(_houseRepository.GetAll().Select(x => _houseMapper.MapToDto(x)));
 }
 catch (Exception exception)
 {
 //logg exception or do anything with it
 return StatusCode((int) HttpStatusCode.InternalServerError);
 }
 }
 [HttpGet("{id:int}", Name = "GetSingleHouse")]
 public IActionResult GetSingle(int id)
 {
 try
 {
 HouseEntity houseEntity = _houseRepository.GetSingle(id);
 if (houseEntity == null)
 {
 return NotFound();
 }
 return Ok(_houseMapper.MapToDto(houseEntity));
 }
 catch (Exception exception)
 {
 //logg exception or do anything with it
 return StatusCode((int)HttpStatusCode.InternalServerError);
 }
 }
 [HttpPatch("{id:int}")]
 public IActionResult Patch(int id, [FromBody] JsonPatchDocument<HouseDto> housePatchDocument)
 {
 try
 {
 if (housePatchDocument == null)
 {
 return BadRequest();
 }
 if (!ModelState.IsValid)
 {
 return BadRequest(ModelState);
 }
 HouseEntity houseEntity = _houseRepository.GetSingle(id);
 if (houseEntity == null)
 {
 return NotFound();
 }
 HouseDto existingHouse = _houseMapper.MapToDto(houseEntity);
 housePatchDocument.ApplyTo(existingHouse, ModelState);
 if (!ModelState.IsValid)
 {
 return BadRequest(ModelState);
 }
 _houseRepository.Update(_houseMapper.MapToEntity(existingHouse));
 return Ok(existingHouse);
 }
 catch (Exception exception)
 {
 //logg exception or do anything with it
 return StatusCode((int)HttpStatusCode.InternalServerError);
 }
 }
 [HttpPost]
 public IActionResult Create([FromBody] HouseDto houseDto)
 {
 try
 {
 if (houseDto == null)
 {
 return BadRequest();
 }
 if (!ModelState.IsValid)
 {
 return BadRequest(ModelState);
 }
 HouseEntity houseEntity = _houseMapper.MapToEntity(houseDto);
 _houseRepository.Add(houseEntity);
 return CreatedAtRoute("GetSingleHouse", new { id = houseEntity.Id }, _houseMapper.MapToDto(houseEntity));
 }
 catch (Exception exception)
 {
 //logg exception or do anything with it
 return StatusCode((int)HttpStatusCode.InternalServerError);
 }
 }
 [HttpPut("{id:int}")]
 public IActionResult Update(int id, [FromBody] HouseDto houseDto)
 {
 try
 {
 if (houseDto == null)
 {
 return BadRequest();
 }
 if (!ModelState.IsValid)
 {
 return BadRequest(ModelState);
 }
 HouseEntity houseEntityToUpdate = _houseRepository.GetSingle(id);
 if (houseEntityToUpdate == null)
 {
 return NotFound();
 }
 houseEntityToUpdate.ZipCode = houseDto.ZipCode;
 houseEntityToUpdate.Street = houseDto.Street;
 houseEntityToUpdate.City = houseDto.City;
 _houseRepository.Update(houseEntityToUpdate);
 return Ok(_houseMapper.MapToDto(houseEntityToUpdate));
 }
 catch (Exception exception)
 {
 //logg exception or do anything with it
 return StatusCode((int)HttpStatusCode.InternalServerError);
 }
 }
 [HttpDelete("{id:int}")]
 public IActionResult Delete(int id)
 {
 try
 {
 HouseEntity houseEntityToDelete = _houseRepository.GetSingle(id);
 if (houseEntityToDelete == null)
 {
 return NotFound();
 }
 _houseRepository.Delete(id);
 return NoContent();
 }
 catch (Exception exception)
 {
 //logg exception or do anything with it
 return StatusCode((int)HttpStatusCode.InternalServerError);
 }
 }
}

Now we can go ahead and test this with a tool like postman or whatever:

Create an ASP.NET Core 1.1 WebAPI

AspNetCoreWebApi3

Prev.
Next
recommended for you

0 0 Fabian 2023年01月17日 11:38:08How to Create an ASP.NET Core WebAPI
Kooperation Von MyCSharp.de Und Der BASTA! Neue Angular-Kurstermine Angekündigt
Cookie-Manage consent
We use cookies and similar technologies (tracking pixels), as far as this is technically necessary for the provision of our services (e.g. language settings), as well as to the extent that you have given your consent to the processing (e.g. analysis and marketing cookies).

With these cookies, personal data is processed by us and by third parties (some of which are also based in the USA). The USA is not certified by the European Court of Justice as having an adequate level of data protection. There is a risk in particular that your data may be subject to access by US authorities for control and surveillance purposes, and that there will be no effective legal remedies available. By clicking on "Accept All", you agree that cookies may be used by us and by third parties (even in the USA). With the exception of the absolutely necessary cookies, which serve the proper functioning of the website and are not selectable, you can deselect the cookies for statistics and marketing.
Functional Always active
The technical storage or access is absolutely necessary for the lawful purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of transmitting a message over an electronic communications network.
Preferences
The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
Statistics
The technical storage or access that is used exclusively for statistical purposes. The technical storage or access, which is used solely for anonymous statistical purposes. Without a subpoena, the voluntary consent of your internet service provider, or additional records from third parties, the information stored or accessed for this purpose alone can generally not be used to identify you.
Marketing
The technical storage or access is required to create user profiles, to send advertising or to track the user on a website or across multiple websites for similar marketing purposes.
Manage options Manage services Manage {vendor_count} vendors Read more about these purposes
View preferences
{title} {title} {title}

AltStyle によって変換されたページ (->オリジナル) /