0

I am working on a ASP.NET Web Api and I'm having an issue figuring out how to display data to the api.

So to explain what I need to figure out. After accessing the objects from the Json file and converting JSON to a .NET type. I believe I should be able to display these on the api?

For example, whatever field I ask for, it should return the result sorted by that field. Now I need to display this data in different ports. For example.

  • https://host:port/api/books returns all unsorted (Book1-Book13)
  • https://host:port/api/books/id returns all sorted by id (Book1-Book13)
  • https://host:port/api/books/id/1 returns all with id containing '1' sorted by id (Book1, Book10-13)

I have followed this tutorial by microsoft and the application seems to be working in regards to starting and using different ports.

This is the code I have set up in my controller class BooksController.cs

 [Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
    [HttpGet]
    public ActionResult GetAllBooks()
    {
        string[] books = { "book1", "book2", "book3" };

        if (!books.Any())
            return NotFound();
        return Ok(books);
    }

    [HttpGet("id/{id}")]
    public string GetBookById(int id)
    {

        return $"book: {id}";
    }

    [HttpGet("author/{author}")]
    public string GetBookByAuthor(string author)
    {
        return $"author: {author}";
    }

This is my startup.cs

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
        GetAllBooks();
    }
    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<TodoContext>(opt =>
                                           opt.UseInMemoryDatabase("TodoList"));
        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }

    
    public void GetAllBooks()
    {
        string _path = $"C:\\Users\\filip\\source\\repos\\NexerGroupApi\\NexerGroupApi\\books.json";

        string jsonFromFile;
        using (var reader = new StreamReader(_path))
        {
            jsonFromFile = reader.ReadToEnd();
        }

        var booksFromJsonFile = JsonConvert.DeserializeObject<List<NexerGroupApi.Book>>(jsonFromFile);

        JArray bookArray = JArray.FromObject(booksFromJsonFile);

        IList<Book> bookList= bookArray.Select(p => new Book
        {
            Id = (string)p["Id"],
            Author = (string)p["Author"],
            Title = (string)p["Title"],
            Genre = (string)p["Genre"],
            Price = (string)p["Price"],
            Publish_date = (string)p["Publish_date"],
            Description = (string)p["Description"]
        }).ToList();
    }

    public class Book
    {
        public string Id { get; set; }
        public string Author { get; set; }
        public string Title { get; set; }
        public string Genre { get; set; }
        public string Price { get; set; }
        public string Publish_date { get; set; }
        public string Description { get; set; }
    }

I wonder where I actually set up my GetAllBooks() method so that I can then access them in the BooksController.cs and be able to do api/books/bookNameFromJsonFile and show that specific object with all of it's contents such as id, author, title, genre, etc.

I know that my IList<Book> bookList in the class startup.cs does not work, for some reason every variable get's null value. Although not sure why.

8
  • @mason Okay so I've been looking at a couple tutorials now - youtube.com/… - I will update my question and maybe you could answer to that? Commented Apr 22, 2021 at 17:18
  • You do not need to setup an individual route for every single book you have. You need to structure your route to have parameters. Check the documentation. Commented Apr 22, 2021 at 17:31
  • @mason Okay, I've checked out that link And a youtube video. As I understand it I then do for example [HttpGet("{id}")] and I then loop through my books in that method, then if I enter "api/book/2 it will retrieve the book where id=2. Does it work so that I can then do [HttpGet("{author}")] where I have a string author and so If I do api/books/authorname I will actually recieve the book/s from that author? Commented Apr 22, 2021 at 20:19
  • I edited the question to show you what I thought would work. Is there maybe an issue in my startup.cs that leads to this error code? Commented Apr 22, 2021 at 20:26
  • 1
    It's your route definitions. Like the error says, your route matches multiple endpoints. Most likely it's mad because it can't tell which action method to hit for a URL like /api/books/1, it doesn't know whether 1 is an Id or an Author. For GetBookByAuthor, you might change it to [HttpGet("author/{author}")], then someone who wished to look up books by author would hit the URL /api/books/author/Tom%20Clancy, and change GetBookById to [HttpGet("id/{id}")] so they can hit the URL /api/books/id/1. That makes it unambiguous. Just put some thought into how your structure the routes. Commented Apr 22, 2021 at 20:45

1 Answer 1

1

As the error suggests, you have two routes that are ambiguous [HttpGet("{id}")] and [HttpGet("{author}")]

Given

When you hit api/books/1 the 1 could be the author or the bookid.

Consider changing the route of the author method to: [HttpGet("author/{author}")]

[HttpGet("{id}")]
public string GetBookById(int id)
{
    
    return $"book: {id}";
}

[HttpGet("author/{author}")] 
public string GetBookByAuthor(string author)
{
    return $"author: {author}";
}

So requests would route as follows:

api/books/1 would invoke GetBookById

api/books/author/jsmith would invoke GetBookByAuthor

Edit: Add list filtering:

To find a Book from the List<Book> then this can be done:

private readonly List<Book> _bookList; //populate this with books from json file

[HttpGet("{id}")]
public IActionResult GetBookById(int id)
{
    var book = GetBookObjectById(id);
    if(book is null) return NotFound();
    return Ok(book);
}

private Book GetBookObjectById(int id)
{
    return _bookList.FirstOrDefault(book => book.Id == id);
}

private Book GetBookObjectByAuthor(string author)
{
    return _bookList.FirstOrDefault(book => book.Author == author);
}
Sign up to request clarification or add additional context in comments.

10 Comments

Thanks for the tip! I fixed this issue and have set up so that i can enter for example api/books/id/1 or api/books/title/bookTitle . One thing im wondering about now is the following.. I get my Json data from a file.json Where do I setup this to load all the data? In the end I want to be able to do api/books/bookNameFromJsonFile and show that specific object with all of it's contents such as id, author, title, genre, etc. If you look at my startup.cs I have set it up there and then you can see how my booksController.cs looks like, is this the wrong way?
I updated my question so If you check it out now you can see where I'm at and what's bothering me. I haven't been able to find anything that I can really understand yet.
If this is just a learning exercise then I would personally just put the loadbook code you have in Startup.cs into the controller so it's created in the constructor. In a real world you'd make a BookService and inject it into the controller using dependency injection.
Your json deserialise code can also be greatly improved - take a look at the microsoft docs here: learn.microsoft.com/en-us/dotnet/standard/serialization/…
This is more of a learning excercise than a real world scenario. I know I should be learning the correct way to begin with, but I have it easier learning this way then improving the code after I understand what I'm doing to begin with. Instead of just Ctrl+V everything. I have succesfully printed all the books now. But is there any way to print these as json objects? where id1 is in 1 {} and so on? This is how I display them public ActionResult GetAllBooks(){if (!bookList.Any())return NotFound(); return Ok(bookList);} but it's a complete hassle and cant be read.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.