1
votes

Good day,

We are experiencing an issue with serialization where a request object set with a value for one property ends up being received by the service with the value assigned to a different property. Please see below for more information.

We are using the 3.9.71 version of ServiceStack NuGet packages. The solution is made up of the following projects:

  • Project.Host: Used for self-hosting ServiceStack and contains Service classes.
  • Project.DTO: All services DTOs and surrounding classes.
  • Project.Tests: Contains unit tests.

The problems has been identified to only one class/service, namely MinimalUser and MinimalUserService, which you can see code for both below:

MinimalUser.cs

namespace Project.DTO
{
    [Route("/User/{Identity}", "GET")]
    [Route("/User/{Username}", "GET")]
    [Route("/User/{DisplayName}", "GET")]    
    public class MinimalUser : IReturn<MinimalUser>
    {
        #region Properties

        public int? Identity { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
        public string DisplayName { get; set; }
        public string Email { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Language { get; set; }
        public string TimeZone { get; set; }
        public string Culture { get; set; }
        public List<string> Roles { get; set; }
        public List<string> Permissions { get; set; }
        public DiscUserDetails DiscUserDetails { get; set; }

        #endregion

        #region Constructors
        public MinimalUser() { } 

        public MinimalUser(UserAuth auth)
        {
            if (auth != null)
            {
                this.Identity = auth.Id;
                this.Username = auth.UserName;
                this.DisplayName = auth.DisplayName;
                this.Email = auth.Email;
                this.FirstName = auth.FirstName;
                this.LastName = auth.LastName;
                this.Language = auth.Language;
                this.TimeZone = auth.TimeZone;
                this.Culture = auth.Culture;
                this.Roles = auth.Roles;
                this.Permissions = auth.Permissions;
                this.DiscUserDetails = auth.Get<DiscUserDetails>();
            }
        }

        #endregion

        #region Methods

        public static MinimalUser FromUserAuth(UserAuth auth)
        {
            return auth == null ? new MinimalUser() : new MinimalUser
            {
                Identity = auth.Id,
                Username = auth.UserName,
                DisplayName = auth.DisplayName,
                Email = auth.Email,
                FirstName = auth.FirstName,
                LastName = auth.LastName,
                Language = auth.Language,
                TimeZone = auth.TimeZone,
                Culture = auth.Culture,
                Roles = auth.Roles,
                Permissions = auth.Permissions,
                DiscUserDetails = auth.Get<DiscUserDetails>()
            };
        }

        #endregion
    }
}

DiscUserDetails.cs

namespace Project.DTO
{
    public class DiscUserDetails
    {
        public int? LocationId { get; set; }
        public bool IsActive { get; set; }
        public byte NumberOfFailedLoginAttempts { get; set; }
        public bool MustChangePasswordAtNextLogon { get; set; }
        public int? LastAcceptedPolicyId { get; set; }
    }
}

MinimalUserService.cs

namespace Project.Services
{
    [Authenticate]
    [RequiredRole(new string[] { RoleNames.Admin })]
    public class MinimalUserService : Service
    {
        IUserAuthRepository authRepo = AppHost.Resolve<IUserAuthRepository>() as OrmLiteAuthRepository;

        /// <summary>
        /// Return a minimalist structure of user insensitive information.
        /// </summary>
        /// <param name="request">The request containing the ID of the user.</param>
        /// <returns>A minimalist structure of user insensitive information.</returns>
        public object Get(MinimalUser request)
        {
            if (request.Identity != null)            
                return new MinimalUser(authRepo.GetUserAuth(request.Identity.ToString()));
            else if (request.Username != null)
                return new MinimalUser(authRepo.GetUserAuthByUserName(request.Username)); 
            else
                return null;
        }            
    }
}

From my test project, I run the following test:

[TestMethod]
public void GetMinimalUserByUsername()
{
    AuthResponse authResponse = client.Post<AuthResponse>("/auth", new Auth
    {
        UserName = "accountwithadminrole",
        Password = "blablabla",
        RememberMe = true,
        provider = CredentialsAuthProvider.Name
    });

    MinimalUser request = new MinimalUser
    {
        DisplayName = BaseAccounts.System,
    };

    MinimalUser user = client.Get<MinimalUser>(request);

    Assert.IsNotNull(user);
}

I clearly see, before issuing the client.Get method, that the request object have all its properties set to null except for the DisplayName which has the value "system". When this request is received by the MinimalUserService Get method, the value "system" is now assigned to the property UserName and DisplayName is null.

Also, I've tried to comment properties one by one in the MinimalUser class, suspecting one of its field could be causing serialization problem and I would end up having random 'Bad Request' when commenting a certain number of properties. Although, I could comment a properties randomly and one property that previously caused a 'Bad Request' would not do it depending on others properties commented out.

I'm really confused about how this can possibly happens. I feel the service and DTO are simple compared to others from this same project but I'm sure I'm doing something really stupid here.

Don't hesitate to ask for more details, it will be my pleasure to give all information you need.

Thank you.

1

1 Answers

1
votes

The reason for Username to be populated instead of DisplayName is because of the routes you have defined for MinimalUser. In MinimalUser.cs you defined 2 identical routes:

[Route("/User/{Identity}", "GET")]
[Route("/User/{Username}", "GET")]
[Route("/User/{DisplayName}", "GET")]

Username and Displayname are both strings. This makes it impossible for ServiceStack to determine the appropriate route direct the request to as it cannot differentiate between the routes. You can fix this by either removing a route, or by adding additional text to one of the routes; eg /User/ByDisplayName/{Username}.