I also don't like this change. We have angular in a separate repo. Is there a way to make the ABP Solution work with the "old" folder structure?
You need to look in the backend logs. 500 errors are logged there
It looks like the "ProviderKey" column in the "AbpUserLogins" table is now encrypted, or in a different format. After clearing the table, things started to work again...
Not sure if that's documented somewhere
Going further with this, I have tried it using just ASP.NET Core, and the type seems to be represented just fine in ApiExplorer with this. I'll send you the project
Actually, going further it seems the issue is not class vs struct, but that the type is used in a route path, e.g:
[HttpPost("/class/{testClass}")]
public ActionResult FromTestClass(TestClass testClass)
{
return NoContent();
}
This produces 2 parameters, one for the route {testClass} with no Type and one for the function itself with the correct type info.
So this seems to be the culprit here
Going further with this, I have tried it using just ASP.NET Core, and the type seems to be represented just fine in ApiExplorer with this. I'll send you the project
It must be something with ASP.NET Core then (or the way ABP uses the information), you can get rid of StronglyTypedId package / code and just use this for AgentId:
namespace Repro.ProxyGeneration.Models.Test
{
public struct AgentId
{
public int Value { get; }
public AgentId(int value)
{
Value = value;
}
}
}
This leads to the same issue. The workaround seems to work and my proxies do get generated again, thank you
From what I can see is that it fails here in @abp\ng.schematics\utils\common.js
Object.values(controller.actions || {}).forEach(a => {
a.returnValue.type = sanitizeTypeName(a.returnValue.type);
a.returnValue.typeSimple = sanitizeTypeName(a.returnValue.typeSimple);
a.parametersOnMethod?.forEach(p => {
p.type = sanitizeTypeName(p.type);
p.typeAsString = sanitizeTypeName(p.typeAsString);
p.typeSimple = sanitizeTypeName(p.typeSimple);
});
a.parameters?.forEach(p => {
p.type = sanitizeTypeName(p.type);
p.typeSimple = sanitizeTypeName(p.typeSimple);
});
});
Specifically here:
a.parameters?.forEach(p => {
p.type = sanitizeTypeName(p.type); <--------
p.typeSimple = sanitizeTypeName(p.typeSimple);
});
Looking into my api-definition, I get for instance this generated parameter on a method:
"paramters": [
{
"nameOnMethod": "agentApiKeyId",
"name": "agentApiKeyId",
"jsonName": null,
"type": null,
"typeSimple": null,
"isOptional": false,
"defaultValue": null,
"constraintTypes": [],
"bindingSourceId": "Path",
"descriptorName": ""
}
]
notice how the type is null.
This particular method is in a Controller (however we observe the same from ApplicationServices as well):
[HttpPost]
[Route("api-keys/{agentApiKeyId}")]
public async Task<ActionResult> SetAgentApiKeyStatusAsync(AgentApiKeyId agentApiKeyId, bool isEnabled)
{
await _agentAppService.SetApiKeyStatusAsync(agentApiKeyId, isEnabled);
return NoContent();
}
Now, AgentApiKeyId itself is a value object struct (generated by the StronglyTypedId project) :
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by the StronglyTypedId source generator
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
#pragma warning disable 1591 // publicly visible type or member must be documented
namespace MyCompany.MyProject.Agent.ValueObjectId
{
[System.Text.Json.Serialization.JsonConverter(typeof(AgentApiKeyIdSystemTextJsonConverter))]
[System.ComponentModel.TypeConverter(typeof(AgentApiKeyIdTypeConverter))]
readonly partial struct AgentApiKeyId : System.IComparable<AgentApiKeyId>, System.IEquatable<AgentApiKeyId>
{
public int Value { get; }
public AgentApiKeyId(int value)
{
Value = value;
}
public static readonly AgentApiKeyId Empty = new AgentApiKeyId(0);
public bool Equals(AgentApiKeyId other) => this.Value.Equals(other.Value);
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
return obj is AgentApiKeyId other && Equals(other);
}
public override int GetHashCode() => Value.GetHashCode();
public override string ToString() => Value.ToString();
public static bool operator ==(AgentApiKeyId a, AgentApiKeyId b) => a.Equals(b);
public static bool operator !=(AgentApiKeyId a, AgentApiKeyId b) => !(a == b);
public int CompareTo(AgentApiKeyId other) => Value.CompareTo(other.Value);
public class EfCoreValueConverter : Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter<AgentApiKeyId, int>
{
public EfCoreValueConverter() : this(null) { }
public EfCoreValueConverter(Microsoft.EntityFrameworkCore.Storage.ValueConversion.ConverterMappingHints mappingHints = null)
: base(
id => id.Value,
value => new AgentApiKeyId(value),
mappingHints
) { }
}
class AgentApiKeyIdTypeConverter : System.ComponentModel.TypeConverter
{
public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Type sourceType)
{
return sourceType == typeof(int) || sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
return value switch
{
int intValue => new AgentApiKeyId(intValue),
string stringValue when !string.IsNullOrEmpty(stringValue) && int.TryParse(stringValue, out var result) => new AgentApiKeyId(result),
_ => base.ConvertFrom(context, culture, value),
};
}
public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Type sourceType)
{
return sourceType == typeof(int) || sourceType == typeof(string) || base.CanConvertTo(context, sourceType);
}
public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, System.Type destinationType)
{
if (value is AgentApiKeyId idValue)
{
if (destinationType == typeof(int))
{
return idValue.Value;
}
if (destinationType == typeof(string))
{
return idValue.Value.ToString();
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
class AgentApiKeyIdSystemTextJsonConverter : System.Text.Json.Serialization.JsonConverter<AgentApiKeyId>
{
public override AgentApiKeyId Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options)
{
return new AgentApiKeyId(reader.GetInt32());
}
public override void Write(System.Text.Json.Utf8JsonWriter writer, AgentApiKeyId value, System.Text.Json.JsonSerializerOptions options)
{
writer.WriteNumberValue(value.Value);
}
}
}
}
It seems like struct's are not correctly processed when generating the api-definition. Is this something that can be fixed?
I was able to get a stack trace by using this:
yarn nx g @abp/nx.generators:generate-proxy --verbose
maybe it helps?
TypeError: Cannot read properties of null (reading 'replace')
at sanitizeTypeName (W:\DEV\ProductName\abp\angular\node_modules\@abp\ng.schematics\utils\common.js:35:41)
at W:\DEV\ProductName\abp\angular\node_modules\@abp\ng.schematics\utils\common.js:58:26
at Array.forEach (<anonymous>)
at W:\DEV\ProductName\abp\angular\node_modules\@abp\ng.schematics\utils\common.js:57:27
at Array.forEach (<anonymous>)
at W:\DEV\ProductName\abp\angular\node_modules\@abp\ng.schematics\utils\common.js:49:49
at Array.forEach (<anonymous>)
at sanitizeControllerTypeNames (W:\DEV\ProductName\abp\angular\node_modules\@abp\ng.schematics\utils\common.js:37:38)
at W:\DEV\ProductName\abp\angular\node_modules\@abp\ng.schematics\commands\api\index.js:31:92
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
{
"name": "ProductName",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve --open",
"start:development-staging": "ng server --open --configuration development-staging",
"build": "ng build",
"build:docker-compose": "ng build --configuration docker-compose",
"build:development-staging": "ng build --configuration development-staging",
"build:prod": "ng build --configuration production",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"lint": "ng lint"
},
"private": true,
"dependencies": {
"@abp/ng.components": "~7.4.2",
"@abp/ng.core": "~7.4.2",
"@abp/ng.oauth": "~7.4.2",
"@abp/ng.setting-management": "~7.4.2",
"@abp/ng.theme.shared": "~7.4.2",
"@amcharts/amcharts4": "^4.10.38",
"@angular-builders/jest": "^16.0.0",
"@angular/animations": "^16.2.12",
"@angular/common": "^16.2.12",
"@angular/compiler": "^16.2.12",
"@angular/core": "^16.2.12",
"@angular/forms": "^16.2.12",
"@angular/localize": "16.2.12",
"@angular/platform-browser": "^16.2.12",
"@angular/platform-browser-dynamic": "^16.2.12",
"@angular/router": "^16.2.12",
"@chiragrupani/karma-chromium-edge-launcher": "^2.1.1",
"@ngxs/store": "^3.7.6",
"@swimlane/ngx-charts": "^20.0.0",
"@volo/abp.commercial.ng.ui": "~7.4.2",
"@volo/abp.ng.account": "~7.4.2",
"@volo/abp.ng.audit-logging": "~7.4.2",
"@volo/abp.ng.gdpr": "~7.4.2",
"@volo/abp.ng.identity": "~7.4.2",
"@volo/abp.ng.language-management": "~7.4.2",
"@volo/abp.ng.openiddictpro": "~7.4.2",
"@volo/abp.ng.saas": "~7.4.2",
"@volo/abp.ng.text-template-management": "~7.4.2",
"@volo/abp.ng.theme.lepton": "7.4.2",
"@volo/language-management": "~7.4.2",
"@yaireo/tagify": "^4.17.0",
"bootstrap-icons": "^1.11.2",
"jest": "^29.7.0",
"karma-junit-reporter": "^2.0.1",
"ngx-bootstrap-multiselect": "^4.0.0",
"ngx-editor": "^16.0.0",
"rxjs": "7.8.1",
"tslib": "^2.1.0",
"zone.js": "~0.13.3"
},
"devDependencies": {
"@abp/ng.schematics": "~7.4.2",
"@angular-devkit/build-angular": "^16.2.10",
"@angular-eslint/builder": "16.3.1",
"@angular-eslint/eslint-plugin": "16.3.1",
"@angular-eslint/eslint-plugin-template": "16.3.1",
"@angular-eslint/schematics": "16.3.1",
"@angular-eslint/template-parser": "16.3.1",
"@angular/cli": "^16.2.10",
"@angular/compiler-cli": "^16.2.12",
"@angular/language-service": "^16.2.12",
"@types/jasmine": "~3.6.0",
"@types/node": "^18.11.0",
"@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.59.2",
"eslint": "^8.39.0",
"jasmine-core": "~4.0.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.1.0",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.7.0",
"ng-packagr": "^16.2.3",
"typescript": "~5.1.6"
},
"resolutions": {
"prosemirror-view": "1.31.7"
}
}
We have tried this with the application before-upgrade now (v6.0.2) and it worked (I stated previously that it didn't, but I executed it in the wrong directory)
So it seems there is some issue with the upgrade from 6.0.2 to 7.4.2? Any pointers?