Attività di "cangunaydin"

  • ABP Framework version: v7.0.3
  • UI type: Angular
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): yes
  • Exception message and stack trace:
  • Steps to reproduce the issue:"

Hello. I have 2 questions that i couldn't understand. I use abp cli 7.0.3

  1. I have created a brand new app from scratch with -t app-pro template with this cli command

abp new Doohlink -t app-pro -u angular -dbms PostgreSQL --separate-auth-server -m maui -csf

If i want to do npm install inside angular folder i got an error about peerdependency here is the short version of the error.

Could not resolve dependency: peer @ng-bootstrap/ng-bootstrap@"12.1.2" from @volosoft/abp.ng.theme.lepton-x@2.0.4 node_modules/@volosoft/abp.ng.theme.lepton-x @volosoft/abp.ng.theme.lepton-x@"^2.0.0-rc.2" from the root project

Conflicting peer dependency: @ng-bootstrap/ng-bootstrap@12.1.2 node_modules/@ng-bootstrap/ng-bootstrap peer @ng-bootstrap/ng-bootstrap@"12.1.2" from @volosoft/abp.ng.theme.lepton-x@2.0.4 node_modules/@volosoft/abp.ng.theme.lepton-x @volosoft/abp.ng.theme.lepton-x@"^2.0.0-rc.2" from the root project

Fix the upstream dependency conflict, or retry this command with --force or --legacy-peer-deps to accept an incorrect (and potentially broken) dependency resolution.

when i do yarn it executes and if i do yarn start everything works fine. What is the reason behind this should i use yarn all the time and why npm is having problems?

  1. if i want to add a module with abp add-module command then i can not build those projects with ng build command. here is the command that i use.

abp add-module Doohlink.CreativeManagement --new -t module-pro --add-to-solution-file --startup-project .\aspnet-core\src\Doohlink.HttpApi.Host\Doohlink.HttpApi.Host.csproj -s .\aspnet-core\Doohlink.sln

when i try to run

ng build creative-management

it gives me an error.

Error: Could not find the '@angular-devkit/build-ng-packagr:build' builder's node package.

"@angular-devkit/build-angular": "^15.0.1", is inside my dev dependencies. and i can see it inside node-modules folder.

is this a bug? or am i doing sth wrong over here? Thank you.

Thank you for the information. Do you have any information about the release date? Also can i learn if it is a major update or is it possible to fix it in my project without updating lepton version.

  • ABP Framework version: v7.0.1
  • UI type: Angular
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): yes
  • Exception message and stack trace:
  • Steps to reproduce the issue:"

Hello, I am trying to use Dev extreme scheduler in my angular app. I am using commercial module with leptonx theme but behavior is the same for leptonx lite. When DxScheduler is rendered initially, it becomes unresponsive and styles are broken inside of it. here is the picture of how it looks.

normally it should look like this.

if you do window resize, click a button or if you load another page first and then switch to the page where scheduler is implemented then it renders the component again and it works fine. But when you refresh the page that the scheduler is included it breaks again. Probably some style is conflicting with devx styles.

To reproduce the issue you can follow these steps.

  1. Create new app from scratch, abp new Acme.BookStore -t app -u angular -m none --separate-auth-server --database-provider ef -csf
  2. On angular project, just follow the guidelines of devx getting started (https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Add_DevExtreme_to_an_Angular_CLI_Application/) which is.
    • add devx packages. yarn add devextreme@22.2 devextreme-angular@22.2
    • then add devx style to angular.json
    • add dx-viewport class to body.
    • add DxSchedulerModule to module imports in HomeModule
  3. Afterwards i have implemented this demo from devx angular demos. (https://js.devexpress.com/Demos/WidgetsGallery/Demo/Scheduler/SimpleArray/Angular/Light/) which looks like this. here is the home.component.ts
    import { AuthService } from '@abp/ng.core';
import { Component } from '@angular/core';
import { FakeAppointmentService } from '../services/fake-appointment.service';
import { Appointment } from '../services/fake-appointment.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss'],
})
export class HomeComponent {
  appointmentsData: Appointment[] = [];

  currentDate: Date = new Date(2021, 2, 28);

  get hasLoggedIn(): boolean {
    return this.authService.isAuthenticated;
  }

  constructor(private authService: AuthService, private fakeService: FakeAppointmentService) {
    this.appointmentsData = fakeService.getAppointments();
  }

  login() {
    this.authService.navigateToLogin();
  }
}

and here is the fake-appointment.service.ts

import { Injectable } from '@angular/core';

export class Appointment {
  text: string;

  startDate: Date;

  endDate: Date;

  allDay?: boolean;
}

const appointments: Appointment[] = [
  {
    text: 'Website Re-Design Plan',
    startDate: new Date('2021-03-29T16:30:00.000Z'),
    endDate: new Date('2021-03-29T18:30:00.000Z'),
  }, {
    text: 'Book Flights to San Fran for Sales Trip',
    startDate: new Date('2021-03-29T19:00:00.000Z'),
    endDate: new Date('2021-03-29T20:00:00.000Z'),
    allDay: true,
  }, {
    text: 'Install New Router in Dev Room',
    startDate: new Date('2021-03-29T21:30:00.000Z'),
    endDate: new Date('2021-03-29T22:30:00.000Z'),
  }, {
    text: 'Approve Personal Computer Upgrade Plan',
    startDate: new Date('2021-03-30T17:00:00.000Z'),
    endDate: new Date('2021-03-30T18:00:00.000Z'),
  }, {
    text: 'Final Budget Review',
    startDate: new Date('2021-03-30T19:00:00.000Z'),
    endDate: new Date('2021-03-30T20:35:00.000Z'),
  }, {
    text: 'New Brochures',
    startDate: new Date('2021-03-30T21:30:00.000Z'),
    endDate: new Date('2021-03-30T22:45:00.000Z'),
  }, {
    text: 'Install New Database',
    startDate: new Date('2021-03-31T16:45:00.000Z'),
    endDate: new Date('2021-03-31T18:15:00.000Z'),
  }, {
    text: 'Approve New Online Marketing Strategy',
    startDate: new Date('2021-03-31T19:00:00.000Z'),
    endDate: new Date('2021-03-31T21:00:00.000Z'),
  }, {
    text: 'Upgrade Personal Computers',
    startDate: new Date('2021-03-31T22:15:00.000Z'),
    endDate: new Date('2021-03-31T23:30:00.000Z'),
  }, {
    text: 'Customer Workshop',
    startDate: new Date('2021-04-01T18:00:00.000Z'),
    endDate: new Date('2021-04-01T19:00:00.000Z'),
    allDay: true,
  }, {
    text: 'Prepare 2021 Marketing Plan',
    startDate: new Date('2021-04-01T18:00:00.000Z'),
    endDate: new Date('2021-04-01T20:30:00.000Z'),
  }, {
    text: 'Brochure Design Review',
    startDate: new Date('2021-04-01T21:00:00.000Z'),
    endDate: new Date('2021-04-01T22:30:00.000Z'),
  }, {
    text: 'Create Icons for Website',
    startDate: new Date('2021-04-02T17:00:00.000Z'),
    endDate: new Date('2021-04-02T18:30:00.000Z'),
  }, {
    text: 'Upgrade Server Hardware',
    startDate: new Date('2021-04-02T21:30:00.000Z'),
    endDate: new Date('2021-04-02T23:00:00.000Z'),
  }, {
    text: 'Submit New Website Design',
    startDate: new Date('2021-04-02T23:30:00.000Z'),
    endDate: new Date('2021-04-03T01:00:00.000Z'),
  }, {
    text: 'Launch New Website',
    startDate: new Date('2021-04-02T19:20:00.000Z'),
    endDate: new Date('2021-04-02T21:00:00.000Z'),
  },
];
@Injectable({
  providedIn: 'root'
})
export class FakeAppointmentService {

  constructor() { }
  getAppointments(): Appointment[] {
    return appointments;
  }
}

by the way when i do the same steps with new angular app from angular cli, it works perfectly so probably sth is wrong when the leptonx theme is initializing. maybe some styles are overriding. Hope this was clear. Thank you for the assistance.

Hello, I have a case that i need to apply passwordless authentication for my project. I use seperated auth server with openiddict. What i want to do is,

  • get an email address from the user
  • check if that email address is in my database. (this is an email not stored in AbpUsers table)
  • if so create a code and send that code to user's email address.
  • Show a textbox for verification
  • if the user input is the same with the code sent, sign in the user.
  • Apply refreshtoken when the login is expired.

Is there any code sample so i can take a look or any resources that i can use? I was thinking to extend the Login page from Volo.Abp.Account.Pro.Public.Web. Is sth else needed like Extending authorize method of OpenIdDict module? Also how should i think about refreshtoken in this case?

I have blazor web assembly front end and .net maui app that will use this kind of auth.

Thanks in advance.

Hello again sorry for my late response. From the reply I didn't understand some parts.

You can design it as a single request posting data to multiple microservices using the aggregate pattern. It should work fine.

Let's assume i am doing a call to the gateway posting data for Product Entity. if you look at the image.

I do one post to the gateway then i need to seperate (or decompose) it to 3 different microservice, how you are going to do it with aggregator pattern? As i know it Ocelot can not make it.(correct me if i am wrong). Can KrakenD do that? cause when I look at the docs i couldn't see any example for that. https://www.krakend.io/docs/endpoints/response-manipulation/#merge-example

It is not caching, it is data duplication. You can also apply caching on it.

What I wanted to say over here is if you do data duplication, some service needs to own it.Then Owner (Microservice) can use it in its bounded context for some kind of behavior. For ex if you do search microservice and use it for that I agree it is the right way to do. But if you take it somewhere just to read data from, then i believe it becomes caching cause no one owns it.

Anyway I started to write a viewmodel composition kind of gateway for my purpose. Here is what i have done so far.

1- I created interface ICompositionHandleService in my project. It inherits IRemoteService 2- Then at the startup, I do find the classes that implements this interface. Registering them to dependency injection container. 3- I have created a class that overrides the AbpServiceConvention. I am creating composition routes over here. sth like this.

[Dependency(ReplaceServices = true)]
    [ExposeServices(typeof(IAbpServiceConvention), typeof(AbpServiceConvention))]
    public class CompositionServiceConvention : AbpServiceConvention
    {
        private readonly CompositionOptions _compositionOptions;
        private readonly IServiceProvider _serviceProvider;
        public CompositionServiceConvention(IOptions<AbpAspNetCoreMvcOptions> options,
            IConventionalRouteBuilder conventionalRouteBuilder,
            CompositionOptions compositionOptions,
            IServiceProvider serviceProvider) : base(options, conventionalRouteBuilder)
        {
            _compositionOptions = compositionOptions;
            _serviceProvider = serviceProvider;
        }

        protected override void ApplyForControllers(ApplicationModel application)
        {

            CreateCompositionRoutes();

            UpdateCompositionControllers(application);
            
            //original code here.
            RemoveDuplicateControllers(application);

            foreach (var controller in GetControllers(application))
            {
                var controllerType = controller.ControllerType.AsType();

                var configuration = GetControllerSettingOrNull(controllerType);

                // rest is same
            }   
}

so to examplify it let's assume that i have an interface

public interface IProductDetailService
{
    Task<ProductCompositionDto> GetAsync(Guid id);
}
 public class ProductCompositionDto : CompositionViewModelBase
    {
        public ProductDto Product { get; set; }

        public ProductPriceDto ProductPrice { get; set; }

    }

and two service one is on **Sales ** Namespace.

 namespace Sales;
 
 public class ProductCompositionService : CompositionService, 
        IProductDetailService,
        ICompositionHandleService,
        IRemoteService,
        ITransientDependency
    {
        private readonly IProductPriceAppService _productPriceAppService;
        public ProductMergeCompositionService(IProductPriceAppService productPriceAppService)
        {
            _productPriceAppService = productPriceAppService;
        }

        public async Task<ProductCompositionDto> GetAsync(Guid id)
        {
            var result = CompositionContext.HttpRequest.GetComposedResponseModel<ProductCompositionDto>();
            result.ProductPrice = await _productPriceAppService.GetAsync(id);
            return result;
        }
		
    }

the other one is on **Marketing ** Namespace

namespace Marketing;
public class ProductCompositionService : CompositionService,
            IProductDetailService,
         ICompositionHandleService,
        IRemoteService,
        ITransientDependency
    {
        private readonly IProductAppService _productAppService;
        public ProductMergeCompositionService(IProductAppService productPriceAppService)
        {
            _productAppService = productPriceAppService;
        }

        public async Task<ProductCompositionDto> GetAsync(Guid id)
        {
            var result = CompositionContext.HttpRequest.GetComposedResponseModel<ProductCompositionDto>();
            result.Product = await _productAppService.GetAsync(id);
            return result;
        }
    }

since these classes shares the same name and same interface they are gonna be duplicated as 2 routes (api endpoints). To avoid that i remove one of them in CompositionServiceConvention class. I managed to register this as an api controller.

And as the last step I have added IAsyncActionFilter to my project. To handle the requests for composition routes. here is how i write the action filter.

 public class CompositionOverControllersFilter : IAsyncActionFilter, ITransientDependency
    {
        CompositionOptions _compositionOptions;
        public CompositionOverControllersFilter(CompositionOptions compositionOptions)
        {
            _compositionOptions = compositionOptions;
        }

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            if (context.HttpContext.GetEndpoint() is RouteEndpoint endpoint)
            {
                Debug.Assert(endpoint.RoutePattern.RawText != null, "endpoint.RoutePattern.RawText != null");
                var rawTemplate = endpoint.RoutePattern.RawText;
                var templateHttpMethod = context.HttpContext.Request.Method;
                var compositionRouteMatched = _compositionOptions.CompositionRouteRegistry.Where(o => o.Route == rawTemplate && o.Method.Method == templateHttpMethod).FirstOrDefault();

                if (compositionRouteMatched != null)
                {
                    var compositionHandler = new CompositionHandler();
                    var viewModel = await compositionHandler.HandleComposableRequest(context.HttpContext, compositionRouteMatched);
                    if (viewModel != null)
                        context.Result = new ObjectResult(viewModel);
                    else
                        context.Result = new OkResult();
                    return;

                }
            }

            await next();
        }

    }

and it works fine as you can see if the route is matched i am handling the request on compositionHandler class. Over there I use some reflections. Calling the Sales.ProductCompositionService and Marketing.CompositionService classes GetAsync(Guid id) methods and waiting for them to finish and composing the result.

There are 3 problems i am facing here. I would be so glad if you can help me out with those problems.

  1. When i call two methods from different classes sometimes i got this error. (not all the time)

2022-10-13 00:46:31.738 +02:00 [ERR] There is already a database API in this unit of work with given key: Volo.Abp.PermissionManagement.EntityFrameworkCore.IPermissionManagementDbContext_Server=(LocalDb)\MSSQLLocalDB;Database=MarketPlace;Trusted_Connection=True Volo.Abp.AbpException: There is already a database API in this unit of work with given key: Volo.Abp.PermissionManagement.EntityFrameworkCore.IPermissionManagementDbContext_Server=(LocalDb)\MSSQLLocalDB;Database=MarketPlace;Trusted_Connection=True at Volo.Abp.Uow.UnitOfWork.AddDatabaseApi(String key, IDatabaseApi api)

it seems like this is related with unitofwork but since i am invoking the methods with reflection i am not expecting for abp to create a unitofwork. Could it be unit of work is created when i call these 2 methods by reflection?

  1. As you can see after i composed the viewmodel I return ObjectResult and terminating the middleware. Is this the right way to do it? What is the response that normally abp should return? And how it works for abp if i call an endpoint from razor page / mvc. Should it be still ObjectResult? (is this gonna break the things.)
  2. Is there any modelbinding helper that abp uses already in its framework? I have tried couple of things to bind the httprequest to my model but i couldn't make it work with methods expects 2 or multiple parameters. if i have an interface like this.
Task CreateAsync(Guid id, CreateProductDto input) //not working in this case

i couldn't bind it to the CreateProductDto complex type. don't know why. But if i have a case like this.

Task CreateAsync(CreateProductDto input) //working in this case

here is the code that i use.

public class RequestModelBinder:ISingletonDependency
{
    IModelBinderFactory modelBinderFactory;
    IModelMetadataProvider modelMetadataProvider;
    IOptions<MvcOptions> mvcOptions;

    public RequestModelBinder(IModelBinderFactory modelBinderFactory, IModelMetadataProvider modelMetadataProvider, IOptions<MvcOptions> mvcOptions)
    {
        this.modelBinderFactory = modelBinderFactory;
        this.modelMetadataProvider = modelMetadataProvider;
        this.mvcOptions = mvcOptions;
    }
      public async Task<object> Bind(HttpRequest request,Type type) 
    {
        //always rewind the stream; otherwise,
        //if multiple handlers concurrently bind
        //different models only the first one succeeds
        request.Body.Position = 0;

        var modelType = type;
        var modelMetadata = modelMetadataProvider.GetMetadataForType(modelType);
        var actionContext = new ActionContext(
            request.HttpContext,
            request.HttpContext.GetRouteData(),
            new ActionDescriptor(),
            new ModelStateDictionary());
        var valueProvider =
            await CompositeValueProvider.CreateAsync(actionContext, mvcOptions.Value.ValueProviderFactories);


        var modelBindingContext = DefaultModelBindingContext.CreateBindingContext(
            actionContext,
            valueProvider,
            modelMetadata,
            bindingInfo: null,
            modelName: "");

        modelBindingContext.Model = Activator.CreateInstance(type);
        modelBindingContext.PropertyFilter = _ => true; // All props

        var factoryContext = new ModelBinderFactoryContext()
        {
            Metadata = modelMetadata,
            BindingInfo = new BindingInfo()
            {
                BinderModelName = modelMetadata.BinderModelName,
                BinderType = modelMetadata.BinderType,
                BindingSource = modelMetadata.BindingSource,
                PropertyFilterProvider = modelMetadata.PropertyFilterProvider,
            },
            CacheToken = modelMetadata,
        };

        await modelBinderFactory
            .CreateBinder(factoryContext)
            .BindModelAsync(modelBindingContext);

        return Convert.ChangeType(modelBindingContext.Result.Model,type);
    }

it was a long message, i hope i can explain the things :) Thanks for reading and assistance

Hello again, Thanks for the link, i have watched it now and totally agree on some parts. Also i think if your search needs to be complex you definitely need another microservice for that. But also i have some parts that i disagree.

But before that i am curious about your opinion, how to do the save operations? How you should do that in a microservice architecture if you need to have multiple requests. You can design your pages according to a call that you are gonna do. but that is a kind of limitation from a ui perspective.(Ex: First update name and description, then user will switch to another modal or page to update the price and so on).

The parts that i disagree on the video is, if you hold all the information in a seperate db from different services( i am not saying this is not the way,in some cases you should, for ex,if you wanna do search, paging and sorting together). Basically what you do is kind of cache, and what i believe is in most of the cases, you can do the cache in the gateway instead of another service. And if you do on the gateway level you can give different cache timeout values.

What i mean by that is if marketing is holding the information about name,description. and Sales holding the information about the price. Price information can be cached per day but marketing can be cached for a week so you don't need to call each microservice whenever requests comes(or you can get that information directly from db or text document).

I also started to implement a kind of viewmodel composition gateway on a scratch abp template. I have a question about where the conventional endpoint routes are configured. I want to create an extra endpoint middleware that will intercept the call coming to the gateway and dispatch it to multiple methods and compose the returning values. I have tried to look at the source code but couldn't find where you invoke the method info for the application service endpoints. Can you direct me to the necessary part?

I think in AbpServiceConvention all the services and controllers are converted to endpoints, but i couldn't connect the dots, where and how this class have been used. https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs#L22

Thanks for reading and your input.

Hello Galip, Thanks for the answer. I have checked out the "gateway aggregator pattern". And yes this is the thing that i am looking for at the first sight. But there is always but... :) aggregator pattern solves some of my problems for the above example i give. For ex:

If i want to get the details of the product with the id 1. yes aggregator pattern most likely solves my problem. But what about list with search? and what about if i am going to do paging and sorting? and what about multiple column sorting? those are kind of difficult scenarios since data has been shared on different bounded contexts. (pictures are from ViewModel Composition Series of Mauro Servienti, https://milestone.topics.it/2019/02/28/into-the-darkness-of-viewmodel-lists-composition.html)

Also another problem is Decomposition. What i mean by that is when i want to create product from scratch from the nice user interface with all the properties in one page and hit the save button. I need to send every piece of data to the related bounded context. Sth like this. (picture is from the video that i shared, from a ddd conference)

so to manage the things that i have mentioned over here, they have implemented a kind of asp.net core route interceptor which gets the request and calling the parties that is participated in that route, so for ex.

if i do a Get Request on the gateway, sth like api/product/1

then the interceptor will catch this request and look at the contributors (with mediatr pattern) that is registered for this route and call their handle() methods so dto (or viewmodel) can be composed from each contributors.

similarly if i do a Post Request on the gateway, sth like api/product/

then the interceptor will catch this request and look at the contributors (with mediatr pattern) that is registered for this route and call their handle() method but sth is little different this time since you need a transactional boundary they use message broker to do the operation instead of http request.

In conclusion my point is, i have decided to use this concept on abp project, but i am not sure what i am going to gain or loose, so i need to implement and see if it works at the end. So if you have any ideas how this can be done inside abp or any advices about which way should i go, i would like to hear it very much. Thanks for your time and reading it anyway.

Hello again, I think we couldn't understand each other :) I know that i can create razor pages. My question is out of scope of what kind of user interface tech. you use.

just let me try another way to explain myself.

Let's think about 3 different bounded contexts.

1- Sales 2- Marketing 3- Shipping

All of them has product properties related to product.

So let's say.

Marketing

 public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
    }

Sales

 public class ProductPrice
    {
        public int ProductId { get; set; }

        public decimal Price { get; set; }
    }

Shipping

 public class ProductShippingOptions
    {
        public int Id { get; set; }
        public int ProductId { get; set; }
        public List<ShippingOption> Options { get; set; } = new List<ShippingOption>();
    }

    public class ShippingOption
    {
        public int Id { get; set; }
        public int ProductShippingOptionsId { get; set; }
        public string Option { get; set; }
        public int EstimatedMinDeliveryDays { get; set; }
        public int EstimatedMaxDeliveryDays { get; set; }
    }

so each bounded context is responsible for product details that they care.

So let's think about a moment I am listing products on my web app (it doesn't matter which tech i use). The user wants to see name price and shipping options of the product on a listed view.

If my web app is Monolith App, How should i compose those 3 different services together in a list? If my web app is Microservice, How should i compose those 3 different services together in a list?

3 bounded contexts are different modules and they don't know about each other.

As i see on EshopOnAbp, most of the data is copied between bounded contexts.
here is the code from EshopOnAbp. This is the OrderItem from Ordering Bounded Context.

public class OrderItem : Entity<Guid>
{
    public string ProductCode { get; private set; }
    public string ProductName { get; private set; }
    public string PictureUrl { get; private set; }
    public decimal UnitPrice { get; private set; }
    public decimal Discount { get; private set; }
    public int Units { get; private set; }
    public Guid ProductId { get; private set; }

here is the product from Catalog Bounded Context

 public class Product : AuditedAggregateRoot<Guid>
    {
        /// <summary>
        /// A unique value for this product.
        /// ProductManager ensures the uniqueness of it.
        /// It can not be changed after creation of the product.
        /// </summary>
        [NotNull]
        public string Code { get; private set; }

        [NotNull]
        public string Name { get; private set; }

        public float Price { get; private set; }

        public int StockCount { get; private set; }

        public string ImageName { get; private set; }
        

And in Basket Bounded Context. the app calls the Catalog Bounded Context to get the Product info and caching it.(through _productPublicGrpcClient). This is some kind of composition in microservice level but isn't this should be composed in the gateway instead of Basket Service?

public class BasketItemDto
{
    public Guid ProductId { get; set; }
    public string ProductName { get; set; }
    public string ProductCode { get; set; }
    public string ImageName { get; set; }
    public int Count { get; set; }
    public float TotalPrice { get; set; }
}

is there any other way like viewmodel composition and decomposition that I can use so we don't need to copy the staff around for microservices and i am curious how this composition can happen if i decided to use my modules as monolith?

Of course, the UI layer should be one web app, actually PublicWebApp is it, It just references the UI class library of other modules.

as i understand all the microservice modules do not have any presentation layer which means EshopOnAbp.PublicWeb project do not depend on any microservice module presentation layer (cause there is none, which is not needed on microservice modules in this case). It implements the necessary user interface inside that project. For ex:

  public class ProductDetailModel : AbpPageModel
    {
        [BindProperty(SupportsGet = true)]
        public int OrderNo { get; set; }

        public ProductDto Product { get; private set; }
        public bool IsPurschased { get; private set; }

        private readonly IPublicProductAppService _productAppService;
        private readonly IOrderAppService _orderAppService;

        public ProductDetailModel(
            IPublicProductAppService productAppService,
            IOrderAppService orderAppService)
        {
            _productAppService = productAppService;
            _orderAppService = orderAppService;
        }

        public async Task OnGet(Guid id)
        {
            IsPurschased = (await _orderAppService.GetMyOrdersAsync(new GetMyOrdersInput())).Any(p => p.Items.Any(p => p.ProductId == id));
            Product = await _productAppService.GetAsync(id);
        }
    }

this is a sample from EshopOnAbp, it is ProductDetail page as you can see it is dependent on OrderAppService and ProductAppService while creating the ui. You can do this if you implement the presentation layer out of your modules, like in the demo project EshopOnAbp.

I will try to ask the question from another perspective. If I need to convert the EshopOnAbp project to monolith app, in where should i create the user interface for ProductDetail page?

Cause product belongs to Catalog Module and Order belongs to ordering module. Or you shouldn't implement the ProductDetail page in any modules presentation layer and keep it in your main app?

Hello @liangshiwei As i understand (pls correct me if i am wrong), In microservice solution you don't use the user interface(or web layer) for microservices instead one web app that will unify whole views. And this web app(in EshopOnAbp,it is PublicWebApp) do the requests through gateway (WebPublicGateWay) by using reverse proxy.

So here is the question my project will stay monolith for a while and according to changes it might evolve to microservices in the future, while my modules are monolith where should i implement the user interface to create an order, and if this is going to be inside the module, what should i do to list the products?

Thank you for your patience and for your help a lot.

81 - 90 di 95
Made with ❤️ on ABP v8.2.0-preview Updated on marzo 25, 2024, 15:11