portaldacalheta.pt
  • Κύριος
  • Άνοδος Του Απομακρυσμένου
  • Τεχνολογία
  • Τάσεις
  • Το Μέλλον Της Εργασίας
Πίσω Μέρος

Δημιουργία ASP.NET Web API με ASP.NET Core



Εισαγωγή

Πριν από μερικά χρόνια, πήρα το βιβλίο 'Pro ASP.NET Web API'. Αυτό το άρθρο είναι το απόσπασμα ιδεών από αυτό το βιβλίο, λίγο CQRS, και η δική μου εμπειρία στην ανάπτυξη συστημάτων διακομιστή-πελάτη.

Σε αυτό το άρθρο, θα καλύψω:



  • Πώς να δημιουργήσετε ένα REST API από το μηδέν χρησιμοποιώντας .NET Core, EF Core, AutoMapper και XUnit
  • Πώς να βεβαιωθείτε ότι το API λειτουργεί μετά από αλλαγές
  • Πώς να απλοποιήσετε όσο το δυνατόν περισσότερο την ανάπτυξη και την υποστήριξη του συστήματος REST API

Γιατί ASP.NET Core;

Το ASP.NET Core παρέχει πολλές βελτιώσεις σε σχέση με το ASP.NET MVC / Web API. Πρώτον, είναι τώρα ένα πλαίσιο και όχι δύο. Μου αρέσει πολύ γιατί είναι βολικό και υπάρχει λιγότερη σύγχυση. Δεύτερον, έχουμε κοντέινερ καταγραφής και DI χωρίς πρόσθετες βιβλιοθήκες, κάτι που με εξοικονομεί χρόνο και μου επιτρέπει να επικεντρωθώ στη σύνταξη καλύτερου κώδικα αντί να επιλέξω και να αναλύσουμε τις καλύτερες βιβλιοθήκες.



Τι είναι οι επεξεργαστές ερωτημάτων;

Ένας επεξεργαστής ερωτημάτων είναι μια προσέγγιση όταν όλη η επιχειρησιακή λογική που σχετίζεται με μία οντότητα του συστήματος είναι ενθυλακωμένη σε μία υπηρεσία και οποιαδήποτε πρόσβαση ή ενέργειες με αυτήν την οντότητα εκτελούνται μέσω αυτής της υπηρεσίας. Αυτή η υπηρεσία ονομάζεται συνήθως {EntityPluralName} QueryProcessor. Εάν είναι απαραίτητο, ένας επεξεργαστής ερωτήσεων περιλαμβάνει μεθόδους CRUD (δημιουργία, ανάγνωση, ενημέρωση, διαγραφή) για αυτήν την οντότητα. Ανάλογα με τις απαιτήσεις, δεν μπορούν να εφαρμοστούν όλες οι μέθοδοι. Για να δώσουμε ένα συγκεκριμένο παράδειγμα, ας ρίξουμε μια ματιά στο ChangePassword. Εάν η μέθοδος ενός επεξεργαστή ερωτημάτων απαιτεί δεδομένα εισόδου, τότε θα πρέπει να παρέχονται μόνο τα απαιτούμενα δεδομένα. Συνήθως, για κάθε μέθοδο, δημιουργείται μια ξεχωριστή κατηγορία ερωτημάτων, και σε απλές περιπτώσεις, είναι δυνατό (αλλά όχι επιθυμητό) να επαναχρησιμοποιηθεί η κλάση ερωτημάτων.



Ο στόχος μας

Σε αυτό το άρθρο, θα σας δείξω πώς να δημιουργήσετε ένα API για ένα μικρό σύστημα διαχείρισης κόστους, συμπεριλαμβανομένων βασικών ρυθμίσεων για έλεγχο ταυτότητας και ελέγχου πρόσβασης, αλλά δεν θα μπω στο υποσύστημα ελέγχου ταυτότητας. Θα καλύψω ολόκληρη τη λογική του συστήματος με αρθρωτές δοκιμές και θα δημιουργήσω τουλάχιστον μία δοκιμή ενοποίησης για κάθε μέθοδο API σε ένα παράδειγμα μιας οντότητας.

Απαιτήσεις για το ανεπτυγμένο σύστημα: Ο χρήστης μπορεί να προσθέσει, να επεξεργαστεί, να διαγράψει τα έξοδά του και να δει μόνο τα έξοδά του.



Ολόκληρος ο κωδικός αυτού του συστήματος είναι διαθέσιμος στις Github .

Ας αρχίσουμε λοιπόν να σχεδιάζουμε το μικρό αλλά πολύ χρήσιμο σύστημά μας.



API επιπέδων

Ένα διάγραμμα που δείχνει επίπεδα API.

Το διάγραμμα δείχνει ότι το σύστημα θα έχει τέσσερα επίπεδα:



  • Βάση δεδομένων - Εδώ αποθηκεύουμε δεδομένα και τίποτα περισσότερο, χωρίς λογική.
  • DAL - Για πρόσβαση στα δεδομένα, χρησιμοποιούμε το μοτίβο Μονάδα εργασίας και, κατά την εφαρμογή, χρησιμοποιούμε το ORM EF Core με κώδικα πρώτα και μοτίβα μετεγκατάστασης.
  • Επιχειρηματική λογική - για να ενσωματώσουμε επιχειρηματική λογική, χρησιμοποιούμε επεξεργαστές ερωτημάτων, μόνο αυτό το επίπεδο επεξεργάζεται επιχειρηματική λογική. Η εξαίρεση είναι η απλούστερη επικύρωση, όπως υποχρεωτικά πεδία, τα οποία θα εκτελεστούν μέσω φίλτρων στο API.
  • REST API - Η πραγματική διεπαφή μέσω της οποίας οι πελάτες μπορούν να εργαστούν με το API μας θα εφαρμοστεί μέσω του ASP.NET Core. Οι διαμορφώσεις διαδρομής καθορίζονται από χαρακτηριστικά.

Εκτός από τα περιγραφόμενα επίπεδα, έχουμε πολλές σημαντικές έννοιες. Το πρώτο είναι ο διαχωρισμός των μοντέλων δεδομένων. Το μοντέλο δεδομένων πελάτη χρησιμοποιείται κυρίως στο επίπεδο REST API. Μετατρέπει τα ερωτήματα σε μοντέλα τομέα και αντίστροφα από ένα μοντέλο τομέα σε ένα μοντέλο δεδομένων πελάτη, αλλά τα μοντέλα ερωτημάτων μπορούν επίσης να χρησιμοποιηθούν σε επεξεργαστές ερωτημάτων. Η μετατροπή γίνεται χρησιμοποιώντας το AutoMapper.

Δομή έργου

Χρησιμοποίησα το VS 2017 Professional για τη δημιουργία του έργου. Συνήθως μοιράζομαι τον πηγαίο κώδικα και τις δοκιμές σε διαφορετικούς φακέλους. Είναι άνετο, φαίνεται καλό, οι δοκιμές στο CI εκτελούνται βολικά και φαίνεται ότι η Microsoft συνιστά να το κάνετε με αυτόν τον τρόπο:



Δομή φακέλων στο VS 2017 Professional.

Περιγραφή Έργου:



Εργο Περιγραφή
Εξοδα Έργο για ελεγκτές, αντιστοίχιση μεταξύ μοντέλου τομέα και μοντέλου API, διαμόρφωση API
Έξοδα.Api.Common Σε αυτό το σημείο, συλλέγονται κλάσεις εξαίρεσης που ερμηνεύονται με συγκεκριμένο τρόπο από φίλτρα για την επιστροφή σωστών κωδικών HTTP με σφάλματα στον χρήστη
Έξοδα.Api.Models Έργο για μοντέλα API
Έξοδα. Δεδομένα. Πρόσβαση Σχέδιο διεπαφών και υλοποίηση του προτύπου Μονάδα Εργασίας
Έξοδα. Data.Model Έργο για μοντέλο τομέα
Έξοδα. Ερωτήσεις Έργο για επεξεργαστές ερωτημάτων και συγκεκριμένες κατηγορίες ερωτημάτων
Έξοδα. Ασφάλεια Έργο για τη διεπαφή και εφαρμογή του περιβάλλοντος ασφαλείας του τρέχοντος χρήστη

Αναφορές μεταξύ έργων:

κοινά μεγέθη οθόνης για αποκριτικό σχεδιασμό

Διάγραμμα που δείχνει αναφορές μεταξύ έργων.

Έξοδα που δημιουργήθηκαν από το πρότυπο:

Λίστα δαπανών που δημιουργήθηκαν από το πρότυπο.

Άλλα έργα στο φάκελο src ανά πρότυπο:

Λίστα άλλων έργων στο φάκελο src ανά πρότυπο.

Όλα τα έργα στο φάκελο δοκιμών ανά πρότυπο:

Λίστα έργων στο φάκελο δοκιμών ανά πρότυπο.

Εκτέλεση

Αυτό το άρθρο δεν θα περιγράφει το μέρος που σχετίζεται με το περιβάλλον εργασίας χρήστη, αν και έχει εφαρμοστεί.

Το πρώτο βήμα ήταν να αναπτυχθεί ένα μοντέλο δεδομένων που βρίσκεται στη συναρμολόγηση Expenses.Data.Model:

Διάγραμμα της σχέσης μεταξύ ρόλων

Το Expense Η κλάση περιέχει τα ακόλουθα χαρακτηριστικά:

public class Expense { public int Id { get; set; } public DateTime Date { get; set; } public string Description { get; set; } public decimal Amount { get; set; } public string Comment { get; set; } public int UserId { get; set; } public virtual User User { get; set; } public bool IsDeleted { get; set; } }

Αυτό το μάθημα υποστηρίζει «μαλακή διαγραφή» μέσω του IsDeleted χαρακτηριστικό και περιέχει όλα τα δεδομένα για μία δαπάνη ενός συγκεκριμένου χρήστη που θα μας είναι χρήσιμα στο μέλλον.

Τα User, Role, και UserRole τάξεις αναφέρονται στο υποσύστημα πρόσβασης. Αυτό το σύστημα δεν προσποιείται ότι είναι το σύστημα του έτους και η περιγραφή αυτού του υποσυστήματος δεν είναι ο σκοπός αυτού του άρθρου. Επομένως, το μοντέλο δεδομένων και ορισμένες λεπτομέρειες της εφαρμογής θα παραλειφθούν. Το σύστημα οργάνωσης πρόσβασης μπορεί να αντικατασταθεί από ένα πιο τέλειο, χωρίς να αλλάξει η επιχειρηματική λογική.

Στη συνέχεια, το πρότυπο Unit of Work εφαρμόστηκε στο Expenses.Data.Access συναρμολόγηση, φαίνεται η δομή αυτού του έργου:

Έξοδα. Δεδομένα. Δομή έργου πρόσβασης

Απαιτούνται οι ακόλουθες βιβλιοθήκες για συναρμολόγηση:

  • Microsoft.EntityFrameworkCore.SqlServer

Είναι απαραίτητο να εφαρμοστεί ένα EF περιβάλλον που θα εντοπίσει αυτόματα τις αντιστοιχίσεις σε έναν συγκεκριμένο φάκελο:

public class MainDbContext : DbContext { public MainDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { var mappings = MappingsHelper.GetMainMappings(); foreach (var mapping in mappings) { mapping.Visit(modelBuilder); } } }

Η χαρτογράφηση γίνεται μέσω του MappingsHelper τάξη:

public static class MappingsHelper { public static IEnumerable GetMainMappings() { var assemblyTypes = typeof(UserMap).GetTypeInfo().Assembly.DefinedTypes; var mappings = assemblyTypes // ReSharper disable once AssignNullToNotNullAttribute .Where(t => t.Namespace != null && t.Namespace.Contains(typeof(UserMap).Namespace)) .Where(t => typeof(IMap).GetTypeInfo().IsAssignableFrom(t)); mappings = mappings.Where(x => !x.IsAbstract); return mappings.Select(m => (IMap) Activator.CreateInstance(m.AsType())).ToArray(); } }

Η αντιστοίχιση στα μαθήματα βρίσκεται στο Maps φάκελος και αντιστοίχιση για Expenses:

public class ExpenseMap : IMap { public void Visit(ModelBuilder builder) { builder.Entity() .ToTable('Expenses') .HasKey(x => x.Id); } }

Διεπαφή IUnitOfWork:

public interface IUnitOfWork : IDisposable { ITransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Snapshot); void Add(T obj) where T: class ; void Update(T obj) where T : class; void Remove(T obj) where T : class; IQueryable Query() where T : class; void Commit(); Task CommitAsync(); void Attach(T obj) where T : class; }

Η εφαρμογή του είναι ένα περιτύλιγμα για EF DbContext:

τι μπορεί να κάνει το ρουμπίνι στις ράγες
public class EFUnitOfWork : IUnitOfWork { private DbContext _context; public EFUnitOfWork(DbContext context) { _context = context; } public DbContext Context => _context; public ITransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Snapshot) { return new DbTransaction(_context.Database.BeginTransaction(isolationLevel)); } public void Add(T obj) where T : class { var set = _context.Set(); set.Add(obj); } public void Update(T obj) where T : class { var set = _context.Set(); set.Attach(obj); _context.Entry(obj).State = EntityState.Modified; } void IUnitOfWork.Remove(T obj) { var set = _context.Set(); set.Remove(obj); } public IQueryable Query() where T : class { return _context.Set(); } public void Commit() { _context.SaveChanges(); } public async Task CommitAsync() { await _context.SaveChangesAsync(); } public void Attach(T newUser) where T : class { var set = _context.Set(); set.Attach(newUser); } public void Dispose() { _context = null; } }

Η διεπαφή ITransaction δεν θα χρησιμοποιηθεί:

public interface ITransaction : IDisposable { void Commit(); void Rollback(); }

Η εφαρμογή του απλώς τυλίγει το EF συναλλαγή:

public class DbTransaction : ITransaction { private readonly IDbContextTransaction _efTransaction; public DbTransaction(IDbContextTransaction efTransaction) { _efTransaction = efTransaction; } public void Commit() { _efTransaction.Commit(); } public void Rollback() { _efTransaction.Rollback(); } public void Dispose() { _efTransaction.Dispose(); } }

Επίσης σε αυτό το στάδιο, για τις δοκιμές μονάδας, το ISecurityContext απαιτείται διεπαφή, η οποία καθορίζει τον τρέχοντα χρήστη του API (το έργο είναι Expenses.Security):

public interface ISecurityContext { User User { get; } bool IsAdministrator { get; } }

Στη συνέχεια, πρέπει να ορίσετε τη διεπαφή και την εφαρμογή του επεξεργαστή ερωτημάτων, ο οποίος θα περιέχει όλη τη λογική της επιχείρησης για εργασία με κόστος - στην περίπτωσή μας, IExpensesQueryProcessor και ExpensesQueryProcessor:

public interface IExpensesQueryProcessor { IQueryable Get(); Expense Get(int id); Task Create(CreateExpenseModel model); Task Update(int id, UpdateExpenseModel model); Task Delete(int id); } public class ExpensesQueryProcessor : IExpensesQueryProcessor { public IQueryable Get() { throw new NotImplementedException(); } public Expense Get(int id) { throw new NotImplementedException(); } public Task Create(CreateExpenseModel model) { throw new NotImplementedException(); } public Task Update(int id, UpdateExpenseModel model) { throw new NotImplementedException(); } public Task Delete(int id) { throw new NotImplementedException(); } }

Το επόμενο βήμα είναι να διαμορφώσετε το Expenses.Queries.Tests συνέλευση. Εγκατέστησα τις ακόλουθες βιβλιοθήκες:

  • Moq
  • FluentAssertions

Στη συνέχεια, στο Expenses.Queries.Tests συναρμολόγηση, καθορίζουμε το προσάρτημα για δοκιμές μονάδας και περιγράφουμε τις δοκιμές μονάδας μας:

public class ExpensesQueryProcessorTests { private Mock _uow; private List _expenseList; private IExpensesQueryProcessor _query; private Random _random; private User _currentUser; private Mock _securityContext; public ExpensesQueryProcessorTests() { _random = new Random(); _uow = new Mock(); _expenseList = new List(); _uow.Setup(x => x.Query()).Returns(() => _expenseList.AsQueryable()); _currentUser = new User{Id = _random.Next()}; _securityContext = new Mock(MockBehavior.Strict); _securityContext.Setup(x => x.User).Returns(_currentUser); _securityContext.Setup(x => x.IsAdministrator).Returns(false); _query = new ExpensesQueryProcessor(_uow.Object, _securityContext.Object); } [Fact] public void GetShouldReturnAll() { _expenseList.Add(new Expense{UserId = _currentUser.Id}); var result = _query.Get().ToList(); result.Count.Should().Be(1); } [Fact] public void GetShouldReturnOnlyUserExpenses() { _expenseList.Add(new Expense { UserId = _random.Next() }); _expenseList.Add(new Expense { UserId = _currentUser.Id }); var result = _query.Get().ToList(); result.Count().Should().Be(1); result[0].UserId.Should().Be(_currentUser.Id); } [Fact] public void GetShouldReturnAllExpensesForAdministrator() { _securityContext.Setup(x => x.IsAdministrator).Returns(true); _expenseList.Add(new Expense { UserId = _random.Next() }); _expenseList.Add(new Expense { UserId = _currentUser.Id }); var result = _query.Get(); result.Count().Should().Be(2); } [Fact] public void GetShouldReturnAllExceptDeleted() { _expenseList.Add(new Expense { UserId = _currentUser.Id }); _expenseList.Add(new Expense { UserId = _currentUser.Id, IsDeleted = true}); var result = _query.Get(); result.Count().Should().Be(1); } [Fact] public void GetShouldReturnById() { var expense = new Expense { Id = _random.Next(), UserId = _currentUser.Id }; _expenseList.Add(expense); var result = _query.Get(expense.Id); result.Should().Be(expense); } [Fact] public void GetShouldThrowExceptionIfExpenseOfOtherUser() { var expense = new Expense { Id = _random.Next(), UserId = _random.Next() }; _expenseList.Add(expense); Action get = () => { _query.Get(expense.Id); }; get.ShouldThrow(); } [Fact] public void GetShouldThrowExceptionIfItemIsNotFoundById() { var expense = new Expense { Id = _random.Next(), UserId = _currentUser.Id }; _expenseList.Add(expense); Action get = () => { _query.Get(_random.Next()); }; get.ShouldThrow(); } [Fact] public void GetShouldThrowExceptionIfUserIsDeleted() { var expense = new Expense { Id = _random.Next(), UserId = _currentUser.Id, IsDeleted = true}; _expenseList.Add(expense); Action get = () => { _query.Get(expense.Id); }; get.ShouldThrow(); } [Fact] public async Task CreateShouldSaveNew() { var model = new CreateExpenseModel { Description = _random.Next().ToString(), Amount = _random.Next(), Comment = _random.Next().ToString(), Date = DateTime.Now }; var result = await _query.Create(model); result.Description.Should().Be(model.Description); result.Amount.Should().Be(model.Amount); result.Comment.Should().Be(model.Comment); result.Date.Should().BeCloseTo(model.Date); result.UserId.Should().Be(_currentUser.Id); _uow.Verify(x => x.Add(result)); _uow.Verify(x => x.CommitAsync()); } [Fact] public async Task UpdateShouldUpdateFields() { var user = new Expense {Id = _random.Next(), UserId = _currentUser.Id}; _expenseList.Add(user); var model = new UpdateExpenseModel { Comment = _random.Next().ToString(), Description = _random.Next().ToString(), Amount = _random.Next(), Date = DateTime.Now }; var result = await _query.Update(user.Id, model); result.Should().Be(user); result.Description.Should().Be(model.Description); result.Amount.Should().Be(model.Amount); result.Comment.Should().Be(model.Comment); result.Date.Should().BeCloseTo(model.Date); _uow.Verify(x => x.CommitAsync()); } [Fact] public void UpdateShoudlThrowExceptionIfItemIsNotFound() { Action create = () => { var result = _query.Update(_random.Next(), new UpdateExpenseModel()).Result; }; create.ShouldThrow(); } [Fact] public async Task DeleteShouldMarkAsDeleted() { var user = new Expense() { Id = _random.Next(), UserId = _currentUser.Id}; _expenseList.Add(user); await _query.Delete(user.Id); user.IsDeleted.Should().BeTrue(); _uow.Verify(x => x.CommitAsync()); } [Fact] public async Task DeleteShoudlThrowExceptionIfItemIsNotBelongTheUser() { var expense = new Expense() { Id = _random.Next(), UserId = _random.Next() }; _expenseList.Add(expense); Action execute = () => { _query.Delete(expense.Id).Wait(); }; execute.ShouldThrow(); } [Fact] public void DeleteShoudlThrowExceptionIfItemIsNotFound() { Action execute = () => { _query.Delete(_random.Next()).Wait(); }; execute.ShouldThrow(); }

Μετά την περιγραφή των δοκιμών μονάδας, περιγράφεται η εφαρμογή ενός επεξεργαστή ερωτημάτων:

public class ExpensesQueryProcessor : IExpensesQueryProcessor { private readonly IUnitOfWork _uow; private readonly ISecurityContext _securityContext; public ExpensesQueryProcessor(IUnitOfWork uow, ISecurityContext securityContext) { _uow = uow; _securityContext = securityContext; } public IQueryable Get() { var query = GetQuery(); return query; } private IQueryable GetQuery() { var q = _uow.Query() .Where(x => !x.IsDeleted); if (!_securityContext.IsAdministrator) { var userId = _securityContext.User.Id; q = q.Where(x => x.UserId == userId); } return q; } public Expense Get(int id) { var user = GetQuery().FirstOrDefault(x => x.Id == id); if (user == null) { throw new NotFoundException('Expense is not found'); } return user; } public async Task Create(CreateExpenseModel model) { var item = new Expense { UserId = _securityContext.User.Id, Amount = model.Amount, Comment = model.Comment, Date = model.Date, Description = model.Description, }; _uow.Add(item); await _uow.CommitAsync(); return item; } public async Task Update(int id, UpdateExpenseModel model) { var expense = GetQuery().FirstOrDefault(x => x.Id == id); if (expense == null) { throw new NotFoundException('Expense is not found'); } expense.Amount = model.Amount; expense.Comment = model.Comment; expense.Description = model.Description; expense.Date = model.Date; await _uow.CommitAsync(); return expense; } public async Task Delete(int id) { var user = GetQuery().FirstOrDefault(u => u.Id == id); if (user == null) { throw new NotFoundException('Expense is not found'); } if (user.IsDeleted) return; user.IsDeleted = true; await _uow.CommitAsync(); } }

Μόλις η επιχειρησιακή λογική είναι έτοιμη, αρχίζω να γράφω τις δοκιμές ενοποίησης API για να προσδιορίσω τη σύμβαση API.

Το πρώτο βήμα είναι η προετοιμασία ενός έργου Expenses.Api.IntegrationTests

  1. Εγκατάσταση πακέτων nuget:
    • FluentAssertions
    • Moq
    • Microsoft.AspNetCore.TestHost
  2. Δημιουργήστε μια δομή έργου Δομή φακέλου δαπανών
  3. Δημιουργώ ένα Συλλογή με τη βοήθεια του οποίου καθορίζουμε τον πόρο που θα δημιουργηθεί στην αρχή κάθε δοκιμαστικής εκτέλεσης και θα καταστραφεί στο τέλος κάθε δοκιμαστικής εκτέλεσης.
[CollectionDefinition('ApiCollection')] public class DbCollection : ICollectionFixture { } ~~~ And define our test server and the client to it with the already authenticated user by default:

δημόσια κλάση ApiServer: IDisposable {public const string Username = 'admin'; public const string Κωδικός πρόσβασης = 'admin';

private IConfigurationRoot _config; public ApiServer() { _config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile('appsettings.json') .Build(); Server = new TestServer(new WebHostBuilder().UseStartup()); Client = GetAuthenticatedClient(Username, Password); } public HttpClient GetAuthenticatedClient(string username, string password) { var client = Server.CreateClient(); var response = client.PostAsync('/api/Login/Authenticate', new JsonContent(new LoginModel {Password = password, Username = username})).Result; response.EnsureSuccessStatusCode(); var data = JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result); client.DefaultRequestHeaders.Add('Authorization', 'Bearer ' + data.Token); return client; } public HttpClient Client { get; private set; } public TestServer Server { get; private set; } public void Dispose() { if (Client != null) { Client.Dispose(); Client = null; } if (Server != null) { Server.Dispose(); Server = null; } } } ~~~

Για την ευκολία εργασίας με HTTP αιτήματα σε δοκιμές ενσωμάτωσης, έγραψα έναν βοηθό:

public class HttpClientWrapper { private readonly HttpClient _client; public HttpClientWrapper(HttpClient client) { _client = client; } public HttpClient Client => _client; public async Task PostAsync(string url, object body) { var response = await _client.PostAsync(url, new JsonContent(body)); response.EnsureSuccessStatusCode(); var respnoseText = await response.Content.ReadAsStringAsync(); var data = JsonConvert.DeserializeObject(respnoseText); return data; } public async Task PostAsync(string url, object body) { var response = await _client.PostAsync(url, new JsonContent(body)); response.EnsureSuccessStatusCode(); } public async Task PutAsync(string url, object body) { var response = await _client.PutAsync(url, new JsonContent(body)); response.EnsureSuccessStatusCode(); var respnoseText = await response.Content.ReadAsStringAsync(); var data = JsonConvert.DeserializeObject(respnoseText); return data; } }

Σε αυτό το στάδιο, πρέπει να ορίσω ένα συμβόλαιο REST API για κάθε οντότητα, θα το γράψω για έξοδα API REST:

Διεύθυνση URL Μέθοδος Σωματότυπος Τύπος αποτελεσμάτων Περιγραφή
Δαπάνη ΠΑΙΡΝΩ - Αποτέλεσμα δεδομένων Λάβετε όλα τα έξοδα με πιθανή χρήση φίλτρων και ταξινομητών σε μια παράμετρο ερωτήματος «εντολές»
Έξοδα / {id} ΠΑΙΡΝΩ - Μοντέλο εξόδων Λάβετε έξοδα ανά αναγνωριστικό
Εξοδα ΘΕΣΗ Δημιουργία ΜοντέλουExpense Μοντέλο εξόδων Δημιουργήστε νέα εγγραφή εξόδων
Έξοδα / {id} ΒΑΖΩ ΕνημέρωσηExpenseModel Μοντέλο εξόδων Ενημέρωση υπάρχοντος κόστους

Όταν ζητάτε μια λίστα με τα κόστη, μπορείτε να εφαρμόσετε διάφορες εντολές φιλτραρίσματος και ταξινόμησης χρησιμοποιώντας το Βιβλιοθήκη AutoQueryable . Ένα παράδειγμα ερωτήματος με φιλτράρισμα και ταξινόμηση:

/expenses?commands=take=25%26amount%3E=12%26orderbydesc=date

Η τιμή της παραμέτρου εντολών αποκωδικοποίησης είναι take=25&amount>=12&orderbydesc=date. Έτσι μπορούμε να βρούμε σελιδοποίηση, φιλτράρισμα και ταξινόμηση τμημάτων στο ερώτημα. Όλες οι επιλογές ερωτήματος είναι πολύ παρόμοιες με τη σύνταξη OData, αλλά δυστυχώς, το OData δεν είναι ακόμη έτοιμο για το .NET Core, οπότε χρησιμοποιώ μια άλλη χρήσιμη βιβλιοθήκη.

λάβετε στοιχείο ανά κατηγορία angularjs

Στο κάτω μέρος εμφανίζονται όλα τα μοντέλα που χρησιμοποιούνται σε αυτό το API:

public class DataResult { public T[] Data { get; set; } public int Total { get; set; } } public class ExpenseModel { public int Id { get; set; } public DateTime Date { get; set; } public string Description { get; set; } public decimal Amount { get; set; } public string Comment { get; set; } public int UserId { get; set; } public string Username { get; set; } } public class CreateExpenseModel { [Required] public DateTime Date { get; set; } [Required] public string Description { get; set; } [Required] [Range(0.01, int.MaxValue)] public decimal Amount { get; set; } [Required] public string Comment { get; set; } } public class UpdateExpenseModel { [Required] public DateTime Date { get; set; } [Required] public string Description { get; set; } [Required] [Range(0.01, int.MaxValue)] public decimal Amount { get; set; } [Required] public string Comment { get; set; } }

Μοντέλα CreateExpenseModel και UpdateExpenseModel Χρησιμοποιήστε χαρακτηριστικά σχολιασμού δεδομένων για να πραγματοποιήσετε απλούς ελέγχους σε επίπεδο REST API μέσω χαρακτηριστικών.

Στη συνέχεια, για κάθε HTTP μέθοδος, ένας ξεχωριστός φάκελος δημιουργείται στο έργο και τα αρχεία σε αυτό δημιουργούνται με βάση για κάθε HTTP μέθοδο που υποστηρίζεται από τον πόρο:

Κονσόλα διαχειριστή πακέτων

Υλοποίηση του τεστ ενοποίησης για τη λήψη καταλόγου δαπανών:

[Collection('ApiCollection')] public class GetListShould { private readonly ApiServer _server; private readonly HttpClient _client; public GetListShould(ApiServer server) { _server = server; _client = server.Client; } public static async Task Get(HttpClient client) { var response = await client.GetAsync($'api/Expenses'); response.EnsureSuccessStatusCode(); var responseText = await response.Content.ReadAsStringAsync(); var items = JsonConvert.DeserializeObject(responseText); return items; } [Fact] public async Task ReturnAnyList() { var items = await Get(_client); items.Should().NotBeNull(); } }

Υλοποίηση του τεστ ενοποίησης για τη λήψη των δεδομένων δαπανών ανά αναγνωριστικό

[Collection('ApiCollection')] public class GetItemShould { private readonly ApiServer _server; private readonly HttpClient _client; private Random _random; public GetItemShould(ApiServer server) { _server = server; _client = _server.Client; _random = new Random(); } [Fact] public async Task ReturnItemById() { var item = await new PostShould(_server).CreateNew(); var result = await GetById(_client, item.Id); result.Should().NotBeNull(); } public static async Task GetById(HttpClient client, int id) { var response = await client.GetAsync(new Uri($'api/Expenses/{id}', UriKind.Relative)); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(result); } [Fact] public async Task ShouldReturn404StatusIfNotFound() { var response = await _client.GetAsync(new Uri($'api/Expenses/-1', UriKind.Relative)); response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.NotFound); } }

Υλοποίηση του τεστ ενοποίησης για τη δημιουργία εξόδων:

[Collection('ApiCollection')] public class PostShould { private readonly ApiServer _server; private readonly HttpClientWrapper _client; private Random _random; public PostShould(ApiServer server) { _server = server; _client = new HttpClientWrapper(_server.Client); _random = new Random(); } [Fact] public async Task CreateNew() { var requestItem = new CreateExpenseModel() { Amount = _random.Next(), Comment = _random.Next().ToString(), Date = DateTime.Now.AddMinutes(-15), Description = _random.Next().ToString() }; var createdItem = await _client.PostAsync('api/Expenses', requestItem); createdItem.Id.Should().BeGreaterThan(0); createdItem.Amount.Should().Be(requestItem.Amount); createdItem.Comment.Should().Be(requestItem.Comment); createdItem.Date.Should().Be(requestItem.Date); createdItem.Description.Should().Be(requestItem.Description); createdItem.Username.Should().Be('admin admin'); return createdItem; } }

Εφαρμογή του τεστ ενοποίησης για αλλαγή δαπανών:

[Collection('ApiCollection')] public class PutShould { private readonly ApiServer _server; private readonly HttpClientWrapper _client; private readonly Random _random; public PutShould(ApiServer server) { _server = server; _client = new HttpClientWrapper(_server.Client); _random = new Random(); } [Fact] public async Task UpdateExistingItem() { var item = await new PostShould(_server).CreateNew(); var requestItem = new UpdateExpenseModel { Date = DateTime.Now, Description = _random.Next().ToString(), Amount = _random.Next(), Comment = _random.Next().ToString() }; await _client.PutAsync($'api/Expenses/{item.Id}', requestItem); var updatedItem = await GetItemShould.GetById(_client.Client, item.Id); updatedItem.Date.Should().Be(requestItem.Date); updatedItem.Description.Should().Be(requestItem.Description); updatedItem.Amount.Should().Be(requestItem.Amount); updatedItem.Comment.Should().Contain(requestItem.Comment); } }

Υλοποίηση του τεστ ενοποίησης για την αφαίρεση εξόδων:

[Collection('ApiCollection')] public class DeleteShould { private readonly ApiServer _server; private readonly HttpClient _client; public DeleteShould(ApiServer server) { _server = server; _client = server.Client; } [Fact] public async Task DeleteExistingItem() { var item = await new PostShould(_server).CreateNew(); var response = await _client.DeleteAsync(new Uri($'api/Expenses/{item.Id}', UriKind.Relative)); response.EnsureSuccessStatusCode(); } }

Σε αυτό το σημείο, έχουμε ορίσει πλήρως το συμβόλαιο REST API και τώρα μπορώ να αρχίσω να το εφαρμόζω βάσει του ASP.NET Core.

Εφαρμογή API

Προετοιμάστε το Έργο Έξοδα. Για αυτό, πρέπει να εγκαταστήσω τις ακόλουθες βιβλιοθήκες:

  • Αυτόματος χάρτης
  • AutoQueryable.AspNetCore.Filter
  • Microsoft.ApplicationInsights.AspNetCore
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.SqlServer.Design
  • Microsoft.EntityFrameworkCore.Tools
  • Swashbuckle.AspNetCore

Μετά από αυτό, πρέπει να ξεκινήσετε τη δημιουργία της αρχικής μετεγκατάστασης για τη βάση δεδομένων ανοίγοντας το Package Manager Console, μεταβαίνοντας στο Expenses.Data.Access έργο (επειδή το πλαίσιο EF βρίσκεται εκεί) και εκτελεί το Add-Migration InitialCreate εντολή:

Τεκμηρίωση API

Στο επόμενο βήμα, προετοιμάστε το αρχείο διαμόρφωσης appsettings.json εκ των προτέρων, το οποίο μετά την προετοιμασία θα πρέπει ακόμη να αντιγραφεί στο έργο Expenses.Api.IntegrationTests γιατί από εκεί, θα τρέξουμε το δοκιμαστικό API παρουσίας.

{ 'Logging': { 'IncludeScopes': false, 'LogLevel': { 'Default': 'Debug', 'System': 'Information', 'Microsoft': 'Information' } }, 'Data': { 'main': 'Data Source=.; Initial Catalog=expenses.main; Integrated Security=true; Max Pool Size=1000; Min Pool Size=12; Pooling=True;' }, 'ApplicationInsights': { 'InstrumentationKey': 'Your ApplicationInsights key' } }

Η ενότητα καταγραφής δημιουργείται αυτόματα. Πρόσθεσα το Data ενότητα για να αποθηκεύσετε τη συμβολοσειρά σύνδεσης στη βάση δεδομένων και το ApplicationInsights κλειδί.

Διαμόρφωση εφαρμογής

Πρέπει να διαμορφώσετε διαφορετικές υπηρεσίες διαθέσιμες στην εφαρμογή μας:

Ενεργοποίηση του ApplicationInsights: services.AddApplicationInsightsTelemetry(Configuration);

Καταχωρήστε τις υπηρεσίες σας μέσω κλήσης: ContainerSetup.Setup(services, Configuration);

ContainerSetup είναι μια τάξη που δημιουργήθηκε, οπότε δεν χρειάζεται να αποθηκεύουμε όλες τις εγγραφές υπηρεσιών στο Startup τάξη. Η τάξη βρίσκεται στο φάκελο IoC του έργου Έξοδα:

public static class ContainerSetup { public static void Setup(IServiceCollection services, IConfigurationRoot configuration) { AddUow(services, configuration); AddQueries(services); ConfigureAutoMapper(services); ConfigureAuth(services); } private static void ConfigureAuth(IServiceCollection services) { services.AddSingleton(); services.AddScoped(); services.AddScoped(); } private static void ConfigureAutoMapper(IServiceCollection services) { var mapperConfig = AutoMapperConfigurator.Configure(); var mapper = mapperConfig.CreateMapper(); services.AddSingleton(x => mapper); services.AddTransient(); } private static void AddUow(IServiceCollection services, IConfigurationRoot configuration) { var connectionString = configuration['Data:main']; services.AddEntityFrameworkSqlServer(); services.AddDbContext(options => options.UseSqlServer(connectionString)); services.AddScoped(ctx => new EFUnitOfWork(ctx.GetRequiredService())); services.AddScoped(); services.AddScoped(); } private static void AddQueries(IServiceCollection services) { var exampleProcessorType = typeof(UsersQueryProcessor); var types = (from t in exampleProcessorType.GetTypeInfo().Assembly.GetTypes() where t.Namespace == exampleProcessorType.Namespace && t.GetTypeInfo().IsClass && t.GetTypeInfo().GetCustomAttribute() == null select t).ToArray(); foreach (var type in types) { var interfaceQ = type.GetTypeInfo().GetInterfaces().First(); services.AddScoped(interfaceQ, type); } } }

Σχεδόν όλοι οι κώδικες σε αυτήν την τάξη μιλούν από μόνες τους, αλλά θα ήθελα να μπω στο ConfigureAutoMapper μέθοδος λίγο περισσότερο.

private static void ConfigureAutoMapper(IServiceCollection services) { var mapperConfig = AutoMapperConfigurator.Configure(); var mapper = mapperConfig.CreateMapper(); services.AddSingleton(x => mapper); services.AddTransient(); }

Αυτή η μέθοδος χρησιμοποιεί την τάξη βοηθού για να βρει όλες τις αντιστοιχίσεις μεταξύ μοντέλων και οντοτήτων και το αντίστροφο και παίρνει το IMapper διεπαφή για να δημιουργήσετε το IAutoMapper περιτύλιγμα που θα χρησιμοποιηθεί σε ελεγκτές. Δεν υπάρχει τίποτα το ιδιαίτερο για αυτό το περιτύλιγμα - παρέχει απλώς μια βολική διεπαφή στο AutoMapper μεθόδους.

public class AutoMapperAdapter : IAutoMapper { private readonly IMapper _mapper; public AutoMapperAdapter(IMapper mapper) { _mapper = mapper; } public IConfigurationProvider Configuration => _mapper.ConfigurationProvider; public T Map(object objectToMap) { return _mapper.Map(objectToMap); } public TResult[] Map(IEnumerable sourceQuery) { return sourceQuery.Select(x => _mapper.Map(x)).ToArray(); } public IQueryable Map(IQueryable sourceQuery) { return sourceQuery.ProjectTo(_mapper.ConfigurationProvider); } public void Map(TSource source, TDestination destination) { _mapper.Map(source, destination); } }

Για να ρυθμίσετε το AutoMapper, χρησιμοποιείται η βοηθητική κλάση, η αποστολή της οποίας είναι η αναζήτηση αντιστοιχίσεων για συγκεκριμένες κλάσεις χώρου ονομάτων. Όλες οι αντιστοιχίσεις βρίσκονται στο φάκελο Έξοδα / Χάρτες:

llc vs s corp vs c corp
public static class AutoMapperConfigurator { private static readonly object Lock = new object(); private static MapperConfiguration _configuration; public static MapperConfiguration Configure() { lock (Lock) { if (_configuration != null) return _configuration; var thisType = typeof(AutoMapperConfigurator); var configInterfaceType = typeof(IAutoMapperTypeConfigurator); var configurators = thisType.GetTypeInfo().Assembly.GetTypes() .Where(x => !string.IsNullOrWhiteSpace(x.Namespace)) // ReSharper disable once AssignNullToNotNullAttribute .Where(x => x.Namespace.Contains(thisType.Namespace)) .Where(x => x.GetTypeInfo().GetInterface(configInterfaceType.Name) != null) .Select(x => (IAutoMapperTypeConfigurator)Activator.CreateInstance(x)) .ToArray(); void AggregatedConfigurator(IMapperConfigurationExpression config) { foreach (var configurator in configurators) { configurator.Configure(config); } } _configuration = new MapperConfiguration(AggregatedConfigurator); return _configuration; } } }

Όλες οι αντιστοιχίσεις πρέπει να εφαρμόζουν μια συγκεκριμένη διεπαφή:

public interface IAutoMapperTypeConfigurator { void Configure(IMapperConfigurationExpression configuration); }

Ένα παράδειγμα χαρτογράφησης από οντότητα σε μοντέλο:

public class ExpenseMap : IAutoMapperTypeConfigurator { public void Configure(IMapperConfigurationExpression configuration) { var map = configuration.CreateMap(); map.ForMember(x => x.Username, x => x.MapFrom(y => y.User.FirstName + ' ' + y.User.LastName)); } }

Επίσης, στο Startup.ConfigureServices μέθοδος, ο έλεγχος ταυτότητας μέσω διακριτικών JWT Bearer έχει ρυθμιστεί:

services.AddAuthorization(auth => { auth.AddPolicy('Bearer', new AuthorizationPolicyBuilder() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser().Build()); });

Και οι υπηρεσίες κατέγραψαν την εφαρμογή ISecurityContext, η οποία θα χρησιμοποιηθεί για τον προσδιορισμό του τρέχοντος χρήστη:

public class SecurityContext : ISecurityContext { private readonly IHttpContextAccessor _contextAccessor; private readonly IUnitOfWork _uow; private User _user; public SecurityContext(IHttpContextAccessor contextAccessor, IUnitOfWork uow) { _contextAccessor = contextAccessor; _uow = uow; } public User User { get { if (_user != null) return _user; var username = _contextAccessor.HttpContext.User.Identity.Name; _user = _uow.Query() .Where(x => x.Username == username) .Include(x => x.Roles) .ThenInclude(x => x.Role) .FirstOrDefault(); if (_user == null) { throw new UnauthorizedAccessException('User is not found'); } return _user; } } public bool IsAdministrator { get { return User.Roles.Any(x => x.Role.Name == Roles.Administrator); } } }

Επίσης, αλλάξαμε λίγο την προεπιλεγμένη καταχώριση MVC για να χρησιμοποιήσουμε ένα προσαρμοσμένο φίλτρο σφάλματος για να μετατρέψουμε εξαιρέσεις στους σωστούς κωδικούς σφάλματος:

services.AddMvc(options => { options.Filters.Add(new ApiExceptionFilter()); });

Υλοποίηση του ApiExceptionFilter φίλτρο:

public class ApiExceptionFilter : ExceptionFilterAttribute { public override void OnException(ExceptionContext context) { if (context.Exception is NotFoundException) { // handle explicit 'known' API errors var ex = context.Exception as NotFoundException; context.Exception = null; context.Result = new JsonResult(ex.Message); context.HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; } else if (context.Exception is BadRequestException) { // handle explicit 'known' API errors var ex = context.Exception as BadRequestException; context.Exception = null; context.Result = new JsonResult(ex.Message); context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; } else if (context.Exception is UnauthorizedAccessException) { context.Result = new JsonResult(context.Exception.Message); context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; } else if (context.Exception is ForbiddenException) { context.Result = new JsonResult(context.Exception.Message); context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } base.OnException(context); } }

Είναι σημαντικό να μην ξεχνάτε Swagger, για να λάβετε μια εξαιρετική περιγραφή API για άλλους https://www.toptal.com/api :

services.AddSwaggerGen(c => { c.SwaggerDoc('v1', new Info {Title = 'Expenses', Version = 'v1'}); c.OperationFilter(); });

Το Startup.Configure Η μέθοδος προσθέτει μια κλήση στο InitDatabase μέθοδο, η οποία μετεγκαθιστά αυτόματα τη βάση δεδομένων μέχρι την τελευταία μετεγκατάσταση:

private void InitDatabase(IApplicationBuilder app) { using (var serviceScope = app.ApplicationServices.GetRequiredService().CreateScope()) { var context = serviceScope.ServiceProvider.GetService(); context.Database.Migrate(); } }

Swagger είναι ενεργοποιημένο μόνο εάν η εφαρμογή εκτελείται στο περιβάλλον ανάπτυξης και δεν απαιτεί έλεγχο ταυτότητας για πρόσβαση σε αυτήν:

app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint('/swagger/v1/swagger.json', 'My API V1'); });

Στη συνέχεια, συνδέουμε τον έλεγχο ταυτότητας (μπορείτε να βρείτε λεπτομέρειες στο αποθετήριο):

ConfigureAuthentication(app);

Σε αυτό το σημείο, μπορείτε να εκτελέσετε δοκιμές ενοποίησης και να βεβαιωθείτε ότι όλα έχουν συνταχθεί, αλλά τίποτα δεν λειτουργεί και μεταβείτε στον ελεγκτή ExpensesController.

Σημείωση: Όλοι οι ελεγκτές βρίσκονται στο φάκελο 'Έξοδα / Διακομιστής' και χωρίζονται υπό όρους σε δύο φακέλους: Ελεγκτές και RestApi. Στο φάκελο, οι ελεγκτές είναι ελεγκτές που λειτουργούν ως ελεγκτές στο παλιό καλό MVC - δηλαδή, επιστρέφουν τη σήμανση και στο RestApi, REST ελεγκτές.

Πρέπει να δημιουργήσετε την κατηγορία Expenses / Server / RestApi / ExpensesController και να την κληρονομήσετε από την κλάση Controller:

public class ExpensesController : Controller { }

Στη συνέχεια, ρυθμίστε τη δρομολόγηση του ~ / api / Expenses πληκτρολογήστε επισημαίνοντας την κλάση με το χαρακτηριστικό [Route ('api / [controller]')].

Για να αποκτήσετε πρόσβαση στη λογική της επιχείρησης και στο χαρτογράφο, πρέπει να κάνετε τις ακόλουθες υπηρεσίες:

private readonly IExpensesQueryProcessor _query; private readonly IAutoMapper _mapper; public ExpensesController(IExpensesQueryProcessor query, IAutoMapper mapper) { _query = query; _mapper = mapper; }

Σε αυτό το στάδιο, μπορείτε να ξεκινήσετε την εφαρμογή μεθόδων. Η πρώτη μέθοδος είναι να αποκτήσετε μια λίστα δαπανών:

[HttpGet] [QueryableResult] public IQueryable Get() { var result = _query.Get(); var models = _mapper.Map(result); return models; }

Η εφαρμογή της μεθόδου είναι πολύ απλή, παίρνουμε ένα ερώτημα στη βάση δεδομένων που έχει χαρτογραφηθεί στο IQueryable από ExpensesQueryProcessor, το οποίο με τη σειρά του επιστρέφει ως αποτέλεσμα.

Το προσαρμοσμένο χαρακτηριστικό εδώ είναι QueryableResult, το οποίο χρησιμοποιεί το AutoQueryable βιβλιοθήκη για χειρισμό σελιδοποίησης, φιλτραρίσματος και ταξινόμησης από την πλευρά του διακομιστή. Το χαρακτηριστικό βρίσκεται στο φάκελο Expenses/Filters. Ως αποτέλεσμα, αυτό το φίλτρο επιστρέφει δεδομένα τύπου DataResult στον πελάτη API.

public class QueryableResult : ActionFilterAttribute { public override void OnActionExecuted(ActionExecutedContext context) { if (context.Exception != null) return; dynamic query = ((ObjectResult)context.Result).Value; if (query == null) throw new Exception('Unable to retreive value of IQueryable from context result.'); Type entityType = query.GetType().GenericTypeArguments[0]; var commands = context.HttpContext.Request.Query.ContainsKey('commands') ? context.HttpContext.Request.Query['commands'] : new StringValues(); var data = QueryableHelper.GetAutoQuery(commands, entityType, query, new AutoQueryableProfile {UnselectableProperties = new string[0]}); var total = System.Linq.Queryable.Count(query); context.Result = new OkObjectResult(new DataResult{Data = data, Total = total}); } }

Ας δούμε επίσης την εφαρμογή της μεθόδου Post, δημιουργώντας μια ροή:

[HttpPost] [ValidateModel] public async Task Post([FromBody]CreateExpenseModel requestModel) { var item = await _query.Create(requestModel); var model = _mapper.Map(item); return model; }

Εδώ, πρέπει να δώσετε προσοχή στο χαρακτηριστικό ValidateModel, το οποίο εκτελεί απλή επικύρωση των δεδομένων εισαγωγής σύμφωνα με τα χαρακτηριστικά σχολιασμού δεδομένων και αυτό γίνεται μέσω των ενσωματωμένων ελέγχων MVC.

public class ValidateModelAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { if (!context.ModelState.IsValid) { context.Result = new BadRequestObjectResult(context.ModelState); } } }

Πλήρης κωδικός ExpensesController:

[Route('api/[controller]')] public class ExpensesController : Controller { private readonly IExpensesQueryProcessor _query; private readonly IAutoMapper _mapper; public ExpensesController(IExpensesQueryProcessor query, IAutoMapper mapper) { _query = query; _mapper = mapper; } [HttpGet] [QueryableResult] public IQueryable Get() { var result = _query.Get(); var models = _mapper.Map(result); return models; } [HttpGet('{id}')] public ExpenseModel Get(int id) { var item = _query.Get(id); var model = _mapper.Map(item); return model; } [HttpPost] [ValidateModel] public async Task Post([FromBody]CreateExpenseModel requestModel) { var item = await _query.Create(requestModel); var model = _mapper.Map(item); return model; } [HttpPut('{id}')] [ValidateModel] public async Task Put(int id, [FromBody]UpdateExpenseModel requestModel) { var item = await _query.Update(id, requestModel); var model = _mapper.Map(item); return model; } [HttpDelete('{id}')] public async Task Delete(int id) { await _query.Delete(id); } }

συμπέρασμα

Θα ξεκινήσω με προβλήματα: Το κύριο πρόβλημα είναι η πολυπλοκότητα της αρχικής διαμόρφωσης της λύσης και η κατανόηση των επιπέδων της εφαρμογής, αλλά με την αυξανόμενη πολυπλοκότητα της εφαρμογής, η πολυπλοκότητα του συστήματος είναι σχεδόν αμετάβλητη, κάτι που είναι μεγάλο συν όταν συνοδεύει ένα τέτοιο σύστημα. Και είναι πολύ σημαντικό να έχουμε ένα API για το οποίο υπάρχει ένα σύνολο δοκιμών ενοποίησης και ένα πλήρες σύνολο δοκιμών μονάδων για επιχειρηματική λογική. Η επιχειρησιακή λογική διαχωρίζεται εντελώς από την τεχνολογία διακομιστή που χρησιμοποιείται και μπορεί να δοκιμαστεί πλήρως. Αυτή η λύση είναι κατάλληλη για συστήματα με πολύπλοκο API και πολύπλοκη επιχειρηματική λογική.

Αν θέλετε να δημιουργήσετε μια γωνιακή εφαρμογή που καταναλώνει το API σας, ρίξτε μια ματιά Γωνιακός πυρήνας 5 και ASP.NET από τους συναδέλφους ApeeScapeer Pablo Albella.

Κατανόηση των βασικών

Τι είναι ένα αντικείμενο μεταφοράς δεδομένων;

Ένα αντικείμενο μεταφοράς δεδομένων (DTO) είναι μια αναπαράσταση ενός ή περισσότερων αντικειμένων σε μια βάση δεδομένων. Μια μεμονωμένη οντότητα βάσης δεδομένων μπορεί να αναπαρασταθεί με ή χωρίς αριθμό DTO

Τι είναι το API Ιστού;

Ένα διαδικτυακό API παρέχει μια διεπαφή στην επιχειρησιακή λογική ενός συστήματος πρόσβασης στη βάση δεδομένων και η υποκείμενη λογική ενσωματώνεται στο API.

Τι είναι το REST API;

Η πραγματική διεπαφή μέσω της οποίας οι πελάτες μπορούν να εργαστούν με ένα Web API. Λειτουργεί μόνο μέσω πρωτοκόλλου HTTP.

Τι είναι η δοκιμή μονάδας;

Ο έλεγχος μονάδας είναι ένα σύνολο μικρών, ειδικών, πολύ γρήγορων δοκιμών που καλύπτουν μια μικρή μονάδα κώδικα, π.χ. τάξεις. Σε αντίθεση με τις δοκιμές ενοποίησης, η δοκιμή μονάδας διασφαλίζει ότι όλες οι πτυχές της μονάδας δοκιμάζονται μεμονωμένα από άλλα στοιχεία της συνολικής εφαρμογής.

Τι είναι η δοκιμή ενοποίησης API Ιστού;

Ο έλεγχος ενοποίησης είναι ένα σύνολο δοκιμών σε ένα συγκεκριμένο τελικό σημείο API. Σε αντίθεση με τον έλεγχο μονάδας, η δοκιμή ενοποίησης ελέγχει ότι όλες οι μονάδες κώδικα που τροφοδοτούν το API λειτουργούν όπως αναμενόταν. Αυτές οι δοκιμές μπορεί να είναι πιο αργές από τις δοκιμές μονάδας.

Τι είναι το ASP.NET Core;

Το ASP.NET Core είναι μια επανεγγραφή και η επόμενη γενιά του ASP.NET 4.x. Είναι πολλαπλής πλατφόρμας και συμβατή με κοντέινερ Windows, Linux και Docker.

s corp vs c corp vs partnership

Τι είναι το διακριτικό JWT Bearer;

Ένα διακριτικό JWT (JSON Web Token) είναι ένα ανώνυμο και υπογεγραμμένο αντικείμενο JSON που χρησιμοποιείται ευρέως σε σύγχρονες εφαρμογές Web & Mobile για την παροχή πρόσβασης σε ένα API. Αυτά τα διακριτικά περιέχουν τις δικές τους αξιώσεις και γίνονται αποδεκτά εφόσον η υπογραφή είναι έγκυρη.

Τι είναι το Swagger;

Το Swagger είναι ένα έγγραφο που χρησιμοποιείται στη βιβλιοθήκη ένα REST API. Η ίδια η τεκμηρίωση μπορεί επίσης να χρησιμοποιηθεί για τη δημιουργία ενός πελάτη για το API για διαφορετικές πλατφόρμες, αυτόματα.

Όλα όσα πρέπει να ξέρετε για το CVS-Aetna Merger

Διαδικασίες Χρηματοδότησης

Όλα όσα πρέπει να ξέρετε για το CVS-Aetna Merger
Πώς να τελειοποιήσετε και να εκμεταλλευτείτε τα απομακρυσμένα εργαστήρια UX

Πώς να τελειοποιήσετε και να εκμεταλλευτείτε τα απομακρυσμένα εργαστήρια UX

Διαδικασία Σχεδιασμού

Δημοφιλείς Αναρτήσεις
Οι αρχές του σχεδιασμού και η σημασία τους
Οι αρχές του σχεδιασμού και η σημασία τους
Πώς να σχεδιάσετε εξαιρετικές εμπειρίες για το Διαδίκτυο των πραγμάτων
Πώς να σχεδιάσετε εξαιρετικές εμπειρίες για το Διαδίκτυο των πραγμάτων
Διαχείριση προϊόντων που ενισχύεται από την επιχειρηματική νοοτροπία
Διαχείριση προϊόντων που ενισχύεται από την επιχειρηματική νοοτροπία
Ενθάρρυνση ευκαιριών και δράσης σε απομακρυσμένο χώρο εργασίας
Ενθάρρυνση ευκαιριών και δράσης σε απομακρυσμένο χώρο εργασίας
Αρχές Σχεδιασμού - Εισαγωγή στην Οπτική Ιεραρχία
Αρχές Σχεδιασμού - Εισαγωγή στην Οπτική Ιεραρχία
 
Γιατί πρέπει να κάνετε αναβάθμιση σε Java 8 ήδη
Γιατί πρέπει να κάνετε αναβάθμιση σε Java 8 ήδη
Διαχείριση εμποδίων διαπολιτισμικής επικοινωνίας
Διαχείριση εμποδίων διαπολιτισμικής επικοινωνίας
Εκμάθηση ροής Apache Spark: Αναγνώριση τάσεων Twitter Trending Hashtags
Εκμάθηση ροής Apache Spark: Αναγνώριση τάσεων Twitter Trending Hashtags
Μείωση του κόστους σε ένα ψηφιακό μέλλον πετρελαίου και φυσικού αερίου
Μείωση του κόστους σε ένα ψηφιακό μέλλον πετρελαίου και φυσικού αερίου
Κατανόηση των συστημάτων και προτύπων σχεδιασμού
Κατανόηση των συστημάτων και προτύπων σχεδιασμού
Δημοφιλείς Αναρτήσεις
  • πώς να ξεκινήσετε με το bootstrap
  • τι είναι aws πιστοποιημένος αρχιτέκτονας λύσεων
  • llc c corporation εναντίον s corporation
  • hack αναπλήρωσης πιστωτικής κάρτας
  • τι είναι το μοντέλο αντικειμένου σελίδας στο σελήνιο
  • τι είναι ένας κόμβος Javascript
Κατηγορίες
  • Άνοδος Του Απομακρυσμένου
  • Τεχνολογία
  • Τάσεις
  • Το Μέλλον Της Εργασίας
  • © 2022 | Ολα Τα Δικαιώματα Διατηρούνται

    portaldacalheta.pt