简体   繁体   中英

MAUI+ASP.NET DTOs

I have a project consisting of 2 parts:

  • ASP.NET API using Entity Framework
  • .NET MAUI Client App

I use DTOs for comunication from/to the API in order not to expose other properties of my entities. Thanks to this approach I was able to separate Entity data and data that are sent from the API.

At first I used these DTOs also in the MAUI UI. But after some time I started to notice that they contains UI-specific properties, attributes or methods that have no purpose for the API itself, so they are redundant in requests.


EXAMPLE:

1 - API will receive request from MAUI to get exercise based on it's name

2- ExerciseService returns: ExerciseEntity and ExerciseController use AutoMapper to Map ExerciseEntity -> ExerciseDto ommiting ExerciseId field (only admin can see this info in the DB ) and returning it in the API response

3 - MAUI receives from the API ExerciseDto . But in the client side it also want to know if data from ExerciseDto are collapsed in the UI. So because of that I add IsCollapsed property into the ExerciseDto . But now this is a redundant property for the API, because I dont want to persist this information in the database.


QUESTIONS:

Should I map these DTOs to new objects on the client side?

Or how to approach this problem?

Is there an easier way how to achieve the separation?

Because having another mapping layer will add extra complexity and a lot of duplicate properties between DTOs and those new client objects.

Normally if you use clean architecture approach your DTOs shoud contain no attributes and other specific data relevant just for some of your projects, to be freely usable by other projects in a form of dependency.

Then you'd have different approaches to consume DTOs in a xamarin/maui application, for example:

APPROACH 1. Mapping (of course) into a class that is suitable for UI. Here you have some options, use manual mapping, write your own code that uses reflection or use some third party lib using same reflection. Personally using all of them, and when speaking of third party libs Mapster has shown very good to me for api and mobile clients.

APPROACH 2. Subclass DTO. The basic idea is to deserialize dto into the derived class, then call Init(); if needed. All properties that you manually implemented as new with OnPropertyChanged will update bindings after being popupated by deserializer/mapper and you alse have a backup plan to call RaiseProperties(); for all of the props, even thoses who do not have OnPropertyChanged in place so they can update bindings if any.

Example: our Api DTO

 public class SomeDeviceDTO
    {
        public int Id { get; set; }
        public int Port { get; set; } 
        public string Name { get; set; } 
    }

Our derived class for usage in mobile client:

  public class SomeDevice : SomeDeviceDTO, IFromDto
        {
        // we want to be able to change this Name property in run-time and to 
        // reflect changes so we make it bindable (other props will remain without 
        // OnPropertyChanged BUT we can always update all bindings in code if needed 
        // using RaiseProperties();):
        
            private string _name;
            public new string Name
            {
                get { return _name; }
                set
                {
                    if (_name != value)
                    {
                        _name = value;
                        OnPropertyChanged();
                    }
                }
            }  

       // ADD any properties you need for UI
       // ...

       #region IFromDto

            public void Init()
            {
                //put any code you'd want to exec after dto's been imported
                // for example to fill any new prop with data derived from what you received 

            }
    
            public void RaiseProperties()
            {
                var props = this.GetType().GetProperties();
                foreach (var property in props)
                {
                    if (property.CanRead)
                    {
                        OnPropertyChanged(property.Name);
                    }
                }
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
            protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }

#endregion
        
        }
    
      public interface IFromDto : INotifyPropertyChanged
        {
            //
            // Summary:
            //     Can initialize model after it's being loaded from dto
            void Init();
    
            //
            // Summary:
            //     Notify all properties were updated
            void RaiseProperties();
        }

We can get it like: var device = JsonConvert.DeserializeObject<SomeDevice>(jsonOfSomeDeviceDTO);

We then can call Init(); if needed..

Feel free to edit this answer to add more approaches..

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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