.NET Core 2.0 With Angular 4 and MySQL, Part 5: GET Request

In theprevious post, we created a repository pattern for collecting data from a database.

Now, it is time to use that repository for business logic.

You will keep all the database logic inside the repository classes. Controllers will be responsible for handling requests, model validation, and returning responses to the front-end part of the application.

By doing so, your controllers won’t be overwhelmed with the code thus making the code easier to read and maintain as well.

If you want to see all the basic instructions and complete navigation for this series, please follow the following link: Introduction page for this tutorial.

The source code is available for download at .NET Core, Angular 4 and MySQL. Part 5 – Source Code

Controllers and Routing in Web API

To create the controller, right click on the Controllers folder inside the main project and add the new item. Then from the menu choose Web API Controller Class and name it OwnerController.cs.

Because we are going to start from scratch, delete all actions inside the controller class that look like this:

using Microsoft.AspNetCore.Mvc;

namespace AccountOwnerServer.Controllers
{
    [Route("api/[controller]")]
    public class OwnerController : Controller
    {

    }
}

Every Web API controller class inherits from the controller abstract class that provides all the necessary behavior for the derived class.

Also, above the controller class you can see this part of code:

[Route("api/[controller]")]

This represents the routing and we will talk a little bit about the routing inside Web APIs.

Web API routingroutes the incoming HTTP requests to the particular action method inside the Web API controller.

There are two types of routings:

  1. Convention based routing
  2. Attribute routing

Convention based routingis named thusly because it establishes a convention for the URL paths. The first part makes the mapping for the controller name, the second part makes the mapping for the action method, and the third part is used for the optional parameter. You can configure this type of routing in the Startup class in the Configure method.

Attribute routinguses the attributes to map the routes directly to the action methods inside the controller. Usually, we place the base route above the controller class, as you can notice in our Web API controller class. Similarly, for the specific action methods, we create their routes right above them.

GetAllOwners HTTP GET Request

So let’s start.

First, change the base route from [Route(“api/[controller]”)] to [Route(“api/owner”)] . Even though the first rout will work just fine, with the second example we are getting more specific in order to show that this routing should point to OwnerController .

Now it is time to create the first action method to return all the owners from the database.

In the IOwnerRepository interface, create a definition for the method GetAllOwners :

public interface IOwnerRepository
{
    IEnumerable GetAllOwners();
}

Then implement that interface inside the OwnerRepository class:

namespace Repository
{
    public class OwnerRepository: RepositoryBase, IOwnerRepository
    {
        public OwnerRepository(RepositoryContext repositoryContext)
            :base(repositoryContext)
        {
        }

        public IEnumerable GetAllOwners()
        {
            return FindAll()
                .OrderBy(ow => ow.Name);
        }
    }
}

Finally, you need to return all the owners by using the GetAllOwners method inside the Web API action.

The purpose of the action methods, inside Web API controllers, is not only to return the results. It is the main purpose, but not the only one. You need to pay attention to the status codes of your Web API responses as well. Additionally, you’ll have to decorate your actions with the HTTP attributes which will mark the type of the HTTP request to that action.

You can read more on HTTP and find some HTTP request examples in Part 1 of our HTTP series .

Modify OwnerController with the following code:

using Contracts;
using Microsoft.AspNetCore.Mvc;
using System;

namespace AccountOwnerServer.Controllers
{
    [Route("api/owner")]
    public class OwnerController : Controller
    {
        private ILoggerManager _logger;
        private IRepositoryWrapper _repository;

        public OwnerController(ILoggerManager logger, IRepositoryWrapper repository)
        {
            _logger = logger;
            _repository = repository;
        }

        [HttpGet]
        public IActionResult GetAllOwners()
        {
            try
            {
                var owners = _repository.Owner.GetAllOwners();

                _logger.LogInfo($"Returned all owners from database.");

                return Ok(owners);
            }
            catch (Exception ex)
            {
                _logger.LogError($"Something went wrong inside GetAllOwners action: {ex.Message}");
                return StatusCode(500, "Internal server error");
            }
        }
    }
}

Let me explain this code a bit.

First of all, you inject the logger and repository services inside the constructor. Then by decorating the GetAllOwners action with the [HttpGet] attribute, you are mapping this action to the GET request. Finally, you use both injected parameters to log the messages and to get the data from the repository class.

The IActionResult interface supports using a variety of methods, which return not only the result but the status codes as well. In this situation, the OK method returns all the owners and also the status code 200 which stands for OK. If an exception occurs, we will return the internal server error with the status code 500.

You can read more about status codes by reading The HTTP series – References

Because there is no route attribute right above the action, the route for the action GetAllOwners will be api/owner (http://localhost:5000/api/owner) .

Code Permissions and Testing the Result

I would like to point out one more thing insidethe GetAllOwners action. Right now, if you look at the repository structure, its classes inherit from the abstract RepositoryBase class and also from its own interface which then inherits from IRepositoryBase interface. With this hierarchy in place, by typing ” _repository.Owner. ” you are able to call the custom method from the OwnerRepository class and also all of the methods from the abstract RepositoryBase class.

If you want to avoid that type of behavior and to allow actions inside the controller to call only methods from the repository user classes, all you need to do is to remove IRepositoryBase inheritance from IOwnerRepository . Consequently, only repository user classes will be able to call generic methods from the RepositoryBase class. Likewise, action methods communicate only with repository user classes.

It is all up to you and how you want to organize your code and permissions.

To check the result, use the Postman tool to send requests towards the application.

Start the application, start the Postman, and create a request like this:

Excellent, everything is working as planned.

Before you continue, I would like to show you one more thing. If you look at the model classes, you’ll notice that all the properties have the same name as the columns they are mapped to. But you can have a property with a different name than the column it points to, and still map to each other. To achieve this, you need to use the attribute [Column].

So, let’s do something like that.

Change property names from AccountId and OwnerId to just Id in Owner and Account classes. Also, add the [Column] property which will map the Id property to right column in the database. Your classes should look like this:

[Table("Account")]
public class Account
{
    [Key]
    [Column("AccountId")]
    public Guid Id { get; set; }

    [Required(ErrorMessage = "Date created is required")]
    public DateTime DateCreated { get; set; }

    [Required(ErrorMessage = "Account type is required")]
    public string AccountType { get; set; }

    [Required(ErrorMessage = "Owner Id is required")]
    public Guid OwnerId { get; set; }
}
[Table("Owner")]
public class Owner
{
    [Key]
    [Column("OwnerId")]
    public Guid Id { get; set; }

    [Required(ErrorMessage = "Name is required")]
    [StringLength(60, ErrorMessage = "Name can't be longer than 60 characters")]
    public string Name { get; set; }

    [Required(ErrorMessage = "Date of birth is required")]
    public DateTime DateOfBirth { get; set; }

    [Required(ErrorMessage = "Address is required")]
    [StringLength(100, ErrorMessage = "Address can not be loner then 100 characters")]
    public string Address { get; set; }
}

Now let’s continue.

GetOwnerById HTTP GET Request

Modify the IOwnerRepository interface like this:

public interface IOwnerRepository
{
    IEnumerable GetAllOwners();
    Owner GetOwnerById(Guid ownerId);
}

Then implement the interface in OwnerRepository.cs:

public Owner GetOwnerById(Guid ownerId)
{
    return FindByCondition(owner => owner.Id.Equals(ownerId))
            .FirstOrDefault();
}

Finally, change OwnerController like this:

[HttpGet("{id}")]
public IActionResult GetOwnerById(Guid id)
{
    try
    {
        var owner = _repository.Owner.GetOwnerById(id);

        if (owner == null)
        {
            _logger.LogError($"Owner with id: {id}, hasn't been found in db.");
            return NotFound();
        }
        else
        {
           _logger.LogInfo($"Returned owner with id: {id}");
           return Ok(owner);
        }
   }
   catch (Exception ex)
   {
        _logger.LogError($"Something went wrong inside GetOwnerById action: {ex.Message}");
        return StatusCode(500, "Internal server error");
   }
}

Use Postman to send valid and invalid requests to check the results.

Code Refactoring

The code above works great, but could it work even better?

Whenever you can, you should try to avoid returning null as much as possible because null could be the reason for many potential bugs in your code. So, let’s change the code a bit, to avoid working with nulls.

First, change the repository method:

public Owner GetOwnerById(Guid ownerId)
{
    return FindByCondition(owner => owner.Id.Equals(ownerId))
            .DefaultIfEmpty(new Owner())
            .FirstOrDefault();
}

Right here we did a nice trick. We are no longer returning Null if OwnerId is invalid, but, instead, we’re returning the new Owner object with all the default property values inside it. By doing this we are ensuring that the OwnerId property will always return the new GUID value because the new Owner object is created. With that knowledge it is easy to change owner == null to something different:

var owner = _repository.Owner.GetOwnerById(id);

if (owner.Id.Equals(Guid.Empty))
{
     _logger.LogError($"Owner with id: {id}, hasn't been found in db.");
     return NotFound();
}

This is just one of many solutions you could write. Similarly, you could write different conditions or an extension method and then call it like: if(owner.IsEmptyObject()).

DTO and Owner Details Request

Let’s continue on by creating some logic to return the Owner object with its account details.

First, you will create an extended owner model (or DTO) which will help you return the owner with all related accounts to it.

In the Entities project, create the new folder. Give it the name ExtendedModels and inside it create a class called OwnerExtended :

public class OwnerExtended
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Address { get; set; }

    public IEnumerable Accounts { get; set; }

    public OwnerExtended()
    {
    }

    public OwnerExtended(Owner owner)
    {
        Id = owner.Id;
        Name = owner.Name;
        DateOfBirth = owner.DateOfBirth;
        Address = owner.Address;
    }
}

You don’t want to touch the Owner model, let’s keep it clean as it is. If you want to add some properties to the model class, you can always create the new extended models as you just did. Notice the property Accounts which will bind all accounts related to the certain owner. Also, there is a constructor which takes the owner object as the parameter and maps it to all properties inside this class. All you have to do is to bind all accounts related to the Owner object using the foreign key.

Let’s modify the interface accordingly:

public interface IOwnerRepository
{
    IEnumerable GetAllOwners();
    Owner GetOwnerById(Guid ownerId);
    OwnerExtended GetOwnerWithDetails(Guid ownerId);
}

Also, modify the repository class:

public OwnerExtended GetOwnerWithDetails(Guid ownerId)
{
    return new OwnerExtended(GetOwnerById(ownerId))
    {
        Accounts = RepositoryContext.Accounts
            .Where(a => a.OwnerId == ownerId)
    };
}

You can see the advantage of removing the null value from the GetOwnerById method. Because now, if you send the wrong ownerId value, GetOwnerById will always return at least an empty Owner object. Thus mapping it into ownerExtended won’t throw an error as it would if it was a null.

Finally, modify the controller:

[HttpGet("OwnerDetails/{id}")]
public IActionResult GetOwnerWithDetails(Guid id)
{
    try
    {
        var owner = _repository.Owner.GetOwnerWithDetails(id);

        if (owner.Id.Equals(Guid.Empty))
        {
            _logger.LogError($"Owner with id: {id}, hasn't been found in db.");
            return NotFound();
        }
        else
        {
            _logger.LogInfo($"Returned owner with details for id: {id}");
            return Ok(owner);
        }
    }
    catch (Exception ex)
    {
        _logger.LogError($"Something went wrong inside GetOwnerWithDetails action: {ex.Message}");
        return StatusCode(500, "Internal server error");
    }
}

Result:

Conclusion

Requests using GET should only retrieve the data from the database, and all the actions inside the OwnerController class are written like that.

By reading this post you’ve learned:

  • How to work with a controller class.
  • What routing is and how to use it.
  • How to handle GET requests in Web API.
  • The way to use DTOs while handling requests.

Thank you all for reading and I hope you found something useful in it.

Stay tuned for the next article, where we will apply the same principles learned here to support the POST, PUT, and DELETE requests in our application.

For any suggestions or questions, don’t hesitate to leave the comment in the comment section below.

PHP Zone责编内容来自:PHP Zone (源链) | 更多关于

阅读提示:酷辣虫无法对本内容的真实性提供任何保证,请自行验证并承担相关的风险与后果!
本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 前端开发 » .NET Core 2.0 With Angular 4 and MySQL, Part 5: GET Request

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录