Hello @ismcagdas thank you for the answer. Over here I try to simplify the things, but sometimes deleting operations in our project can have some business rules. For ex deleting an order can only be done in some kind of state. I understand it can't be done for now, but what I would like to ask is
can I mark some entities with an interface. So instead of using IRepository interface use another repository interface and restrict users to use IRepository on the application layer somehow with those entities?
Hello, Right now on my asp.net zero project, i am trying to refactor and apply some rules from ddd. So in my case, i need a business validation on my domain before delete happens. To give an example, let me simplify this. Let's say that I have Book Entity which holds information like
public class Book: FullAuditedEntity,IMustHaveTenant
{
public Book(string name,int tenantId)
{
Name=name;
TenantId=tenantId;
}
public void AssignCategory(string categoryName){
Check.NotNull(categoryName,nameOf(categoryName));
CategoryName=categoryName;
}
public int TenantId { get; set; }
public string Name {get;private set;}
public string CategoryName{get; private set}
}
public class BookManager: DomainServiceBase,IBookManager
{
private readonly IRepository<Book> _bookRepository;
public BookManager(IRepository<Book> bookRepository){
_bookRepository=bookRepository;
}
public DeleteBook(Book book){
//Check sth from database.
var isExistWithSameCategoryName=await _bookRepository.GetAll().AnyAsync(o=>o.CategoryName==book.CategoryName && o.Id!=book.Id);
if (isExistWithSameCategoryName)
throw new UserFriendlyException("You can not delete");
//if validation pass then delete
_bookRepository.Delete(book);
}
}
as you can see in the above example i want my clients to use only BookManager (my domain service) to do delete operation. But since delete is an option in repository, i can not restrict the clients over here. They can still use repositories delete method on the application layer. Is there any solution to restrict the delete process? I couldn't get my head around it.
And second question is about TenantId, since i implement IMustHaveTenant interface TenantId property should be public, that means clients constructing the entity can change that value. is there a way to restrict it also?
Thank you for the assistance. hope i can explain what i am looking for.
Thank you @ismcagdas, i am closing this issue.
Hello @ismcagdas
Thank you for the reply. I was wondering, if this behavior is new? antiforgery token is not used on anz version 8 or 9? Was it like this from the start of anz or if sth has changed on the latest version? Cause normally I am working on google chrome to test the things that i developed and when i try to get the antiforgery token in chrome browser, it gives me a valid value and it is not null.
Probably on my chrome browser, cookie with antiforgery token has been set long time ago. When i try to open the application on incognito mode then cookie value is null. so i can not upload file in incognito mode but i can upload the file when i open the app with my normal chrome browser.
Normally we used the devex file uploader to make chunk uploads on the app so for every chunk that is gonna be sent to the server side, we decided to add authentication token value first without antiforgery. Then one of our developers try to upload a file and he got an error on the server side.
ERROR 2022-05-14 18:19:42,118 [orker] idateAntiforgeryTokenAuthorizationFilter - The required antiforgery request token was not provided in either form field "__RequestVerificationToken" or header value "X-XSRF-TOKEN". Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The required antiforgery request token was not provided in either form field "__RequestVerificationToken" or header value "X-XSRF-TOKEN". at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext) at Abp.AspNetCore.Mvc.Antiforgery.AbpValidateAntiforgeryTokenAuthorizationFilter.OnAuthorizationAsync(AuthorizationFilterContext context) INFO 2022-05-14 18:19:42,126 [orker] c.Infrastructure.ControllerActionInvoker - Authorization failed for the request at filter 'Abp.AspNetCore.Mvc.Antiforgery.AbpAutoValidateAntiforgeryTokenAuthorizationFilter'.
then we decided also to add an antiforgery token with the request. as i mentioned in my first post. Then as you can expect it didn't work if you do not have an old cookie value that has been set for the site. Right now we have removed all the cookies and remove the antiforgery token code that we are sending with the request.Now everything is working fine. But it is still a mystery how that cookie value is written if anz didn't use antiforgery token at all in all versions?
Please answer the following questions before submitting an issue. YOU MAY DELETE THE PREREQUISITES SECTION.
Hello, I have checked couple of documentation including aspnet boilerplate, here i have couple of questions. In my angular app, When i open my google chrome in incognito mode and try to get abp.security.antiForgery.getToken() it returns null. Also when i check my cookies abp.utils.getCookieValue(abp.security.antiForgery.tokenCookieName); this is also null. Probably abp.security.antiForgery.getToken() calls abp.utils.getCookieValue(abp.security.antiForgery.tokenCookieName) so my question is where can i get the antiforgery token? also when i look at the back end calls it doesn't include X-XSRF-TOKEN header. Is this normal? I bumped into this problem when i try to use devexpress file uploader
uploadFile = (file:File, uploadInfo:UploadInfo) => {
let formData = new FormData();
for (let key of Object.keys(this.data)) {
formData.append(key, this.data[key].toString());
}
formData.append('chunkSize', this.chunkSize.toString());
formData.append('file', uploadInfo.chunkBlob, file.name);
const antiForgeryToken=abp.security.antiForgery.getToken();
const headers = new HttpHeaders()
.set('Authorization', 'Bearer ' + abp.auth.getToken())
.set('Content-Range', this.getContentRange(file, uploadInfo))
.set(abp.security.antiForgery.tokenHeaderName,antiForgeryToken );
return this._httpClient
.post(AppConsts.remoteServiceBaseUrl + this.url, formData, { headers: headers })
.toPromise();
};
here is the code that i am trying to send to the server, but abp.security.antiForgery.getToken() is always null. I have checked AppPreBootstrap to find out how the cookie value is set, i couldn't find anything over there. Also when i look at the backend code i see AntiForgeryController, what is this controller used for? can you explain also the purpose of this controller? and How antiforgery works on anz?
Hello @ismcagdas, I was working on this weekend and i come up with a solution, so if someone wants to implement this maybe it can be helpful who is reading this.
First of all there is no direct option to extend the generated code from nswag option. However there is one trick you can do by using templates.
that solved my problem. now i can use copyWith method for each dto that is generated.
public copyWith(modifyObject: { [P in keyof this]?: this[P] }): this {
return Object.assign(Object.create(this.constructor.prototype), { ...this, ...modifyObject });
}
and if someone is curious about it, i have also found a library that creates immutable objects in an efficient way by using proxy in angular. https://immerjs.github.io/immer/ it supports complex classes but you need to add [immerable] = true attribute to your object, so you can do the same trick if you want to use this library. https://immerjs.github.io/immer/complex-objects
Hello @ismcagdas. To do the option 1, I need to configure service.config.nswag file. Do you know how can i change the configuration? to inherit a custom base class?
"codeGenerators": {
"openApiToTypeScriptClient": {
"className": "{controller}ServiceProxy",
"moduleName": "",
"namespace": "",
"typeScriptVersion": 2.7,
"template": "Angular",
"promiseType": "Promise",
"httpClass": "HttpClient",
"useSingletonProvider": false,
"injectionTokenType": "InjectionToken",
"rxJsVersion": 6.0,
"dateTimeType": "Luxon",
"nullValue": "Undefined",
"generateClientClasses": true,
"generateClientInterfaces": false,
"generateOptionalParameters": false,
"exportTypes": true,
"wrapDtoExceptions": false,
"exceptionClass": "ApiException",
"clientBaseClass": null,
"wrapResponses": false,
"wrapResponseMethods": [],
"generateResponseClasses": true,
"responseClass": "SwaggerResponse",
"protectedMethods": [],
"configurationClass": null,
"useTransformOptionsMethod": false,
"useTransformResultMethod": false,
"generateDtoTypes": true,
"operationGenerationMode": "MultipleClientsFromPathSegments",
"markOptionalProperties": false,
"generateCloneMethod": false,
"typeStyle": "Class",
"classTypes": [],
"extendedClasses": [],
"extensionCode": "service.extensions.ts",
"generateDefaultValues": true,
"excludedTypeNames": [],
"excludedParameterNames": [],
"handleReferences": false,
"generateConstructorInterface": true,
"convertConstructorInterfaceData": false,
"importRequiredTypes": true,
"useGetBaseUrlMethod": false,
"baseUrlTokenName": "API_BASE_URL",
"queryNullValue": "",
"inlineNamedDictionaries": false,
"inlineNamedAny": false,
"templateDirectory": null,
"typeNameGeneratorType": null,
"propertyNameGeneratorType": null,
"enumNameGeneratorType": null,
"serviceHost": null,
"serviceSchemes": null,
"output": "../src/shared/service-proxies/service-proxies.ts"
},
if i give clientBaseClass: "ObjectCopier" and implement the class in "service.extensions.ts". It is creating it only on service classes. not on dtos, do you know a way to do it?
class ObjectCopier {
public copyWith(modifyObject: { [P in keyof this]?: this[P] }): this {
return Object.assign(Object.create(this.constructor.prototype), { ...this, ...modifyObject });
}
}
Hello, I have a problem with immutability in angular project, I did some research about this and there are various ways to do this. But generated code from nswag is sometimes giving some problems. First of all as i see it there are 3 ways to do the immutability in plain javascript way.
// "Spread"
{ ...food }
const food = { beef: '🌽', bacon: '🥓' };
const newFood = {
...food,
beef: '🥩',
};
// "Object.assign"
Object.assign({}, food)
let newCar=Object.assign(Object.create(myCar), {wheels: 3});
// "JSON"
JSON.parse(JSON.stringify(food))
third option with JSON is out of scope because it would be very difficult to copy the object with a changed property. and it is not most of the time very readable all around the project. and you loose methods in the class when you do the conversion.
Spread operator was my first approach to make the objects immutable, it is working fine until some point that i understand that spread operator spreads only the plain properties. This cause a problem if you have some methods on the class that you want to clone with the immutable object. One good example is Swagger generated DTOs. They have .toJSON() method in their classes if you use spread operator for those dtos then you can not send the dto to server side. Which gives an error.
Second approach works fine (of course if you want to shallow copy your object). If it is possible i want to make this generic and use it in every dto classes. https://mauricereigman.medium.com/immutable-typescript-classes-with-object-copywith-typesafe-a84fff3971dc I search for nswag configuration that i can inherit all dtos from the second approach sth like this
class ObjectCopier {
public copyWith(modifyObject: { [P in keyof this]?: this[P] }): this {
return Object.assign(Object.create(this.constructor.prototype), { ...this, ...modifyObject });
}
}
but only thing i could do was to extend service classes. is there a way to extend all of the dtos in generated dtos from nswag?
Also i know that there are many other libraries out there. Some of them are Ramda,loadash-fp, immutable.js these are all fine and i can use them, but they mostly work with property path. So one good example can be found in loadash-fp
const myBooking = {
id: '23484',
client: {
id: '458982',
name: 'John Doe',
email: '[email protected]',
vip: false,
business: false,
family: false,
},
rooms: [
{
type: 'standard',
beds: 2,
},
{
type: 'standard',
beds: 1,
}
],
extras: {
lateCheckout: false,
spa: false,
minibar: false,
seasights: false,
}
}
const upgradeBooking = (propertypath, value, booking) => {
return _.set(propertypath, value, booking);
}
const updatedBookingA = upgradeBooking('client.vip', true, myBooking);
so you are loosing the type-safe approach. If some change happens in dtos then 'client.vip' will not throw an error on compile time. there is another library that i have found out for this, called immutable-assign https://github.com/engineforce/ImmutableAssign but this library i believe use spread operator so you loose the methods for dtos.
another library that i have found out is monolite which works fine. This is the closest i can get until now. but this library is so small it is not updated regularly so i don't want to depend on this library https://github.com/kube/monolite
So as a conclusion, i am down to 2 or 3 options
so the question here is what would be the best way to do, to clone the object with typesafe, immutable, readable and keeping the methods in tact inside the class?
Hello @ismcagdas, I don't think someone will reply a solution for this. I know that there is no one right solution for this problem, but i was thinking maybe you come up with sth similar in the community or support. Should i raise the same question on abp commercial support instead? Since I use abp on another project and ddd is more focused on that framework?
Hello, I have a question about how to share the same concept in different bounded context. below image is not from my app but sth i can refer to point out my problem.
As you can see in the image, client has different purpose in different contexts. In this case on "Billing BC" and "Scheduling BC". Scheduling BC doesn't need the information about credit cards and etc. To schedule the client you don't need those information. In these kind of cases to not break the ddd rules what is the best approach to get the data into different context. Let's say i want to schedule the client. How should i get the client data? From a different bounded context? I have read that some people are suggesting to duplicate the data between different bcs but i do not know if it is the best approach since i am working on single db. here is a stackoverflow question about it. https://stackoverflow.com/questions/66663259/domain-driven-design-how-to-connect-two-contexts and the second option in this answer can be done (Reuse of persistence object (db tables) in several contexts). Then my question turns into how i should organize this in anz project since it is shared between different contexts.
Should i create 3 folders in core project like. SharedAggregate * Client Aggregate Scheduling * Schedule Aggregate Billings * Billing Aggregate
to point out client is shared with different contexts? Do you have any suggestions for this?