简体   繁体   English

为什么来自 Angular 9 服务的 HTTP PUT 方法创建一个新的数据库实体而不是更新原始数据库实体?

[英]Why does HTTP PUT method from Angular 9 service CREATE a new DB entity instead of UPDATING the original?

I am new to Angular SPA and MVC in general, and I am currently working in Visual Studio 2017 on a MVC Core 2.2 Web Application project with EF Core ORM (the Model) and an Angular 9 front-end.我一般是 Angular SPA 和 MVC 的新手,我目前在 Visual Studio 2017 中使用 EF Core ORM(模型)和 Angular 9 前端在 MVC Core 2.2 Web 应用程序项目上工作。 The the purpose of this project is to learn Angular/AspNetCore MVC/EF Core to replace a .NET 4.5 Winforms application that uses WCF and EF 6. The source code examples I'm working with are from three different books by the same author, and I am trying to learn how to put all the pieces together.该项目的目的是学习 Angular/AspNetCore MVC/EF Core 以替换使用 WCF 和 EF 6 的 .NET 4.5 Winforms 应用程序。我正在使用的源代码示例来自同一作者的三本不同的书籍,我正在努力学习如何将所有部分组合在一起。

My test application has an MVC API Controller that receives HTTP requests from my Angular service.我的测试应用程序有一个 MVC API 控制器,它接收来自我的 Angular 服务的 HTTP 请求。 Using my Angular Template, I can GET one or all items from my local MSSQL database, and I can CREATE and DELETE items with no problems.使用我的 Angular 模板,我可以从我的本地 MSSQL 数据库中获取一个或所有项目,并且我可以毫无问题地创建和删除项目。 But, when I try to UPDATE an existing item, my result is the creation of another item with the updated data, and the original item still exists with the old data.但是,当我尝试更新现有项目时,结果是使用更新的数据创建了另一个项目,而原始项目仍然与旧数据一起存在。

When I Edit ID 2010 and change the name and price, I get a newly created Chess item - instead of an updated #2010.当我编辑 ID 2010 并更改名称和价格时,我得到一个新创建的国际象棋项目 - 而不是更新的 #2010。

角度模板 1

Here's my Angular service:这是我的 Angular 服务:

(other imports above)
import { Product } from "./product.model";

export const REST_URL = new InjectionToken("rest_url");

@Injectable()
export class RestDataSource {

  constructor(private http: HttpClient, @Inject(REST_URL) private url: string) { }

  getData(): Observable<Product[]> {
        return this.sendRequest<Product[]>("GET", this.url);
  }

  saveProduct(product: Product): Observable<Product> {
        return this.sendRequest<Product>("POST", this.url, product);
  }

  updateProduct(product: Product): Observable<Product> {
    return this.sendRequest<Product>("PUT", this.url, product);
  }

  deleteProduct(id: number): Observable<Product> {
    return this.sendRequest<Product>("DELETE", `${this.url}/${id}`);
  }

  private sendRequest<T>(verb: string, url: string, body?: Product)
    : Observable<T> {
    return this.http.request<T>(verb, url, {
          body: body,
          headers: new HttpHeaders({
            "Access-Key": "<secret>",
            "Application-Name": "exampleApp"
            })
        });
    }
  }

Here is my Angular model feature module:这是我的 Angular 模型功能模块:

 import { NgModule } from "@angular/core";
 import { Model } from "./repository.model";
 import { HttpClientModule, HttpClientJsonpModule } from "@angular/common/http";
 import { RestDataSource, REST_URL } from "./rest.datasource";

 @NgModule({
   imports: [HttpClientModule, HttpClientJsonpModule],
   providers: [Model, RestDataSource,
       { provide: REST_URL, useValue: `http://${location.hostname}:51194/api/products` }]
})
export class ModelModule { }

Here is my Angular 9 model.repository.ts:这是我的 Angular 9 model.repository.ts:

  import { Injectable } from "@angular/core";
  import { Product } from "./product.model";
  import { Observable } from "rxjs";
  import { RestDataSource } from "./rest.datasource";
  
  @Injectable()
  export class Model {
    private products: Product[] = new Array<Product>();
    private locator = (p: Product, id: number) => p.id == id;
  
      constructor(private dataSource: RestDataSource) {
        this.dataSource.getData().subscribe(data => this.products = data);
      }

    //removed GET, DELETE methods that are working

        //this method below is supposed to CREATE (POST) if Product has no ID 
        //and UPDATE (PUT) if there is a Product ID, but the update is not working

      saveProduct(product: Product) {
        if (product.id == 0 || product.id == null) {
           this.dataSource.saveProduct(product).subscribe(p => this.products.push(p));
        } else {
          this.dataSource.updateProduct(product).subscribe(p => {
            const index = this.products.findIndex(item => this.locator(item, p.id));
            this.products.splice(index, 1, p);
          });
      }
    }
  }

Picture of HTTP PUT Request Method with Chrome F12:使用 Chrome F12 的 HTTP PUT 请求方法图片: 铬 F12

Here is my MVC API Controller:这是我的 MVC API 控制器:

    using Microsoft.AspNetCore.Mvc;
    using Core22MvcNg9.Models;

    namespace Core22MvcNg9.Controllers {

    [Route("api/products")]
    public class ProductValuesController : Controller {
      private IWebServiceRepository repository;

      public ProductValuesController(IWebServiceRepository repo) 
          => repository = repo;
  
      [HttpGet("{id}")]
      public object GetProduct(int id) {
          return repository.GetProduct(id) ?? NotFound();
      }
  
      [HttpGet]
      public object Products() { 
          return repository.GetProducts(); 
      }
  
      [HttpPost]
      public int StoreProduct([FromBody] Product product) {
          return repository.StoreProduct(product);
      }
  
      [HttpPut]
      public void UpdateProduct([FromBody] Product product) {
          repository.UpdateProduct(product);
      }
  
      [HttpDelete("{id}")]
      public void DeleteProduct(int id) {
          repository.DeleteProduct(id);
      }
   }
  }

Here is the MVC Model Web Service Repository (interface):这是 MVC 模型 Web 服务存储库(接口):

  namespace Core22MvcNg9.Models {

     public interface IWebServiceRepository {
  
       object GetProduct(int id);

       object GetProducts();

       int StoreProduct(Product product);

       void UpdateProduct(Product product);

       void DeleteProduct(int id);
      }
  }

Here is the MVC Web Service Model Repository Implementation Class:这是 MVC Web 服务模型存储库实现类:

  using System.Linq;
  using Microsoft.EntityFrameworkCore;
  
  namespace Core22MvcNg9.Models {
  
  public class WebServiceRepository : IWebServiceRepository {
       private ApplicationDbContext context;
  
       public WebServiceRepository(ApplicationDbContext ctx) => context = ctx;
  
       public object GetProduct(int id)
       {
           return context.Products.Include(p => p.Category)
               .Select(p => new {
                   Id = p.ProductID,
                   Name = p.Name,
                   Category = p.Category,
                   Price = p.Price
               })
               .FirstOrDefault(p => p.Id == id);
       }
       
       public object GetProducts() {
           return context.Products.Include(p => p.Category)
               .OrderBy(p => p.ProductID)
               .Select(p => new {
                   Id = p.ProductID,
                   Name = p.Name,
                   Category = p.Category,
                   Price = p.Price
                });
       }
  
       public int StoreProduct(Product product) {
           context.Products.Add(product);
           context.SaveChanges();
           return product.ProductID;
       }
  
       public void UpdateProduct(Product product) {
           context.Products.Update(product);
           context.SaveChanges();
       }
  
       public void DeleteProduct(int id) {
           context.Products.Remove(new Product { ProductID = id });
           context.SaveChanges();
       }
    }
  }

This is the MVC Startup.cs:这是 MVC Startup.cs:

  (other using statements above)    
  using Microsoft.AspNetCore.SpaServices.AngularCli;
  using Microsoft.Extensions.DependencyInjection;
  using Core22MvcNg9.Models;

  namespace Core22MvcNg9
  {
      public class Startup
      {
          public Startup(IConfiguration configuration)
          {
              Configuration = configuration;
          }

          public IConfiguration Configuration { get; }

          // Use this method to add services to the container.
          public void ConfigureServices(IServiceCollection services)
          {
              services.AddDbContext<ApplicationDbContext>(options => 
                  options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
              services.AddTransient<IProductRepository, EFProductRepository>();
              services.AddTransient<IWebServiceRepository, WebServiceRepository>();
              services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        
              // In production, the Angular files will be served from this directory
              services.AddSpaStaticFiles(configuration =>
              {
                  configuration.RootPath = "exampleApp/dist"; 
              });
          }

          // Use this method to configure the HTTP request pipeline.
          public void Configure(IApplicationBuilder app, IHostingEnvironment env)
          {
              if (env.IsDevelopment())
              {
                  app.UseDeveloperExceptionPage();
                  app.UseStatusCodePages();
              }
              else
              {
                  app.UseExceptionHandler("/Error");
              }

              app.UseStaticFiles();
              app.UseSpaStaticFiles();
        
              SeedData.EnsurePopulated(app);

              app.UseMvc(routes =>
              {
                  routes.MapRoute(
                      name: "pagination",
                      template: "Products/Page{productPage}",
                      defaults: new { Controller = "Product", action = "List" });
              });

              app.UseSpa(spa =>
              {

                  spa.Options.SourcePath = "exampleApp"; 

                  if (env.IsDevelopment())
                  {
                      spa.UseAngularCliServer(npmScript: "start");
                  }
              });
          }
      }
  }

Can you help?你能帮我吗? Thanks for your patience in reading a long post, let me know if you need any thing else.感谢您耐心阅读长篇文章,如果您需要其他任何东西,请告诉我。

On guidance from @derpirscher comments:关于@derpirscher 评论的指导:

I set a breakpoint in MVC code on my API Controller method UpdateProduct([FromBody] Product product).我在 API 控制器方法 UpdateProduct([FromBody] Product product) 的 MVC 代码中设置了一个断点。

This method showed the product.ProductID value as 0, so the method was not finding the "ProductID" in the message body as the [FromBody] attribute implies.此方法将 product.ProductID 值显示为 0,因此该方法没有像 [FromBody] 属性所暗示的那样在消息正文中找到“ProductID”。

This reminded me that the Angular data model uses "id" as the identity of the product, not ProductID - which I had changed in MVC code and model, including data context.这提醒我,Angular 数据模型使用“id”作为产品的标识,而不是 ProductID——我在 MVC 代码和模型中更改了它,包括数据上下文。

So, I changed the data context in the MVC Model/Repositories and Controllers back to "Id" for the product identity, dropped and rebuilt the database using dotnet migration, and the Update is now working, matching the "id" from Angular service to the "Id" in MVC API Controller using [From Body].因此,我将 MVC 模型/存储库和控制器中的数据上下文更改回产品标识的“Id”,使用 dotnet 迁移删除并重建数据库,更新现在正在运行,将 Angular 服务中的“id”匹配到使用 [From Body] 的 MVC API 控制器中的“Id”。

My Angular 9 html/component still needs work to address a "property 'id' of null" issue, but I'm glad the MVC Update is now working.我的 Angular 9 html/组件仍然需要努力解决“属性 'id' 为 null”的问题,但我很高兴 MVC 更新现在正在工作。

PUT request don't randomly change to become POST requests. PUT请求不会随机更改为POST请求。 And also your screenshot of the browser dev-tools show, that the request is indeed a PUT request.还有你的浏览器开发工具截图显示,该请求确实是一个PUT请求。

The error may be at the server.错误可能出在服务器上。 Set a breakpoint and check what happens when you hit your PUT endpoint and what happens within context.Products.Update and context.SaveChanges .设置断点并检查当您点击PUT端点时会发生什么以及在context.Products.Updatecontext.SaveChanges会发生什么。 Maybe the request body isn't interpreted correctly at the server and so instead of an update an insert is done ...也许请求正文在服务器上没有被正确解释,所以不是更新而是插入......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM