We are seeing issue in ABP forms module, when we try to create forms or view form, we see error in UI (some Json related issue). We need an immediate fixfor this as we are going live in 2 days.
Also, we have added a MVC sub-module(which contains ABP forms) in our main angular module and when we try to access this forms module, even though we are logged in, sometimes it still asks for loggin again. Apart from this, we face issue with tenant login as well in forms module.
3 Answer(s)
-
0
- Solution Below is a checklist to quickly stabilize the ABP Forms module in your setup (Angular main app hosting an MVC sub-module that contains Volo.Forms). It addresses the JSON error on create/view, the unexpected login prompts, and tenant login problems.
A) Fix the JSON error on Create/View Form
- Common cause: invalid/unsupported DTO members or circular/complex types being posted to the backend. Ensure your DTOs (inputs to create/update forms, question definitions, etc.) contain only serializable primitives/arrays and do not include System.Type or complex runtime types. If a property must exist only for internal logic, mark it to skip validation/serialization.
- If you have a custom input with a Type property (or similar), remove it or mark it ignored/disabled for validation to avoid recursive/serializer issues:
public class MyFormInputDto { // Bad: public Type GridDefinitionType { get; set; } [DisableValidation] // avoids recursive validation and reflection-related exceptions [JsonIgnore] // make sure it's not serialized over HTTP public Type GridDefinitionType { get; set; } }- If you extended or wrapped the Forms endpoints, ensure your controller action parameter models do not have self-referencing arrays/objects that the model binder can’t handle.
- Check server logs for exceptions like “Property accessor 'DeclaringMethod' …” or validation errors produced by ABP’s object validator; that indicates you should remove/ignore the problematic field(s) as above.
B) Prevent random re-login when accessing the MVC sub-module from Angular
- You are mixing Angular (OIDC/OAuth2 via implicit/code flow) with MVC sub-application that uses cookie authentication. When rendering the embedded MVC module, if it is on a different origin or uses a different cookie scheme, you can be prompted to log in again.
- Ensure these:
- IdentityServer client definitions include both your Angular app and the MVC host of the Forms module as separate clients with proper RedirectUris/PostLogoutRedirectUris and AllowedCorsOrigins.
- If the Forms (MVC) is served under your same host/gateway, use the same domain and a consistent cookie configuration. For same-site issues when embedding or cross-origin, set cookie SameSite=None and Secure:
ConfigureApplicationCookie(options => { options.Cookie.SameSite = SameSiteMode.None; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; });- If you proxy the MVC forms through a gateway (YARP), make sure the gateway forwards authentication headers and cookies as-is and that CORS/forwarded headers are configured.
- If the MVC module requires permissions, ensure the current user has the Forms module permissions; otherwise you may see redirects to login due to authorization challenges.
C) Multi-tenancy consistency (tenant login in forms module)
- Ensure the tenant resolution is consistent between Angular and the MVC sub-module:
- If you use subdomain tenancy or header-based tenancy, configure the gateway to forward X-Tenant or host headers unchanged so that the MVC module can resolve the tenant the same way as Angular.
- If your Angular app sets the tenant via AbpTenantKey header (X-Tenant-ID or X-Abp-Tenant), ensure:
- The same header is forwarded to the MVC host through the gateway.
- The MVC app has the same MultiTenancy options.
- If tenants are selected via domain (e.g., tenant1.example.com), serve the MVC sub-module from the same subdomain or configure proper ForwardedHeaders to preserve Host:
app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost });- Double-check the ApplicationUrl, CorsOrigins, and RedirectAllowedUrls for all hosts in appsettings to include your gateway and UI URLs.
D) When hosting SignalR (used by Forms notifications) behind YARP
- If your Forms UI uses SignalR endpoints (e.g., /signalr-hubs/*), add a small middleware in gateway to translate access_token query into Authorization header (WebSockets/ServerSentEvents):
app.Use(async (ctx, next) => { var accessToken = ctx.Request.Query["access_token"]; if (!string.IsNullOrEmpty(accessToken) && ctx.Request.Path.StartsWithSegments("/signalr-hubs")) { ctx.Request.Headers["Authorization"] = "Bearer " + accessToken; } await next(); });- Ensure YARP routes map:
- API endpoints: /api/forms/* (or /api/chat/* in similar examples) to the MVC/HttpApi host
- Hubs: /signalr-hubs/{**everything} to the same host
- Make sure the user has the required permissions for any hub endpoints otherwise 401/403 will occur after negotiate.
E) CORS and Redirects (Angular + MVC)
- In your AuthServer/HttpApiHost appsettings:
- App:ClientUrl should point to Angular.
- App:CorsOrigins must include Angular and Gateway origins (comma-separated).
- App:RedirectAllowedUrls must include Angular and MVC URLs if they do redirects.
- In Angular environment:
- oAuthConfig.issuer matches your AuthServer URL.
- apis.default.url points to your gateway or the HttpApi host consistently.
F) Quick verification steps
- Clear caches (Redis/distributed cache) after changing permissions/tenants.
- Verify the current user has the necessary Forms module permissions.
- Confirm the browser’s storage has a valid access token for the Angular app and that the MVC app receives a valid cookie or Authorization header (depending on flow).
- Test in a single-origin setup first (serve Forms MVC under the same domain/port via gateway) to eliminate cross-site cookie issues, then reintroduce separation if needed.
- Sources
- ABP Forms module overview (Pro):
- https://abp.io/docs/latest/modules/forms
- Related guidance about avoiding problematic DTO properties during validation/serialization (DisableValidation for non-serializable members) inspired by community support resolution:
- https://abp.io/support/questions/2902
- Example of handling SignalR through a gateway and converting access_token to Authorization header (community Q&A pattern):
- https://abp.io/support/questions/5007
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
-
0
Hi,
We could reproduce this problem, the problem occured in the module. The problem will be fixed in a patch version of v10.0.x
-
0
hi
Can you try code below:
using System.Collections.Generic; using System.Linq; using Riok.Mapperly.Abstractions; using Volo.Abp.DependencyInjection; using Volo.Abp.Mapperly; using Volo.Abp.ObjectMapping; using Volo.Forms.Choices; using Volo.Forms.Forms; using Volo.Forms.Questions; using Volo.Forms.Questions.ChoosableItems; namespace DefaultNamespace; public class MyFormWithQuestionsToFormWithDetailsDto : IObjectMapper<FormWithQuestions, FormWithDetailsDto>, ITransientDependency { private MyFormWithQuestionsToFormWithDetailsDtoMapper _mapper; public MyFormWithQuestionsToFormWithDetailsDto(MyFormWithQuestionsToFormWithDetailsDtoMapper mapper) { _mapper = mapper; } public FormWithDetailsDto Map(FormWithQuestions source) { var dto = _mapper.Map(source); _mapper.AfterMap(source, dto); return dto; } public FormWithDetailsDto Map(FormWithQuestions source, FormWithDetailsDto destination) { _mapper.Map(source, destination); _mapper.AfterMap(source, destination); return destination; } } [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] public partial class MyFormWithQuestionsToFormWithDetailsDtoMapper : MapperBase<FormWithQuestions, FormWithDetailsDto> { public override partial FormWithDetailsDto Map(FormWithQuestions source); public override partial void Map(FormWithQuestions source, FormWithDetailsDto destination); public partial QuestionDto Map(QuestionBase source); public partial List<QuestionDto> Map(IEnumerable<QuestionBase> source); public partial ChoiceDto Map(Choice source); public partial List<ChoiceDto> Map(IEnumerable<Choice> source); public override void AfterMap(FormWithQuestions source, FormWithDetailsDto destination) { destination.Description = source.Form.Description; destination.Title = source.Form.Title; destination.Id = source.Form.Id; destination.CreationTime = source.Form.CreationTime; destination.TenantId = source.Form.TenantId; destination.DeleterId = source.Form.DeleterId; destination.CreatorId = source.Form.CreatorId; destination.DeletionTime = source.Form.DeletionTime; destination.IsDeleted = source.Form.IsDeleted; destination.LastModificationTime = source.Form.LastModificationTime; destination.LastModifierId = source.Form.LastModifierId; destination.IsQuiz = source.Form.IsQuiz; destination.IsCollectingEmail = source.Form.IsCollectingEmail; destination.CanEditResponse = source.Form.CanEditResponse; destination.IsAcceptingResponses = source.Form.IsAcceptingResponses; destination.HasLimitOneResponsePerUser = source.Form.HasLimitOneResponsePerUser; destination.RequiresLogin = source.Form.RequiresLogin; foreach (var question in destination.Questions) { var sourceQuestion = source.Questions.FirstOrDefault(q => q.Id == question.Id); if (sourceQuestion == null) { continue; } question.IsRequired = (sourceQuestion as IRequired)?.IsRequired ?? false; question.HasOtherOption = (sourceQuestion as IHasOtherOption)?.HasOtherOption ?? false; question.Choices = Map((sourceQuestion as IChoosable)?.GetChoices().OrderBy(t => t.Index).ToList()) ?? []; question.QuestionType = sourceQuestion.GetQuestionType(); } } }